로머트 C. 마틴의 Clean Architecture 책을 읽고 정리한 내용입니다.

 

12장. 컴포넌트


  • 배포 단위의 컴포넌트
    • 컴포넌트는 시스템의 구성 요소로 배포할 수 있는 가장 작은 단위
    • 잘 설계된 컴포넌트라면 반드시 독립적으로 배포 가능한, 따라서 독립적으로 개발 가능한 능력을 갖춰야 함

 

 

 

 

13장. 컴포넌트 응집도


  • 컴포넌트 응집도에 관련된 세 가지 원칙
    • REP: 재사용/릴리스 등가 원칙(Reuse/Release Equivalence Principle)
    • CCP: 공통 폐쇄 원칙(Common Closure Principle)
    • CRP: 공통 재사양 원칙(Common Reuse Principle)

 

[REP: 재사용/릴리스 등가 원칙]

  • 소개
    • 재사용 단위는 릴리스 단위와 같다.
  • 소프트웨어 설계와 아키텍처 관점에서의 원칙
    • 단일 컴포넌트는 응집성 높은 클래스와 모듈들로 구성되어야 함을 뜻함
    • 임의로 선택된 클래스와 모듈로 구성되어서는 안 됨
    • 컴포넌트를 구성하는 모든 모듈은 서로 공유하는 중요한 테마나 목적이 있어야 함
    • 하나의 컴포넌트로 묶인 클래스와 모듈은 반드시 함께 릴리스 할 수 있어야 한다.
  • 다른 원칙과의 관계
    • CCP와 CRP는 REP를 엄격하게, 하지만 제약을 가하는 측면에서 정의

 

[CCP: 공통 폐쇄 원칙]

  • 소개
    • 동일한 이유로 동일한 시점에 변경되는 클래스를 같은 컴포넌트로 묶어라. 서로 다른 시점에 다른 이유로 변경되는 클래스는 다른 컴포넌트로 분리하라.
  • 컴포넌트 관점의 SRP
    • SRP를 컴포넌트 관점에서 다시 쓴 것
    • 단일 컴포넌트는 변경의 이유가 여러 개 있어서는 안된다고 말함
  • 애플리케이션의 유지보수성
    • 대다수의 애플리케이션에서 유지보수성은 재사용성보다 훨씬 중요함
    • 코드가 반드시 변경되어야 한다면, 여러 컴포넌트에서 발생하기보다는, 차라리 변경 모두가 단일 컴포넌트에서 발생하는 편이 나음
  • CCP의 권장사항
    • CCP는 같은 이유로 변경될 가능성이 있는 클래스는 모두 한곳으로 묶을 것을 권함
    • 물리적 또는 개념적으로 강하게 결합되어 항상 함께 변경되는 클래스들은 하나의 컴포넌트에 속해야 한다.
    • 이를 통해 소프트웨어를 릴리스, 재검증, 배포하는 일과 관련된 작업량을 최소화 할 수 있음
  • OCP와 관련성
    • CCP의 폐쇄와 OCP의 폐쇄는 같은 뜻
    • 100% 완전한 폐쇄란 불가능하므로 전략적으로 폐쇄해야 함
    • CCP에서는 동일한 유형의 변경에 대해 닫혀 있는 클래스들을 하나의 컴포넌트로 묶음으로써 변경이 필요한 요구사항이 발생했을 때, 변경이 영향을 주는 컴포넌트들이 최소한으로 한정될 가능성이 확실히 높아짐
  • SRP와의 유사성
    • CCP는 컴포넌트 수준의 SRP
    • 두 원칙의 교훈
      • 동일한 시점에 동일한 이유로 변경되는 것들을 한데 묶어라. 서로 다른 시점에 다른 이유로 변경되는 것들은 서로 분리하라.

 

[CRP: 공통 재사용 원칙]

  • 소개
    • 컴포넌트 사용자들을 필요하지 않는 것에 의존하게 강요하지 마라
  • 클래스와 모듈을 어디서 위치시킬 것인가?
    • 클래스와 모듈을 어느 컴포넌트에 위치시킬지 결정할 때 도움이 되는 원칙
    • 같이 재사용되는 경향이 있는 클래스와 모듈들은 같은 컴포넌트에 포함해야 한다고 말함
  • 컴포넌트 내부의 수많은 의존성
    • 개별 클래스가 단독으로 재사용되는 경우는 거의 없음
    • 대체로 재사용 가능한 클래스는 재사용 모듈의 일부로써 해당 모듈의 다른 클래스와 상호작용하는 경우가 많음
    • 이런 클래스들이 동일한 컴포넌트에 포함되어야 한다고 말함
    • 이러한 컴포넌트 내부에서는 클래스들 사이에 수많은 의존성이 있으리라고 예상할 수 있음
  • 컴포넌트 의존 시 주의점
    • 의존하는 컴포넌트가 있다면 해당 컴포넌트의 모든 클래스에 대해 의존함을 확실히 인지해야 함
    • 한 컴포넌트에 속한 클래스들은 더 작게 그룹지을 수 없음
    • 그중 일부 클래스에만 의존하고 다른 클래스와는 독립적일 수 없음을 확실히 인지
  • 동일한 컴포넌트로 묶어서는 안되는 클래스
    • 강하게 결합되지 않은 클래스들을 동일한 컴포넌트에 위치시켜서는 안된다고 말함
  • ISP와의 관계
    • CRP는 ISP의 포괄적인 버전
    • 두 원칙의 요약
      • 필요하지 않은 것에 의존하지 말라.

 

