* 소프트웨어를 제대로 만드는 일

> 프로그램을 동작하게 만들기는 그리 어려운 일이 아니다. 하지만 프로그램을 제대로 만드는 일은 전혀 다르다. 소프트웨어를 올바르게 만드는 일은 어렵다. 

- 소프트웨어를 제대로 만들려면 적정 수준의 지식과 기술을 겸비해야 하지만, 대다수의 젊은 프로그래머는 이 수준에 도달하지 못했다.

- 또한 사고력과 통찰력을 갖춰야 하지만, 대다수의 프로그래머는 시간을 들여 이러한 능력을 개발하지 않는다

- 그리고 어느 정도의 훈련과 헌신이 필요하지만, 대다수의 프로그래머는 훈련과 헌신이 필요하리라는 생각조차 하지 않는다

- 소프트웨어를 올바르게 만들려면 무엇보다도 기술을 향한 열정과 전문가가 되려는 열망이 필수


> 반면 소프트웨어를 제대로 만들게 되면 마법과도 같은 일이 벌어진다

- 소수의 프로그래머만으로 프로그램이 지속적으로 동작하도록 만들 수 있다

- 거대한 요구사항 문서와 이슈가 수없이 등록된 이슈 추적 시스템도 필요없다

- 전 세계의 칸막이로 나뉜 작은 사무실에서 휴일도 없이 일해야 하는 프로그래머가 없어도 된다


> 제대로 된 소프트웨어를 만들면 

- 아주 적은 인력만으로도 새로운 기능을 추가하거나 유지보수 할 수 있다

- 변경은 단순해지고 빠르게 반영할 수 있다

- 결함은 적어지고 잦아든다

- 최소한의 노력으로 기능과 유연성을 최대화할 수 있다


* Architecture vs Design

> 차이가 없다

> Architecture

- 저수준의 세부사항과는 분리된 고수준의 무언가를 가리킬 때 흔히 사용

> Design

- 저수준의 구조 또는 결정사항 등을 의미할 때가 많다


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


* 현대 개발자들의 문제점

> 현대의 대다수 개발자는 뼈 빠지게 일한다. 하지만 그들의 뇌는 잠에 취해 있다. 훌륭하고 깔끔하게 잘 설계된 코드가 중요하다는 사실을 알고 있는
     바로 그 뇌가 잠자고 있다

> 개발자가 속는 더 잘못된 거짓말은 "지저분한 코드를 작성하면 단기간에는 빠르게 갈 수 있고, 장기적으로 볼 때만 생산성이 낮아진다"는 견해다.
    나중에 기회가 되면 엉망이 된 코드를 정리하는 태세로 전환할 수 있다고 자신의 능력을 과신하게 된다. 하지만 이는 그저 진실을 오인한 것일
    뿐이다. 진실은 다음과 같다. 엉망으로 만들면 깔끔하게 유지할 때보다 항상 더 느리다 (빨리 가는 유일한 방법은 제대로 가는 것이다)


* 소프트웨어의 2가지 가치

> Behavior (행위)

- 이해관계자를 위해 기계가 수익을 창출하거나 비용을 절약하도록 만드는 것

> Software (소프트웨어)

- 이해관계자가 기능에 대한 생각을 바꾸면, 이러한 변경사항을 간단하고 쉽게 적용할 수 있어야 한다
    : 기계의 행위를 쉽게 변경할 수 있도록 하기 위해서 '부드러워'야 한다
    : 변경사항을 적용하는 데 드는 어려움은 변경되는 범위에 비례해야 하며, 변경사항의 형태와는 관련이 없어야 한다
        (아키텍처가 특정 형태를 다른 형태보다 선호하면 할수록, 새로운 기능을 이 구조에 맞추는 게 더 힘들어진다.
        따라서 아키텍처는 형태에 독립적이어야 하고, 그럴수록 더 실용적이다)

> 굳이, 따지자면 후자가 더 중요하다

- 아키텍처가 후순위가 되면 시스템을 개발하는 비용이 더 많이 들고, 일부 또는 전체 시스템에 변경을 가하는 일이 현실적으로 불가능해진다.

- 이러한 상황이 발생하도록 용납했다면, 이는 결국 소프트웨어 개발팀이 스스로 옳다고 믿는 가치를 위해 충분히 투쟁하지 않았다는 뜻이다


* Paradigm

> 프로그래밍을 하는 방법으로 대체로 언어에는 독립적이다

- 어떤 프로그래밍 구조를 사용할 지, 그리고 언제 이 구조를 사용해야 하는지를 결정

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

- 아래 3가지 패러다임은 goto문, 함수 포인터, 할당문을 앗아간다

