본문 바로가기

Refactoring

흑속에서 진주찾기

시작

juso.go.kr 에서 제공하는 주소검새 API를 이용하는 아주 기초적인 수준의 코드에서 어떻게 라이브러리화를 하고 TDD를 적용하는지 차례대로 발전해나가는 과정을 사내 교육을 위해서 틈틈히 준비하다 여기에도 한번 남겨본다.

본 소스는 초급개발자들에게 도움이 되는 과정일 것이다. 단순하게 어떤 기능만을 만들었던 내소스가 어떻게 가치를 가지는 라이브러리가 되어 재사용이 되고 TDD를 적용하여 매번 테스트의 편의성을 추구하게 되는지 보여준다.

단, 일련의 과정을 나누는 기준은 오로지 나의 경험에 의한 과정이기 때문에 이것이 항상 정답은 아니다. 여기서 이야기하고자 하는 것은 기능 자체에서만 봤다면 단순한 기능이지만 실제 재사용성을 어떻게 극대화 할 수 있는지 만약 기회가 된다면 이런식의 접근이 도움이 될 수도 있다는 예시이다.

본소스는 https://github.com/sorrymommy/JusoRefactoring 에 공유되어있으며 브랜치별로 보면 처음 단계에서 발전해나가는 단계까지 볼 수 있다. (처음에는 step01~step04까지 만들었으나 Factory 패턴을 넣어 DesignPattern에 대해서 설명을 하고자 step05까지 만들었다. IoC/DI 컨테이너를 이용하여 IoC와 DI를 하는 이유와 당위성에 대해서 설명하는 과정까지 진행하고 싶은데 이건 오반가? 하는 생각이 들어 주저하고 있다.)

주소 검색 기능

단순 구현 (step01)

