본문 바로가기
독서/클린 아키텍처

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

by yoon_seon 2024. 7. 19.

3장. 패러다임 개요


구조적 프로그래밍

  • 최초로 적용된 패러다임으로 1968년 데이크스트라가 발견했다.
  • 데이크스트라는 무분별한 점프는 프로그램 구조에 해롭다는 사실을 제시했다.
  • 이러한 점프를 if/then/else와 do/while/until과 같이 더 익숙한 구조로 대체했다.
  • 구조적 프로그래밍은 제어흐름의 직접적인 전환에 대해 규칙을 부과한다.

 

객체 지향 프로그래밍

  • 1966년 요한 달과 크리스텐 니가드에 의해 발견됐다.
  • 함수 호출 스택 프레임을 힙으로 옮기면, 함수 호출이 반환된 이후에도 함수에서 선언된 지역 변수가 오랫동안 유지될 수 있음을 발견했다.
    • 이러한 함수가 클래스의 생성자가 되었고, 지역 변수는 인스턴스 변수, 중첩 함수는 메서드가 되었다.
  • 함수 포인터를 특정 규칙에 따라 사용하는 과정을 통해 필연적으로 다형성이 등장하게 되었다.
  • 객체 지향 프로그래밍은 제어흐름의 간접적인 전환에 대해 규칙을 부과한다.

 

함수형 프로그래밍

  • 알론조 처치는 수학문제를 해결하는 과정에서 람다 계산법을 발명했고, 함수형 프로그래밍은 연구 결과에 직접적인 영향을 받아 만들어졌다.
  • 람다 계산법의 기초가 되는 개념은 불변성으로, 심볼의 값이 변경되지 않는다는 개념이다. 이는 함수형 언어에는 할당문이 전혀 없다는 뜻이기도 하다.
  • 함수형 프로그래밍은 할당문에 대해 규칙을 정의한다.

 

생각할 거리

  • 각 패러다임은 프로그래머에게서 권한을 박탈하고 새로운 권한을 부여하지 않는다.
  • 각 패러다임은 부정적인 의도를 가지는 일종의 추가적인 규칙을 부과한다.
  • 즉, 패러다임은 무엇을 해야 할지를 말하기보다는 무엇을 하면 안되는지를 말해준다.
  • 패러다임이 더이상 우리에게서 가져갈 수 있는게 없기 때문에 앞으로도 딱 세가지 밖에 없을 것이다.
  • 세 패러다임은 1958~1968 10년 동안 모두 만들어졌고 이후 수십 년이 지났지만, 새롭게 등장한 패러다임은 전혀 없다.

 

결론

  • 패러다임의 역사로부터 얻을 수 있는 이러한 교훈은 아키텍처와 모두 관계가 있다.
    • 우리는 아키텍처 경계를 넘나들기 위한 메커니즘으로 다형성을 이용한다.
    • 우리는 함수형 프로그래밍을 이용하여 데이터 위치와 접근 방법에 규칙을 부과한다.
    • 우리는 모듈 기반 알고리즘으로 구조적 프로그래밍을 사용한다.

 

 

4장. 구조적 프로그래밍


증명

  • 데이크스크라는 goto 문장이 모듈을 더 작은 단위로 재귀적으로 분해하는 과정에 방해가 되는 경우가 있다는 사실을 발견했다.
  • 반면 if/then/else와 do/while과 같은 분기와 반복이라는 단순한 제어 구조에 goto문을 사용하는 것이 좋은 방식임을 발견했다.
  • 이러한 제어 구조만을 사용한다면 증명 가능한 단위로까지 모듈을 재귀적으로 세분화하는 것이 가능해 보였다.
  • 이러한 제어 구조는 뵘과 야코피니가 데이크스트라보다 2년 앞서 모든 프로그램을 순차, 분기, 반복으로 표현할 수 있다는 사실을 증명했다.

 

