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: 안정된 추상화 원칙
      • 컴포넌트는 안정된 정도만큼만 추상화 되어야 한다.
    • 컴포넌트의 안정성, 추상성을 측정하는 지표를 제시한다. 시스템이 매우 크고 릴리즈가 잦다면 충분히 활용해 볼만한 지표라고 생각이 든다. 나의 경우는 아직까지는 활용하기 어려워 보인다.

시스템이나 프로젝트가 커지면서 이에 비례하게 유지관리 비용이 증가한다면 좋지 못한 방법을 사용하고 있음이 분명하다. 이를 해결하기 위한 고민을 하는 것이 중요하다.

댓글