더 나은 엔지니어가 되기 위해/읽고 쓰며 정리하기

클린 아키텍처 2부 - 프로그래밍 패러다임

흠시 2020. 8. 8. 14:58

이 글은 로버트 C. 마틴의 클린 아키텍처를 읽고 나름대로 중요하다고 생각한 부분만 정리한 글이다.

 

들어가며

책의 1부에는 클린 아키텍처가 왜 중요한 지, 무엇을 추구하는지 등에 대해 나오는데 굳이 적지는 않겠다. 클린 아키텍처 책을 찾는 사람이라면, 이미 그 중요성이나 필요성을 느끼고 온 사람이지 않을까 싶어서다.

여기서는 내가 생각한 핵심만 정리해보려고 한다.

 

개요

예나 지금이나, 프로그래밍 언어의 기본적 기능은 달라지지 않았다. 1946년 앨런 튜닝이 최초의 코드를 작성할 때 사용한 소프트웨어 규칙과 지금의 소프트웨어 규칙은 조금도 다르지 않다. 프로그램은 여전히 순차, 분기, 반복, 참조로 구성된다.

프로그래밍 언어의 기본적 기능은 달라지지 않았지만, 이 기능을 어떻게 사용할 것인가에 대한 생각은 많이 발전해왔다. 이러한 생각은 여러 시행착오 끝에 시대를 거쳐 만들어져 온 것이며, 우리는 이를 "패러다임"이라고 부른다.

마틴 파울러(이하 밥 아저씨)가 말하는 이러한 패러다임은 3가지가 있다.

  • 구조적 프로그래밍
  • 객체 지향 프로그래밍
  • 함수형 프로그래밍

그리고 이러한 패러다임들은 우리에게 무엇을 해야할 지를 말하기보다는 무엇을 "해서는 안되는지"를 말해준다.

 

구조적 프로그래밍

goto 문을 사용하지 말고, if/then/else 로 프로그래밍을 제어하라는 규칙이다.
사실 지금으로 보면 너무나 당연한 얘기지만, 프로그래밍 초창기에 goto 문이 언어에 포함될 때 등장한 패러다임이다.

요지는 이렇다.
goto 문을 사용하게 되면, 우리는 더 작은 단위의 모듈로 분리할 수 없다. goto 문으로 코드 여기저기를 돌아다닐 수 있으니깐, 분리해야 될 지점을 잡기도, 코드의 제어 순서가 어떻게 되는지도 굉장히 신경 쓰인다.
그래서 goto 문을 사용하지 않고, 순차, 분기, 반복만을 이용한다. 그러면 우리는 더 작은 단위의 모듈로 분리할 수 있다. 분리해야 될 지점도 좀 더 명확해진다. 그리고 goto 문 없이도 사실 순차, 분기, 반복만을 이용해 모든 프로그램을 표현할 수 있다.

구조적 프로그래밍을 지키면 우리는 이제 코드를 마음껏 분리할 수 있는데, 바로 이 분리는 다음과 같은 의의가 있다.

  • 코드의 큰 흐름과 큰 흐름 내부의 작은 흐름을 분리하여 표현할 수 있다.
    • 큰 흐름은 추상적인 것, 작은 흐름은 세부적인 것을 표현하게 된다.
    • 이렇게 하면 읽기 좋고, 큰 흐름 혹은 작은 흐름에 따라 표현해야 할 코드가 명확해진다.
    • 보통 큰 흐름은 보통 고수준, 작은 흐름은 저수준이라고 표현한다.
  • 작은 컴포넌트로 분리함으로써, 작은 것부터 하나씩 테스트 가능해진다.
    • 우리는 거대한 코드가 옳게 동작하는지 한 번에 알기 어렵다.
    • 따라서 이 거대한 코드를 테스트 가능한 세부적인 단위로 나눈 후, 테스트를 통해 옳게 동작하는지 알 수 있다.

구조적 프로그래밍이 오늘날까지 가치 있는 이유는 바로 이 마지막 의의인 "더 작은 세부적인 것들의 분리를 통한 반증 가능성"에 있다. "반증 가능성"이라고 표현한 이유는, 우리가 이 프로그래밍이 항상 옳은지는 테스트할 수 없지만, 적어도 어느 경우에서 틀린지는 테스트할 수 있기 때문이다. 이는 마치 기존 가설을 반증하며 옳다고 생각하는 것을 수립해나가는 과학과 비슷한데, 이 때문에, 밥 아저씨는 프로그래밍은 수학이 아니라 과학에 가깝다고 말한다.

정리하면, 이 패러다임에서 얻을 수 있는 교훈은 다음과 같다.

모듈, 컴포넌트, 서비스가 쉽게 반증 가능하도록(테스트하기 쉽도록)
만들기 위해 컴포넌트를 잘 분리해야 한다.

 

객체 지향 프로그래밍