[컴포넌트 응집도에 대한 균형 다이어그램]

  • 상충되는 세 원칙
    • ERP와 CCP
      • 포함 원칙
      • 컴포넌트를 더욱 크게 만듬
    • CRP
      • 배제 원칙
      • 컴포넌트를 더욱 작게 만듬
    • 이 원칙들이 균형을 이루는 방법을 찾아야 함
  • 균형 다이어그램
    • 프로젝트 초기에는 CCP가 REP보다 훨씬 중요
      • 개발 가능성이 재사용성보다 더욱 중요하기 때문
    • 프로젝트의 컴포넌트 구조는 시간과 성숙도에 따라 변함

 

[결론]

  • 시간이 흐름에 따라 프로젝트의 초점이 개발가능성에서 재사용성으로 바뀌고, 그에 따라 컴포넌트를 구성하는 방식도 진화함

로머트 C. 마틴의 Clean Architecture 책을 읽고 정리한 내용입니다.

 

7장. SRP: 단일 책임 원칙


  • SRP에 대한 오해
    • 부적절한 이름 때문에 SOLID 원칙 중에서 그 의미가 가장 잘 전달되지 못한 원칙
    • 모든 모듈이 단 하나의 일만 해야 한다는 의미로 받아들이기 쉬움
      • 해당 원칙은 따로 있음 - 함수는 반드시 하나의, 단 하나의 일만 해야 한다는 원칙
      • 이 원칙은 커다란 함수를 작은 함수들로 리팩터링하는 더 저수준에서 사용됨
      • 이 원칙은 SOLID 원칙이 아니며, SRP도 아님
    • 진정한 SRP의 의미: 단일 모듈은 변경의 이유가 하나, 오직 하나뿐이어야 한다.  
  • SRP의 다른 버전
    • 소프트웨어 시스템은 사용자와 이해관계자를 만족시키기 위해 변경됨
    • SRP가 말하는 변경의 이유란 바로 이들 사용자와 이해관계자를 가르킴
    • SRP의 다른 버전: 하나의 모듈은 하나의, 오직 하나의 사용자 또는 이해관계자에 대해서만 책임져야 한다.  
  • SRP의 최종 버전
    • 사용자나 이해관계자라는 단어는 두 명 이상일 경우 여기에 쓰기에는 올바르지 않음
    • 여기에서는 집단, 즉 해당 변경을 요청하는 한 명 이상의 사람들을 가르킴 - 이러한 집단은 액터
    • SRP의 최종버전: 하나의 모듈은 하나의, 오직 하나의 액터에 대해서만 책임져야 한다.
  • 모듈이란?
    • 가장 단순한 정의는 소스 파일
    • 코드를 소스 파일에 저장하지 않는 경우
      • 단순히 함수와 데이터 구조로 구성된 응집된 결합
  • 응집
    • 응집된(cohesive)이라는 단어가 SRP를 암시
    • 단일 액터를 책임지는 코드를 함께 묶어주는 힘이 바로 응집성(cohesion)
    • SRP를 이해하는 가장 좋은 방법은 이 원칙을 위반하는 징후들을 살펴보는 것

 

[징후 1: 우발적 중복]

  • 급여 애플리케이션의 Employee
    • 해당 클래스가 세 가지 메서드 calculatePay(), reportHours(), save()를 가짐
      • calculatePay(): 회계팀에서 기능을 정의, CFO 보고를 위해 사용
      • reportHours(): 인사팀에서 기능을 정의, COO 보고를 위해 사용
      • save(): DBA가 기능을 정의, CTO 보고를 위해 사용
    • 이들 세 가지 메서드가 서로 매우 다른 세명의 액터를 책임지기 때문에 SRP를 위반
    • 단일 클래스에 세 액터가 서로 결합되었고, 이로 인해 다른 팀에서 결정한 조치가 다른팀이 의존하는 무언가에 영향을 줄 수 있음
  • 문제의 원인
    • 서로 다른 액터가 의존하는 코드를 너무 가까이 배치했기 때문에 발생
    • SRP는 서로 다른 액터가 의존하는 코드를 서로 분리하라고 말함

 

[징후 2: 병합]

  • 문제의 발생
    • CTO팀에 속한 DBA는 테이블 스키마를 수정하고, COO팀에 속한 인사 담당자는 보고서 포맷을 변경하기로 결정
    • 서로 다른 팀의 개발자가 변경사항을 적용하면 병합이 발생
    • 발생한 병합은 CTO와 COO를 곤경에 빠뜨리고 CFO도 영향을 받게 됨
  • 문제의 원인
    • 많은 사람이 서로 다른 목적으로 동일한 소스 파일을 변경하는 경우에 해당
    • 이 문제를 벗어나는 방법은 서로 다른 액터를 뒷받침하는 코드를 서로 분리하는 것

 

