티스토리 뷰

etc

Clean Architecture 1/2

사용자 fistful 2019.09.10 20:34

Photo by Lance Anderson on Unsplash


개요

 

링크: http://aladin.kr/p/xM1l6

로버트 C 마틴 (Robert Cecil Martin, Uncle Bob) Clean Architecture 읽고 정리해본다.

 

회사 업무중 소프트웨어 아키텍처에 대한 실제적 고민이 많아진 시점에 만나게 책이라 그런지 읽고 많이 배웠다.

소프트웨어 아키텍처에 대한 원칙과 절차, 그리고 방법론을 명쾌히 주장하고 근거를 제시해주니 좋은 출발점이 된다.

 

 

1. 소개

 

좋은 소프트웨어 아키텍처를 위한 규칙은 유행을 타거나 발전하는게 아니라 보편적이며 변하지 않는 것이다.

소프트웨어 아키텍처의 목표는 적은 인원으로 개발, 유지 보수할 있는 시스템이다.

시스템을 동작하게 하는 보다, 시스템을 쉽게 변경할 있는 것이 중요하다.

 

구분

속의 문장과 나의 생각

옮긴이의

"지난 반세기 동안 하드웨어는 작아지고 빨라졌지만, 소프트웨어를 구성하는 것들은 조금도 바뀌지 않았다.

그렇기에 좋은 소프트웨어 아키텍처를 만드는 원칙은 보편적이며 시간이 지나더라도 변함이 없다."

서문

"아키텍처 규칙은 동일하다!"

"나는 소프트웨어 아키텍처의 규칙은 다른 모든 변수에 독립적이라는 결론을 내렸다"

"세월이 흘러도 변치 않는 규칙"

 

아키텍처의 규칙은 시간과 공간을 넘어서는 보편적인 것이라 한다. 유행을 타거나 발전하지 않는다는 .

그렇다면 한번 배워두면 두고두고 써먹을 있겠구나!

1. 소개

 

"소프트웨어를 올바르게 만드는 일은 어렵다."

 

누구나 동작하게 만들 수는 있지만 올바르게 만들기는 어렵다는 것이다.

올바르게만 만들면, 적은 인원으로 기능 추가, 유지보수가 가능하다.

1. 설계와 아키텍처란?

"소프트웨어 아키텍처의 목표 필요한 시스템을 만들고, 유지보수하는 투입되는 인력을 최소화 하는데 있다."

 

목표는 투입인력의 최소화이다.

 

(토끼와 거북이 우화의 잠든 토끼처럼)

"훌륭하고 깔끔하게 설계된 코드가 중요하다 사실을 알고 있는 바로 뇌가 잠자고 있다."

 

- 잘못된 생각1. 일단 출시하고 나중에 코드 정리하자

- 잘못된 생각2. 지저분한 코드는 당장은 빠르게 구현가능하다. 다만 장기적 생산성이 낮아진다.

"빨리가는 유일한 방법은 제대로 가는 것이다."

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

소프트웨어 시스템이 제공하는 가치 가지는 행위와 구조이다. Behavior and Architecture

 

행위 (Behavior): 기능명세나 요구사항의 구체화, 코딩 디버깅

구조 (Architecture): 소프트웨어는 그대로 소프트해야 한다.

- 기능에 대한 생각이 바뀌면, 변경사항이 어떤 형태 (Shape) 이건 손쉽게 바뀌어야 한다.

- 형태 (Shape) 에는 무관하고, 양적인 범위 (Scope) 따라 변경의 어려움이 결정되어야 한다.

- 좋은 구조가 아니면, 계속되는 변경사항에 점점 복잡해지고, 개발비용이 증가하게 된다.

"아키텍처는 형태에 독립적이어야 하고, 그럴수록 실용적이다."

 

행위는 "시스템이 동작하도록 만드는 "

구조는 "시스템을 쉽게 변경할 있도록 하는 " 훨씬 중요하다.

 

"아키텍처를 위해 투쟁하라"

"따라서 기능의 긴급성이 아닌 아키텍처의 중요성을 설득하는 일은 소프트웨어 개발팀이 마땅히 책임져야 한다. "

 

 

2. 벽돌부터 시작하기: 프로그래밍 패러다임

 

프로그래밍 패러다임은 세가지가 전부이며, 셋은 모두 하지 못하게 하는 것들이다. 새로운 권한이나 능력이 덧붙여진 것이 아니다.

