본문 바로가기

Architecture & Design Pattern

[Architecture] Clean Architecture를 읽고, 1편 (1~6장 - 아키텍쳐의 의미 & 개발자의 역할 & 프로그래밍 패러다임)

중간고사가 끝난 김에 오랜만에 읽고 싶던 책을 펼쳤다. 아키텍쳐에 대한 고민이 많아지는 시점에 답답한 부분이 많아서 책을 읽고 조금이나마 해소하고 싶었다.

 

책의 제목이 Clean Architecture인 만큼, 도입에서 Architecture가 무엇인지에 대해 정의한다.

 

저자는 모든 소프트웨어 시스템이 이해관계자(기획자, 클라이언트 등)에게 두 가지의 가치, 즉 행위(behavior)와 구조(structure)를 제공해야 한다고 주장한다. 여기서 행위는 이해관계자가 요구하는 요구사항, 기능과 같은 것들을 말하며, 구조는 이러한 요구사항에 언제든지 대처할 수 있도록 유연함을 제공하는 아키텍쳐를 말한다. 본디 소프트웨어는 '부드러움'과 '제품'의 합성어이기 때문에 이와 같은 부드러움을 제공할 수 있어야 한다고 한다. 그리고 이 두 가치에 대해 많은 개발자들이 덜 중요한 가치에 집중하고 있어 소프트웨어 시스템이 쓸모없게 만든다고 하는데, 그것이 바로 '행위' 즉, 기능에만 집중하는 것이다.

 

행위에만 집중하는 일이 왜 비효율적인 소프트웨어를 만드는 것일까? 이해관계자는 개발자에게 일정 주기로 스스로 생각하기에 적절한 양의 '요구사항'을 전달할 것이다. 이러한 요구사항이 하나, 둘씩 쌓여서 훌륭한 '행위'를 제공하는 소프트웨어 시스템이 만들어졌다고 할 때, 이해관계자는 다시 한 번 또 다른 '적절한 양'의 요구사항을 전달할 것이다. 그러나 이것이 개발자에게도 여전히 '적절한 양'으로 다가오는가에 대한 것은 다른 문제이다. 이미 소프트웨어의 복잡도가 증가한 상태이기 때문에, 같은 범위와 주제의 요구사항이라도 복잡한 시스템 상에 새로운 사항을 추가하는 것 같이 어려운 문제가 되어버린다.

 

그리고 인상적으로 남는 구절이 하나 있었다.

변경사항을 적용하는 데 드는 어려움은 변경되는 범위에 비례하며, 변경사항의 형태와는 관계가 없어야 한다.

최근 아키텍쳐에 대해 공부하며 기능을 단위로 분리하고, 이들의 결합도를 낮추고는 것의 중요성에 대해서는 진행중인 토이 프로젝트를 유지, 보수하면서 어느정도 감을 잡아 가고 있다고 생각했다. 그러나 여기에 논리적인 근거가 있냐고 묻는다면 그다지 자신 있는 대답이 나올 것 같지 않다. 정말로 커다란 프로젝트를 다뤄본 실제적인 경험이 없기 때문이다. 그래도 지금 시점에서 확신할 수 있는 것은 프로젝트의 규모가 클수록 위에서 말한 '복잡도'는 증가할 것이며, 이러한 프로젝트에 '일정한 형태'의 요구사항이 지속적으로 들어온다면 난이도도 지속적으로 증가할 것이라는 사실이다. 그렇기에 '형태에 독립적인 아키텍쳐'가 더욱 중요해진다고 생각한다.

 


2부. 프로그래밍 패러다임.

 

2부에서는 프로그래밍 패러다임들을 소개한다. 이유는 아키텍쳐에 대한 논의가 나타나는 뼈대가 코드이기 때문이다. 도입부에서 패러다임이란 프로그래밍을 하는 방법으로, 구조적, 객체지향, 함수형 프로그래밍의 3가지가 존재한다고 하며, 동시에 앞으로 이를 제외한 패러다임이 나올 수는 없다고 말한다. 전혀 생각하지 못했던 분위기에 흥미를 가지고 읽기 시작했다.

 