> 소프트웨어, 즉 컴퓨터 프로그램은 sequence (순차), selection (분기), iteration (반복), indirection (참조) 으로 구성된다. 그 이상도 그 이하도 아니다


* Structured Programming

> 1968년 Edsger Wybe Dijkstra (에츠허르 비버 데이크스트라) 가 발견했으며, 무분별한 Jump(Go-to)는 프로그램 구조에 해롭다는 사실을 제시했다

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


> Sequential statement (순차 구문) + Selection (분기) + Iteration (반복)


* Object-Oriented Programming

> 1966년 Ole Johan Dahl (올레 요한 달)과 Kristen Nygaard (크리스텐 니가드) 에 의해 등장했다

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


> Encapsulation (캡슐화) + Inheritance (상속) + Polymorphism (다형성)

> 다형성은 Dependency Inversion (의존성 역전) 을 가능하게 한다 (제어흐름과 의존성이 반대가 된다)

- 이는 OO의 힘이며, 아키텍트는 소스 코드 의존성을 원하는 방향으로 설정할 수 있게 해준다.
    결과적으로 배포 독립성을 가질 수 있으며, 이는 개발 독립성을 갖는 것으로 이어진다

: Independent Deployability (배포 독립성)

-> 특정 컴포넌트의 소스 코드가 변경되면 해당 컴포넌트만 독립적으로 배포할 수 있게 만들어준다

: Independent Developability (개발 독립성)

-> 서로 다른 팀에서 각 모듈을 독립적으로 개발할 수 있다

> 소프트웨어 아키텍트 관점에서,
     OO란 다형성을 이용하여 전체 시스템의 모든 소스 코드 의존성에 대한 절대적인 제어권한을 획득할 수 있는 능력이다

- 플러그인 아키텍처를 구성할수 있다.
 이를 통해, 고수준의 정책을 포함하는 모듈은 저수준의 세부사항을 포함하는 모듈에 대해 독립성을 보장할 수 있다. 저수준의 세부사항
    중요도가 낮은 플러그인 모듈로 만들 수 있고, 고수준의 정책을 포함하는 모듈과는 독립적으로 개발하고 배포할 수 있다


* Functional Programming

> Alonzo Church (알론조 처치)는 1930년 대에 앨런 튜링도 똑같이 흥미를 느꼈던 어떤 수학적 문제를 해결하는 과정에서 람다 계산법을 발명했는데, 

이러한 연구결과에 직접적인 영향을 받아 만들어졌다.

- 1958년 John McCarthy (존 매카시) 가 만든 LISP 언어의 근간이 되는 개념이 람다 계산법이다. 

- 람다 계산법의 기초가 되는 개념은 immutability 로, symbol 의 값이 변경되지 않는다는 개념이다.

> 함수형 프로그래밍은 (변수) 할당문에 대해 규칙을 부과한다


> 불변성과 아키텍처

- 함수형 언어에서는 변수는 변경되지 않는다 (자바는 Mutable Variable 사(가변변수) 사용)

: 어떠한 변수도 갱신되지 않는다면, Race Condition, Dead Lock, Concurrent Update 문제가 발생하지 않는다

- 따라서, 가능한 한 많은 처리를 불변 컴포넌트로 옮겨야 하고, 가변 컴포넌트에서는 가능한 한 많은 코드를 빼내야 한다.
    (가변 컴포넌트와 불변 컴포넌트로 분리)


* SOLID Principle


* Component

> 시스템의 구성 요소로 배포할 수 있는 가장 작은 단위

: 자바의 경우 jar 파일
: 루비에서는 gem 파일
: 닷넷에서는 DLL 파일

> 런타임에 플러그인 형태로 결합할 수 있는 동적 링크 파일

> 잘 설계된 컴포넌트라면 반드시 독립적으로 배포 가능한, 따라서 독립적으로 개발 가능한 능력을 갖춰야 한다


* Principles of Component Cohesion (REP, CCP, CRP)


* Principles of Component Coupling (ADP, SDP, SAP)


* Software Architect

> 소프트웨어 아키텍트는 프로그래이며, 앞으로도 계속 프로그래머로 남는다. 코드에서 탈피하여 고수준의 문제에 집중해야 한다는 거짓말에 절대로
    속아 넘어가서는 안 된다. 코드와 동떨어져서는 안 된다. 소프트웨어 아키텍트는 코드와 동떨어져서는 안 된다. 계속 프로그래밍 작업을 맡을 뿐만
    아니라 동시에 나머지 팀원들이 생산성을 극대화할 수 있는 설계를 하도록 방향을 이끌어준다