결국 소프트웨어는 급격히 발전하는 기술이 아닌 것이다.

 

구분

속의 문장과 나의 생각

2. 벽돌부터 시작하기:

프로그래밍 패러다임

(튜링은) "프로그램을 단순히 데이터라고 이해한 최초의 사람이었다."

"패러다임이란 프로그래밍을 하는 방법"

(구조적, 객체지향, 함수형) "이들 가지 외의 패러다임은 존재하지 않을 것이다."

3. 패러다임 개요

구조적 프로그래밍: 제어흐름의 직접적인 전환에 대해 규칙을 부과한다.

- goto 빼앗아감. 코드를 아무대로나 뛰어넘지 못하게 것이다.

객체지향 프로그래밍: 제어흐름의 간접적인 전환에 대해 규칙을 부과한다.

- 함수 포인터 빼앗아감. 다형성을 통한 의존성 역전을 말한다. 표현은 충분히 이해가 안됨

함수형 프로그래밍: 할당문에 대해 규칙을 부과한다.

- 할당문 빼앗아감. 불변성을 준다는 것은 값을 할당하여 변경할 없다는 것이다.

 

" 패러다임은 프로그래머에게서 권한을 박탈한다."

", 패러다임은 무엇을 해야할 지를 말하기보다는 무엇을 해서는 안되는지를 말해준다."

 

이상 빼앗아 것이 없다. 따라서, 패러다임은 세가지 말고는 없다.

4. 구조적 프로그래밍

Structured Programming

"모든 프로그램을 순차, 분기, 반복 이라는 가지 구조만으로 표현할 있다는 사실을 증명했다."

- Sequence, Selection, Iteration goto 없이 표현 가능하다! goto 문을 없애도 된다!

 

구조적 프로그래밍은 goto 문을 없앤 프로그래밍이다.

결과로 모듈을 작은 단위로, 재귀적으로 분해할 있게 . (방해물인 goto 문의 제거)

 

프로그램의 코드가 올바르다는 증명은 어떻게 있는가?

1) 유클리드 공리구조를 이용한 증명은 실패

2) 과학적 방법을 이용한 반증으로 만족

- 열심히 반증을 하려고 노력했는데도 안된다면 "목표에 부합할 만큼은 충분히 "이다.

- 예를 들어, 절대 에러가 없는지는 모르겠지만 100대를 100만번 작동시켰는데도 괜찮으면 된거다.

 

구조적 프로그래밍은 이처럼 반증가능한 단위를 만들어낼 있는 능력 있다.

5. 객체 지향 프로그래밍

Object-Oriented Programming

OOP 무얼까?

- 데이터와 함수의 조합 아니다.

- 실제 세계의 모델링 아니다.

 

1) 캡슐화는 OOP만의 특성이 아니다 (Encapsulation)

2) 상속도 OOP만의 특성이 아니다 (Inheritance)

3) 핵심은 다형성이다 (Polymorphism)

"말하려는 요지는 함수를 가리키는 포인터를 응용한 것이 다형성이라는 점이다."

"OOP 언어를 사용하면 다형성은 대수롭지 않은 일이 된다."

"OOP의 등장으로 언제 어디서든 러그인 아키텍처를 적용할 수 있게 되었다."

 

다형성을 통해 의존성 역전 (dependency inversion) 가능하다.

"OOP 언어로 개발된 시스템을 다루는 소프트웨어 아키텍트는 시스템의 소스코드 의존성 전부에 대해

방향성을 결정할 수 있는 절대적인 권한을 갖는다."

"OOP 다형성을 이용하여 전체 시스템의 모든 소스 코드 의존성에 대한 절대적인 제어권한 획득할 있는 능력이다."

6. 함수형 프로그래밍

Functional Programming

"함수형 언어에서 변수는 변경되지 않는다."

"동시성 애플리케이션에서 마주치는 모든 문제, (중략) 가변 변수가 없다면 절대로 생기지 않는다."

- race condition, deadlock, concurrent update 등등

 

무한한 저장공간, 무한한 CPU 속도는 불가능하므로 타협이 필요하다.

- 가변 컴포넌트와 불변 컴포턴트를 분리한다.

"애플리케이션을 제대로 구조화하려면 변수를 변경하는 컴포넌트와 변경하지 않는 컴포넌트를 분리해야 한다는 것이다."

 

 

3. 설계 원칙

 

