Word Finder 개발기 3
나 스스로가 모자라고 더 많이 배워야 하는 상황에서 한 번 쓴 코드가 바뀌지 않을 것이라고 생각하지 않는다. 오히려 내가 배우는 만큼 훨씬 더 많이 바뀔 것이다. 어느 프로젝트나 마찬가지지만, 이런 내 상황에서는 유지보수성에 높은 우선순위를 둘 수 밖에 없었다.
유지보수
유지보수란 무엇인가? 프로그램 내부의 오류를 쉽게 잡을 수 있고, 외부의 변화에 유연하게 대응 가능하며, 다른 사람이 알아보기도 쉬운 것이라고 생각한다. 그리고 개인적으로 확장성도 그 안에 포함되는 것 같다. 단지 변수 이름을 알맞게 짓거나 함수를 잘개 쪼개는 것만으로는 유지보수성을 챙기기 어렵다. 무언가 구조적인 방법이 필요했다.
클린 아키텍쳐
로버트 마틴이 클린 아키텍쳐에서 이야기한 아키텍쳐를 최대한 적용하려 노력했다. 간단한 프로그램이지만 모듈들을 최대한 기능에 따라 분리하고 입출력에 더 가까운 모듈이 그렇지 않은 모듈을 참조하고 반대의 경우는 발생하지 않도록 하였다.
예를 들어 검색 기능을 살펴보자. 사용자가 느끼는 과정은 다음과 같다.
- 검색창에 단어를 친다.
- 검색 버튼을 누르거나 엔터키를 누른다.
- 창에 단어의 뜻이 나타난다.
이 프로그램에서 진행되는 일련의 과정은 다음과 같다.
- 검색 버튼이나 엔터키가 눌리면 검색창의 단어를 target으로 저장한다.
- target를 매개변수로 WordInput를 생성한다.
- WordInput은 두 스크래퍼 중 하나를 만들어 반환하는데, 예를 들어,
- get_naver_finder() - 네이버 사전을 긁어오는 NaverFinder를 target을 매개변수로 생성해 반환한다.
- get_wiki_finder() - 위키백과를 긁어오는 WikipediaFinder를 target을 배개변수로 생성해 반환한다.
- 이후 각 Finder에서 찾은 페이지html을 Presenter를 통해 원하는 부분만 문자열(result)로 반환한다.
- 해당 문자열(result)을 GUI 창에 띄운다.
"검색창 -> 페이지 탐색과 html반환 -> html에서 원하는 부분만 GUI에 출력"으로 구현할 수 있는 간단한 과정을, 이를 분리하고 그 중간에 WordInput이나 Presenter를 도입한 이유는 무엇일까?
만약 NaverFinder가 검색창 입력 target을 직접 받아 처리하고 원하는 단어의 뜻만 문자열로 출력한다고 해보자. 구현은 쉬울 것이다. target을 바로 NaverFinder에게 넘기고 .find()의 반환 값 result를 GUI에 띄우면 된다. target과 result를 원하는 형태로 가공하는 것(예를 들어 올바른 입력값인지 검증하거나, 출력값의 공백문자를 없앤다거나)은 NaverFinder 안에 포함될 것이다(자잘한 함수로 나눌 수는 있겠지만 어찌되었건 같은 컴포넌트로 묶인다고 해보자).
만약 사용자가 요구하기를, 검색 창에 단어를 입력하는 방식이 아니라 파일에 일련의 단어들을 주욱 나열해 놓고 그것 전체를 검색해주기 바란다고 하자. NaverFinder 컴포넌트를 계속 사용하기 바란다면 프로그램은 입력이 검색창 문자열인지, 단어 파일인지, 파일이라면 올바른 파일인지 검증해야 한다. 그리고 파일의 내용을 원하는 형태로 다시 가공해야하고…
이번에는 사용자가 GUI 화면을 추가해달라 요청했다고 하자. 단순 문자열 옆에 웹 페이지 자체를 보여주기 바란다. 이제 NaverFinder는 html의 일부를 문자열로 반환하는 것뿐만 아니라 html 전체를 반환해야한다. NaverFinder는 이제 둘을 나누어 반환해야 한다. .get_html()과 .get_str()이라고 하자. 원래는 .find()였지만 이제 함수명이 달라졌다. 따라서 이를 사용하는 모든 컴포넌트를 수정해야 한다. 컴파일 언어라면 재컴파일 해야할 것이다.
너무나 많은 노력이 드는 작업이다. 이에 대해 클린 아키텍쳐는 이를 분리해 경계를 나눌 것을 조언한다. NaverFinder는 일종의 정책이다. 프로그램의 가장 본질적인 기능이라는 뜻이다. 프로그램은 사전에서 단어를 검색해야하고, NaverFinder는 그 역할을 하는 가장 중심의 컴포넌트다. 코드의 추가에는 열려있고 수정에는 닫혀있어야하는 유스케이스다(use case).
만약 NaverFinder의 입력과 출력 양쪽에 대응하는 두 인터페이스가 있다면 어떨까? 입력장치가 바뀌었을 때 그 입력장치에서 주어진 입력을 NaverFinder가 원하는 형식으로 바꾸어주는 적절한 인터페이스와, 출력장치가 바뀌었을 때 NaverFinder의 출력을 적절하게 바꾸어 출력 장치로 전달해주는 인터페이스를 올바르게 구현하고 선택할 수 있다면? NaverFinder는 전혀 수정하지 않고 계속 사용할 수 있을 것이다.
WordInput은 입력 인터페이스의 역할을 한다. 비록 지금은 NaverFinder에게 필요한 같은 문자열을 입력받지만, 입력장치는 바뀌기 굉장히 쉽기 때문에 완충역할을 해준다. NaverFinder는 html 전체를 반환한다. 현재 GUI 출력에 필요한 것은 뜻 문자열 뿐이므로, Presenter가 html을 원하는 문자열로 바꾼다. 따라서 다음과 같은 데이터 흐름을 갖는다.
검색창 입력 -> WordInput -> NaverFinder -> Presenter -> GUI 출력
마무리
아직 경험이 많이 부족해 책에 대한 깊은 이해는 하지 못했다고 생각한다. 그래도 여러 프로젝트를 하며 머릿속을 맴돌던 소프트웨어 아키텍쳐에 대한 개념을 확실히 잡게 해준 책이었다. 이 프로젝트를 진행하면서 제대로된 프로그램 구조를 생각하고 구현할 수 있게 되었다.