공부
클린아키텍쳐 2부 정리
삶의안식처
2024. 5. 8. 11:04
3장 패터다임 개요
1. 구조적 프로그래밍
- 최초로 적용된 패러다임
- 1968 에츠허르 비버 데이크스트라(Edger Wybe Dijkstra)발견
- goto는 프로그램 구조에 해롭다
- if/then/else, do/while/until로 대체
- 구조적 프로그래밍은 제어흐름의 직접적인 전환에 대해 규칙을 부과
- goto와 같은 직접적인 전환을 제공하지 않아서 해결
2. 객체 지향 프로그래밍
- 두번째로 도입
- 등장은 1966년 올레 요한달(Ole Johan Dahl), 크리스텐 니가드(Kristen Nygaard)
- 발견
- 함수 호출 스택 프레임을 힙으로 옮길 시 return이후 함수에서 선언된 지역변수가 오래 유지
- 힙으로 옮긴 함수 -> 클래스 생성자
- 함수와 내부의 지역변수가 메모리 해제 전까지 남아있음
- 이것이 객체의 기반이 됌
- 지역변수 -> 인스턴스 변수
- 지역변수는 스택에 저장
- 함수 호출과 함께 소멸
- 중첩함수 -> 메서드
- 함수 포인터를 특정 규칙에 따라 사용하는 과정을 통해 필연적으로 다형성이 등장
- 객체지향 프로그래밍은 제어흐름의 간접적인 전환에 대해 규칙을 부과한다
3. 함수형 프로그래밍
- 최근에 도입 시작
- 3자기 중 가장 먼저 만들어짐
- 컴퓨터 프로그래밍 자체보다 먼저 등장
- 알론조 처치(Alonzo Church)는 람다 계산법 발명
- 함수형 프로그래밍은 람다 계산법 기반으로 만들어짐
- 불변성
- 심볼의 값이 변경되지 않음
- 할당문이 없음
- 함수형 언어가 변수값을 변경할 수 있는 방법을 제공, 까다로운 조건 하에만 가능
- 함수형 프로그래밍은 할당문에 대해 규칙을 부과한다.
4. 생각할거리
- 각 패러다임은 프로그래머에게서 권한을 박탈
- 어느 패러다임도 새로운 권한을 부여하지 않음
- 부정적인 의도를 갖는 추가적인 규칙을 부과
- 패러다임은 무엇을 해서는 안되는지에 대해 말해줌
- 구조적 프로그래밍 : goto
- 객체지향 프로그래밍 : 함수포인터
- 함수형 프로그래밍 : 할당문
- 1958~1968 10년동안 모두 만들어졌지만 이후 새롭게 등장한 패러다임은 없음
5. 결론
- 아키텍처와의 관계
- 경계를 넘기 위해 다형성을 이용
- 함수형 프로그래밍을 이용하여 데이터의 위치와 접근 방법에 대해 규칙을 부과
- 모듈의 기반 알고리즘으로 구조적 프로그래밍을 이용
- 아키텍처의 3가지 큰 관심사
4장 구조적 프로그래밍
1. 증명
- 프로그래밍은 어렵고, 프로그래머는 프로그래밍을 잘하지 못함
- 인간의 두뇌로 감당하기엔 너무 많은 세부사항
- 작은 세부사항을 간과하면 예상 외의 방식으로 실패
- 증명이란 수학적 원리를 적용하여 문제를 해결하고자함
- 프로그래머는 입증된 구조를 이용
- 구조를 코드와 결합
- 코드가 올바르단 사실을 증명
- goto문
- 모듈을 더 작은 단위로 재귀적으로 분해하는 과정에 방해
- 모듈을 분해할 수 없다면 분할 정복 접근법을 사용할 수 없음
- 문제가 되지 않는 경우
- if/the/else와 do/while과 같은 분기의 반복
- 모듈이 이러한 종류의 제어구조만 사용 시 증명 가능한 단위로 모듈을 재귀적으로 세분화가 가능
- 순차 실행과 결합했을 때 특별
- 다익스트라보다 2년전 뵘과 야코피니가 발견
- 모든 프로그램을 순차, 분기, 반복이라는 세가지 구조만으로 표현
- 구조적 프로그래밍의 탄생
- 순차
- 분기
- 열거법을 재적용하여 처리
- 분기를 통한 각 경로를 열거
- 반복
2. 해로운 성명서
- 1968년 CACM(Communications of the ACM)에 goto문의 해로움
- 컴퓨터언어가 진화하면서 goto문장은 거의 사라졌다.
- 대다수의 현대적 언어는 goto를 포함하지 않음
- 현재의 우리 모두는 구조적 프로그래머
- 제어흐름을 제약 없이 직접 전환할 수 있는 선택권 자체를 언어에서 제공하지 않기 때문
3. 기능적 분해
- 구조적 프로그래밍을 통해 모듈을 증명 가능한 더 작은 단위로 분해
- 모듈을 기능적으로 분해할 수 있음
- 분해한 기능들은 구조적 프로그래밍의 제한된 제어 구조를 이용하여 표현
- 1970~80에 구조적 설계, 구조적 분석이 인기
- 프로그래머
- 대규모 시스템을 모듈과 컴포넌트로 나눔
- 모듈과 컴포넌트는 입증할 수 있는 작은 기능들로 세분화 가능
4. 테스트
- 다익스트라는 테스트는 버그가 있음을 보여줄 뿐, 버그가 없음을 보여줄 수는 없다.
- 프로그램이 잘못되었음을 테스트를 통해 증명 가능
- 프로그램이 맞다고 증명할 수는 없음
- 테스트에 충분한 노력을 들였을 때
- 프로그램이 목표에 부합할 만큼은 충분히 참이라고 여길 수 있음
5. 결론
- 구조적 프로그래밍이 가치 있는 이유
- 프로그래밍에서 반증 가능한 단위를 만들어 낼 수 있는 능력
- 현대적 언어가 아무런 제약 없는 goto문을 지원하지 않는 이유
- 아키텍쳐 관점에서 기능적 분해를 최고의 실천법 중 하나로 여기는 이유
- 소프트웨어는 반증 가능성에 의해 주도
- 소프트웨어 아키텍트는 모듈, 컴포넌트, 서비스가 쉽게 반증 가능하도록(테스트하기 쉽도록) 만들기 위해 분주히 노력해야한다.
5장 객체 지향 프로그래밍
1. 개요
- 좋은 아키텍쳐를 만드는 일은 객체 지향 설계 원칙을 이해하고 응용
- 객체 지향(Object Oriented)는 무엇인가?
- 데이터와 함수의 조합
- 객체지향의 발명이전인 1966보다 훨씬 이전부터 프로그래머는 데이터 구조를 함수에 전달
- 실제 세계를 모델링하는 새로운 방법
- 실제 세계를 모델링한다는것이 무엇을 의미?
- 왜 그 방향을 추구해야 하는가?
- 현실세계와 의미적으로 가까움
- 소프트웨어를 좀 더 쉽게 이해
- 3가지 주문에 기댐
- 캡슐화
- 상속
- 다형성
- 3가지 개념을 적절하게 조합
- OO는 최소한 3가지 요소를 반드시 지원해야 함
2. 캡슐화
- 데이터와 함수를 쉽고 효과적으로 캡슐화하는 방법을 OO언어가 제공
- 데이터와 함수가 응집력 있게 구성된 집단을 서로 구분짓는 선을 그을 수 있다
- 바깥에서는 데이터는 은닉(private)
- 일부 함수만이 외부에 노출(public)
- OO언어가 아닌 c언어 에서도 캡슐화 가능
- 헤더에 원하는 함수 및 변수만 선언
- c파일에는 그 외 숨기고 싶은 것이나 구현
- 오히려 c++에서 public, private를 헤더에 선언하면서 깨짐
- 기술적인 이유로 클래스와 멤버변수를 해당 클래스의 헤더 파일에 선언할 것을 요구
- c++컴파일러는 클래스의 인스턴스 크기를 알 수 있어야 하기 때문
- java와 c#에서는 헤더와 분리하는 방식을 버림
- 캡슐화는 더욱 심하게 훼손
- 따라서 OO가 강력한 캡슐화에 의존한다는 정의는 받아들이기 힘들다
- OO 프로그래밍은 프로그래머가 충분히 올바르게 행동함으로 캡슐화된 데이터를 우회해서 사용하지 않을 거라는 믿음을 기반
- 하지만 실제로 c의 완벽한 캡슐화는 약화함
3. 상속
- 상속만큼은 OO언어가 확실히 제공
- 상속이란 단순히 어떤 변수와 함수를 하나의 유효 범위로 묶어서 재정의하는 일에 불과
- OO이전에도 가능은 함, 물론 상속만큼 편리하진 않음
- 따라서 OO이전에도 상속과 비슷한 기법이 사용되었다
- 캡슐화에 대해서 OO에 점수를 줄 수 없고, 상속에 대해서만 0.5점 줄 수 있음
4. 다형성
- OO이전의 다형성
- c언어의 STDIN, STDOUT, FILE등
- FILE에는 열기, 닫기, 읽기, 쓰기, 탐색을 표준함수 포인터로 존재
- STDIN을 FILE*로 선언
- getchar는 STDIN->read()로 표현 가능
- FILE 데이터 구조의 read포인터가 가리키는 함수를 단순히 호출
- 이런 기법이 OO가 지닌 다형성의 근간
- 함수를 가리키는 포인터를 응용한 것이 다형성
- 따라서 OO가 새롭게 만든 것은 아님
- 대신 다형성을 좀 더 안전하고 편리하게 사용하게 해줌
- 함수포인터를 직접 사용하는 방식의 문제
- 함수포인터는 위험
- 포인터를 초기화 하는 관례를 준수
- 포인터를 통해 모든 함수를 호출하는 관례를 준수
- 만약 지키지 않으면 버그 발생, 디버깅이 어려움
- OO는 다형성에서 쉽게 사용 가능하므로 제어흐름을 간접적으로 전환하는 규칙을 부과
- 함수의 포인터를 사용해 다형성을 사용하지 않음
5. 다형성이 가진 힘
- 1950년 후반에 프로그램이 장치 독릭적이어야 하는 사실을 배움
- 천공카드 -> 자기테이프로 바뀔때 많은 부분 수정
- 따라서 플러그인 아키텍쳐는 입출력장치 독립성을 지원하기 위해 만들어짐
- 이는 거의 모든 운영체제에서 구현
- 하지만 프로그래머는 직접 작성하는 프로그램에서는 이러한 개념을 확장하여 적용하지 않음
- 함수를 가리키는 포인터를 사용하면 위험을 수반하기 때문
- OO등장으로 플러그인 아키텍처를 쉽게 적용
6. 의존성 역전
- 다형성을 안전하고 편리하게 적용할 수 있는 메커니즘이 등장하기 이전
- main함수 -> 고수준의 함수 -> 중간 수준 함수 -> 저수준의 함수 호출
- 따라서 코드 의존성의 방향은 제어흐름을 따름
- 고수준 함수 호출
- 즉 제어흐름은 시스템의 행위에 따라 결정
- 소스코드 의존성은 제어흐름에 따라 결정
- 다형성 메커니즘이 등장한 이후
- 인터페이스를 통해 소스코드 의존성이 제어 흐름과 반대
- 의존성 역전(dependency inversion)
- OO언어가 다형성을 안전하고 편리하게 제공한다는 사실은 코드 의존성을 어디서든 역전시킬 수 있음
- 소프트웨어 아키텍트는 소스코드 의존성 전부에 대해 방향을 결정할 수 있는 절대적인 권한 획득
- 소스코드의 의존성이 제어흐름의 방향과 일치되도록 제한되지 않음
- 이것이 바로 OO의 지향점
- 다형성으로 할 수 있는 일
- 비즈니스 로직을 플러그인화
- 비즈니스 로직에서 UI코드나 database를 호출하지 않음
- 따라서 3가지 모듈을 분리하여 컴포넌트 또는 배포 가능단위로 컴파일 가능
- 서로 의존하지 않음, 따라서 독립적으로 배포 가능
- 변경사항이 서로 영향을 끼치지 않음
- 배포 독립성
- 특정 컴포넌트의 소스코드가 변경되면 코드가 포함된 컴포넌트만 다시 배포
- 개발 독립성
- 시스템의 모듈을 독립적으로 배포 가능함에 따라 서로 다른 팀에서 각 모듈을 독립적으로 개발
7. 결론
- OO란 다형성을 이용하여 전체 시스템의 모든 소스 코드 의존성에 대한 절대적인 제어 권한을 획득할 수 있는 능력
- OO를 이용하여 플러그인 아키텍처 구성
- 모듈의 독립성 보장
- 독립적으로 개발과 배포가 가능
6장 함수형 프로그래밍
1. 정수를 제곱하기
- 리스프언어에서는 함수를 괄호 안에 넣는 방식으로 호출 (range)
- 가장 안쪽 함수 호출부터 시작해서 바깥으로 진행
- 자바의 가변변수
- 함수형 언어에는 변수는 변경되지 않음
2. 불변성과 아키텍처
- 아키텍처가 변수의 가변성을 염려하는 이유
- 경합(race)
- 교착상태(deadlock)
- 동시 업데이트(concurrent update)
- 만약 어떠한 변수도 갱신되지 않으면 경합조건이나 동시 업데이트 문제가 일어나지 않음
- 동시성 어플리케이션에서 마주치는 모든 문제, 다수의 스레드와 프로세스를 사용하는 어플리케이션에서 마주하는 모든 문제는 가변 변수가 없으면 일어나지 않음
- 불변성은 저장공간이 무한하고 프로세서의 속도가 무한히 빠르다면 가능하지만 자원이 무한대가 아니면 타협이 필요
3. 가변성의 분리
- 가변 컴포넌트와 불변 컴포넌트를 분리
- 불변 컴포넌트에서는 순수하게 함수형 방식으로만 작업이 처리
- 불변 컴포넌트에서는 어떠한 가변 변수도 사용하지 않음
- 불변 컴포넌트는 가변 컴포넌트들과 통신함
- 가변 컴포넌트에서는 트랜잭션 메모리, 재시도 기법으로 동시 갱신과 경합 조건으로 부터 가변 변수를 보호
- 애플리케이션을 제대로 구조화
- 변수를 변경하는 컴포넌트와 변경하지 않는 컴포넌트를 분리
- 가변 변수들을 보호하는 적절한 수단을 동원해 뒷받침해야 함
- 현명한 아키텍트
- 가능한 많은 처리를 불변 컴포넌트로 이동
- 가변컴포넌트에서는 가능한 많은 코드를 빼야함
4. 이벤트 소싱
- 이벤트 소싱은 트랜잭션을 저장하자는 전략
- 상태가 필요해지면 단순히 상태의 시작점부터 모든 트랜잭션 처리
- 데이터 저장소에서 삭제되거나 변경되는 것이 없음
- CRUD가 아닌 CR만 수행
- 저장공간과 처리능력이 충분하면 어플리케이션이 완전한 불변성을 갖도록 만들 수 있음
- 완전한 함수형으로 만들 수 있음
- 소스 코드 버전 관리 시스템이 정확히 이 방식으로 동작
5. 결론
- 3가지 패러다임
- 구조적 프로그래밍은 제어 흐름의 직접적인 전환에 부과되는 규율
- 객체 지향 프로그래밍은 제어흐름의 간접적인 전환에 부과되는 규율
- 함수형 프로그래밍은 변수 할당에 부과되는 규율
- 3가지 패러다임은 한정을 통해 권한과 능력을 뺐음
- 소프트웨어는 순차, 분기, 반복, 참조로 구성