[해결책]

  • 문제의 해결책
    • 다양한 해결책 모두가 메서드를 각기 다른 클래스로 이동시키는 방식
    • 가장 확실한 해결책은 데이터와 메서드를 분리하는 방식
      • 아무런 메서드가 없는 간단한 데이터 구조인 EmployeeData 클래스를 만들고 세 개의 클래스가 공유하도록 함
      • 각 클래스는 자신의 메서드에 반드시 필요한 소스 코드만을 포함
      • 세 클래스는 서로의 존재를 몰라야 함 - 우연한 중복을 피할 수 있음
  • 퍼사드(Facade) 패턴 
    • 이 해결책은 세 가지 클래스를 인스턴스화하고 추적해야하 한다는 단점이 존재
    • 이러한 난관에서 빠져나올때 흔히 쓰는 기법이 퍼사드 패턴
      • 코드가 거의 없는 EmployeeFacade를 생성, 세 클래스의 객체를 생성하고, 요청된 메서드를 가지는 객체로 위임하는 일을 책임짐
      • 가장 중요한 업무 규칙을 데이터와 가깝게 배치하는 방식도 존재
        • 가장 중요한 메서드는 기존의 Employee 클래스에 그대로 유지하되, Employee 클래스를 덜 중요한 나머지 메서드들에 대한 퍼사드로 사용할 수 있음

 

[결론]

  • SRP는 메서드와 클래스 수준의 원칙
  • 상위의 두 수준에서도 다른 형태로 다시 등장
    • 컴포넌트 수준에서는 공통 폐쇄 원칙(Common Closure Principle)
    • 아키텍처 수준에서는 아키텍처 경계(Architectural Boundary)의 생성을 책임지는 변경의 축(Axis of Change)

 

 

 

 

8장. OCP: 개방-폐쇄 원칙


  • OCP의 의미
    • 소프트웨어 개체(artifact)는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.
    • 다시 말해 소프트웨어 개체의 행위는 확장할 수 있어야 하지만, 이때 개체를 변경해서는 안 된다.
    • 소프트웨어 아키텍처를 공부하는 가장 근본적인 이유

 

[사고 실험]

  • 재무제표 
    • 재무제표를 웹 페이지로 보여주는 시스템에서 동일한 정보를 보고서 형태로 변환해서 출력을 요청
    • 소프트웨어 아키텍처가 훌륭하다면 변경되는 코드의 양이 가능한 한 최소화. 이상적인 변경량은 0
  • 변경량을 최소화하는 방법
    • 서로 다른 목적으로 변경되는 요소를 적절하게 분리하고(SRP), 이들 요소 사이의 의존성을 체계화함으로써(DIP) 변경량을 최소화할 수 있음
  • 컴포넌트 계층구조의 조직화
    • 아키텍트는 기능이 어떻게, 왜, 언제 발생하는지에 따라서 기능을 분리하고, 분리한 기능을 컴포넌트의 계층구조로 조직화함
    • 이와 같이 조직화하면 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있음

 

[결론]

  • OCP의 목표
    • OCP는 시스템의 아키텍처를 떠받치는 원동력 중 하나
    • 시스템을 확장하기 쉬운 동시에 변경으로 인해 시스템이 너무 많은 영향을 받지 않도록 하는데 있음
    • 이러한 목표를 달성하려면 시스템을 컴포넌트 단위로 분리하고, 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있는 형태의 의존성 계층구조가 만들어지도록 해야 함

 

 

 

 

9장. LSP: 리스코프 치환 원칙


 

  • 리스코프의 하위 타입에 대한 정의
    • 여기에서 필요한 것은 다음과 같은 치환(substitution) 원칙이다. S 타입의 객체 o1 각각에 대응하는 T 타입 객체 o2가 있고, T 타입을 이용해서 정의한 모든 프로그램 P에서 o2의 자리에 o1을 치환하더라도 P의 행위가 변하지 않는다면, S는 T의 하위 타입이다.

 

[정사각형/직사각형 문제]

  • LSP를 위반하는 전형적인 문제
    • 만약 Rectangle의 하위 타입으로 Square가 존재하면 Square는 하위 타입으로 적합하지 않음
      • Rectangle의 높이와 너비는 서로 독립적으로 변경될 수 있는 반면, Square의 높이와 너비는 반드시 함께 변경되기 때문
    • 이런 형태의 LSP 위반을 막기 위한 유일한 방법은 Rectangle이 실제로는 Square인지를 검사하는 메커니즘을 User에 추가하는 것
      • 이렇게 하면 User의 행위가 사용하는 타입에 의존하게 되므로, 타입을 서로 치환할 수 없게 됨

 

[LSP와 아키텍처]

  • LSP 변천사
    • 객체 지향 초창기, LSP는 상속을 사용하도록 가이드하는 방법 정도로 간주
    • 시간이 지나면서 LSP는 인터페이스와 구현체에도 적용되는 더 광범위한 소프트웨어 설계 원칙으로 변모
  • LSP의 이해
    • 아키텍처 관점에서 이해하는 최선의 방법은 이 원칙을 어겼을 때 시스템 아키텍처에서 무슨 일이 일어나느지 관찰하는 것

 

[결론]

  • LSP는 아키텍처 수준까지 확장할 수 있고, 반드시 확장해야만 함

 

 

 

 

10장. ISP: 인터페이스 분리 원칙


  • ISP의 유래
    • 다수의 사용자가 한 클래스의 각 하위 메서드를 사용한다면 다른 메서드를 사용하지 않음에도 다른 메서드레 의존하게 됨
    • 이러한 문제는 인터페이스 단위로 분리해서 해결 가능

 

[ISP와 아키텍처]

  • ISP를 사용하는 동기
    • 일반적으로, 필요 이상으로 많은 걸 포함하는 모듈에 의존하는 것은 해로운 일
    • 소스 코드 의존성의 경우, 불필요한 재컴파일과 재배포를 강제하기 때문
    • 더 고수준인 아키텍처 수준에서도 마찬가지 상황이 발생

 

