본문 바로가기

공부

클린아키텍쳐 2부 정리

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문의 해로움
    • 10년이상 논쟁
    • 다익스트라 승리
  • 컴퓨터언어가 진화하면서 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함수 -> 고수준의 함수 -> 중간 수준 함수 -> 저수준의 함수 호출
    • 따라서 코드 의존성의 방향은 제어흐름을 따름
    • 고수준 함수 호출
      • 모듈 지정
        • include, import, using등
    • 즉 제어흐름은 시스템의 행위에 따라 결정
    • 소스코드 의존성은 제어흐름에 따라 결정
  • 다형성 메커니즘이 등장한 이후
    • 인터페이스를 통해 소스코드 의존성이 제어 흐름과 반대
      • 인터페이스는 런타임에는 존재하지 않음
    • 의존성 역전(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가지 패러다임은 한정을 통해 권한과 능력을 뺐음
  • 소프트웨어는 순차, 분기, 반복, 참조로 구성

'공부' 카테고리의 다른 글

클린아키텍쳐 1부 정리  (0) 2024.05.08