기술도서

클린 아키텍처 요약 - 2부 벽돌부터 시작하기:프로그래밍 패러다임

jasNote 2024. 2. 26. 13:09

 

 

개인적 견해는 기울임꼴로 작성했다.

 

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


3장. 패러다임


구조적 프로그래밍

  • 무분별한 점프 구조(goto)의 해로움을 깨닫고 if, while등 더 익숙한 구조로 대체했다.
  • 제어흐름의 직접적인 전환에 규칙을 부과했다.

 

객체지향 프로그래밍

  • 객체 지향 프로그래밍은 제어흐름의 간접적인 전환에 부과되는 규율이다.
  • 함수 포인터를 특정 규칙에 따라 사용하는 과정을 통해 필연적으로 다형성이 등장하게 되었다.
  • 함수 호출 스택 프레임을 힙(heap)으로 옮기면, 함수 호출 반환 후에도 함수에서 선언된 지역변수가 오랫동안 유지될 수 있음을 발견했다.

 

함수 로드 방식을 스택에서 힙으로 바꾸면서 소멸했던 함수가 유지 가능해졌다. 이는 객체지향을 가능케하는 출발점으로 보인다.

 

함수형 프로그래밍

  • 람다 계산법의 기초가 되는 개념은 불변성으로, 심볼의 값이 변경되지 않는다는 뜻이다.
  • 함수형 언어가 변수 값을 변경할 수 있는 방법을 제공하기는 하지만, 굉장히 까다로운 조건 아래에서만 가능하다.
  • 할당문에 대해 규칙을 부과한다.

 

생각할 거리

  • 패러다임의 변화는 개발자의 권한을 뺏는 것이다.
  • 1958년 부터 10년에 걸쳐나온 패러다임이 전부이기 때문에  구조적, 객체지향적, 함수형 이 전부이고 앞으로도 그럴 것이다.

결론

  • 패러다임(구조,객체지향, 함수형)과 아키텍처의 관심사(함수, 컴포넌트 분리, 데이터 관리)가 어떻게 연관되는지 주목하자.

 


4장. 구조적 프로그래밍


증명

  • 모듈을 더 작은 단위로 분해할 때, goto가 방해되는 것을 증명했다.
  • 순차, 분기, 반복 이 세가지 제어 구조로 사용했을 때 goto를 사용하더라도 문제가 되지 않았다.

 

기능적 분해

  • 거대한 기술서를 받더라도 문제를 고수준의 기능들로 분해할 수 있어야 한다.
  • 그리고 각 기능은 다시 저수준의 함수들로 분해할수 있고 분해과정을 끝임 없이 반복할 수 있어야 한다.

 

테스트

  • 테스트는 버그가 있음을 보여줄 뿐, 버그가 없음을 보여줄 수 없다.
  • 그러므로 증명 가능한 세부 기능 집합으로 재귀적으로 분해하는 것을 강요한다.
  • 거짓을 증명하려는 테스트가 실패한다면, 충분히 참이라고 여기게 된다.

 

결론

  •  모듈, 컴포넌트, 서비스가 쉽게 반증 가능하도록 기능적인 분해가 필요하다.

 


5장. 객체지향


"객체지향은 실제 세계를 모델링하는 새로운 방법으로 얼버무리는 수준에 지나지 않는다. 그 정의가 모호하다." 라며  시작한다.

 

캡슐화

  • 데이터는 은닉되고 일부 함수만이 외부에 노출되는 개념
  • C언어에서 누렸던 완벽한 캡슐화(.h, .c)는 훼손됐다. java, C# 등 헤더와 구현체를 분리하는 방식을 모두 버렸다.
  • OO언어는 캡슐화를 거의 강제하지 않는다.c언어는 헤더파일(.h)과 소스파일(.c)이 각각 분리 되어있다.

 

외부에 노출시킬 코드는 헤더파일에 선언하고 핵심로직은 소스파일에 정의하여 숨겨놓는다. 그리고 컴파일된 후, 정적 라이브러리(.lib) 또는 동적 라이브러리(.dll) 형태로 제공되어, 실행 파일에 링크되어 사용되기 때문에 .c파일을 직접 노출시키지 않는다.OO에서의 캡슐화는 접근제한자를 활용한 정보은닉 즉, 프로그램 수준에서의 은닉이다. c언어는 핵심 코드 자체를 은닉하기 때문에 은닉 수준이 다르다는 점에서 캡슐화를 강제하지 않는다고 표현한 것 같다.

 

 

상속

  • 상속은 단순히 변수와 함수를 묶어서 재정의하는 것이다.
  • c언어에서도 상속은 흔히 사용하던 기법이다. 다만, 언어의 도움 없이 손수 구현해서 사용했다.
  • OO는 데이터 구조에 가면을 씌우는 일(상속)을 상당히 편리한 방식으로 제공했다고 볼 수는 있다.

c언어는 부모를 상속 받는 기능이 없다. 때문에 부모에서 필요로하는 변수들을 자식에서 정의하고 부모 타입으로 업캐스팅하여 사용한다.

 

 