[결론]

  • 교훈
    • 불필요한 짐을 실은 무언가에 의존하면 예상치 못한 문제에 빠진다는 사실

 

 

 

 

11장. DIP: 의존성 역전 원칙


  • 유연성이 극대화된 시스템
    • 소스 코드 의존성이 추상(abstraction)에 의존하며 구체(concretion)에는 의존하지 않는 시스템
    • 정적 타입 언어
      • use, import, include 구문은 오직 인터페이스나 추상 클래스 같은 추상적인 선언만을 참조해야 한다는 뜻
    • 동적 타입 언어
      • 소스 코드 의존 관계에서 구체 모듈은 참조해서는 안된다
  • 비현실적인 아이디어
    • 소프트웨어 시스템이라면 구체적인 많은 장치에 반드시 의존
    • 구체 클래스에 대한 소스 코드 의존성은 벗어날 수 없고, 벗어나서도 안 됨
    • 안정적인 클래스는 변경될 일이 거의 없으며, 있더라도 엄격하게 통제됨
    • 이러한 이유로 DIP를 논할 때 운영체제나 플랫폼 같이 안정성이 보장된 환경에 대해서는 무시하는 편
  • 의존을 피해야 하는 것
    • 변동성이 큰(volatile) 구체적인 요소
    • 이 구체적인 요소는 우리가 개발하는 중이라 자주 변경될 수 밖에 없는 모듈들

 

[안정된 추상화]

  • 인터페이스와 구현체의 변동성
    • 추상 인터페이스에 변경이 생기면 이를 구체화된 구현체들도 따라서 수정해야 한다.
    • 반대로 구체적인 구현체에 변경이 생기더라도 그 구현체가 구현하는 인터페이스는 대다수의 경우 변경될 필요가 없음
    • 따라서 인터페이스는 구현체보다 변동성이 낮음
  • 안정된 소프트웨어 아키텍처
    • 변동성이 큰 구현체에 의존하는 일은 지양하고, 안정된 추상 인터페이스를 선호하는 아키텍처
  • 구체적인 코딩 실천법
    • 변동성이 큰 구체 클래스를 참조하지 말라.
      • 대신 추상 인터페이스를 참조
      • 이 규칙은 객체 생성 방식을 강하게 제약하며, 일반적으로 추상 팩토리를 사용하도록 강제함
    • 변동성이 큰 구체 클래스로부터 파생하지 말라.
      • 이전 규칙의 따름정리
      • 정적 타입 언어에서 상속은 모든 관계 중에서 가장 강력한 동시에 뻣뻣해서 사용하기 어려움
      • 따라서 상속은 아주 신중하게 사용해야 함
    • 구체 함수를 오버라이드 하지 말라.
      • 대체로 구체 함수는 소스 코드 의존성을 필요로 함
      • 따라서 구체 함수를 오버라이드 하면 이러한 의존성을 제거할 수 없게 되며, 실제로는 그 의존성을 상속하게 됨
      • 차라리 추상 함수로 선언하고 구현체들에서 각자의 용도에 맞게 구현해야 함
    • 구체적이며 변동성이 크다면 절대로 그 이름을 언급하지 말라.
      • DIP 원칙을 다른 방식으로 풀어쓴 것

 

[팩토리]

  • 주의점
    • 변동성이 큰 구체적인 객체는 특별히 주의해서 생성해야 함
    • 사실상 모든 언어에서 객체를 생성하려면 해당 객체를 구체적으로 정의한 코드에 대해 소스 코드 의존성이 발생하기 때문
    • 자바 등 대다수의 객체 지향 언어에서 이처럼 바람직하지 못한 의존성을 처리할 때 추상 팩토리를 사용하곤 함
  • 아키텍처 경계
    • 추상 팩토리는 구체적인 것들과 추상적인 것들을 분리함
    • 소스 코드 의존성은 분리할 때 모두 한 방향, 즉 추상적인 쪽으로 향함
  • 두 가지 컴포넌트
    • 추상 컴포넌트
      • 애플리케이션의 모든 고수준 업무 규칙을 포함
    • 구체 컴포넌트
      • 업무 규칙을 다루기 위해 필요한 모든 세부사항을 포함

 

[구체 컴포넌트]

  • 피할 수 없는 DIP 위배
    • DIP 위배를 모두 없앨 수 없음
    • 하지만 DIP를 위배하는 클래스들은 적은 수의 구체 컴포넌트 내부로 모을 수 있고, 시스템의 나머지 부분과는 분리할 수 있음
    • 대표적인 예시로 main 함수

 

[결론]

  • 고수준의 아키텍처 원칙을 다루게 되면서 DIP는 몇 번이고 계속 등장
  • DIP는 아키텍처 다이어그램에서 가장 눈에 드러나는 원칙

로머트 C. 마틴의 Clean Architecture 책을 읽고 정리한 내용입니다.

 

3장. 패러다임 개요


  • 세 가지 패러다임
    • 구조적 프로그래밍(structured programming)
    • 객체지향 프로그래밍(object-oriented programming)
    • 함수형 프로그래밍(functional programming)

 

[구조적 프로그래밍]

  • 최초로 적용된 패러다임
  • 제어흐름의 직접전인 전환에 대해 규칙을 부과

 

