본문 바로가기
나의 서재

[책]Refactoring 리팩토링

by esstory 2008. 11. 17.

 

Refactoring - 10점
Martin Fowler 지음, 윤성준.조재박 옮김/대청(대청미디어)

 

얼마 전에 조그마 한 프로그램을 하나 만든 적이 있다.

프로그램을 목적에 맞게 적당히, 대충 만들고 잊고 지냈는데, 언제나 그렇듯 새로운 개발 요건이 들어와 프로그램을 수정해야 했다.

간단한 수정이라 고민할 필요 없이 손 가는 대로 몇 줄 코딩 하다 보니, 별 것도 아닌 코드가 지저분해 지는 것을 느꼈다.

예전에는 하나의 경우만 염두에 두고 짠 코드여서 일관성 있게 쭉 읽혀지는 코드였는데, 새로운 경우가 생기면서 코드 중간에

 

if (이전경우이면)

else // 새로운경우이면

 

과 같은 코드가 계속 반복되는 것을 발견했다.

간단한 케이스가 하나 추가된 것일 뿐인데 코드는 급격히 가독성  떨어지고 이전보다 2배 가까이 코드가 늘어 나는 것을 보면서 아무리 바빠도 내가 너무 성급하게 접근했구나하고 코딩을 잠시 멈추게 되었다.

 

결국 각 케이스에 대한 부분만 처리하는 함수를 별도로 빼고 나머지 코드는 다양한 케이스에 상관없이 쓸 수 있도록 Refactoring 을 감행했고, 코드는 이전보다 그다지 늘어나지 않게 되었으며, 가독성도 함께 좋아 졌다.

 

 

94년부터 프로그램 개발 일을 하면서 내가 가장 많이 작업한 것이 무엇이었나 곰곰이 생각해 보면 신규 기능을 개발하는 것도 아니었고, 디버깅도 아니었던 거 같다. 내가 가장 많이 해 왔던 일은 꾸준한 Refactoring 이었다.

 

올해 들어서도 회사 내 큰 프로젝트를 진행하면서 천만라인에 육박하는 회사 코드를 전부 뒤집어 엎고 개선 & 수정하는 작업에 들어 갔다. (여태까지 중 가장 길게 재작업 진행 중..)  물론 변경/개선하는 코드를 Refactoring 이라고 할 수는 없겠지만, 프로젝트에 따른 수정을 해 나가면서도 전체적으로 잘못된 프로그램들의 로직을 보정하고, 알고리즘을 개선하는 작업을 병행하고 있다.


내가 아주 많은 시간을 할애하고 있는 이 리팩토링에 지침이 되 준 책이 바로 "리팩토링" 이다.  책을 2002년에 구입했고 이번까지 포함해서 3번을 읽었는데 아직도 구석 구석에 정곡을 찌르는 내용이 상당히 많고 도움이 많이 되고 있다. 특히 대규모 리팩토링 작업을 할때면 늘 다시 한번 이 책을 꺼내 들고 싶어 진다.

 

 

 


리팩토링은 위험하다는 것이다. 리팩토링은 작동중인 코드를 변경하는 것이 필요한데, 이것은 미묘한 버그를 유발할 수 있다. 적절히 수행되지 않는다면, 리팩토링은 여러분을 며칠 전, 심지어 몇 주전으로 돌아가게 할 수도 있다. “

추천사 중에서


가끔은 후 배들이 더 잘해 보려고 수정한 코드 때문에 곤욕을 치르곤 한다. 리팩토링한 다음 테스트를 제대로 하지 않아서인데, 혼자 개발과 테스트를 병행하다 보니 아쉬움이 많이 남는다. 리팩토링은 늘 찬성하지만, 항상 기존 프로그램과 100% 호환되는지 확인하지 않으면 큰 문제를 야기하곤 한다.

프로젝트 매니저는 별로 기뻐하지 않았다. 스케줄은 빡빡하고 할 일도 많은데 이들 프로그래머가 이틀동안이나 작업을 하고도 몇 달 후에 납품해야 하는 시스템에 새로운 기능을 추가한 것이 없기 때문이다. 예전의 코드도 잘 작동했고 디자인도 그럭저럭 괜찮았다. 프로젝트는 학자들이 즐겁게 하는 코드가 아닌, 작동하는 코드를 납품하는 것이다.

