-
Clean Architecture - 프로그래밍 패러다임Book/Clean Architecture 2022. 9. 18. 12:03
패러다임이란 프로그래밍을 하는 방법으로 대체적으로 언어에 독립적이다.
패러다임은 어떤 프로그래밍 구조를 사용할지 언제 이 구조를 사용하는지 결정한다.
현재까지는 구조적(Structrued), 객체지향(Object-Oriented), 함수형(Functional) 프로그래밍
이렇게 3가지 패러다임이 존재하고 아마 세 가지 외에 다른 패러다임은 존재하지 않을 것이다. <- 밥 아저씨 생각 뒤에 그 이유를 말해준다고 합니다.
구조적 프로그래밍
최초로 만들어진 패러다임은 아니지만 최초로 적용된 패러다임이다.
무분별한 goto문은 프로그램 구조에 해롭다는 사실로 출발해서 점프구조를 if/then/else do/while/until과 같은 구조로 대체했다.
한마디로 구조적 프로그래밍은 제어흐름의 직접적인 전환에 대해 규칙을 부과한다.
객체지향 프로그래밍
두번째로 만들어지고 두 번째로 도입된 패러다임.
함수 호출 스택프레임을 heap으로 옮기면서 함수 호출이 반환된 후에도 함수에서 선언한 지역변수가 유지될 수 있음을 발견했다.
이 함수가 바로 클래스의 생성자이다.
함수 포인터를 특정 규칙에 따라 사용하는 과정을 통해서 필연적으로 다형성이 등장하게 됐다.
객체지향 프로그래밍은 제어 흐름의 간접적인 전환에 대해 규칙을 부과한다.
- 구조적프로그래밍이 직접적인 전환에 규칙을 부여한다는 건 알겠는데 객체지향이 간접적인 전환에 대해 규칙을 부과한다는 말이 뭔 말인지 이해를 못 했다.
포인터와 관계가 있어 보이는데 더 읽어봐야 이해가 갈듯!
- 뒤에서 다형성과 관련해서 설명을 해준다.
함수형 프로그래밍
가장 처음 만들어진 패러다임이지만 가장 나중에 도입된 패러다임.
수학적 문제를 해결하는 과정에서 람다 계산법을 발명했는데 이 계산법에 영향을 받아 함수형 프로그래밍이 생겨났다.
람다 계산법에 기초는 불변성으로 값이 변경되지 않는다는 개념이다.
함수형 언어가 변수의 값을 변경하는 방법을 제공하지만 매우 까다로운 조건에서만 가능하다.
함수형 프로그래밍은 할당문에 대해 규칙을 부과한다.
정리
각 패러다임은 개발자에게서 권한을 박탈한다.
새로운 권한을 부여하는 것이 아니라 추가적인 규칙을 부여한다.
즉, 패러다임은 무엇을 해야 할지를 말하는 게 아니라 무엇을 하면 안 되는지를 말해준다.
각각의 패러다임은 goto, 함수 포인터, 할당문을 제한했다.
이 세 가지 말고 우리에게서 더 가져갈 건 없다.
그러므로 무엇인가를 제한하는 부정적 의도를 가진 패러다임은 이 세 가지가 전부일 것이다.
- 이래서 더 이상의 패러다임은 생기지 않을 것이라고 주장한 것!
왜 패러다임을 알아봤을까?
모듈 기반 알고리즘으로 구조적 프로그래밍을 사용하고
아키텍처의 경계를 넘나들기 위해서 다형성을 이용하고
함수형 프로그래밍을 이용해서 데이터의 위치와 접근방법에 대해 규칙을 부과한다.
이 세 가지 패러다임과 아키텍처의 세 가지 관심사(함수, 컴포넌트 분리, 데이터 관리)가 어떻게 연관되는지에 주목해보자.
구조적 프로그래밍
증명이라는 수학적인 원리를 적용하여 문제를 해결하려고 했다.
이 과정에서 goto 문장이 모듈을 더 작은 단위로 분해하는 과정에 방해가 되는 경우가 있다는 것을 발견한다.
모듈을 분해할 수 없다면 증명할 때 필수적인 기법인 분할 정복 접근법을 사용할 수 없게 된다.
반면 모듈을 분해할 때 문제가 되지 않는 goto문도 있었는데 이게 바로 if/then/else do/while과 같은 분기문과 반복문이었다.
제어 흐름을 아무 제약 없이 직접 전환 할 수 있던 과거와 달리 현재는 지원하지 않거나 아주 제한적으로 사용하도록 변했다.
구조적 프로그래밍을 통해서 모듈을 증명 가능한 더 작은 단위로 분해할 수 있게 됐다.
이 말은 즉 모듈을 기능적으로 분해할 수 있다는 의미이고
어떠한 문제라도 고수준의 기능으로 분해할 수 있고 이 고수준의 기능을 저수준의 기능으로 분해할 수 있다.
이 기법을 사용하면 개발자는 대규모 시스템을 모듈과 컴포넌트로 나눌 수 있고 이 둘은 입증할 수 있는 아주 작은 기능들로 세분화할 수 있다.
구조적 프로그래밍은 프로그램을 증명 가능한 작은 기능 집합으로 재귀적으로 분해하도록 한다.
그리고 테스트를 통해서 기능들이 거짓인지 증명하려고 한다.
거짓인지 증명하는 게 실패한다 해서 이 프로그램이 맞다는 의미는 아니다.
이것이 의미하는 것은 이 기능들은 목표에 충족시킬 만큼만 참이라고 여길 수 있게 되는 것이다.
(언제든 우리가 생각하지 못한 반례가 있을 수 있는 가능성이 있다. 테스트가 통과한다고 해서 무조건 맞는 게 아니다.)
구조적 프로그래밍이 가치 있는 이유는 프로그래밍에서 반증 가능한 단위를 만들어 낼 수 있는 능력 때문이다.
모듈, 컴포넌트, 서비스가 쉽게 반증 가능하도록 즉, 테스트하기 쉽도록 만들기 위해서 노력해야 한다.
이를 위해서 구조적 프로그래밍과 비슷한 제한적인 규칙들을 활용해야 한다.
객체지향 프로그래밍
좋은 아키텍처를 만드는 것은 객체 지향 설계 원칙을 이해하고 응용하는데서 출발한다.
객체지향(Object Oriented)란 이미 많이 정리된 게 있으니 여기서의 설명은 넘어가고
아키텍처의 관점에서 보는 객체지향이란 무엇일까?
캡슐화
데이터와 함수가 집단을 서로 구분 짓는 선을 그을 수 있게 된다. 밖에서 데이터는 숨겨지고 일부 함수만 외부에 노출된다.
하지만 실제로 많은 객체지향 언어가 캡슐화를 강제하지 않는다.
오히려 C언어에서 더 강력한 캡슐화가 가능했다.
즉 아키텍처 관점에서 크게 중요한 부분이 아니다.
상속
상속이란 단순히 어떤 변수와 함수를 하나의 범위로 묶어서 재정의하는 일에 불과하다.
이건 언어에 관련 없이 C에서도 가능한 방법이다.
물론 다중 상속이나 업캐스팅 같은 것을 쉽게 해 준 것은 맞다.
마찬가지로 아키텍처 관점에서 중요한 부분이 아니다.
다형성
C에서도 다형성을 이용할 수 있다.
함수를 가리키는 포인터를 응용한 것이 다형성이다.
객체지향 언어는 다형성을 새로 제공한것은 아니지만 안전하고 편리하게 사용할수 있게 했다.
포인터를 이용하는 것은 위험을 수반하게 되는데 객체지향언어는 이러한 단점을 없애주었다.
그래서 객체지향은 제어 흐름을 간접적으로 전환하는 규칙을 부과한다고 할 수 있다.
세 관점에서 본다면 캡슐화와 상속보다는 다형성이 아키텍처 측면에서 의미가 크다.
이 책에서 들고 있는 예시는 복사하는 함수에서 입출력 장치가 변경되면 프로그램에서 변경되어야 하는 부분이 있는가를 설명한다.
복사 코드는 입출력 드라이버 코드에 의존하지 않기 때문에 새로운 입출력 장치가 생겨도 복사 코드는 변경할 필요가 없다.
이를 입출력 드라이버가 복사 프로그램에 플러그인(Plugin)되었다고 한다.
플러그인 아키텍처는 입출력 장치 독립성을 지원하기 위해서 만들어졌고 등장 이후 거의 모든 OS에서 구현되었다.
개발 초기에는 포인터를 직접 사용해야 했기 때문에 플러그인 개념을 프로그램에 적용하기 쉽지 않았지만 객체지향이 등장하고 나서 이러한 위험이 사라졌기 때문에 플러그인 아키텍처를 적용할 수 있게 됐다.
의존성 역전
다형성을 자유롭게 사용하지 못했을 때의 메커니즘을 생각해보자.
전형적인 호출트리의 경우 고수준 함수가 중간 수준 함수를 중간 수준 함수가 저수준 함수를 호출한다.
이러한 경우 소스코드 의존성의 방향은 반드시 제어 흐름의 방향을 따르게 된다.
하지만 다형성을 추가하면 이 흐름이 깨지게 된다.
인터페이스를 통해서 간접적으로 고수준 모듈이 저수준 모듈의 함수를 호출한다.
이렇게 되면 고수준 모듈과 인터페이스 간의 코드 의존성이 제어 흐름과 반대가 된다.
객체지향 언어가 다형성을 안전하고 편리하게 제공한다는 것은 인터페이스를 추가함으로써 소스코드의 의존성을 어디에서든 역전시킬 수 있다는 뜻이다.
그럼 개발자가 코드의 의존성 방향을 결정할 수 있는 절대적 권한을 가지게 된다.
이것이 바로 아키텍처 관점에서 객체지향이 가지는 의미이다.
예를 들어 이걸 이용하면 비즈니스 룰이 UI와 DB에 의존하는 대신에 시스템의 코드 의존성을 반대로 배치하여 UI와 DB가 비즈니스 룰에 의존하게 만들 수 있다.
즉 비즈니스룰 코드에서는 UI나 DB를 호출하지 않는다.
그렇게 되면 이 3가지를 분리된 컴포넌트로 만들 수 있고 비즈니스 룰을 포함하는 컴포넌트는 UI와 DB에 의존하지 않는다.
이렇게 되면 특정 컴포넌트에 소스코드가 변경돼도 나머지 컴포넌트에 영향을 주지 않는다. 그렇게 되면 배포 독립성과 개발 독립성이 생긴다.
정리하자면 객체지향은 다형성을 이용해서 전체 시스템의 모든 소스코드 의존성에 대한 절대적인 제어 권한을 가지게 만들어 준다.
이를 이용하면 플러그인 아키텍처를 구축할 수 있고 고수준 모듈은 저수준 모듈에 대해서 독립성을 보장받게 된다.
함수형 프로그래밍
함수형 언어에서는 변수가 변경되지 않는다.
아키텍처 관점에서 이게 왜 중요할까?
멀티 스레드와 프로세스를 사용하는 앱에서 마주치는 모든 문제는 가변 변수가 없다면 일어나지 않는다.
저장공간이 무한하고 프로세스가 무한히 빠르다면 불변성의 문제는 크게 문제 될 게 없다.
하지만 현실을 그렇지 않으므로 적절한 선에서 타협해야 한다.
앱 내부의 서비스를 가변 컴포넌트와 불변 컴포넌트로 분리하는 일
변수를 변경해야 하는 컴포넌트와 변경하지 않는 컴포넌트로 분리해야 한다.
그리고 이렇게 하기 위해서 가변 변수를 보호하는 수단을 동원해야 한다.
다시 한번 정리
- 구조적 프로그래밍은 제어 흐름의 직접적인 전환에 제한을 건다.
- 객체지향 프로그래밍은 제어흐름의 간접적인 전환에 제한을 건다.
- 함수형 프로그래밍은 변수 할당에 제한을 건다.
728x90'Book > Clean Architecture' 카테고리의 다른 글
Clean Architecture - 컴포넌트 원칙 (1) 2022.12.14 Clean Architecture - 설계 원칙 (0) 2022.09.26 Clean Architecture - Robert C. Martin (0) 2022.09.17