juso.go.kr에서 API를 키를 발급받아야 하는 우선과정을 거친다. 다만 테스트를 위한 키는 신청즉시 발급되며, 사용기간도 길게는 90일까지 테스트를 할 수 있다. (신청 : https://business.juso.go.kr/addrlink/openApi/apiReqst.do)

콘솔 프로그램으로 간단히 구현한 화면이다.

이 소스를 리팩토링한다면 단순하게는 메서드로 검색조건을 빼서 검색어를 넣으면 다른 소스에서 카피해서 쓸 수 있을 것이다.

Step02

주소를 찾아주는 역활을 클래스로 분리하여 구현을 하였다. 그리고 문자열로 리턴되어 나오는 API 결과도 객체로 변환할 수 있도록 데이터 클래스를 선언하였다.

이정도 과정에서 파일을 조금만 더 정리하면 다른 프로젝트에서도 카피하여 붙여넣어 쓸 수 있을 것이다. 하지만 내가 만든 소스를 다른 사람들도 쉽게 쓸 수 있도록 하고 싶다면? DLL등으로 제공하여 사용하도록 하고 싶다면? 아니면 Nuget 패키지로 배포가 가능하도록 하고 싶다면? 이런 큰 상상은 아니더라도 최소한 메인 프로젝트와 분리하여 별도로 소스의 이력이관리가 되는 것이 더 이득일 것이라는 생각은 누구나 해봤을 것이다. 최소한 메인프로젝트에서 관리되어야할 부분이 아니라 별도의 라이브러리로 나누는 것이 메인프로젝트의 깔끔함을 유지하는 좋은 방법일 것이다.

Step03

별도의 라이브러리 프로젝트로 분리하고 콘솔 프로그램에서 사용하고 있는 코드

별도의 라이브러리 프로젝트로 분리를 하고 Data 클래스를 별도 네임스페이스로 분류를 하였다. 관련성 있는 소스들끼리 모아두었다.

JusoFinder 클래스는 주소를 찾는 일에 집중하도록 하였다.

JusoParameter가 URL Parameter로 만들어져야하는 역활을 담당하도록 하였다. 덕분에 NameValueCollection의 확장메서드도 만들어야 했다.

RestRequest 클래스는 웹을 통하여 API를 호출하고 문자열로 결과를 리턴해주는 역활을 하는 클래스이다.

JusoParser 클래스는 문자열로 넘어온 Json 내용을 객체로 변환시키는 역활을 하는 클래스이다.

 

Step04

테스트 프로젝트를 추가하여 주소 찾는 기능을 테스트를 이용하여 테스트할 수 있도록 하였다. 더불어 WebAPI를 호출하는 것을 HttpWebRequest로 직접구현하던 것을 RestSharp 라는 라이브러리를 사용하도록 했다. (RestSharp의 최신버전은 .net 버전 호환성때문에 사용할 수 없어 RestSharp 104 버전을 사용하였다. 그래서 최신 107버전에서는 없어진 IResponse 등의 인터페이스를 이용하고 있다) 그덕분에 RestRequest와 JusoParameterBuilder, NameValueCollectionExtension 클래스가 필요없게 되었다. 

확장성과 테스트 용의성을 위해서 JusoFinder와 JusoParser가 하는 기능을 인터페이스로 분리하고 클래스는 인터페이스를 구현하는 모습으로 코딩을 변경하였다. 

 

이렇게 되면 그 라이브러리를 테스트하는 별도의 프로젝트도 만들어졌다. 주소 찾기 기능이 Console에서 필요하든 Winform에서 필요하든 WPF에서 필요하든 혹은 이것이 웹서비스가 되든 각각의 프레임워크나 구현 인프라에 맞게 사용이 가능한 라이브러리가 된다. 더불어 테스트도 독립적으로 가능한 모습이 맞추어진다.

이런 과정을 반복을 하다보면 직감적으로 어떤 부분이 나누어져야되며 어떤 부분이 라이브러리화 될 것이며, 어떤 부분이 테스트 친화적으로 되어야 될 것인지 알 수 있다. 한번에 지금의 구조로 만들 순 없지만 지속적으로 반복하다보면 나중에는 이런 과정을 매우 짧게 거치고도 좋은 구조의 소스를 만들어낼 수 있을 것이다.

이 구조가 좋고 최선이라는 것이 아니라. 무심코 짜던 간단한 기능도 이렇게 하면 진주처럼 보일 수 있다는 것을 보여주고 싶은 것이다. 초급개발자들이라면 이런 일련의 반복과정을 거치며 소스를 다듬어가는 과정이 필수적으로 필요할 것이다. 그것이 군더더기 없는 좋은 코드를 만들어 가는 훈련일거라 생각한다.

Step05

Step04로 끝내려고 했지만 IJusoParser를 JusoFinder에서 생서하는 모습이 너무 마음에 걸려 Factory로 IJusoParser 클래스를 리턴해주는 클래스를 만들었다.

IoC/DI 컨테이너를 쓴다면 이런 구현이 필요 없을 것이다. 하지만 JusoFinder내부에 IoC/DI 클래스를 쓰는 것에 대해서는 여러가지 생각이 많이 교체되는 지점이다. Nuget Package의 사용은 분명 좋은 메커니즘이다. 하지만 실제 수순하게 비지니스를 처리하는 라이브러리에서는 그런 패키지나 다른 클래스의 의존성이 없으면 없을 수록 좋다. 패키지 의존은 버전 의존에도 같이 맞물려 움직이기 때문에 사실 RestSharp를 쓴 것도 좋은 대안이 아닐 수 있다.

어디까지 적용하고 어디까지는 적용하지 않아야 될 지는 순전히 프로젝트 최고 책임자의 결정이다. 어떤 가치를 더 중하게 여기고 어떤 가치에서 희생을 치룰 것인지 선택을 해야된다. 모든 상황을 만족시키는 은빛탄환은 없다. 세상만사 모든 일에는 일장일단이 있듯 소프트웨어 소스도 그 패러다임을 벗어나지 못한다. 다만 우리는 적시 적소에 빠르게 적응하고 변화를유도하는 방법 그 변화에서도 문제의 발생확률이 가장 낮은 방안에 대해서 고민해야 할 뿐이다.