구조적 프로그래밍은 제어흐름의 직접적인 전환에 대해 규칙을 부과한다

구조적 프로그래밍은 '증명'이라는 수학적 원리로 프로그래밍에 유클리드 계층구조와 같은 '수학적으로' 신뢰 가능한 구조를 만드는 시도를 하는 과정에서 생겨났다. 이러한 계층 구조가 존재하면 프로그래머는 각 모듈들을 올바르게 구현하면, 그 상위 모듈도 올바르게 작동할 수 있게 된다. 데이크스트라라는 사람은 이런 구조를 연구하는 과정에서 일부 goto문이 문제(모듈)의 재귀적 분해를 방해하며, 그렇지 않은 goto문은 if/then/else/while과 같은 분기와 반복에 한정된다는 사실을 발견했다.

 

 그리고 이 발견은 뵘과 야코피니가 증명한 '모든 프로그램은 순차, 분기, 반복이라는 세 가지 구조만으로 표현할 수 있다'와 동일한 의미를 지녔다. 이는 모듈을 증명 가능하게 하는 구조가 프로그램을 구성하는 최소 집합과 동일하다는 사실이었으며, 데이크스트라는 순차 실행에서 '분기', '반복' 구조가 열거법과 귀납법에 의해 올바름을 증명해 냈다. 결국 데이크스트라는 goto문의 해로움에 대해 알리는 글을 투고하고, 이에 대한 논의가 10년 가까이 이어졌으며 goto문의 사용이 지양되는 쪽으로 결론이 났다. 제어흐름에 대한 직접적인 전환(goto문)의 권리를 잃게 된 것이다.

 

어쨌거나 구조적 프로그래밍을 통해 모듈을 증명 가능한 더 작은 단위로 재귀적으로 분해가 가능하게 되었다. 모듈, 컴포넌트, 함수 등으로 분해가 되었으며, 대규모 시스템도 이러한 식으로 세분화할 수 있게 되었다. 그러나 앞서 말한 유클리드 계층구조는 만들어지지 못했으며, 이러한 수학적 접근보다 과학적인 방법으로의 돌파구가 생겼다고 한다.

 

테스트는 버그가 있음을 보여줄 뿐, 버그가 없음을 보여줄 수는 없다

 

과학은 연역의 학문이다. 과학은 반증은 가능하지만 증명은 불가능하다. 반증되지 않은 오랜 사실이 있으면 그것은 연역적으로 참이라고 보고 있다. 프로그래밍의 '테스트'도 마찬가지이다. 아직 테스트코드를 작성한 적은 없지만, '테스트' 자체는 많이 해 본 입장이다. 프로젝트를 진행하며 '테스트'는 밥먹듯이 하게 되는데, '테스트'라는 개념과 의미에 대해 깊이 생각해보지 않았다는 점이 스스로를 반성하게 했다. 스스로 만든 프로그램에 확신을 가지기 위해서는 올바른 테스트 방법을 알아야 한다. 저자는 구조적 프로그래밍의 가치가 '반증 가능한 단위'를 만들어 낼 수 있는 능력에서 온다고 한다. 여기서 '반증'이란 테스트 가능성이다. 좋은 아키텍쳐는 테스트 가능성을 높여준다.

 

객체 지향 프로그래밍은 제어흐름의 간접적인 전환에 대해 규칙을 부과한다

캡슐화 : 객체지향의 힘은 강력한 캡슐화에 의존할까?

 

객체 지향 프로그래밍 장에서는 캡슐화, 상속과 비슷한 구조가 C에서도 구현이 가능하며 거기서부터 시작한 개념이라는 점이 인상적이었다. 오히려 C++에서는 멤버 변수들이 헤더에 노출되며 완전한 캡슐화가 깨졌고, 객체지향 언어에서는 아예 그와 같은 구조를 버리고 클래스의 선언과 정의가 구분되지 않는다. 동시에 객체 지향 언어들은 캡슐화를 거의 강제하지도 않는다. 그래서 캡슐화가 효율적인 아키텍처에 도움이 되는 객체 지향의 핵심적인 특성이라고 보기에는 애매하다. 물론 상속의 경우에는, 기존 언어들보다 객체지향에서 쉬운 인터페이스를 제공하고 있어 어느정도 점수를 줄 수 있다.

 

