제공: 한빛 네트워크
출처: 프리팩토링: 효과적인 시스템 설계와 변경을 위한 프리팩토링 지침 65가지 Chapter 1.
이 장에서는 프리팩토링의 특징들을 간단하게 살펴본 후, 프리팩토링과 리팩토링과의 연관성에 대해서 소개한다. 그리고 여러분이 어떠한 견해를 갖고 있는지와 여러분이 개발하고 있는 상황에 따라 프리팩토링으로부터 무엇을 얻을 수 있는지를 알아본다. 또한 여러분이 처한 개발 상황에서 사용할 수 있는 훌륭한 기법들을 설명할 지침들을 제시할 것이다.
프리팩토링이란 무엇인가?
리팩토링은 외부로 노출되는 기능은 변경하지 않고 코드의 내부 구조를 개선하려고 코드를 바꾸는 기법이다. 프리팩토링은 다른 사람의 경험뿐만 아니라 여러분이 소프트웨어를 개발하면서 얻은 통찰력까지 활용한다. 리팩토링을 하면서 얻은 전문적인 지식도 경험의 일부에 포함된다.
나는 나의 아이디어와 수 년 동안 많은 개발자들에게 들은 아이디어들을 프리팩토링 지침으로 요약하였다. 여러분만의 고유한 지침을 개발하기 위한 시발점으로 이 지침들을 활용하면 좋을 것이다. 많은 지침들이 기본 설계 원칙들과 관련이 있지만 표현 방식은 다르다. 그 밖의 지침들은 극단적인 추상화(Extreme Abstraction)와 극단적인 분할(Extreme Separation), 그리고 극단적인 가독성(Extreme Readability)이라는 개념을 핵심 개념으로 삼고 있으며, 이 개념들은 이 장의 후반부에서 소개할 것이다.
프리팩토링의 또 다른 특징은 인터페이스를 중요하게 생각한다는 점이다. 컴포넌트가 어떻게 작업을 수행하는지보다 어떤 컴포넌트를 사용할 것인지, 즉 인터페이스를 고려함으로써 추상화라는 목표에 좀더 다가갈 수 있다. 리팩토링 역시 인터페이스에 관심을 갖는다. 내부구현을 변경하는 동안 외부로 노출되는 기능은 손대지 않기 위한 기법이다.
이 책에서 소개하고 있는 지침들을 적용해도 설계나 코드에 대한 리팩토링이 필요 없지는 않을 것이다. 그래도 그 전보다 리팩토링해야 하는 작업량은 줄어들 것이다. 여러분은 모든 것을 예측할 수 있는가? 그럴 수 없다. 또, 오늘 결정한 내용이 나중에 바뀌지 않을까? 그렇지 않다. 프로젝트를 시작할 때 모든 것을 생각하거나 안다는 것은 사실상 불가능하다. 여러분은 프로젝트가 진행됨에 따라 더 많은 것들을 배우게 될 것이다. 하지만 특정한 방향으로 안내하기 위해서 여러분의 경험이나 다른 사람들의 경험을 사용할 수 있다. 이로 인해 내일의 변화를 최소화하는 결정을 할 수 있다.
세 가지 극단적인 기법
추상화(Abstraction)와 관심 사항의 분리(Separation Of Concern), 그리고 가독성(Readability)은 다른 지침들의 기본이 된다. 이 개념은 익스트림 프로그래밍(Extreme Programming)이 제시하는 일부 개념들과 유사하다. 만약 추상화가 좋다면, 극단적인 추상화가 더 좋다. 만약 관심 사항의 분리가 좋다면, 극단적인 분리가 더 좋다. 그리고 가독성이 좋다면, 극단적인 가독성이 더 좋다. 많은 지침들이 극단적인 입장을 취하고 있기 때문에, 그러한 지침과 현재 여러분이 사용하고 있는 기법들의 차이를 이해할 수 있어야 한다. 여러분은 여러분의 상황에 가장 적합한 위치를 찾아야 할 것이다.
추상화
추상화는 객체 지향 개념의 핵심 원칙 중 하나다. 여러분은 기능의‘어떻게’가 아니라‘무엇’에 해당하는 부분을 어떻게 구현할 것인지에 대한 세부적인 사항들을 명시하지 않고 기능을 기술할 수 있다. 물론 수작업, 또는 컴퓨터의 도움을 받거나 자동화된 프로시저를 사용해 구현할 수 있을 정도로 시스템을 추상화할 수 있는 수준이 있다. 하지만 가끔은 프로토타입(prototype)과 같이 구체적인 실체를 확인하기 전까지는 시스템이 어떻게 작동할 것인지에 대해서 알 수 없을 정도의 추상화된 수준으로 시스템이 기술되기도 한다.
이 책의 흐름 역시 추상화와 비슷하다. 기능과 인터페이스들은 클래스(class)와 인터페이스(interface), 예외(exception)와 같이 모든 객체 지향 언어에서 공통적으로 지원하는 기능들을 사용해 언어에 영향을 받지 않는 방식으로 기술되었다. 구현에 필요한 순서와 로직(logic)은 의사 코드(pseudocode)를 사용해 기술하였다. 추상화된 내용이 실질적인 코드로 변환될 수 있음을 보여주기 위해서 인터페이스를 추상화된 수준으로 정의한 후에 코드를 제시하였다.
극단적인 추상화의 한 예로, 어떤 지침은 개념들을 절대로 기본 자료형(primitive; 예를 들면 int나 double)으로 기술해서는 안 된다고 제안한다.
관심 사항의 분리
관심 사항의 분리는 서로 다른 클래스와 메서드, 그리고 변수들 간의 책임을 나누는 작업이다. 앞으로 살펴볼 예제 시스템에서 확인할 수 있는 것처럼, Customer 클래스가 전형적인 경우다. 어떤 사람은 Customer 클래스에 이 클래스를 다루는 모든 메서드를 포함시킬 수 있다. 하지만 다른 사람은 고객을 처리하는 여러 개의 클래스를 만들 수 있다. 그 클래스들은 단순히 데이터를 보관하는 데CustomerData 클래스를 포함할 수 있다. Customer-Persistence 클래스는 데이터를 보존하는 역할을 하고, CustomerGUIDisplay 클래스는 출력하기 위한 위젯(widget)을 포함하고 그래픽 사용자 인터페이스(GUI)로부터 Customer에 대한 입력을 받는다. CustomerImportExport 클래스는 텍스트 형태로 저장된 데이터를 읽거나 저장한다. CustomerModel 클래스는 기능들을 포함하고, CustomerBusinessRule 클래스는 기능을 바꿀 수 있는 규칙들을 포함한다.
하나의 클래스를 여러 개의 클래스로 분리해서 한 클래스에서 변경된 내용이 다른 클래스에 영향을 미치지 않도록 할 수 있다. 예를 들어, 여러분이 GUI를 변경한다면 비즈니스 규칙 클래스를 바꿀 필요가 없다. 예제 시스템처럼 반드시 모든 클래스들을 여러 개의 클래스로 나눌 필요는 없지만 적어도 그런 일이 가능하다는 것은 알고 있어야 한다.
가독성
코드는 컴퓨터와 의사소통을 하지만 독자(reader)와도 의사소통을 한다. 코드는 모든 개발자들이 이해할 수 있는 방식으로 작성돼야 한다. 코드가 요구 사항 문장에 가까울수록 요구사항을 만족시키기가 쉽다. 극단적인 가독성의 한 예로 고객이나 소비자가 코드를 읽을 수 있어야 한다는 지침이 있다.
지침들에 대한 소개
개발자로써 우리의 목표는 이해할 수 있고 읽기 편하며 유지 보수가 가능한 코드를 작성하는 것이다. 이 책에서 소개할 지침들은 여러분이 그러한 목표를 달성하는 데 도움을 주기 위한 것이다. 이 지침들이 최고의 기법은 아니다.‘ 최고’라는 말은 여러분이 현재 시스템을 개발하고 있는 상황에서만 결정될 수 있다. 하지만 이 지침들은 여러분의 상황에 가장 적합한 기법들을 만드는 데 필요한 것들을 제안하고 있다.
많은 지침들이 동일한 기본 원칙을 다르게 표현하고 있다. 바탕이 되는 원칙은 활용할 때 교환조건으로 쓸 수 있으며, 이 내용은 기본 원칙을 이용해 만든 지침들에 나타난다. 예를 들면, 관심 사항의 분리라는 지침을 적용하면 일반적으로 더 많은 클래스와 메서드가 만들어진다. 일관성을 유지하면 비록 코드의 양은 증가해도 유사한 작업을 수행하는 시스템이 동일한 구조를 갖게 해 학습 시간을 줄일 수 있다. 인터페이스와 위임(delegation)에 집중하면 위임 메서드의 수가 증가한다.
상황이 가장 중요하다
이런 규칙이 있다. 만병통치약은 없기 때문에 반드시 어떤 기법이 여러분의 응용 프로그램에 적합한지를 잘 판단해야 한다. 여러분은 원칙들을 상황에 맞게 적용해야 한다. 특정한 원칙이나 기법을 쓸지 말지를 결정하는 것은 그것들이 사용되는 상황에 따라서 정해진다. 동일한 원칙이나 방식을 모든 상황에 적용하다 보면, 낭비나 혼란을 초래할 수 있다. 다른 프로그램으로 전환할 때만 사용될 프로그램에 대한 방대한 문서는 그저 낭비일 뿐이다. 심장 박동기(cardiac pacemaker)에 있는 프로그램에 대한 문서를 완벽하게 작성하지 않는다면, 이는 치명적일 수 있다. 마찬가지로 심장 박동기와 항공 전자 제품과 같은 프로그램들은 수많은 오류 처리가 필요하다. 다른 프로그램들은 이보다 좀더 간단하게 오류를 처리할 수 있다. 예를 들면, 웹 브라우저는 서버로부터 응답을 받지 못했을 때만 오류를 표시할 수 있다.
시스템에 참여하고 있는 사람의 수가 시스템의 개발 방법에 영향을 미친다. 나는 개인적으로 사용할 간단한 스크립트는 프리팩토링 하는 시간을 훨씬 적게 투자한다. 하지만 만약 다른 누군가가 스크립트를 실행할 것이라면, 입력 값 검증이나 의미 있는 오류 메시지와 같은 이슈들을 처리하는 데 더 많은 시간을 들인다.
만약 어떤 시스템이 여러분에게 새로운 아키텍처나 기술들을 사용한다면, 솔루션 공간에 대한 초기 조사가 중요하다. 여러분은 필요한 모든 기술들을 시험해보기 위해서 시스템의 최종 모델(end-to-end model)을 만들 수 있다. 이 모델로부터 지침들을 새로운 환경에 어떻게 적용할 수 있는지를 알아내기 위한 단서를 구할 수 있다.
이 책은 추상적인 지침을 알아본 후, 그러한 지침들이 실제로 사용되는 예를 소개한다. 때로는 어떤 지침이 알맞은 상황을 알아내서 해당 지침을 적절하게 적용하는 것이 가장 어려운 이슈기도 하다. 경험을 쌓거나 다른 사람들과의 토론에서 특정한 지침을 적절하게 사용하는데 도움을 얻을 수 있다.
자신의 방식에 익숙해져라
어느 누구도 소프트웨어를 만드는 완벽한 방법을 알지 못한다. 소프트웨어 개발은 여전히 예술이다. 한 가지 방식을 모든 곳에 적용할 수 있는가? 예를 들어, 여러분이 운전하는 차는 무엇인가? 다른 사람이 내가 운전하는 차와 다른 차를 운전한다고 해서 잘못되었다고 할 것인가? 사람마다 요구 사항과 소망, 가치관, 예산이 다르다. 그가 운전하는 차는 그의 가치에 부합할 것이다. 내 생각에는 길거리에 돌아다니는 차 중에서 잘못되었다고 할 수 있을만한 차는 유일하게 허머(hummer) 뿐이다.
이 책에서 소개하고 있는 지침과 주제들은 제안일 뿐이다. 이 제안들을 자신만의 소프트웨어 개발 프로세스를 살펴보기 위한 시발점으로 생각하라. 서로 다른 문제에 대한 여러분의 접근 방법을 분석해 봄으로써 자신의 문제 공간 내에서 본인의 경험에 적용할 수 있는 나만의 프리팩토링 지침들을 만들 수 있다.
경험을 회고하라
이 책에서 소개하고 있는 지침들은 경험으로부터 온 것들이다. 여러분은 프로그램을 개발한 경험이 있다. 여러분은 설계를 처음부터 만들었거나 이전 시스템에서 가져왔거나, 기사 또는 책에서 가져왔을 것이다. 그러한 설계는 잘 작동할 수도 있지만, 애초에 의도했던 문제와 잘 맞지 않는다면 심각한 결함이 발생할 것이다.
프로젝트의 막바지에 회고의 시간(retrospective)을 가질 수 있다(노만 L. 커스(Norman L. Kerth)의 Project Retrospectives: A Handbook for Team Reviews [Dorset House, 2001]를 참고한다). 회고할 때, 프로젝트 팀은 개발 프로세스에 대해서 평가하고 프로젝트의 성공과 실패로부터 배운 교훈들을 간직하게 된다. 많은 경우에 회고의 시간들이 대인 관계와 관련된 이슈들을 중점적으로 다루지만, 여러분은 기술적인 해결책에 대해서도 살펴보아야 한다. “과거를 올바르게 평가하지 않은 사람은 과거를 반복하게 된다”(조지 산타야나(George Santayana)).
기술적인 회고를 할 때, 여러분이 방금 구현을 마친 시스템의 설계를 평가하도록 한다. 아키텍처가 문제 공간에 얼마나 좋았는가? 무엇을 다르게 했는가? 초반에 어떤 질문들에 답해야 했는가? 어떤 요구 사항 때문에 설계가 크게 변경되었는가? 다음번에 사용할 기성(prewritten) 컴포넌트는 무엇인가?회고를 하면서 프로그램에 있는 버그를 발견할지도 모른다. 이때 버그를 곧바로 해결하지 말고, 버그가 발생한 이유를 찾아내기 위해 버그를 분석해야 한다. 예를 들면, 입력을 검증하지 않아서 버그가 발생했을 수도 있다. 따라서 다음번 코드를 작성할 때 사용할 수 있도록 입력 검증을 지침 목록에 추가해야 한다.
다른 사람의 경험을 프로젝트에 활용할 수 있다. 오늘 만드는 것들 중 상당수가 과거에 다른 방법으로 만들어진 것들이다. 패턴에 관한 책들(서문에서 간략하게 소개했던 Design Patterns와 같은 책)은 그러한 경험들을 축적한 것이다. 여러분이 오늘 개발하는 것은 이전 솔루션과는 약간 다를 것이다(또는 이전 솔루션을 그대로 사용할 수도 있다). 따라서 과거에 적용했던 기법들 중에서 어떤 기법들을 오늘 사용할 것인지에 대한 결정이 중요하다. 하지만 프로젝트의 환경에 적합하지 않은 패턴을 억지로 적용시킨다면 안티패턴(antipattern)이 만들어질 것이다.
회고의 시간을 꼭 프로젝트의 막바지에 가질 필요는 없다. 지속적인 회고는 개발 프로세스를 개선하는 데 도움을 줄 수 있다.
이 책의 상황
서문에서 언급했듯이, 이 책에서 소개하는 지침들은 CD 대여점에서 사용할 시스템을 개발한다는 상황을 가정하고 있다. 이 시스템의 개발 방법은 반복적이고 점증적인 프로세스를 혼합한 기민한(agile) 프로세스를 따른다. 개발을 진행하면서 우리는 시스템을 개발할 때 흔히 발생하는 일반적인 상황들, 특히 고객과 의사를 교환하는 상황에 부딪히게 된다. 우리는 유스 케이스(use case)로 작성된 요구 사항들부터 살펴볼 것이다. 특정 유스 케이스에 대한 완벽한 세부 사항들은 해당 유스 케이스가 구현되기 전까지는 채워지지 않는다.
전체 시스템에 대한 1차적인 분석을 수행하면 전체적인 아키텍처가 만들어진다. 전체적인 아키텍처가 큰 그림(big picture)이다. 전체적인 아키텍처는 모든 클래스의 세부 사항을 다 포함하지 않는다. 첫 번째 요구 사항들에 대한 세부 사항을 채운 후, 솔루션을 상세하게 설계하고 구현한다. 폭포수(waterfall) 프로세스를 따르는 프로그래머에게는 우리가 코드를 너무 이른 시간에 작성하는 것처럼 보일 것이다. 반면 익스트림(extreme) 프로그래머에게는 우리가 분석을 지나치게 하는 것처럼 보일 것이다. 나는 두 방식을 어느 정도 타협한 방식을 좋아한다. 개발 초기 단계에 작동하는 시스템을 사용자에게 제공하는 것도 중요하지만, 만들어진 시스템은 문제에 대한 전체적인 솔루션과 합당해야 한다.