Clean Architecture 0
Motivation
업무를 하다 보면 프로그램을 처음부터 만들어야 하는 경우가 종종 생긴다. 그때마다 나름의 경험들을 바탕으로 구조를 어찌 저찌 잡아간다. 그리고 그때마다 조금씩 아쉬울 때가 많았다. 이게 진짜 좋은 구조인가, 꼭 필요한 구조인가, 굳이 필요한가 고민을 하게된다. 그리고 어떤 명확한 기준을 세우기가 힘들었다. 이 책에서 이런 고민에 대한 해답을 조금 얻을 수 있기를 기대하며 읽어본다.
1. 소개
- 나는 어떤 시스템을 만들어 보았나?
제대로 된 소프트웨어를 만들면 아주 적은 인력 만으로도 새로운 기능을 추가하거나 유지보수 할 수 있다.
-
목표: 필요한 시스템을 만들고, 유지보수하는데 투입되는 인력을 최소화
- 새로운 기능을 추가할 때만다 비용이 증가 -> 나쁜 설계
- 빨리 가는 유일한 방법은 제대로 가는 것이다.
-
저자의 현업 에서의 경험에 바탕을 둔 worst practice
- 시장에 요구에 따라 소프트웨어를 빠르게 출시
- 그 동안 코드의 구조는 엉망징찬이 됨
- 점점 생산성이 떨어지고 유지 보수의 비용이 크게 증가함
-
소프트웨어의 두가지 가치: behavior, structure
- structure 를 위한 노력은 더 중요하지만 급하지 않을 경우가 많다. behavior 를 위한 노력은 급하지만 중요하지 않을 수 있다. 아이젠하워 매트릭스의 우선순위를 생각하며 중요한 일을 하기위해 노력해야 한다.
2. 벽돌부터 시작하기: 프로그래밍 패러다임
-
3가지 프로그래밍 패러다임: 구조적, 객체지향, 함수형
- 구조적 프로그래밍
- 에츠허르 비버 데이크스트라가 발견
- 부분별한 점프는 프로그램 주조에 해로움, 이를 if/else, while 등으로 대체
- 제어흐름의 직접적인 전환에 대해 규칙을 부과 (goto)
- 객체지향 프로그래밍
- 스택의 사용에 의한 변수의 life cycle 을 조절할 수 있게 되고 포인터의 사용하는 규칙에 따른 다형성을 적용 (캐스팅)
- 제어흐름의 간접적인 전환에 대한 규칙을 부과 (함수 pointer)
- 함수형 프로그래밍
- 할당문에 대해 규칙을 부과함
- 구조적 프로그래밍
-
프로그래밍 패러다임은 무엇을 하지 말아야 하는지 정한다.
-
세가지 제약 (goto, 함수 포인터, 할당문)은 함수, 컴포넌트 분리, 데이터 관리와 밀접하게 관련됨
-
구조적 프로그래밍
- 데이크스트라는 유클리드 방식을 통해 프로그램을 증명할 수 있는 방법을 고민했다. 이러한 노력은 결과적으로는 결실을 맺지 못했다고 한다. #검증 분야에서 formal verification 방법이 이와 비슷한가? 생각이 든다.
- 소프트웨어 개발은 증명을 통해 참을 입증하는 수학보다는, 증명이 거짓임을 시험적으로 테스트하는 과학에 더 가깝다.
테스트는 버그가 있음을 보여줄 뿐, 버그가 없음을 보여줄 수는 없다. - 데이크스트라
-
객체지향 프로그래밍
- OO 언어는 다형성을 안전하고 편리하게 사용할 수 있게 지원한다. 저자는 상속, 캡슐화 보다도 이러한 다형성을 OO 언어의 큰 특징으로 본다.
- 다형성은 의존성 역전을 만들 수 있고, 이는 소스코드 의존성을 원하는 대로 변경할 수 있게 한다. 예를 들어 시스템의 모듈을 독립적으로 개발하고 배포할 수 있게 한다.
- 이해한 대로 다시 정리해 보면, 다형성은 하나의 인터페이스로 다양한 동작을 할 수 있게 한다. 이러한 기법은 시스템 수준에서 보면 각 모듈의 개발과 배포 (소스코드 그 자체를) 독립시킬 수 있게 한다.
-
함수형 프로그래밍
- 함수형 프로그래밍은 할당문이 없다.
- 변수의 가변성은 race condition, deadlock, concurrent update 문제를 야기한다.
- 가능한 많은 부분을 불변 컴포넌트로 옮기는 것이 좋다. *
-
결론
- 구조적 프로그래밍: 제어흐름의 직접적인 전환에 대해 규칙을 부과
- 객체지향 프로그래밍: 제어흐름의 간접적인 전환에 대해 규칙을 부과
- 함수형 프로그래밍: 할당문에 대해 규칙을 부과함
- 소프트웨어: 순차, 분기, 반복, 참조
3. 설계 원칙
- SRP
- 함수는 하나의 기능만 해야 한다. 여기서 말하는 SRP 는 이러한 의미가 아니다.
- 단일 모듈은 하나의 이유로만 변경되어야 한다.
- 하나의 모듈은 하나의 액터에 대해서만 책임져야 한다.
- 하나의 모듈이 A에 필요한 정보도 제공하고 B가 필요한 정보도 제공하고 있을 때, A에게 제공하는 정봉의 수정이 B에게 제공하는 정보의 변경을 발생시킬 수 있다.
- OCP
- 기능을 추가할 때에는 반드시 기존의 코드를 수정하지 않고, 새로운 코드를 추가할 수 있게 해야 한다.
- LSP
- 하위 객체를 다른 타입으로 치환하더라도 상위 객체의 동작은 변경이 없어야 한다.
- ISP
- 불필요한 무언가에 의존하고 있다면 예상치 못한 문제가 발생할 수 있다.
- DIP
- dependency inversion 을 통해서 하위 모듈들이 변경되더라도 동작에 변경이 없도록 해야 한다.
- 안정된 추상화
- 변동성이 큰 구체 클래스를 직접사용하지 않도록 하는 것이 좋다.
- 팩토리
- 팩토리를 이용해서 dependency 를 최소화 할 수 있다.
4. 컴포넌트 원칙
-
컴포넌트 응집도
- REP: Reuse/Release Equivalence Principle
- CCP: Common Closure Principle
- 동일한 이유로 동일한 시점에 변경되는 클래스는 같은 컴포넌트로 묶어라 (컴포넌트 관점의 SRP)
- CRP: Common Reuse Principle
- 공통 재사용 원칙
- 컴포넌트 사용자들을 필요하지 않는 것에 의존하게 강요하지 마라
- 컴포넌트 관점의 ISP
- REP, CCP, CRP 는 서로 상충되는 개념이여서 이들간의 적정선을 찾는 것이 아키텍트의 역량이 될 수 있다.
- 또한 개발 초기에는 CCP 에 좀더 우선순위가 가는 경향이 있고, 프로그램이 성숙해 나가면서 REP, CRP 에 무게가 실리면서 발전해 나가는 경향이 있다.
-
컴포넌트 결합
- ADP: Acyclic Dependencies Principle
- 의존성 비순환 원칙
- 의존성을 잘 관리하면 새로운 버전이 릴리즈 되더라도 전체 시스템에 이로인한 문제가 발생하지 않도록 버전을 관리하기가 용이해지지만, 의존성에 순환이 생기면 이런 방법을 적용하기 어려워 진다.
- 순환 끊기: DIP 의존성 역전 원칙 사용
- SDP: Stable Dependencies Principle
- 안정성의 방향으로 의존하라
- 안정적인 모듈에 의존하게 만들기
- 변동성이 크면 안정성이 떨어지게 된다.
- 많은 모듈이 의존하게 되면 해당 모듈의 변경이 어려워 진다.
- 안정성이 필요한 모듈과 불안정성이 필요한 모듈을 구분하고 DIP 을 통해 안정한 모듈이 불안정한 모듈을 의존하는 것을 없애는 것이 좋다.
- SAP: 안정된 추상화 원칙
- 컴포넌트는 안정된 정도만큼만 추상화 되어야 한다.
- 컴포넌트의 안정성, 추상성을 측정하는 지표를 제시한다. 시스템이 매우 크고 릴리즈가 잦다면 충분히 활용해 볼만한 지표라고 생각이 든다. 나의 경우는 아직까지는 활용하기 어려워 보인다.
- ADP: Acyclic Dependencies Principle
시스템이나 프로젝트가 커지면서 이에 비례하게 유지관리 비용이 증가한다면 좋지 못한 방법을 사용하고 있음이 분명하다. 이를 해결하기 위한 고민을 하는 것이 중요하다.