다형성 : 객체지향의 핵심적인 이점은 다형성일까?

 

객체지향 언어 이전에도 C에서도 원하는 인터페이스를 구조체로 선언하고, 이에 대한 포인터와 함수를 이용해 구현할 수 있다. 즉, 다형성은 함수를 가리키는 포인터를 응용한 것이다. 객체 지향 언어에서의 이점은, 이러한 포인터를 초기화해야 하는 과정이 필요 없는 편리한 방법을 제공한다. 따라서 실수의 위험이 줄어게 된다.

 

다형성이 중요한 이유는, 새로운 장치나 프로그램에 대한 확장성이 높기 때문이다. 기존에 제공되는 인터페이스만 구현되어 있으면, 모든 장치가 이를 이용할 수 있다. 이는 플러그인 방식이라고 볼 수 있는데, 1950년대 후반에 이미 프로그램이 장치 독립적이어야 한다는 원리가 퍼져 있었고, 실제로 유닉스 운영체제에서 그러한 플러그인 아키텍쳐를 채택하고 있었다. 프로그램의 관점에서는 객체지향 덕분에 편리하게 플러그인 아키텍쳐를 구현할 수 있게 된 것이다.

 

함수형 프로그래밍은 제어흐름의 간접적인 전환에 대해 규칙을 부과한다

 

함수형 프로그래밍에서 가장 흥미로웠던 점은 변수가 변경되지 않는다는 점이다. 그리고 이 점이 아키텍쳐에서 왜 중요한지 생각도 못한 관점을 제시했다. 바로 '경합', '교착', '동시성' 관련 문제가 모두 변수의 가변성으로 인해 발생하는 것이라는 점이다. 만약 변수가 변하지 않는다면 이러한 문제를 마주칠 이유가 없다. 지금까지 말한 아키텍쳐는 모듈을 분리하고 테스트 가능성을 높이는 방향이었는데, 분리된 모듈들이 멀티 스레드 환경에서 하나의 변수에 접근할 일을 당연히 신경써야 할 것이다. 최근 프로젝트에서 싱글턴 객체를 통해 비동기 작업을 처리하는 과정에서 스레드 관련 에러가 난 적이 있기에 위의 관점이 더욱 충격적으로 다가왔다.

 

그래서 핵심은, 현명한 아키텍트에서는 많은 처리를 불변 컴포넌트로 배치하고, 많은 부분을 가변 컴포넌트롭터 빼내야 한다는 것이다.

 

이러한 불변성의 유지 관점에서, 무한한 프로세서와 용량을 가진 컴퓨터에서의 사고 실험이 흥미롭게 다가왔다. 값의 처리가 아니라 트랜잭션 자체를 무한히 보관해 놓고, 필요한 시점의 값이 호출되는 시점에서 트랜잭션들을 계산하여 결과를 내는 것이다. 따라서 어플리케이션에서 CRUD가 아니라 CR만 수행되며, 동시 업데이트 문제가 일어나지 않는다고 한다.

 


결론

 

각 프로그래밍 패러다임들은 우리에게 새로운 권한을 주기보다는 하면 안되는 일에 대한 규칙을 제공한다. 서두에 더이상의 프로그래밍 패러다임이 등장하기 어렵다는 말은 더이상 뺏어갈 권한이 없다는 점에서 나온 말이다. 이 말이 어느정도의 신빙성이 있는지는 아직 컴퓨터 시스템과 프로그래밍 방법론에 대한 지식이 얉아 혼자서 결론내기가 어려운 부분이 있다. 그러나 분명한 것은 소프트웨어의 처리 방식에 대한 분명한 규칙을 제공하는 것이 프로그래밍 패러다임이며, 이를 통해 신뢰 가능한 아키텍트의 근간을 만들 수 있다는 점이다.