보통 객체 지향이 뭐냐고 하면 캡슐화, 상속, 다형성을 말하곤 한다.
이 세 가지 키워드에 대한 밥 아저씨의 말을 짧게 정리하면 다음과 같다.

  • 캡슐화 기능이 있기는 하지만, 다른 언어(C 언어)에 비해 딱히 더 나은 기능을 제공하는 건 아니다.
    • 오히려 C언어가 더 완벽한 캡슐화를 제공했다.
  • 상속이 객체지향에서 새롭게 등장한 방식은 아니지만, 나름대로 편리하게 사용할 수 있게 하긴 했다.
  • 다형성도 새롭게 등장한 방식은 아니지만, 기존의 방식을 크게 개선했고 매우 큰 효과를 냈다.

밥 아저씨는 캡슐화에 대해서는 별로 인정 안 하고 상속 그리고 다형성에 주로 의의로 두었는데, 그중에서 특히 "다형성" 이 아키텍트적인 관점에서 가지는 의의를 매우 크게 보았다.

다형성이란 뭘까? 일반적으로 제어 흐름은 시스템의 행위에 따라 결정되며, 소스 코드의 의존성은 제어 흐름에 따라 결정된다. 그래서 제어 흐름의 방향은 항상 고수준 로직에서 저수준 로직으로 향하게 된다.

이때 다형성이란 걸 두게 되면 조금 다른 일이 벌어지게 된다. 다형성이란, 쉽게 말해 고수준 로직에서 저수준 로직으로 흘러가는 흐름 사이에 추상 로직을 두는 것을 말한다. 이렇게 하면, 프로그램의 로직은 다음처럼 바뀐다.

  • 기존 : (고수준 로직) -> (저수준 로직)
  • 다형성 적용 후 : (고수준 로직) -> (추상 로직) <- (저수준 로직)

-> 으로 만 흘러가던 제어 흐름의 방향에, 갑자기 <- 이 등장했다. 이를 "의존성 역전"이라고 부른다. 제어 흐름의 방향에 역전이 생긴 것이다. 즉, 소스 코드 의존성이 제어 흐름의 방향과 일치되도록 제한되지 않는다. 이제 소스 코드의 의존성을 원하는 방향으로 설정할 수 있다. 저 수준 로직은 이제 그저 "플러그인"에 불과하다.

예를 들어, 저수준 로직이 데이터베이스에 관련된 것이라고 해보자. mysql를 쓰다가 postgres로 데이터베이스를 바꿔도 고수준 로직에 어떠한 영향이 가지 않는다. 데이터베이스는 "데이터베이스"라는 추상 클래스일 뿐 세부적인 사항은 그저 플러그인일 뿐이다.

이렇게 소스 코드의 의존성을 다형성을 통해 제어할 수 있고, 이를 통해 각 소스 코드들은 "독립적"으로 개발, 배포가 가능해진다. 독립이 되므로 신경 써야 할 다른 부수적인 것들은 사라지게 되고 개발 생산성도 올라가게 된다.

정리하면, 이 패러다임에서 얻을 수 있는 교훈은 다음과 같다.

다형성을 이용하여 전체 시스템의 모든 소스 코드 의존성에 대한
절대적인 제어 권한을 획득할 수 있다.

 

함수형 프로그래밍

개인적으로 함수형 프로그래밍은 내가 아직 해보지 않아서인지, 위 두 패러다임의 내용처럼 완전히 공감하며 이해하지는 못했다. 그래도 이해한 만큼만이라도 적어보려 한다.

동시성 애플리케이션(멀티 쓰레드, 프로세스를 사용하는 프로그램)에서의 모든 문제들은 가변 변수 때문에 생긴다. 즉, 가변 변수를 사용하지 않으면 이런 문제들은 아예 생기지가 않는다.
그렇다면 애초에 가변 변수를 안 쓰고, 불변 변수만 사용하면 되지 않을까? 이러한 생각으로, 함수형 프로그래밍에서는 변수는 변경되지 않는다. 아키텍처 관점에서는 이런 함수형 프로그래밍이 훨씬 안전해 보인다. (그래서 의의가 있다.)

그러나, 실제로 모든 변수를 불변으로 사용하는 것은 현실적으로 어려운 일이다. 새롭게 변경되는 모든 값을 가변 변수가 아닌 불변 변수를 새롭게 계속 만들어낸다면, 무한한 저장 공간과 무한한 처리 능력이 필요하다. 만약 저장 공간과 처리 능력이 충분하다면, 애플리케이션이 완전한 불변성을 갖도록 만들 수 있고, 완전한 함수형으로 만들 수 있다.

그렇기 때문에, 어느 정도 타협을 봐야 한다.
불변 컴포넌트와 가변 컴포넌트를 분리하고, 가능한 한 많은 처리는 가변 컴포넌트에서 불변 컴포넌트로 옮겨야 한다.

정리하면, 이 패러다임에서 얻을 수 있는 교훈은 다음과 같다.

불변 컴포넌트와 가변 컴포넌트를 분리하여 관리하자.

정리

지금까지의 내용을 요약하면 다음과 같다.

  • 구조적 프로그래밍은 제어 흐름의 직접적인 전환에 부과되는 규율이다.

    • 여기서 직접이란 goto 를 사용하지 말라는 것이다.
  • 객체 지향 프로그래밍은 제어흐름의 간접적인 전환에 부과되는 규율이다.

    • 여기서 간접이란 함수 포인터를 사용하여 다형성을 구현하지 말라는 것이다.
  • 함수형 프로그래밍은 변수 할당에 부과되는 규율이다.