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

2021.10.12

3장 패러다임 개요

구조적 프로그래밍

  • 1968년 데이크스트라가 무분별한 점프(goto)는 프로그램 구조에 해롭다는 사실을 제시.
  • 구조적프로그래밍은 제어흐름의 직접적인 전환에 대해 규칙을 부과한다는 것으로 요약이 가능.

객체 지향 프로그래밍

  • 두번째로 도입된 패러다임이지만 구조적 프로그래밍보다 2년 앞선 1966년에 등장.
  • 함수 호출 스택 프레임을 힙으로 옮기면 함수의 호출이 반환된 이후에도 함수에서 선언된 지역변수가 오랫동안 유지 될 수 있다는 것을 발견. 이러한 함수가 클래스의 생성자가 됨. ( 지역변수는 멤버변수로, 중첩 함수는 메서드로 )
  • 객체 지향 프로그래밍은 제어흐름의 간적적인 전환에 대해 규칙을 부과한다는 것으로 요약이 가능.

함수형 프로그래밍

  • 최근에 들어서야 도입되기 시작했지만, 세 페러다임 중 가장 먼저 만들어졌다.
  • 람다 계산법이 발명된 후 이에 직접적인 영향을 받아 만들어졌다. 람다 계산법의 기초가 되는 개념은 불변성으로 심볼의 값이 변경되지 않는다는 개념.
  • 함수형 프로그래밍은 할당문에 대해 규칙을 부과한다는 것으로 요약이 가능.

생각할 거리

  • 각 패러다임은 무엇을 해야 할지를 말하기보다는 무엇을 해서는 안 되는지를 말해준다.
  • 세 가지 패러다임은 각각 우리에게 goto문, 함수 포인터, 할당문을 앗아간다.
  • 이 밖에 다른 패러다임이 없다는 또 다른 증거로는 이 패러다임이 1958년부터 1968년에 걸쳐 만들어졌고, 이후로 수십 년이 지났지만 새롭게 등장한 패러다임은 전혀 없다.

결론

  • 아키텍처 경계를 넘나들기 위한 메커니즘으로 다형성을 이용한다.
  • 함수형 프로그래밍을 이용하여 데이터의 위치와 접근 방법에 대해 규칙을 부과한다.
  • 모듈의 기반 알고리즘으로 구조적 프로그래밍을 사용한다.
  • 세 가지 패러다임과 아키텍처의 세 가지 큰 관심사(함수, 컴포넌트 분리, 데이터 관리가) 서로 연관되는지 주목해보자.

4장 구조적 프로그래밍

  • 데이크스트라가 초기에 인식한 문제는 프로그래밍은 어렵고, 프로그래머는 프로그래밍은 잘하지 못한다는 사실이었다.

  • 아주 작은 세부사항이라도 간과하면 프로그램이 결국엔 예상 외의 방식으로 실패하곤 하였다.

  • 증명이라는 수학적 원리를 문제를 해결하고자 했고, 기본적인 알고리즘 증명에 대한 기법을 연구하면서 goto 문장의 좋은 사용방식은 if/then/else와 do/while과 같은 분기와 반복이라는 단순한 제어 구조에 해당한다는 사실을 발견했다.

  • 순차, 분기, 반복이라는 세 가지 구조만으로 모든 프로그래밍을 표현할 수 있다는 사실을 발견했고, 모듈을 증명 가능하게하는 구조가 모든 프로그램을 만들 수 있는 제어 구조의 최소 집합과 동일하였다. 구조적 프로그래밍은 이렇게 탄생하였다.

  • 데이크스트라는 테스트는 버그가 있음을 보여줄 뿐, 버그가 없음을 보여줄 수는 없다라고 말하였다.

    • 프로그래밍이 잘못되었음을 테스트를 통해 증명할 수는 있지만, 프로그램이 맞다고 증명할 수는 없다.
    • 소프트웨어는 과학과 같다. 최선을 다하더라도 올바르지 않음을 증명하는 데 실패함으로써 올바름을 보여주기 때문이다.