서문 중에서

 

충분히 이해가 가면서도 이해가 가지 않는다. 언젠가는 문제가 있는 프로젝트로 인해 더 많은 시간을 소비해야 할 수 있을 테니, 지금이라도 개선할 곳이 있다면 늘 개선할 준비가 되어 있어야 한다.

 

컴퓨터가 이해할 수 있는 코드는 어느 바보나 다 짤 수 있다. 좋은 프로그래머는 사람이 이해할 수 있는 코드를 짠다.

P35

 

나는 내가 가끔 바보 같다 ^^;


 

위와 같이 임시변수는 가능하면 제거하는 것이 좋다. 임시 변수는 종종 쓸데없이 많은 파라미터를 만들므로 문제가 된다. 코드를 따라 가다가 임시변수의 목적이 무엇이었는지 잊기도 쉽다. 특히 길이가 긴 메소드에서는 골칫거리이다. 물론 퍼포먼스 측면에서 요금이 두 번 계산되어야 하므로 손해다. 그러나 이것은 Rental 클래스에서 최적화 될 수 있고, 코드가 적절히 분해되어 있다면 더욱 효과적으로 최적화 될 수 있다.

P41


이 책을 처음 읽었을 때 이 부분에 거부 반응이 없잖아 있었다.

예전에

for (int i = 0; i < Array.Size() ; i++)

과 같은 코드가 있었는데 프로파일링을 해 보니 가장 많이 호출되는 함수가 다름아닌 Array.Size() 것을 찾아 내고는 놀란 적이 있다.

그래서 미리 구해 놓을 수 있는 값들은 임시 변수를 통해 보관하고 재활용하는 편이 당연하다고 생각했는데 이 책은 나의 지론을 깨는 것이었다.

하지만, 시간이 지나고 책을 여러 번 읽을수록 임시 변수를 가능한제거 해야 한다는 저자의 말에 동의한다. 성능문제는 프로파일링을 통해 문제가 된 이후에 수정해도 늦지 않다.

생각하는 프로그래밍에서 p183 에 나오는 아래 격언처럼 희생되는 무언가와, 얻는 무언가의 중요성을 먼저 깨달아야 한다.


“Don Knuth는 미숙한 최적화는 프로그래밍에서 악의 근원이라고 했다. 효율성으로 인해 프로그램의 정확성, 기능성, 유지보수성 등이 희생될 수도 있다. 효율성에 대한 걱정은 그것이 정말 문제가 될 때까지 미루어라

 

또 다른 페이지에서 다시 한번 임시 변수와 성능에 대한 저자의 지적은 계속된다.


이 리팩토링에서 또 하나 생각할 것은 퍼포먼스 문제이다. 기존 코드는 while 루프를 한번만 실행했지만, 새 코드는 세 번 실행한다. 시간이 오래 걸리는 while 루프는 퍼포먼스를 나쁘게 한다. 많은 프로그래머가 이와 같은 단순한 이유로 이런 리팩토링을 하지 않으려 할지도 모른다. 그러나 단지 그럴지도 모른다는 말에 주목하라. 프로파일을 보기 전에는 계산을 위해 루프가 얼마나 많은 시간을 소모할 지, 또는 그 루프가 시스템의 전체 퍼포먼스에 영향을 줄 만큼 자주 호출될 지에 대해 알 수 없다. while 리팩토링에 대해 걱정할 필요없다. 만약 최적화를 하고 있다면 이 부분에 대해 걱정을 해야 겠지만, 그렇지 않으면 이와 같이 리팩토링을 함으로써 최적화를 하기가 더 유리해 질 것이고, 최적화를 효과적으로 하기 위한 더 많은 옵션을 가지게 될 것이다.

P52


책을 여러 번 읽을수록 저자의 의견에 나도 동화 되어 가는 느낌. 하지만, 프로파일을 제대로 하지 않는다면 리팩토링이 오히려 문제를 가져올 수도 있음을 기억해야 한다.

 

디자인이 좋지 않은 코드는 같은 작업을 할 경우 더 많은 코드를 사용한다. 왜냐하면 같은 작업을 하는 코드가 여러 곳에 중복해서 있는 경우가 많기 때문이다. 따라서 디자인을 개선하는 중요한 측면 가운데 하나가 중복된 코드를 제거하는 것이다. [중략]