기능적 분해

  • 구조적 프로그래밍을 통해 모듈을 증명 가능한 더 작은 단위로, 즉 기능적으로 분해할 수 있게 되었다.
  • 즉, 거대한 문제 기술서를 받더라도 문제를 고수준의 기능으로 분해할 수 있다. 그리고 이들 각 기능은 다시 저수준의 함수들로 분해할 수 있고, 이러한 분해 과정은 끝없이 반복할 수 있다.
  • 게다가 이렇게 분해한 기능들은 구조적 프로그래밍의 제한된 제어 구조를 이용하여 표현할 수 있다.
  • 이를 토대로 구조적 분석이나 구조적 설계와 같은 기법이 인기를 끌었고, 프로그래머는 대규모 시스템을 모듈과 컴포넌트로 나눌 수 있고, 모듈과 컴포넌트는 입증할 수 있는 아주 작은 기능들로 세분화할 수 있었다.

 

테스트

  • 데이크스트라는 “테스트는 버그가 있음을 보여줄 뿐, 버그가 없음을 보여줄 수는 없다”라고 말했다.
  • 즉, 프로그램이 잘못되었음을 테스트를 통해 증명할 수는 있지만, 프로그램이 맞다고 증명할 수는 없다.
  • 테스트에 충분한 노력을 들였다면 테스트가 보장할 수 있는 것은 프로그램이 목표에 부합할 만큼 충분히 참이라고 여길 수 있게 해주는 것이 전부다.
  • 프로그램은 테스트를 아무리 많이 수행하더라도 절대로 올바르다고 볼 수 없다.

 

결론

  • 구조적 프로그래밍이 가치 있는 이유는 프로그래밍에서 반증 가능한 단위를 만들어 낼 수 있기 때문이다.
  • 또한 흔히 현대적 언어가 아무런 제약 없는 goto 문장을 지원하지 않는 이유이기도 하다.
  • 뿐만 아니라 아키텍처 관점에서는 기능적 분해를 최고의 실천법 중 하나로 여기는 이유기도하다.
  • 가장 작은 기능에서부터 가장 큰 컴포넌트에 이르기까지 모든 수준에서 반증 가능성에 의해 주도
  • 소프트웨어 아키텍트는 모듈, 컴포넌트, 서비스가 쉽게 반증 가능하도록(테스트하기 쉽도록) 만들기 위해 노력해야한다.
  • 이를 위해 구조적 프로그래밍과 유사한 제한적인 규칙들을 받아들여 활용해야 한다.

 

 

5장. 객체 지향 프로그래밍


  • 좋은 아키텍처를 만드는 일은 객체 지향 설계 원칙(OO)을 이해하고 응용하는 데서 출발함
    • OO : Object-Oriented
  • OO는 캡슐화, 상속, 다형성으로 본질을 설명
  • 객체 지향 언어는 최소한 이 세 가지 요소를 반드시 지원해야 한다고 말함

 

캡슐화?

  • OO를 정의하는 요소 중 하나로 캡슐화를 언급하는 이유는 데이터와 함수를 쉽고 효과적으로 캡슐화하는 방법을 OO 언어가 제공하기 때문
  • 이를 통해 데이터와 함수가 응집력 있게 구성된 집단을 서로 구분 짓는 선을 그을 수 있고 구분선 바깥에서 데이터는 은닉되고, 일부 함수만이 외부에 노출된다.
  • 실제로 많은 OO가 캡슐화를 거의 강제하지 않고, 단지 프로그래머가 캡슐화된 데이터를 우회하지 않을 거라는 믿음을 기반으로 함

 

상속?

  • OO 언어가 더 나은 캡슐화를 제공하지는 못했지만, 상속만큼은 확실히 제공함
  • 하지만 상속이란 단순히 어떤 변수와 함수를 하나의 유효 범위로 묶어서 재정의하는 일에 불과함
  • OO 언어가 고안되기 이전에도 상속과 비슷한 기법이 사용되었지만 상속을 흉내낼 뿐, 편리한 방식은 아님
  • 그러므로 캡슐화에 대해서는 OO에 점수를 줄 수 없고, 상속은 0.5점 정도 줄 수 있지만 이는 그저 그런 점수임

 

다형성?

  • 함수를 가리키는 포인터를 응용한 것이 다형성이며 OO가 새롭게 만든 것은 전혀 없음
  • OO언어는 다형성을 제공하지는 못했지만, 함수에 대한 포인터를 직접 사용하는 위험성을 없애고 다형성을 좀 더 안전하고 편리하게 사용할 수 있게 해줌
  • OO 언어를 사용하면 다형성을 쉽게 사용 가능하므로 OO는 제어흐름을 간접적으로 전환하는 규칙을 부과한다고 결론지을 수 있다.

 