결론

  • 가장 작은 기능에서부터 가장 큰 컴포넌트에 이르기까지 모든 수준에서 소프트웨어는 과학과 같고, 반증 가능성에 의해 주도된다.
  • 소프트웨어 아키텍트는 모듈, 컴포넌트, 서비스가 쉽게 반증 가능하도록(테스트하기 쉽도록) 만들기 위해 분주히 노력해야 한다.

5장 객체 지향 프로그래밍

캡슐화

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

상속

  • 상속이란 단순히 어떤 변수와 함수를 하나의 유효 범위로 묶어서 재정의하는 일에 불과하다.
  • OO 언어가 있기 훨씬 이전에도 C 프로그래머는 순수한 방식으로 구현하기도 하였다.
  • 따라서 OO 언어가 완전히 새로운 개념을 만들지는 못했지만, 데이터 구조에 가면을 씌우는 일을 상당히 편리한 방식으로 제공했다고 볼 수는 있다.

다형성

  • OO언어는 다형성을 좀 더 안전하고 편리하게 사용할 수 있게 해준다.
    • 기존 언어들에서 다형성 구현은 함수 포인터를 활용하여 구현했는데, 이는 관리하기가 매우 힘들다
  • OO언어가 다형성을 안전하고 편리하게 제공한다는 사실은 소스 코드 의존성을 어디에서든 역전시킬 수 있다는 뜻이기도 하다.

결론

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

6장 함수형 프로그래밍

정수를 제곱하기

  • 함수형 언어에서 변수는 변경되지 않는다.

불변성과 아키텍처

  • 아키텍처를 고려할 때 왜 변수의 가변성을 염려하는지
    • 경합(race) 조건, 교착 상태(deadlock) 조건, 동시 업데이트(concurrent update) 문제가 모두 가변 변수로 인해 발생하기 때문이다.
  • 동시성 애플리케이션에서 마주치는 모든 문제, 즉 다수의 스레드와 프로세스를 사용하는 애플리케이션에서 마주치는 모든 문제는 가변 변수가 없다면 절대로 생기지 않는다.

가변성의 분리

  • 애플리케이션을 제대로 구조화하려면 변수를 변경하는 컴포넌트와 변경하지 않는 컴포넌트를 분리해야 한다.
    • 이렇게 분리하려면 가변 변수들을 보호하는 적절한 수단을 동원해 뒷받침해야 한다.
  • 현명한 아키텍트라면 가능한 많은 처리를 불변 컴포넌트로 옮겨야 하고, 가변 컴포넌트에서는 가능한 많은 코드를 뺴내야 한다.

이벤트 소싱

  • 저장 공간과 처리 능력의 한계는 우리의 시야에서 급격히 사라지고 있다.
  • 더 많은 메모리를 확보할수록, 기계가 더 빨라질수록 필요한 가변 상태는 더 적어진다.
    • 플리케이션의 수명주기 동안만 문제없이 동작할 정도의 저장 공간과 처리 능력만 있어도 충분하다.
  • 결과적으로 애플리케이션은 CRUD가 아니라 그저 CR만 수행한다.
  • 저장공간과 처리 능력이 충분하면 애플리케이션이 완전한 불변성을 갖도록 만들 수 있고, 따라서 완전한 함수형으로 만들 수 있다.
    • 스 코드 버전 관리 시스템이 정확히 이 방식으로 동작한다.

결론

  • 구조적 프로그래밍은 제어흐름의 직접적인 전환에 부과되는 규율이다.
  • 객체 지향 프로그래밍은 제어흐름의 간접적인 전환에 부과되는 규율이다.
  • 함수형 프로그래밍은 변수 할당에 부과되는 규율이다.
  • 소프트웨어는 반세기 전과 지금을 비교해보면 작성 규칙은 거의 동일하다.
  • 소프트웨어, 즉 컴퓨터 프로그램은 순차, 분기, 반복, 참조로 구성된다.