코드가 많을수록 정확하게 고치기는 어렵다. 이해해야 할 코드가 더 많기 때문이다. 한쪽의 코드를 조금 고친다고 하더라도 시스템이 기대한 대로 작동하지 않는데, 그것은 다른 쪽에 있는 약간 다른 컨텍스트에서 거의 동일하게 작동하는 부분에 대한 코드를 고치지 않았기 때문이다. 중복을 제거함으로써, 각각의 작업에 대한 코드가 오직 한 곳에만 있게 할 수 있다. 이것은 좋은 디자인의 필수조건이다.

P75

 

리팩토링을 하면서 가장 많이 신경쓰는 부분이다 보니 이제는 중복된 코드를 만날 때 마다 자연스럽게 코드를 단일본만 유지하기 위해 리팩토링을 하게 된다.  코드 중복만큼 나쁜 습관이 있던가.

관련 글: 프로그래밍 이야기 - DRY 원칙을 지키고 습니까?

 

무엇이 프로그램에 대한 작업을 어렵게 하는 걸까? 이 글을 쓰면서 내가 생각할 수 있는 네 가지는 다음과 같다.

-       읽기 어려운 프로그램은 수정하기 어렵다

-       중복된 로직을 가지고 있는 프로그램 수정하기 어렵다.

-       실행중인 코드를 변경해야 하는 특별한 동작을 요구하는 프로그램은 수정하기 어렵다

-       복잡한 조건문이 포함된 프로그램은 수정하기 어렵다.

P81

 

프로그램은 정말 잘하지만, 그 사람이 만든 코드를 읽기 싫어지는 개발자가 분명이 있다. 이 사람들이 만든 코드를 유지 보수해야 할 때는 만든 사람이 투입한 시간보다 더 많은 시간을 소비해야 한다. 아무리 실력이 좋아도 다른 사람이 쉽게 이해할 수 없는 코딩 습관을 가진 사람은 미안하지만 어두운 방구석에서 해킹이나 하고 살았음 좋겠다.

 

변경을 할 때마다 많은 클래스를 조금씩 수정해야 한다면 산탄총 수술의 냄새를 풍기고 있는 것이다. 변경해야 할 것이 여기저기 널려 있다면, 찾기도 어렵고, 변경해야 할 중요한 사항을 빼먹기도 쉽다.

P102

 

예전에 차트 관련 프로그램을 만드는 데 새로운 종류의 차트가 등장할 때 마다 약 6가지 헤더와 소스를 수정해야 하는 경우가 있었다. 어떻게든 수정해야 하는 것들을 한 곳으로 모아 보려고 노력했지만 그 당시에는 실력이 없어서 못 고치고 말았었다. 결과는 매번 새로운 차트가 늘어 날 때마다 잊어 버리지 않고 여러 파일을 수정하느라 고생했던 기억이 난다. 그 소스가 아직 남아 있지 않아 (이제는 담당자가 다른 사람으로 변경됨) 재도전해 볼 수는 없지만, 수정해야 할 곳이 여러 군데라면 언제나 하나의 클래스로 모아 볼 생각을 해야 한다.

 

 

Extract Method는 내가 가장 자주 사랑하는 리팩토링 가운데 하나이다. 나는 지나치게 긴 메소드를 보거나, 또는 목적을 이해하기 위해서 주석이 필요한 코드를 보면 그 부분을 하나의 메소드로 뽑아낸다. 나는 다음과 같은 이유로 짧고, 이해하기 쉬운 이름으로 된 메소드를 좋아한다. 첫째 메소드가 잘 게 쪼개져 있을 때 다른 메소드에서 사용될 확률이 높아진다. 둘째, 고수준의 메소드를 볼 때 일련의 주석을 읽는 것 같은 느낌이 들도록 할 수 있다. 또한 메소드가 잘게 쪼개져 있을 때 오버라이드 하는 것도 훨씬 쉽다. [중략] 작은 메소드는 실제로 이름을 잘 지었을 때만 그 진가가 드러나므로, 이름을 지을 때 주의해야 한다.

P136

 

