🥴
복잡성 제거 - 좋은 설계를 위한 첫 걸음
March 15, 2023
좋은 아키텍처란?
- 풀려는 문제에 잘 어울리는 설계
- 코드 구조(ex. 클래스)가 시스템이 어떻게 동작하는지를 잘 이해할 수 있게 보여 준다
- 변화에 민감: 요구 사항이 진화함에 따라 쉽게 변경이 가능해야 한다
- 이런 게 쉬워야 한다
- 이해하기
- 왜 이렇게 되었는지 이유를 알기
- 유지하기
- 테스트하기
- 더 올바른 경향이 있다
- 성능 개선 작업을 더 부드럽게 해 준다
좋은 아키텍처의 방해물: 복잡성
- 복잡성
- 시스템을 이해하기 어렵고 수정하기 어렵게 만드는 소프트웨어 구조에 관련된 모든 것
- 복잡성 ≠ 코드의 줄 수
- 징후
- 변경 증폭: 작은 변경 → 여러 곳의 많은 편집
- 인지적 부하: 작은 변경 → 많은 선수 지식을 알아야 함
- 알 수 없는 무지: 작은 변경 → 알 수 없는 결과들
복잡성을 조장하는 세 가지 요인
- 의존성
- 코드가 독립적으로 이해되고 수정될 수 없을 때
- 없앨 수는 없으나, 최소화되어야 함
- 코드가 독립적으로 이해되고 수정될 수 없을 때
- 불명확함
- 중요한 정보가 불명확할 때
- 비용: 지금 10~20% 적게 들더라도 일부 비용은 이자로 영원히 남게 될 수 있음(기술 부채)
- 중요한 정보가 불명확할 때
- 전술적 프로그래밍
- 빨리 완성하기
- 비용: 10~20% 더 듦, 영구 무이자
- 빨리 완성하기
복잡성을 낮추는 방법
- 불필요한 정보 감추기(추상화)
- 깊은 모듈
- 범용 인터페이스
- 정보 은닉
- 깔끔한 추상화
- 복잡성을 아래로 끌어내리기
- 추상화 사이의 경계 찾기
깊은 모듈이란?
- 모듈
- 무언가를 구현하고 인터페이스를 제공하는 것(ex. 클래스, 퍼블릭 메소드)
- 추상화
- 중요하지 않은 세부 사항을 감춰 주는 간소화된 뷰
- 깊은 vs 얕은
- 하나의 간단한 요청으로 많은 작업이 완료된다
- 깊은 인터페이스의 예: file.open() 내부의 구현을 몰라도 open만 하면 됨
- 얕은 것들: 래퍼 클래스, 나쁜 추상화들, 하나의 메소드를 위해 여러 번의 메소드를 불러야 하는 경우
- 하나의 간단한 요청으로 많은 작업이 완료된다
범용 인터페이스는 더 깊다
- 범용인가, 특정 용도인가?
- 범용은 지금 비용이 들고, 특정 용도는 나중에 비용이 듦
- 모듈을 범용으로 만들려면?
- 인터페이스: 범용
- 구현: 현재 요구 사항
- 판단을 위한 질문들
- 현재의 요구 사항을 모두 만족하는 것 중에 무엇이 가장 심플한 인터페이스인가?
- 이 메소드가 얼마나 많은 상황에서 사용될 수 있는가?
- 답이 하나라면 이는 적신호
- 이 API는 현재 나의 요구 사항을 해결하는 데에 실제로 사용하기 편한가?
정보 은닉 vs 정보 유출
중요 원칙: 일반적인 케이스들을 최대한 심플하게 만들라
- 불필요한 정보를 감추기
- 정보 누출: 같은 지식이 여러 군데에서 사용된다
- 적신호: 과다 노출
- 사용자가 일반적인 작업들을 위해서 명료하지 않은 기능들을 배워야 한다
복잡성을 아래로 끌어내리기
- 심플한 인터페이스는 심플한 구현보다 낫다
- 주의: 이를 남용하면 오히려 정보 유출로 이어질 수 있음
- 중요 원칙: 아래의 경우에 끌어내리기
- 기존에 있는 기능과 밀접하게 연관되어 있다면
- 앱의 여러 부분들을 더 단순하게 만들어 준다면
- 클래스의 인터페이스를 단순하게 만들어 준다면
추상화 사이의 경계 찾기
- 이럴 때는 합치기
- 정보가 공유된다
- 함께 있는 것이 인터페이스를 단순하게 만들어 준다
- 코드 중복을 없애 준다
- 적신호: 코드 반복은 제대로 추상화가 이뤄지지 않았음을 의미할 수 있다
- 이럴 때는 나누기
- 특정 목적의 API가 범용 클래스 안에 있을 때
- 다른 종류의 범용 매커니즘이 함께 있을 때