[객체 지향 프로그래밍]

  • 제어흐름의 간접적인 전환에 대해 규칙을 부과

 

[함수형 프로그래밍]

  • 가장 먼저 만들어졌지만 최근에 들어서야 도입 시작
  • 할당문에 대해 규칙을 부과

 

[생각할 거리]

  • 각 패러다임은 프로그래머에게서 권한을 박탈
  • 새로운 권한을 부여하지 않음
  • 부정적인 의도를 가지는 일종의 추가적인 규칙을 부과
  • 무엇을 해야할지를 말하기보다는 무엇을 해서는 안 되는지를 말해줌
  • 세 가지 패러다임 각각은 goto문, 함수 포인터, 할당문을 앗아감
  • 프로그래밍 패러다임은 앞으로도 딱 세가지 밖에 없을 것

 

[결론]

  • 아키텍처 경계를 넘나들기 위한 메커니즘으로 다형성을 이용
  • 함수형 프로그래밍을 이용하여 데이터의 위치와 접근 방법에 대해 규칙을 부과
  • 모듈의 기반 알고리즘으로 구조적 프로그래밍을 사용

 

 

 

 

4. 구조적 프로그래밍


[증명]

  • 데이크스트라의 문제 해결
    • 증명이라는 수학적인 원리를 적용하여 문제를 해결하고자 함
      • 프로그래머는 입증된 구조를 이용하고, 이들 구조를 코드와 결합시키며, 코드가 올바르다는 사실을 스스로 증명하게 되는 방식
    • goto 문장이 모듈을 더 작은 단위로 재귀적으로 분해하는 과정에 방해가 되는 경우가 있다는 사실을 발견
    • goto문의 좋은 사용방식은 if/then/else와 do/while과 같은 분기와 반복이라는 단순한 제어구조에 해당한다는 사실을 발견
  • 구조적 프로그래밍의 탄생
    • 이러한 제어 구조는 순차실행과 결합했을 때 특별하다는 사실을 깨달음
    • 이 발견은 모듈을 증명 가능하게 하는 제어 구조가 모든 프로그램을 만들 수 있는 제어 구조의 최소 집합과 동일하다는 사실을 증명

 

[해로운 성명서]

  • goto문의 해로움
    • 데이크스트라는 세 가지 제어 구조에 대한 의견을 피력
    • 많은 논쟁이 있었으나 결국 goto 문장은 사라지게 됨
    • 현재 우리는 모두 구조적 프로그래머
      • 제어흐름을 제약 없이 직업 전활할 수 있는 선택권 자체를 언어에서 제공하지 않기 때문

 

[기능적 분해]

  • 모듈을 증명 가능한 더 작은 단위로 재귀적으로 분해할 수 있게 되었고, 기능적으로 분해할 수 있음을 뜻함
  • 프로그래머는 대규모 시스템을 모듈과 컴포넌트로 나눌 수 있음
  • 모듈과 컴포넌트는 입증할 수 있는 아주 작은 기능들로 세분화 할 수 있음

 

[테스트]

  • 테스트는 프로그램이 잘못되었음을 증명할 수 있지만, 프로그램이 맞다고 증명할 수는 없음
  • 소프트웨어 개발은 수학적인 시도가 아니라 과학과 같음. 올바르지 않음을 증명하는 데 실패함으로써 올바름을 보여주기 때문

 

[결론]

  • 반증 가능한 단위를 만들어 낼 수 있는 능력
    • 구조적 프로그래밍이 오늘날까지 가치 있는 이유
    • 또한 현대적 언어가 아무런 제약 없는 goto 문장은 지원하지 않는 이유
    • 뿐만 아니라 아키텍처 관점에서는 기능적 분해를 최고의 실천법 중 하나로 여기는 이유
  • 소프트웨어는 과학과 같고, 반증 가능성에 의해 주도됨
  • 모듈, 컴포넌트, 서비스가 쉽게 반증 가능하도록 만들기 위해 분주히 노력해야 함

 

 

 

 

객체 지향 프로그래밍


  • OO란 무엇인가?
    • 데이터와 함수의 조합
      • 그다지 만족스러운 대답은 아님
      • o.f()가 f(o)가 다르다는 의미를 내포하는데, 이미 훨씬 이전부터 데이터 구조를 함수에 전달했음
    • 실제 세계를 모델링하는 새로운 방법
      • 얼버무리는 수준
      • 의도조차도 불분명하며 그 정의가 모호함
    • 세 가지 주문
      • 캡슐화, 상속, 다형성
      • OO가 이 세 가지 개념을 적절하게 조합한 것이거나 OO 언어는 최소한 세 가지 요소를 반드시 지원해야 한다고 말함

 

[캡슐화?]

  • 언급하는 이유
    • 데이터와 함수를 쉽고 효과적으로 캡슐화하는 방법을 OO 언어가 제공하기 때문
    • 이를 통해 데이터와 함수가 응집력 있게 구성된 집단을 서로 구분 짓는 선을 그을 수 있음
    • 하지만 이러한 개념이 OO 에만 국한된 것은 아님
  • OO언어의 캡슐화
    • C는 헤더와 구현체를 분리하는 방식으로 완벽한 캡슐화를 제공했음
    • 하지만 자바와 C#은 이 방식을 버렸고, 캡슐화는 심하게 훼손되었음
    • 하지만 많은 OO 언어가 캡슐화를 거의 강제하지 않음
    • OO 프로그래밍은 프로그래머가 충분히 올바르게 행동함으로써 캡슐화된 데이터를 우회해서 사용하지 않을 거라는 믿음을 기반으로 함

 