이견이 있을 수 없는 말이다. 너무 긴 함수는 죄악이다. 함수는

Func()

{

   DoFirst();

   DoSecond();

   DoThird();

}

처럼 위에서 밑으로 읽어 나갈 때 함수 이름만 보고도 대충 어떤 일을 하는 지 짐작할 수 있도록 지어야 하고, Sub 함수들도 가능한 잘게 쪼개져서 가독성을 높이는 것이 중요하다.

 

객체 디자인에서 가장 기본이 되는 것 중의 하나(원칙은 아닐지라도)는 책임을 어디에 둘 지를 결정하는 것이다. 나는 십 년 이상 객체를 가지고 일했지만 처음 시작할 때는 여전히 적당한 위치를 찾지 못한다. 늘 이런 점이 나를 괴롭혔지만 이제는 이런 경우에 리팩토링을 사용하면 된다는 것을 알게 되었다

P169

 

큰 그림을 한번에 다 그리기는 무척 어렵다. 언제나 그렇듯 새로운 요건들이 하나 둘 늘어 가면서 처음 가졌던 설계와 모양이 점점 희미해 져 가고 이전에 만든 메소드들은 클래스를 이리 저리 옮겨 다니거나 통폐합되거나 제거되고, 때로는 완전히 새로운 클래스가 필요할 수도 있다. 리팩토링을 통해 정리가 되지 않은 기능들은 자주 정리하고 깔끔하게 코드를 유지하기 위해 늘 노력이 필요하다

 

나는 종종 GUI 클래스가 비즈니스 로직을 처리하는 경우를 접하곤 한다. GUI 클래스가 비즈니스 로직을 처리해서는 안된다. 동작을 적절한 도메인 클래스로 옮기기 위해서는, 도메인 클래스가 데이터를 가지고 있어야 하고, Duplicate Observed Data(224)를 사용하여 GUI를 지원해야 한다.

P204

 

고백하건데 C++ 을 제대로 알지도 못하고 시작한 MFC 프로그램에서 나는 각종 윈도우 컨트롤들과, 컨트를들에 표시되는 데이터를 다루는 코드를 마구 섞어 사용했다.

결과는 모 당연히, 데이터를 다루는 코드가 화면마다 중복되게 되고, 너무나 타이트하게 연관되어 져서 나중에 떼어 내는 것이 거의 불가능해 보이기까지 해 졌다.

뷰와 데이터를 분리하는 이러한 컨셉은 생각보다 꽤 복잡하다. 잘 만든다 해도 데이터가 중복되기 싶고 데이터와 GUI 간 데이터 세팅과 변경에 대한 이벤트 리스닝 등 코딩량도 상당히 늘어난다.

가끔은 GUI 없이 main() 으로 몇 줄만 코딩해서 서비스를 뚝딱 만들어 내는 서버쪽 개발자들이 정말 정말 부럽다.

 

방향 연관은 두 클래스가 서로 종속되도록 한다. 한쪽 클래스를 수정하면, 다른 쪽 클래스도 수정해야 한다. 만약 두 클래스가 서로 다른 패키지에 속해 있다면, 패키지 간의 상호종속성이 생기는 것이다. 종속성이 많으면 시스템의 결합성이 높아져, 약간의 변경이 예상할 수 없는 많은 문제를 유발하게 된다.

P236


코드 리뷰를 하면서 자주 보는 유형의 아주 나쁜 코딩 습관이다. 두 클래스가 서로 Caller Callee가 되어 버려 하나를 고치면 다른 하나가 영향을 받고 꼬리에 꼬리를 문다. 가끔은 서로 다른 DLL 에서도 상호 참조가 문제가 되는 경우도 있다. 이 경우는 문제가 더 심각해서 결국 컴파일이 되지 않아 동적로딩으로 수정하든지 단방향으로만 종속을 가지도록 고쳐야만 한다.

관련 글: [C++]인터페이스를 이용하여 상호참조와 종속성을 최소화하는 방법

 

 