* Software Architecture

> 시스템을 구축했던 사람들이 만들어낸 시스템의 형태

> 컴포넌트로 분할하는 방법, 분할된 컴포넌트를 배치하는 방법, 컴포넌트가 서로 의사소통하는 방식에 따라 정해진다

> 주된 목적은 시스템의 생명주기를 지원하는 것이다

: 좋은 아키텍처는 시스템을 쉽게 이해하고, 쉽게 개발하며, 쉽게 유지보수하고, 또 쉽게 배포하게 해준다

: 시스템의 수명과 관련된 비용은 최소화하고, 프로그래머의 생산성은 최대화하는 데 있다.


* System 의 2가지 구성요소

> Policy (정책), Detail (세부사항)

> 아키텍트는 Policy 를 가장 핵심적인 요소로 식별하고, 동시에 Detail 은 Policy 에 무관하게 만들 수 있는 형태의 시스템을 구축하는데 있다
    (세부사항에 몰두하지 않은 채 고수준의 정책을 만들 수 있다면, 이러한 세부사항에 대한 결정을 오랫동안 미루거나 연기할 수 있다.
    더 많은 정보를 얻을 수 있고, 이를 기초로 제대로 된 결정을 내릴 수 있다)

: 좋은 아키텍트는 결정되지 않은 사항의 수를 최대화한다

: 좋은 아키텍트는 Detail 을 Policy로부터 신중하게 가려내고, Policy 가 Detail 과 결합되지 않도록 엄격하게 분리한다
    이를 통해, Policy 는 Detail 에 관한 어떠한 지식도 갖지 못하게 되며, 어떤 경우에도 Detail 에 의존하지 않게 된다


* 독립성

> 좋은 아키텍처는 다음을 지원해야 한다


> Use Cases

: 행위를 명확히 하고 외부로 드러내며, 이를 통해 시스템이 지닌 의도를 아키텍처 수준에서 알아볼 수 있게 만들어야 한다

> Operation

: 시스템 운영을 원활히 지원 가능할 수 있는 형태로 구조화 되어야 한다

> Development

: 팀이 의사소통을 쉽게 할 수 있도록 설계되어야 한다

> Deployment

: 시스템이 빌드된후 즉각 배포할 수 있도록 지원해야 한다

> Leaving options open

: 선택사항을 열어 둠으로써, 향후 시스템에 변경이 필요할 때 어떤 방향으로든 쉽게 변경할 수 있도록 해야한다

> Decoupling Layers

: 관련 없는 Layers (UI Layer, Business Logic Layer, DB Layer 등) 간에 수평적으로 분리하여 독립적으로 변경할 수 있도록 해야한다

> Decoupling Use Cases

: 수직적으로 Use Case 에 따라 분할할 수 있어야 한다

> Independent Develop-Ability

: 컴포넌트가 분리되면 팀 사이의 간섭은 줄어든다

> Independent Deployability

: 운영 중인 시스템에서도 Layer와 Use Case 를 Hot-swap 할 수 있어야 한다

> Duplication

: true duplication

- 한 인스턴스가 변경되면, 동일한 변경을 그 인스턴스의 모든 복사본에 반드시 적용해야 한다

: false or accidental duplication

- 중복으로 보이는 두 코드 영역이 각자의 경로로 발전하다면, 즉 서로 다른 속도와 다른 이유로 변경된다면 이 두 코드는 진짜 중복이 아니다

> Decoupling Mode

: Source Level

- 소스 코드 모듈 사이의 의존성을 제어할 수 있다.

- 하나의 모듈이 변하더라도 다른 모듈을 변경하거나 재컴파일하지 않도록 만들 수 있다

: Deployment Level

- jar 파일, DLL, 공유 라이브러리와 같이 배포 가능한 단위들 사이의 의존성을 제어할 수 있다

- 한 모듈듈의 소스 코드가 변하더라도 다른 모듈을 재빌드하거나 재배포하지 않도록 만들 수 있다

: Service Level

- 의존하는 수준을 데이터 구조 단위까지 낮출 수 있고, 순전히 네트워크 패킷을 통해서만 통신하도록 만들 수 있다

- 모든 실행 가능한 단위는 소스와 바이너리 변경에 대해 서로 완전히 독립적이게 된다 (e.g. 서비스 또는 마이크로서비스)


* Software System

> Policy 를 기술한 것

> Architecture 를 개발하는 기술에는 이러한 Policy 를 신중하게 분리하고, 변경되는 양상에 따라 재편성하는 일도 포함된다

: 동일한 이유로 동일한 시점에 변경되는 Policy 는 동일한 Level 에 위치하여, 동일한 컴포넌트에 속해야 한다