[상속?]

  • OO의 상속
    • OO 언어가 더 나은 캡슐화를 제공하지는 못했지만, 상속만큼은 OO 언어가 확실히 제공
    • 하지만 상속이란 단순히 어떤 변수와 함수를 하나의 유효 범위로 묶어서 재정의하는 일
    • OO 언어가 고안되기 훨씬 이전에도 상속과 비슷한 기법이 사용되었으나 상속만큼 편리한 방식은 절대 아님
    • OO 언어가 완전히 새로운 개념을 만들지는 못했지만, 일을 상당히 편리한 방식으로 제공
  • 점수
    • 캡슐화에 대해서는 OO에 점수를 줄 수 없음
    • 상속에 대해서만 0.5점 점도를 부여
    • 그저 그런 점수지만 고려해야 할 속성이 하나 더 남음

 

[다형성?]

  • OO의 다형성
    • OO 언어가 있기 이전에도 다형성을 표현할 수 있었음
    • OO가 새롭게 만든 것은 전혀 없지만, 다형성을 좀 더 안전하고 더욱 편리하게 사용할 수 있게 해줌
    • OO 언어는 위험한 함수 포인터를 지켜야 하는 관례를 없애주며 실수 할 위험이 없음
    • 이러한 이유로 OO는 제어흐름을 간접적으로 전환하는 규칙을 부과한다고 결론지을 수 있음
  • 다형성이 가진 힘
    • 플러그인 아키텍처
      • 입출력 장치 독립성을 지원하기 위해 만들어짐
      • 등장 이후 거의 모든 운영체제에서 구현되었으나 함수를 가리키는 포인터를 사용하면 위험을 수반하기 때문에 직접 작성하는 프로그램에서는 이러한 개념을 확장하여 적용하지 않음
      • OO의 등장으로 언제 어디서든 적용할 수 있게 되었음
    • 의존성 역전
      • 전형적인 호출 트리
        • main 함수가 고수준 함수를 호출하고 고수준 함수는 중간 수준 함수를 호출, 중간 수준 함수는 저수준 함수를 호출
        • 소스 코드 의존성의 방향은 반드시 제어흐름을 따르게 됨
        • 즉, 제어흐름은 시스템의 행위에 따라 결정되며, 소스 코드 의존성은 제어흐름에 따라 결정됨
      • 다형성의 등장 이후
        • 인터페이스를 통해 소스 코드 의존성이 제어 흐름과는 반대가 되는데, 이를 의존성 역전이라고 함
        • 소프트웨어 아키텍트는 시스템의 소스 코드 의존성 전부에 대해 방향을 결정할 수 있는 절대적인 권한을 갖음
      • 배포 독립성과 개발 독립성
        • 업무 규칙이 데이터베이스와 UI에 의존하는 대신 의존성을 반대로 배치하여 데이터베이스와 UI가 업무 규칙에 의존하게 만들 수 있음
        • 결과적으로 업무 규칙, UI, 데이터베이스는 세 가지로 분리된 컴포넌트 또는 배포 가능한 단위로 컴파일할 수 있음
        • 배포 독립성
          • 특정 컴포넌트의 소스 코드가 변경되면, 해당 코드가 포함된 컴포넌트만 다시 배포하면 됨
        • 개발 독립성
          • 시스템의 모듈을 독립적으로 배포할 수 있게 되면, 서로 다른팀에서 각 모듈을 독립적으로 개발할 수 있음
    • 결론
      • 소프트웨어 아키텍트에게 OO란?
        • 다형성을 이용하여 전체 시스템의 모든 소스 코드 의존성에 대한 절대적인 제어 권한을 획득할 수 있는 능력
        • 플러그인 아키텍처를 구성할 수 있고, 이를 통해 고수준의 정책을 포함하는 모듈은 저수준의 세부사항을 포함하는 모듈에 대해 독립성을 보장할 수 있음
        • 저수준의 세부사항은 중요도가 낮은 플러그인 모듈로 만들 수 있고, 고수준의 정책을 포함하는 모듈과는 독립적으로 개발하고 배포할 수 있음

 

 

 

 

함수형 프로그래밍


[정수를 제곱하기]

  • 가변 변수는 프로그램 실행 중에 상태가 변할 수 있음
  • 함수형 프로그래밍은 가변 변수가 전혀 없음
  • 함수형 언어에서 변수는 변경되지 않음

 

[불변성과 아키텍처]

  • 변수의 가변성을 염려하는 이유
    • 경합 조건, 교착상태 조건, 동시 업데이트 문제가 모두 가변 변수로 인해 발생
    • 어떠한 변수도 갱신되지 않는다면 경합 조건이나 동시 업데이트 문제가 일어나지 않음
    • 락이 가변적이지 않다면 교착상태도 일어나지 않음
    • 불변성은 실현 가능하지만 일종의 타협을 해야 함

 

[가변성의 분리]

  • 주요한 타협
    • 애플리케이션 또는 애플리케이션 내부의 서비스를 가변 컴포넌트와 불변 컴포넌트로 분리하는 일
    • 불변 컴포넌트에서는 순수하게 함수형 방식으로 작업, 어떤 가변 변수도 사용하지 않음
    • 변수의 상태를 변경할 수 있는 (순수 함수형 컴포넌트가 아닌) 하나 이상의 다른 컴포넌트와 서로 통신
  • 애플리케이션의 구조화
    • 제대로 하기위해서는 변수를 변경하는 컴포넌트와 변경하지 않는 컴포넌트를 분리리해야 함
    • 이를 위해 가변 변수들을 보호하는 적절한 수단을 동원해 뒷받침해야 함

 