다형성

  • 하나의 인터페이스가 여러 형태를 가질 수 있음.
  • c언어에서도 포인터를 응용해서 다형성을 구현했다. 하지만 포인터 초기화를 수동적으로 해야하는 위험이 있다.
  • OO언어는 다형성을 사용하는데 실수할 위험이 없다.

 

다형성이 가진 힘

  • 의존적인 수많은 프로그램을 만들고 나서야 프로그램은 독립적이어야 한다는 사실을 배웠다.
  • OO의 등장으로 언제 어디서든 플러그인 아키텍처를 적용할 수 있게 되었다.

 

 

의존성 역전

  • 다형성이 없던 시절 의존성의 방향은 반드시 제어흐름을 따랐다.
  • 소스 코드 의존성을 역전시킬수 있다는 의미 : "아키텍처가 의존성 전부에 대해 방향을 결정할 수 있는 힘이 생겼다."

 

결론

  • OO의 다형성을 이용하여 전체 시스템의 모든 소스 코드 의존성에 대한 절대적인 제어 권한을 획득할수 있는 능력이 생겼다.
  • OO를 사용하면 플러그인 아키텍처를 구성할 수 있다.
  • 고수준의 정책을 포함하는 모듈은 저수준의 세부사항을 포함하는 모듈에 대해 독립성을 보장할 수 있다.

 

 

소프트웨어를 구성하는 중요한 규칙이나 비즈니스 로직을 담당하는 고수준 모듈은 세부적인 기능을 담당하는 저수준 모듈(예: 데이터베이스, UI)과는 독립적으로 설계되어야 한다.예를 들어, 애플리케이션의 데이터베이스가 변경되어도 비즈니스 로직을 담당하는 부분은 영향을 받지 않아야 하며, 이를 통해 시스템의 유지보수성과 유연성을 높일 수 있다. 핵심적인 정책은 더 낮은 단계의 세부 구현과 독립되어 있으므로 나중에 세부사항이 바뀌더라도 고수준 정책 부분은 크게 영향을 받지 않게 설계하는 것이 중요한 원칙 중 하나이다.

 


6장. 함수형프로그래밍

 


이 패러다임에서 핵심이 되는 기반은 람다이다. 함수형 언어에서 변수는 변경되지 않는다.

 

불변성과 아키텍처

  • 경합, 교착상태, 동시 업데이트 모두 가변 변수로 인해 발생한다.
  • 동시성 어플리케이션의 문제, 즉 다수의 스레드와 프로세스를 사용할 때의 문제는 가변 변수가 없다면 문제는 발생하지 않는다.
  • 아키텍터라면 동시성 문제에 지대한 관심을 가져야한다.

 

가변성의 분리

  • 불변성과 관련하여 가장 주요한 타협은 가변/불변 컴포넌트를 분리하는 일이다.
  • 불변 컴포넌트는 순수하게 함수형 방식으로만 처리된다.
  • 가변 변수들을 보호하는 적절한 수단을 동원해 뒷받침해야 한다.
  • 현명한 아키텍트라면 가능한 많은 처리를 불변 컴포넌트로, 가변 컴포넌트에서는 가능한 많은 코드를 빼내어야한다.

 

변수 갱신이 프로그램 분석을 어렵게한다. 때문에 최대한 많은 처리를 불변 컴포넌트로 옮겨 단순화해야 한다. 그리고 가변 컴포넌트를 관리하는 적절한 수단을 생각해내야 한다. 여기서 말하는 적절한 수단은 동시성 문제를 해결하는 Lock기술과 관련있다.

 

 

이벤트 소싱

  • 상태가 아닌 트랜잭션을 저장하는 방식
    • 예시1. 계좌잔고를 변경하는 대신 트랜잭션 자체를 저장한다고 생각해보자. 이 전략에서는 가변 변수가 하나도 필요없고 단순하다.
    • 예시2. 소스 코드 버전 관리 시스템이 정확히 이 방식으로 동작한다.
  • CRUD가 아닌 CR만 수행한다.
  • 저장공간과 처리능력의 한계는 점점 사라지면서 이벤트 소싱이 가능해지고 있다. 더 나아가 완전한 함수형 프로그래밍이 가능해 질 수 있다.

 

결론

  • 패러다임 정리
    • 구조적 프로그래밍은 제어흐름의 직접적인 전환에 부과되는 규율이다.
    • 객체 지향 프로그래밍은 제어흐름의 간접적인 전환에 부과되는 규율이다.
    • 함수형 프로그래밍은 변수 할당에 부과되는 규율이다.
  • 이 3개의 패러다임은 해서는 안되는 것에 대한 것들이다.
  • 소프트웨어는 순차, 분기, 반복, 참조로 구성된다. 그 이상도 이하도 아니다.

 

 

저자가 2부에서 하고자 하는 이야기는 "소프트웨어는 변하지 않았다"는 것이다. 그저 과거에 겪었던 굵직한 문제들을 해결하기 위한 패러다임(개발자가 지켜야 할 규칙)이 생겨났을 뿐이다.