: 서로 다른 이유로, 혹은 다른 시점에 변경되는 Policy는 다른 Level 에 위치하며, 반드시 다른 컴포넌트로 분리해야 한다


* Level (수준)

> 입력과 출력까지의 거리

> 시스템의 입력과 출력 모두로부터 멀리 위치할수록 Policy 의 Level 은 높아진다

> 소스 코드 의존성은 그 Level 에 따라 결합되어야 하며, 데이터 흐름을 기준으로 결합되어서는 안 된다


* Business Rules

> 사업적으로 수익을 얻거나 비용을 줄일 수 있는 Rules 혹은 procedures

> 시스템에서 가장 독립적이며 가장 많이 재사용할 수 있는 코드여야 한다

> Critical Business Rules

: 컴퓨터 프로그램으로 구현 여부와 관계없이, 비즈니스 자체적으로 중요한 Business Rules

> Critical Business Data

: Critical Business Rules 가 필요로 하는 data


* Clean Architecture

> 아래 아키텍처들의 목표는 모두 같은데, 바로 Separation of Concerns (관심사의 분리)

: Hexagonal Architecture (=Ports and Adapters / 육각형 아키텍처 / Alistair Cockburn)

: DCI (Data, Context and Interaction / James Coplien, Trygve Reenskaug)

: BCE (Boundary-Control-Entity / Ivar Jacobson)

> 또한, 모두 시스템이 다음과 같은 특징을 지니도록 만든다

: 프레임워크 독립성

- 프레임워크의 존재 여부에 의존하지 않는다

: 테스트 용이성

- 외부 요소가 없이도 테스트 할 수 있다

: UI 독립성

- 시스템의 나머지 부분을 변경하지 않고도 UI를 쉽게 변경할 수 있다

: 데이터베이스 독립성

- 여러 종류의 DB로 교체 할 수 있으며, Business Rule 은 데이터베이스에 결합되지 않는다

: 모든 외부 에이전시에 대한 독립성

- Business Rule은 외부 세계와의 인터페이스에 대해 전혀 알지 못한다


* Architecture의 목적

> 좋은 아키텍처는 Use Case 를 그 중심에 두기 때문에, 프레임워크나 도구, 환경에 전혀 구애받지 않고 Use Case 를 지원하는 구조를 아무런 
    문제 없이 기술할 수 있다


* Dependency Rule

> 소스 코드 의존성은 반드시 안쪽으로, 고수준의 정책을 향해야 한다

> 안쪽으로 이동할수록 추상화와 Policy Level 이 높아진다.

따라서, 가장 안쪽 원은 가장 범용적이며 높은 Level 을 가진다


* Entity

> 컴퓨터 시스템 내부의 객체로써, Critical Business Data 를 기반으로 동작하는 일련의 조그만 Critical Business Rule을 포함한다

즉, 전사적인 Ciritical Business Rule 을 캡슐화한다


* Use Case

> Application-specific Business Rule 을 설명한다

> Entity 내부의 Critical Business Rule 을 어떻게, 그리고 언제 호출할지를 명시하는 규칙을 포함한다.

즉, Entity 로 들어오고 나가는 데이터 흐름을 조정한다

> User Interface 를 기술하지 않는다


* Interface Adapters

> 데이터를 Use Case 와 Entity 에게 가장 편리한 형식에서 데이터베이스나 웹 같은 외부 에이전시에게 가장 편리한 형식으로 변환한다

> Presenter, View, Controller 가 여기에 속한다


* Frameworks & Drivers

> 일반적으로 데이터베이스나 웹 프레임워크 같은 프레임워크나 도구들로 구성된다


* Presenter

> Humble Object Pattern 을 따름

> 아키텍처 경계를 식별하고 보호하는 데 도움이 된다

> 테스트 하기 쉬운 객체다


* View

> Humble Object 이고 테스트하기 어렵다


* Data Mapper (= ORM (Obejct Relational Mapper))

> Database 계층에 위치해야 한다

> Gateway Interface 와 Database 사이의 일종의 Humble Object 이다


* Main Component

> 궁극적인 세부사항으로, 가장 낮은 수준의 정책이다

> 시스템의 초기 진입점으로 OS 를 제외하면 어떤 것도 Main 에 의존하지 않는다

> 모든 Factory 와 Strategy, 그리고 시스템 전반을 담당하는 나머지 기반 설비를 생선한 후, 시스템에서 더 높은 수즌을 담당하는 부분으로

제어권을 넘기는 역할을 맡는다