런 상황은 보통 경우가 많은 조건문이 있을 때 발생한다. 이것은 switch 문이거나, if-then-else구조일 것이다. 어떤 경우든 타입 코드의 값을 테스트하고, 타입 코드의 값에 따라 다른 코드를 실행시킨다. 이런 조건문은 Replace Conditional width Polymorphism을 사용하여 리팩토링할 필요가 있다. 이 리팩토링이 작동하기 위해서는 타입 코드가 다형성 동작을 가지는 상속구조로 바뀌어야 한다. 이런 상속구조는 클래스와 각각의 타입코드에 대한 서브 클래스를 가진다.

P261

 

다형성을 이용하여 가상 인터페이스를 선언하고 조금씩 다른 처리를 가지는 자식들로 switch/if-then-else 를 대체할 수 있을 것이다. 하지만 사실 이렇게 고치면 코딩량이 좀 늘어나고, 코딩하는 데 시간이 걸려 사실 빼먹는 경우가 많다. 게다가 성능도 조금 떨어 질 듯 하고,

그래도 패턴이나, 리팩토링 책에서 이렇게 강조하니 가급적 다형성을 많이 활용하도록 해 볼 생각이다.

 

동일한 코드 조각이 조건문의 모든 분기 안에 있는 경우 동일한 코드를 조건문 밖으로 옮겨라.

if (isSpecialDeal())

{

           total = price * 0.95;

           send();

}

else

{

           total = price * 0.98;

           send();

}

 


if (isSpecialDeal())

           total = price * 0.95;

else

           total = price * 0.98;

 

send();

p281

 

너무 간단한 리팩토링이다. 문제는 코드 리뷰를 해 보면 이런 코드들이 무수히 많다는 것이다. 보통은 귀찮아서 그런 것도 같고, 코드를 여러 줄 짜다 보면 일부분만 공통으로 중간 부분을 빼내기가 어슬픈 경우가 있어서 그런 경우도 있다. 물론 중간 부분만 공통이면, 그 부분을 함수로 빼내어 호출하면 된다. 시작이 반복이면 if 문 이전으로, 종료 부분이 반복이면 if 문 마친 후에 공통으로 빼내는 방법을 늘 고민해야 한다. 물론, 코딩 후에 다시 한번 본인의 코드를 재검토 해 보는 것이 필수다.

 

여러분이 할 수 있는 가장 간단하고 중요한 것은 메소드의 이름을 바꾸는 것이다.

P311

 

이름을 정하는 것은 참 중요하다. 문제는 메소드의 이름을 한글이 아닌 영어로 지어야 하는데, 영어 이름에 대해서는 사람마다 단축용어를 좋아 하는 사람, 모두 풀어 쓰는 것을 좋아 하는 사람, 한글을 소리 나는 대로 영어로 바꾸는 사람(: Get결제일() - 물론 결제일을 발음 그대로 영어로 쓰는 ㅎㅎ), 영어가 짧아 남이 작성한 영어 함수를 한글로 잘 이해를 못하는 사람 등 언어로 인한 장벽이 있다.  대충 타협하기를 짧고, 남이 이해만 잘 한다면 콩글리쉬도 상관없다고 생각한다.

 

여러분이 제작중인 시스템이 있고 기능을 추가해야 할 때, 코드를 정돈하기 위해서 두세 달 동안 프로젝트의 진행을 멈추어야 한다고 매니저를 설득하는 것은 매우 어려울 것이다. 그렇기 때문에 헨젤과 그리텔이 그랬던 것처럼 오늘 조금, 내일 조금, 가장자리를 갉아먹는 듯이 작업을 해야 한다.

P407


다행히 지금 다니고 있는 회사는 매년 약 두 달 정도를 컴파일러를 바꾸거나, 새로운 기반으로 변경하기 위해 대대적인 리팩토링을 감행한다. 물론 기능 개발까지 포함되기는 하지만, 그 기간 동안 잘못된 코드를 많이 제거하고 Legacy 의 문제, 개선 하는 작업들을 진행하고 있다. 이렇게 매년 작업을 하지만, 그 나머지 시간 동안 계속 추가되는 코드와, 전혀 수정되지 않고 10년을 버티는 코드들도 있어서 여전히 여기 저기 깨어진 창문들이 많이 보이고, 성능이나 UI 개선 등 아쉬운 부분도 많다. 어쨌든, 다행스럽게도 일정 수준 이상의 코드 되돌아 보기 작업을 하고 있다는 데 위안을 삼고 싶다.

 

 

 


댓글