좋은 아키텍처란?

  • 풀려는 문제에 잘 어울리는 설계
    • 코드 구조(ex. 클래스)가 시스템이 어떻게 동작하는지를 잘 이해할 수 있게 보여 준다
    • 변화에 민감: 요구 사항이 진화함에 따라 쉽게 변경이 가능해야 한다
  • 이런 게 쉬워야 한다
    • 이해하기
    • 왜 이렇게 되었는지 이유를 알기
    • 유지하기
    • 테스트하기
  • 더 올바른 경향이 있다
  • 성능 개선 작업을 더 부드럽게 해 준다

좋은 아키텍처의 방해물: 복잡성

  • 복잡성
    • 시스템을 이해하기 어렵고 수정하기 어렵게 만드는 소프트웨어 구조에 관련된 모든 것
  • 복잡성 ≠ 코드의 줄 수
  • 징후
    • 변경 증폭: 작은 변경 → 여러 곳의 많은 편집
    • 인지적 부하: 작은 변경 → 많은 선수 지식을 알아야 함
    • 알 수 없는 무지: 작은 변경 → 알 수 없는 결과들

복잡성을 조장하는 세 가지 요인

  • 의존성
    • 코드가 독립적으로 이해되고 수정될 수 없을 때
      • 없앨 수는 없으나, 최소화되어야 함
  • 불명확함
    • 중요한 정보가 불명확할 때
      • 비용: 지금 10~20% 적게 들더라도 일부 비용은 이자로 영원히 남게 될 수 있음(기술 부채)
  • 전술적 프로그래밍
    • 빨리 완성하기
      • 비용: 10~20% 더 듦, 영구 무이자

복잡성을 낮추는 방법

  • 불필요한 정보 감추기(추상화)
    • 깊은 모듈
    • 범용 인터페이스
    • 정보 은닉
  • 깔끔한 추상화
    • 복잡성을 아래로 끌어내리기
    • 추상화 사이의 경계 찾기

깊은 모듈이란?

  • 모듈
    • 무언가를 구현하고 인터페이스를 제공하는 것(ex. 클래스, 퍼블릭 메소드)
  • 추상화
    • 중요하지 않은 세부 사항을 감춰 주는 간소화된 뷰
  • 깊은 vs 얕은
    • 하나의 간단한 요청으로 많은 작업이 완료된다
      • 깊은 인터페이스의 예: file.open() 내부의 구현을 몰라도 open만 하면 됨
      • 얕은 것들: 래퍼 클래스, 나쁜 추상화들, 하나의 메소드를 위해 여러 번의 메소드를 불러야 하는 경우

범용 인터페이스는 더 깊다

  • 범용인가, 특정 용도인가?
    • 범용은 지금 비용이 들고, 특정 용도는 나중에 비용이 듦
  • 모듈을 범용으로 만들려면?
    • 인터페이스: 범용
    • 구현: 현재 요구 사항
  • 판단을 위한 질문들
    • 현재의 요구 사항을 모두 만족하는 것 중에 무엇이 가장 심플한 인터페이스인가?
    • 이 메소드가 얼마나 많은 상황에서 사용될 수 있는가?
      • 답이 하나라면 이는 적신호
    • 이 API는 현재 나의 요구 사항을 해결하는 데에 실제로 사용하기 편한가?

정보 은닉 vs 정보 유출

중요 원칙: 일반적인 케이스들을 최대한 심플하게 만들라

  • 불필요한 정보를 감추기
  • 정보 누출: 같은 지식이 여러 군데에서 사용된다
  • 적신호: 과다 노출
    • 사용자가 일반적인 작업들을 위해서 명료하지 않은 기능들을 배워야 한다

복잡성을 아래로 끌어내리기

  • 심플한 인터페이스는 심플한 구현보다 낫다
  • 주의: 이를 남용하면 오히려 정보 유출로 이어질 수 있음
  • 중요 원칙: 아래의 경우에 끌어내리기
    • 기존에 있는 기능과 밀접하게 연관되어 있다면
    • 앱의 여러 부분들을 더 단순하게 만들어 준다면
    • 클래스의 인터페이스를 단순하게 만들어 준다면

추상화 사이의 경계 찾기

  • 이럴 때는 합치기
    • 정보가 공유된다
    • 함께 있는 것이 인터페이스를 단순하게 만들어 준다
    • 코드 중복을 없애 준다
    • 적신호: 코드 반복은 제대로 추상화가 이뤄지지 않았음을 의미할 수 있다
  • 이럴 때는 나누기
    • 특정 목적의 API가 범용 클래스 안에 있을 때
    • 다른 종류의 범용 매커니즘이 함께 있을 때