> Dependency Injection 프레임워크를 이용해 의존성을 주입하는 일은 여기서 이뤄져야 한다

> 가장 지저분한 Component

> Application 의 Plug-in 이다

즉, 초기 조건과 설정을 구성하고, 외부 자원을 모두 수집한 후, 제어권을 Application 의 고수준 정책으로 넘기는 플러그인이다


* Service Oriented Architecture 와 Microservice Architecture 가 인기 있는 이유

> 상호 결합이 철저하게 분리되는 것처럼 보인다

> 개발과 배포 독립성을 지원하는 것처럼 보인다


* Service 의 장단점

> 장점

: 서비스 사이의 결합이 확실히 분리된다. 개별 변수 수준에서는 각각 결합이 분리된다. 

: 다른 프로세스에서, 혹은 다른 프로세서에서 실행된다

: DevOps 의 일환으로 각 팀이 서비스를 작성, 유지보수, 운영하는 책임을 질 수 있다

> 단점

: 프로세서 내의 또는 네트워크 상의 공유 자원 때문에 결합될 가능성이 여전히 존재한다. 

: 더욱이 서로 공유하는 데이터에 의해 이들 서비스는 강력하게 결합되어 버린다.
    (서비스 사이를 오가는 데이터에 대해 사전에 완벽하게 조율이 필요하다)

: 서비스 기반의 시스템이 아니더라도 충분히 각 컴포넌트별 조직 운영이 가능하다. 또한, 항상 개발, 배포, 운영이 독립적인 것만은 아니다

> 시스템 아키텍처는 시스템 내부에 그어진 경계와 경계를 넘나드는 의존성에 의해 정의된다. 시스템의 구성 요소가 통신하고 실행되는 물리적인
    메커니즘에 의해 아키텍처가 정의되는 것이 아니다


* 테스트 경계

> 테스트는 세부적이며 구체적인 것으로, 의존성은 항상 테스트 대상이 되는 코드를 향한다

> 테스트는 독립적으로 배포 가능하다. 사실 대다수의 경우 테스트 시스템에만 배포한다

> 테스트는 시스템 컴포넌트 중에서 가장 고립되어 있다


* 테스트를 고려한 설계

> 테스트가 시스템의 설계와 잘 통합되지 않으면, 테스트는 깨지기 쉬워지고, 시스템은 뻣뻣해져서 변경하기가 어려워진다

> 시스템의 공통 컴포넌트가 변경되면 수백, 심지어 수천 개의 테스트가 망가진다 (Fragile Tests Problem)

> 시스템과 테스트를 설계할 때, GUI를 사용하지 않고 Business Rule을 테스트할 수 있게 해야 한다


* Clean Embedded Architecture

> 소프트웨어는 닳지 않지만, 펌웨어와 하드웨어에 대한 의존성을 관리하지 않으면 안으로부터 파괴될 수 있다

: 하드웨어는 발전할 수 밖에 없고, 그러한 현실을 염두에 두고 임베디드 코드를 구조화할 수 있어야 한다

: 펌웨어를 수없이 양산하는 일을 멈추고, 코드에게 유효 수명을 길게 늘릴 수 있는 기회를 주어라

> 클린 임베디드 아키텍처는 테스트하기 쉬운 임베디드 아키텍처디

> Software 와 Firmware 가 서로 섞이는 일은 Anti-pattern 이다. 이 안티 패턴을 보이는 코드는 변화에 저항하게 된다.

: 변경하기 어려울 뿐 아니라 변경하는 일 자체가 위험을 수반한다

: 가벼운 변경에도 시스템 전체를 대상으로 회귀 테스트를 전부 실행해야 한다


> HAL (Hardware Abstraction Layer)

: 소프트웨어와 펌웨어 사이의 경계이다

: HAL API는 소프트웨어의 필요에 맞게 만들어져야 한다

: HAL 사용자에게 하드웨어 세부사항을 드러내지 말라

> OS

: 소프트웨어를 펌웨어로부터 분리하는 계층이다

> OSAL (Operation System Abstraction Layer)

: 소프트웨어를 운영체제로부터 격리시킨다


* 데이터베이스는 세부사항이다

> 데이터베이스는 데이터 모델이 아니다. 일개 소프트웨어일 뿐이다. 데이터에 접근할 방법을 제공하는 유틸리티다


* 웹은 세부사항이다

> GUI는 세부사항이다. 웹은 GUI다. 따라서 웹은 세부사항이다


* 프레임워크는 세부사항이다

> 프레임워크를 사용할 수는 있다. 다만 프레임워크와 결합해서는 안 된다.



* 참고 : https://online.flippingbook.com/view/606385/




+ Recent posts