SOLID 유명한 원칙들을 관통하는 목적은 무엇일까?

아래에도 언급하지만, 변경하기 쉽고, 이해하기 쉬우며, 컴포넌트의 기반이 되도록 하는 것이다.

, 적은 인원으로 개발과 유지보수를 하는 것이다.

 

구분

속의 문장과 나의 생각

3. 설계원칙

좋은 소프트웨어 시스템의 시작은 Clean Code

SOLID 원칙을 준수하면 된다.

- "함수와 데이터 구조를 클래스로 배치하는 "

- "그리고 클래스를 서로 결합하는 방법"

 

SOLID 원칙의 목적

- "변경에 유연하다"

- "이해하기 쉽다"

- "많은 소프트웨어 시스템에 사용될 있는 컴포턴트의 기반이 된다."

 

여기서는 코드를 이야기하고 개념인 컴포넌트, 그리고 다음인 고수준 아키텍처를 이야기할 것임

 

1. Single Responsibility Principle: 소프트웨어 모듈의 변경 이유는 하나이다. ( 하나의 액터와 연관있어야 한다.)

2. Open-Closed Principle: 언제든 코드를 추가할 있고 (Open), 변경은 다른 녀석들에게 영향을 주지 않는다. (Closed)

3. Liskov Substitution Principle: 대체 가능한 구성요소는 치환 가능해야 한다.

4. Interface Segregation Principle: 자신이 사용하는 것만 의존한다.

5. Dependency Inversion Principle: 세부사항이 정책에 의존해야 한다. (Business Rule)

7. SRP: 단일 책임 원칙

예부터 들어보자. 직원들의 근무시간을 계산해주는 모듈이 있다.

모듈은 인사부 (액터1), 총무부(액터2) 액터를 위해서 동작해선 안되는 거다.

액터가 변경한 내용이 다른 액터에게 영향을 있다.

 

"단일 모듈은 변경의 이유가 하나, 오직 하나뿐이어야 한다."

= "하나의 모듈은 하나의 , 오직 하나의 사용자 또는 이해관계자에 대해서만 책임져야 한다."

= " 하나의 모듈은 하나의 , 오직 하나의 액터에 대해서만 책임져야 한다."

 

하나의 액터를 책임지는 코드들은 똘똘 뭉쳐있다. = 응집성(Cohesion) 생긴다

8. OCP: 개방-폐쇄 원칙

"소프트웨어 개체 (artifact)는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다."

"OCP 의 목표는 시스템을 확장하기 쉬운 동시에 변경으로 인해 시스템이 너무 많은 영향을 받지 않도록 하는 데 있다."

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

프로그램 P 에서

- T 타입인 o2 자리에 S 타입인 o1 넣더라도 (치환) P 행위가 변하지 않는다면

S T 하위 타입이다.

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

항상 최소한만 의존하도록 만들자는 것이다.

불필요한 기능을 포함한 녀석을 의존하면, 실제 상관 없는 부분이 변경되어도 재배포를 해야하는 상황이 된다.

하나의 거대한 인터페이스 보다는, 여러 개의 구체적인 인터페이스가 낫다.

11. DIP: 의존성 역전 원칙

"구체적인 대상에는 절대로 의존해서는 안된다."

비현실적이긴 하다. 최대한 노력한다고 이해하면 된다.

변할 가능성이 거의 없는 운영체제나 플랫폼은 봐준다.

 

"변동성이 큰 구체적인 클래스를 참조하지 말라."

"변동성이 큰 구체적인 클래스로부터 파생하지 말라."

"구체적인 함수를 오버라이드 하지 말라."

"구체적이며 변동성이 크다면 절대로 그 이름을 언급하지 말라."

 

4. 컴포넌트 원칙

 

비슷한 놈들끼리 똘똘 뭉치는 것과 관련한 원칙을 보자. (Cohesion)

 

REP 하나로 묶인 클래스와 모듈은 반드시 함께 릴리즈 해야 한다는 거다. 당연한 말인데 뭔가 느슨하게 느껴진다.

CCP 같은 이유로 변경되는 클래스를 같은 컴포넌트에 넣어두라는 것이다. 혹시 모르니깐 컴포넌트에 많이 넣어 수록 원칙을 지킬 있겠지?

CRP 컴포넌트 안에 있는 녀석들은 의존성으로 엮여 있는 넘들이어야 한다는 거다. 변경을 하는데 컴포턴트의 아주 일부만 영향을 받는다면 컴포넌트를 충분히 분리해놓지 않은거다.

 