다형성이 가진힘

  • 함수를 가르키는 포인터를 사용하면 위험을 수반하여 대다수의 프로그래머는 사용하지 않았다.
  • OO의 등장으로 언제 어디서든 플러그인 아키텍처를 적용할 수 있게 되었다.

 

의존성 역전

  • 다형성을 안전하고 편리하게 적용할 수 있는 메커니즘이 등장하기 전
    • main 함수 → 고수준 모듈 호출 → 중간 모듈 호출 → 저수준 모듈 호출
    • 소스 코드 의존성은 반드시 제어흐름을 따르게 된다.
    • 이러한 제약 조건으로 인해 소프트웨어 아키텍트에게 남은 선택지는 별로 없었다.
    • 즉, 제어흐름은 시스템의 행위에 따라 결정되며, 소스 코드 의존성은 제어흐름에 따라 결정된다.
  • 다형성이 메커니즘 등장 이후
    • 인터페이스를 통해 소스 코드 의존성이 제어흐름과 반대가 되는 것을 의존성 역전이라고 부른다.
    • OO 언어로 개발된 시스템을 다루는 소프트웨어 아키텍트는 시스템의 소스 코드 의존성 전부에 대해 방향을 결정할 수 있는 절대적인 권한을 갖는다.
    • 즉, 소스 코드 의존성이 제어흐름의 방향과 일치되도록 제한되지 않는다.
  • 결과적으로
    • 업무 규칙, UI, 데이터베이스는 세 가지로 분리된 컴포넌트 또는 배포 가능한 단위(jar, DLL, Gem)로 컴파일 할 수 있음.
    • 따라서 컴포넌트는 개별적이며 독립적으로 배포할 수 있음
    • 특정 컴포넌트의 소스 코드가 변경되면, 해당 코드가 포함된 컴포넌트만 배포하면 된다. = 배포 독립성
    • 시스템의 모듈을 독립적으로 배포할 수 있게 되면, 서로 다른 팀에서 각 모듈을 독립적으로 개발할 수 있다. = 개발 독립성

 

결과

  • OO란 다형성을 이용하여 전체 시스템의 모든 소스 코드 의존성에 대한 절대적인 제어 권한을 획득할 수 있는 능력
  • OO를 사용하면 아키텍트는 플러그인 아키텍처를 구성할 수 있고, 고수준의 정책을 포함하는 모듈은 저수준의 세부사항을 포함하는 모듈에 대해 독립성을 보장할 수 있다.
  • 저수준의 세부사항은 중요도가 낮은 플러그인 모듈로 만들 수 있고, 고수준의 정책을 포함하는 모듈과는 독립적으로 개발하고 배포할 수 있다.

 

 

6장. 함수형 프로그래밍


정수를 제곱하기

  • 자바 프로그램은 가변 변수를 사용하는데, 가변 변수는 프로그램 실행 중에 상태가 변할 수 있다.
  • 불변 변수는 한 번 초기화 되면 절대로 변경되지 않는다.
  • 함수형 언어에서 변수는 변경되지 않는다.

 

불변성과 아키텍처

  • 경합(race) 조건, 교착 상태(deadlock), 동시 업데이트 문제는 모두 가변 변수로 인해 발생한다.
  • 변수가 갱신되지 않으면 경합 조건이나 동시 업데이트 문제가 일어나지 않고, 락(lock)이 가변적이지 않다면 교착상태도 일어나지 않는다.
  • 즉, 다수의 스레드와 프로세스를 사용하는 애플리케이션에서 마주치는 모든 문제는 가변 변수가 없다면 절대로 생기지 않는다.
  • 스레드와 프로세스가 여러 개인 상황에서도 설계한 시스템이 여전히 강건해야하기에, 아키텍트라면 동시성 문제에 관심을 가져야함.
  • 불변성은 저장 공간이 무한하고 프로세서의 속도가 무한히 빠르다면 실현 가능하지만 자원이 무한대가 아니라면 일종의 타협을 해야한다.

 