[이벤트 소싱]

  • 기본 발상
    • 상태가 아닌 트랜잭션을 저장하자는 전략
    • 상태가 필요해지면 단순히 상태의 시작점부터 모든 트랜잭션을 처리
    • 저장 공간과 처리 능력이 충분하면 애플리케이션이 완전한 불변성을 갖도록 만들 수 있음

 

[결론]

  • 요약
    • 구조적 프로그래밍은 제어흐름의 직접적인 전환에 부과되는 규율
    • 객체 지향 프로그래밍은 제어흐름의 간접적인 전환에 부과되는 규율
    • 함수형 프로그래밍은 변수 할당에 부과되는 규율
  • 소프트웨어의 핵심
    • 순차, 분기, 반복, 참조로 구성됨. 그 이상도 이하도 아님

로머트 C. 마틴의 Clean Architecture 책을 읽고 정리한 내용입니다.

 

1장. 설계와 아키텍처란?


  • 설계와 아키텍처의 정의
    • 아키텍처 : 저수준의 세부사항과는 분리된 고수준의 무언가
    • 설계 : 저수준의 구조 도는 결정사항 등을 의미
  • 설계와 아키텍처의 관계
    • 저수준의 설계와 고수준의 아키텍처는 모두 소프트웨어의 전체 설계의 구성요소
    • 단절없이 이어지며 이를 통해 대상 시스템의 구조를 정의
    • 개별로는 존재할 수 없고 이 둘을 구분 짓는 경계는 뚜렷하지 않음
    • 고수준에서 저수준으로 향하는 의사결정의 연속성만이 있음

 

[목표는?]

  • 소프트웨어 아키텍처의 목표
    • 필요한 시스템을 만들고 유지보수하는 데 투입되는 인력을 최소화하는 데 있다.
  • 좋은 설계란?
    • 설계 품질을 재는 척도는 고객의 요구를 만족시키는 데 드는 비용을 재는 척도
    • 이 비용이 낮을 뿐만 아니라 시스템의 수명이 다할 때까지 낮게 유지할 수 잇다면 좋은 설계라고 할 수 있음.
    • 새로운 기능을 출시할 때마다 비용이 증가한다면 나쁜 설계

 

[사례 연구]

  • 실제 데이터로 보는 사례
    • 주요 소프트웨어 출시 때마다 엔지니어링 직원 수가 꾸준히 증가
    • 하지만 동일한 기간의 생산성은 한 곳으로 수렴하는 것처럼 보임
    • 동일한 기간의 코드 라인당 비용도 엄청난 상승
    • 이러한 비용 곡선은 사업 모델의 수익을 엄청나게 고갈시키며, 회사의 성장을 멈추게 하거나 심지어는 완전히 망하게 만듬
    • 이는 엉망진창이 되어 가는 신호
  • 토끼와 거북이
    • 토끼는 타고난 빠르기를 과신한 나머지 경주를 심각하게 받아들이지 않아 낮잠을 자버리고, 거북이에게 패배
    • 토끼 개발자
      • 잠을 자는 것이 아니라 뼈 빠지게 일하지만 훌륭하고 깔끔하게 잘 설계된 코드가 중요하다는 사실을 알고 있는 뇌가 잠자고 있다.
      • 절대 수그러들지 않는 시장의 압박에 시장 출시가 먼저라며 코드 정리를 미룬다.
      • 토끼가 빠르기를 과신한 것처럼 생산성을 유지할 수 있다고 과신한다.
      • '지저분한 코드를 작성하면 빠르게 갈 수 있고 장기적으로 볼 때만 생산성이 낮아진다.라는 거짓말에 속는다.
  • 소프트웨어의 단순한 진리
    • 빨리 가는 유일한 방법은 제대로 가는 것이다.
    • 자신을 과신한다면 재설계하더라도 원래의 프로젝트와 똑같이 엉망으로 내몰린다.

 

[결론]

  • 개발 조직의 최고의 선택지
    • 조직에 스며든 과신을 인지하여 방지
    • 소프트웨어 아키텍처의 품질을 심각하게 고민하기 시작
      • 좋은 소프트웨어 아키텍처가 무엇인지 이해해야 함
      • 비용은 최소화하고 생산성은 최대화할 수 있는 설계와 아키텍처를 가진 시스템을 만들려면 시스템 아키텍처가 지닌 속성을 알고 있어야 함

 

 

 

 

2장. 두 가지 가치에 대한 이야기


  • 이해관계자의 두 가지 가치
    • 소프트웨어 시스템은 행위(behavior)와 구조(structure)라는 두 가지 가치를 이해관계자에게 제공
    • 소프트웨어 개발자는 두 가치 모두 반드시 높게 유지해야 하는 책임을 짐
    • 하지만 한 가치 가치에만 집중하고 나머지 가치는 배제하곤 함. 결국에는 시스템이 쓸모없게 되버림

 