컴포넌트들끼리 어떻게 결합되어야 하는지를 보자

 

ADP 의존성이 순환하면 안된다는 거다. A B 의존하고, B C, C A 의존하는 식은 안된다는 .

DIP 순환을 끊어줄 있고, 결국 의존성 그래프는 DAG (Directed Acyclic Graph) 된다.

SDP 의존성이 가리키는 방향으로 수록 컴포넌트가 의존하는 녀석들보다, 컴포넌트를 의존하는 녀석들이 많아야 한다는 거다.

이것 역시도 DIP 이용하여 = 추상 컴포넌트를 이용하여 SDP 위반 되는 부분을 해결할 있다.

SAP 안정된 컴포넌트는 추상 컴포넌트이고, 불안정한 컴포넌트는 구체 컴포넌트라는 것이다.

불안정한 정도와 추상화 정도를 이용하여 그래프를 그릴 있으며, 주계열에 얼마나 가까이 있는지로 컴포넌트의 건강성을 확인할 있다.

 

구분

속의 문장과 나의 생각

4. 컴포넌트 원칙

SOLID 원칙이 방에 벽돌을 배치하는 법이라면, 컴포넌트 원칙은 빌딩에 방을 배치하는 법이다.

좀더 개념이라는 것이다.

12. 컴포넌트

"컴포넌트는 시스템의 구성요소로 배포할 수 있는 가장 작은 단위다."

- 자바의 jar, 루비의 gem, 닷넷의 DLL

- 컴파일 언어의 경우 바이너리 파일의 결합체

- 인터프리터 언어의 경우 소스파일의 결합체

 

(컴퓨터의 HW 발전으로)

"오늘날에는 .jar 파일, DLL, 공유 라이브러리를 기존 애플리케이션에 플러그인 형태로 배포하는 것이 일상적인 일이 되었다."

"런타임에 플러그인 형태로 결합할 수 있는 동적 링크 파일이 이 책에서 말하는 소프트웨어 컴포넌트에 해당한다."

13. 컴포넌트 응집도

REP (Reuse/Release Equivalence Principle) 재사용/릴리스 등가 원칙

- 릴리스 번호가 할당되어야, 다음에 재사용할때에 어느 버전을 쓸지 아닌가?

- 릴리스 시에 관련 변경 정보 문서도 제공해줘야 한다.

- "하나의 컴포넌트로 묶인 클래스와 모듈은 반드시 함께 릴리스 있어야 한다."

- 같은 버전 번호, 추적 관리, 같은 릴리스 문서에 포함

- 원칙은 중요하지만 어딘가 두리뭉술하다 CCP   CRP 제약하여 엄격하게 만든다.

 

CCP (Common Closure Principle) 공통 폐쇄 원칙

- "동일한 이유로 동일한 시점에 변경되는 클래스를 같은 컴포넌트로 묶어라"

- "아니라면 다른 컴포넌트로 분리하라."

SRP 컴포넌트 버전이라 있다. 변경될 부분이 여러 컴포넌트에 분산되어 있는건 별루

OCP 에서 말하는 Closed Closure 밀접한 연관이 있다. 동일한 유형의 변경에 (하나의 컴포넌트 안에) 닫혀 있는 것이다.

 

CRP (Common Reuse Principle) 공통 재사용 원칙

- "같이 재사용되는 경향이 있는 클래스와 모듈들은 같은 컴포넌트에 포함해야 한다."

- 클래스 하나만 딸랑 재사용 되는 경우는 드물다.

- "컴포넌트 내부에서는 클래스들 사이에 수많은 의존성이 있으리라고 예상할 있다."

- ISP 컴포넌트 버전이라 있다. "필요치 않은 것에 의존하지 말라"

 

 

1) REP CCP 지킨다면 (전반적으로 컴포넌트가 커진다, CRP 경우는 컴포넌트가 작아진다)

= 하나의 컴포넌트에 속한 클래스, 모듈을 같이 릴리스하면서, 동일시점 동일 변경되는 녀석들을 한데 묶어두면

CRP 약화되어서, 불필요한 릴리스가 늘어난다

2) REP CRP 지킨다면

= 하나의 컴포넌트에 속한 클래스, 모듈을 같이 릴리스하면서, 함께 쓰이지 않는 녀석들은 가능한 나눠주면