가변성의 분리

  • 불변성과 관련하여 중요한 타협 중 하나는 가변 컴포넌트와 불변 컴포넌트를 분리하는 일이다.
    • 불변 컴포넌트에서는 순수하게 함수형 방식으로만 작업이 처리되며, 어떠한 가변 변수도 사용되지 않는다.
    • 불변 컴포넌트는 가변 컴포넌트와 서로 통신한다.
    • 상태 변경은 동시성 문제에 노출하는 꼴이므로 트랜잭션 메모리와 같은 실천법을 사용하여 동시 업데이트와 경합 조건 문제로부터 가변 변수를 보호해야한다.
  • 핵심은 애플리케이션을 제대로 구조화하려면 가변 컴포넌트와 불변 컴포넌트를 분리해야한다.
  • 그리고 분리할 때, 가변 변수들을 보호하는 적절한 수단을 동원해 뒷받침 해야한다.
  • 현명한 아키텍트라면 가능한 많은 처리를 불변 컴포넌트로 옮겨야하고, 가변 컴포넌트에서는 많은 코드를 빼내야한다.

 

이벤트 소싱

  • 트랜잭션 수는 끝없이 증가하므로 무한한 저장 공간과 처리 능력이 필요하다.
  • 하지만 애플리케이션 수명주기 동안만 문제없이 동작할 정도의 저장 공간과 처리 능력만 사용하는 이벤트 소싱을 활용하면 된다.
  • 이벤트 소싱은 상태가 아닌 트랜잭션을 저장하자는 전략이다.
  • 상태가 필요해지면 단순히 상태의 시작점부터 모든 트랜잭션을 처리한다.
  • 지름길로 매일 자정에 상태를 계산 후 저장하고 필요할 때 자정 이후의 트랜잭션만을 처리할 수 있다.
  • 이렇게 하면 애플리케이션은 CRUD가 아니라 CR만 수행한다. 그래서 동시 업데이트 문제 또한 일어나지 않는다.
  • 저장 공간과 처리 능력이 충분하면 애플리케이션이 완전한 불변성을 갖도록 만들 수 있고, 따라서 완전한 함수형으로 만들 수 있다.

 

결론

  • 3가지 패러다임 요약
    • 구조적 프로그래밍은 제어흐름의 직접적인 전환에 부과하는 규율이다
    • 객체 지향 프로그래밍은 제어흐름의 간접적인 전환에 부과되는 규율이다.
    • 함수형 프로그래밍은 변수 할당에 부과되는 규율이다.
  • 이 3가지 패러다임은 모두 우리에게서 무언가를 앗아가며, 각 패터다임은 코드를 작성하는 방식의 형태를 한정시키고, 권한이나 능력을 보태지 않는다.
  • 지난 반세기 동안 우리가 배운 것은 해서는 안되는 것에 대해서이며 소프트웨어는 급격히 발전하는 기술이 아니라는 것이다.
  • 도구는 달라졌고 하드웨어도 변했지만, 소프트웨어의 핵심은 여전히 그대로다.
  • 소프트웨어는 순차, 분기, 반복, 참조로 구성되고 그 이상도 이하도 아니다.

 


모든 내용은 [클린 아키텍처] 서적의 정리한 내용이며, 망나니 개발자님의 블로그에서 정리 방법을 참고했습니다.

 

클린 아키텍처: 소프트웨어 구조와 설계의 원칙 | 로버트 C. 마틴 - 교보문고

클린 아키텍처: 소프트웨어 구조와 설계의 원칙 | 살아있는 전설이 들려주는 실용적인 소프트웨어 아키텍처 원칙 소프트웨어 아키텍처의 보편 원칙을 적용하면 소프트웨어 수명 전반에서 개발

product.kyobobook.co.kr

 

[개발서적] 클린 아키텍처 2부 벽돌부터 시작하기: 프로그래밍 패러다임 - 내용 정리 및 요약

이번에는 로버트 C 마틴의 클린 아키텍처를 읽은 내용을 정리해보도록 하겠습니다. 개인적인 설명은 기울임으로 표시해두었으니, 읽으면서 참고하시면 될 것 같습니다. 3장. 패러다임 개요 [ 구

mangkyu.tistory.com

 

 

댓글