[행위]

  • 프로그래머를 고용하는 이유
    • 이해관계자를 위해 기계가 수익을 창출하거나 비용을 절약하도록 만들기 위함
    • 이해관계자가 기능 명세서나 요구사항 문서를 구체화할 수 있도록 도움
    • 이해관계자의 기계가 이러한 요구사항을 만족하도록 코드를 작성
    • 만약 기계가 요구사항을 위반한다면, 디버거를 열고 문제를 고침
    • 많은 프로그래머가 이러한 활동이 전부라고 생각하지만 그들은 틀렸음.

 

[아키텍처]

  • 소프트웨어란?
    • 부드러운(soft)와 제품(ware)이라는 단어의 합성어
  • 소프트웨어의 본연의 목적
    • 반드시 부드러워야 하며 변경하기 쉬워야 한다.
    • 변경사항을 적용하는 데 드는 어려움은 변경되는 범위(scope)에 비례해야 하며, 변경사항의 형태(shape)와는 관련이 없어야 한다.
  • 소프트웨어 개발 비용 증가의 주된 요인
    • 변경사항의 범위와 형태의 차이에 있음
    • 개발 비용은 요청된 변경사항의 크기에 비례
    • 시스템의 형태와 요구사항의 형태가 서로 맞지 않으면 새로운 요청사항이 발생할 때마다 조금 더 힘들어짐
  • 문제의 원인
    • 시스템의 아키텍처가 문제
    • 아키텍처가 특정 형태를 다른 형태보다 선호하면 할수록, 새로운 기능을 이 구조에 맞추는 게 더 힘들어 짐
    • 따라서 아키텍처는 형태에 독립적이어야 하고, 그럴수록 더 실용적

 

[더 높은 가치]

  • 동작하는 프로그램 vs 변경이 쉬운 프로그램
    • 업무 관리자에게 묻는다면 동작하는 것이 더 중요하다고 얘기하며, 개발자는 대체로 동조하는 태도를 취함
    • 아래와 같은 양 극단의 사례를 검토하는 방식으로 반박가능
      • 완벽하게 동작하지만 수정이 불가능하다면 프로그램의 요구사항 변경 시 동작하지 않게 되고 결국 프로그램이 돌아가도록 만들 수 없게 된다.
      • 동작은 하지 않지만 변경이 쉬운 프로그램을 준다면 프로그램이 돌아가도록 만들 수 있고, 변경사항이 발생하더라도 여전히 동작하도록 유지보수할 수 있다.
    • 변경이 불가능한 프로그램은 존재하지 않지만 현실적으로 분가능한 시스템은 존재
      • 변경에 드는 비용이 변경으로 창출되는 수익을 초과하는 경우

 

[아이젠하워 매트릭스]

  • 드와이트 D. 아이젠하워가 고안한 매트릭스
    • 내겐 두 가지 유형의 문제가 있습니다. 하나는 긴급하며, 다른 하나는 중요합니다. 긴급한 문제는 중요하지 않으며, 중요한 문제는 절대 긴급하지 않습니다.
    • 중요함
      긴급함
      중요함
      긴급하지 않음
      중요하지 않음
      긴급함
      중요하지 않음
      긴급하지 않음
  • 소프트웨어의 매트릭스
    • 소프트웨어의 첫 번째 가치인 행위는 긴급하지만 매번 높은 중요도를 가지는 것은 아님
    • 소프트웨어의 두 번째 가치인 아키텍처는 중요하지만 즉각적인 긴급성을 필요로 하는 경우는 절대 없음
    • 우선순위는 다음과 같이 매길 수 있음
      • 1. 긴급하고 중요한
      • 2. 긴급하지는 않지만 중요한
      • 3. 긴급하지만 중요하지 않은
      • 4. 긴급하지도 중요하지도 않은
    • 아키텍처는 1,2번에, 행위는 1,3번에 위치함
  • 업무 관리자와 개발자의 흔한 실수
    • 긴급하지만 중요하지 않은 항목을 첫 번째로 격상시켜 버림
    • 긴급하지만 중요하지 않은 기능과 진짜로 긴급하면서 중요한 기능을 구분하지 못함
    • 업무 관리자는 보통 아키텍처의 중요성을 평가할 만한 능력을 겸비하지 못하지때문에 이것을 해결하기 위해 소프트웨어 개발팀은 마땅히 책임져야함

 

[아키텍처를 위해 투쟁하라] 

  • 가치를 위한 투쟁
    • 개발팀은 회사에서 가장 중요하다고 스스로 믿는 가치를 위해 투쟁
    • 관리팀도 자신만의 가치를 위해 투쟁하며, 마케팅팀, 영업팀, 운영팀 또한 마찬가지
    • 효율적인 소프트웨어 개발팀은 이러한 투쟁에서 정면으로 맞서 싸움
    • 소프트웨어를 안전하게 보호해야 할 책임이 있는 소프트웨어 개발자도 이해관계자
  • 소프트웨어 아키텍트의 역할
    • 시스템이 제공하는 특성이나 기능보다는 시스템의 구조에 더 중점을 둠
    • 이러한 특성과 기능을 개발하기 쉽고, 간편하게 수정할 수 있으며, 확장하기 쉬운 아키텍처를 만들어야 함
  • 아키텍처가 후순위가 된다면...
    • 시스템을 개발하는 비용이 더 많이 들고, 일부 또는 전체 시스템에 변경을 가하는 일이 현실적으로 불가능해짐
    • 이는 소프트웨어 개발팀이 스스로 옳다고 믿는 가치를 위해 충분히 투쟁하지 않았다는 뜻

+ Recent posts