잘게 나눠진 컴포넌트 때문에 하나의 변경에 여러 컴포넌트들을 수정해줘야 한다.

3) CCP CRP 지킨다면

= 동일시점 동일 변경되는 녀석들을 한데 묶어두면서, 함께 쓰이지 않는 녀석들은 가능한 나눠주면

재사용하기가 어려워진다. (이건 이해가 안된다)

 

개발 초기에는 삼각형의 오른쪽의 특성을 많이 살리다가 (=개발하다보니 자주 변경시키고 해야는 거다)

차차 왼쪽으로 옮겨간다 (= 배포하고, 관리하는게 많아지는것이겠다)

"실제로 수행하는 자체보다는 프로젝트가 발전되고 사용되는 방법과 관련이 깊다."


< 이미지 출처: https://cdn.shortpixel.ai/client/q_glossy,ret_img,w_608/https://www.prototechsolutions.com/wp-content/uploads/2019/07/Blog-Post-Image.png>

14. 컴포넌트 결합

ADP (Acycle Dependencies Principle) 의존성 비순환 원칙

- "의존성 구조에 순환이 있어서는 된다." 순환이 있으면 숙취 증후군 (the morning after syndrome) 생긴다.

- 비순환 방향 그래프이다. DAG (Directed Acyclic Graph)

- 발생한 순환을 끊으려면 DIP (Dependency Inversion Principle) 먹여줘야 한다.

- 이를 통해서 컴포넌트 의존성 구조는 서서히 흐트려지고 (Jitter, 체로 흔들듯 구조가 펴진다고 생각하자) 성장한다.

- "컴포넌트는 시스템에서 가장 먼저 설계할 있는 대상이 아니며, 오히려 시스템이 성장하고 변경될 함께 진화한다"

- 컴포넌트 의존성 다이어그램은

- 애플리케이션 기능을 기술한다 (x)

- 애플리케이션 빌드 가능성 (buildability) 유지 보수성 (maintainability) 보여주는 지도와 같다. (o)

 

SDP (Stable Dependencies Principle) 안정된 의존성 원칙

- 컴포넌트가 변경되었을때 함께 변경되어야 하는 (=의존하는) 컴포넌트들이 많으면 변경하기 힘들다. 이것이 안정된 컴포턴트이다.

- 불안정성 = 바깥으로 나가는 의존성 개수 / (바깥으로 + 안으로) 이다.

- 따라서, 내가 의존하는 것은 없고, 나를 의존하는 개수는 많으면 불안전성이 0 되는 거다. (=변경하기 어렵다, 의존하는 놈들도 변경해줘야 하니)

- SDP 따르면 의존성 방향으로 수록 불안전성 값이 작아져야 한다.

- SDP 위반하는 녀석 = 이전 단계보다 불안정성 값이 커지는 녀석이다.

- DIP 도입하여 해결! 추상 컴포넌트 (=인터페이스만 포함하는 컴포넌트) 사용하는 거다.

 

SAP (Stable Abstraction Principle) 안정된 추상화 원칙

- "안정된 컴포넌트는 추상 컴포넌트여야 하며, (중략) 불안정한 컴포넌트는 반드시 구체 컴포넌트여야 한다."

- SDP + SAP DIP 컴포넌트 버전이다.

- 추상화 정도 = 추상클래스와 인터페이스의 개수 / 전체 클래스 개수

 

주계열 (Main Sequence)

- 불안전성을 x 축으로 하고, 추상화 정도를 y 축으로 하는 그래프를 그릴 있다.

- 가능하면 추상적이면서 변동가능성이 낮거나, - 구체적이면서 변동가능성이 높아야 한다.

- "컴포넌트가 위치할 있는 가장 바람직한 지점은 주계열의 종점이다."

 

- 컴포넌트가 주계열에서 얼마나 떨어져 있는가를 측정해서 컴포넌트가 얼마나 바람직한지 측정할 있으며

- 시간에 따른 컴포넌트와 주계열과의 거리 변화를 측정해서 거리가 멀어지는 변화시점을 분석해볼 수도 있다.

< 이미지 출처: https://cdn.shortpixel.ai/client/q_glossy,ret_img,w_530/https://www.prototechsolutions.com/wp-content/uploads/2019/07/blog-image-3-website.png >


댓글
댓글쓰기 폼
공지사항
Total
98,262
Today
714
Yesterday
1,019
링크
TAG
more
«   2019/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30          
글 보관함