목차
리팩토링은 외부 동작을 바꾸지 않으면서 내부 구조를 개선하는 방법으로, 소프트웨어 시스템을 변경하는 프로세스이다. 이것은 버그가 끼어 들 가능성을 최소화하면서 코드를 정리하는 정형화된 방법이다. 본질적으로 우리가 리팩토링을 할 때, 우리는 코드가 작성된 후에 코드의 디자인을 개선하는 것이다.
리팩토링의 목적은 소프트웨어를 보다 이해하기 쉽고, 수정하기 쉽도록 만드는 것이다.
리팩토링은 겉으로 보이는 소프트웨어의 기능을 변경하지 않는다. 리팩토링 후에도 소프트웨어는 여전히 동일한 기능만을 가지고 있다.
리팩토링은 소프트웨어의 디자인을 개선시킨다.
사람들이 코드를 수정함에 따라 코드는 원래의 구조를 잃을 것이다. 정기적인 리팩토링은 코드를 정돈하고 디자인을 유지하도록 도와준다. 디자인을 개선하는 중요한 측면 가운데 하나가 중복된 코드를 제거하는 것이다. 중복을 제거함으로써, 각각의 작업에 대한 코드가 오직 한 곳에만 있게 할 수 있다. 이것은 좋은 디자인의 필수조건이다.
리팩토링은 소프트웨어를 더 이해하기 쉽게 만든다.
리팩토링을 하면 코드의 목적은 더욱 명확해질 것이며 코드를 더 읽기 쉽게 만들도록 도와준다. 이해하기 쉽게 만든다는 것은 다른 면으로도 유용하다. 익숙하지 않은 코드를 이해하는데 도움을 받고자 리팩토링을 사용한다. 실제로 내가 이해한 것을 더 잘 반영하도록 코드를 수정하고, 그 코드를 다시 실행시켜 여전히 제대로 동작하는지를 봄으로써 내가 제대로 이해했는지 확인한다.
리팩토링은 버그를 찾도록 도와준다.
코드를 잘 이해하면 버그도 쉽게 찾을 수 있게 된다. 코드가 무슨 작업을 하는지 깊이 이해 하고 새로 이해한 것을 코드에 반영한다. 프로그램의 구조를 명확히 함으로써 내가 만든 가정의 진위가 명확해지면, 이 시점에서는 버그가 눈에 들어오지 않을 수 없다.
리팩토링은 프로그램을 빨리 작성하도록 도와준다.
리팩토링은 코드를 보다 빨리 개발할 수 있도록 도와준다. 소프트웨어 개발의 속도를 어느 정도로 유지하기 위해서는 좋은 디자인이 필수다. 리팩토링은 시스템의 디자인이 나빠지는 것을 멈추게 하여, 소프트웨어를 보다 빨리 개발할 수 있도록 도와준다. 또한 디자인을 향상시키기도 한다.
대부분의 경우 리팩토링은 별도의 시간을 내서 할 것이 아니라, 틈틈이 계속 해야 한다. 리팩토링 자체를 목적으로 삼는 것이 아니라, 어떤 다른 것을 하기 위해 리팩토링을 하는 것이고, 리팩토링은 그 다른 것을 하는 데 도움을 준다.
삼진 규칙
어떤 것을 처음 할 때는, 그냥 한다. 두 번째로 비슷한 어떤 것을 하게 되면, 중복되도록 한다. 세 번째로 비슷한 것을 하게 되면, 그때 리팩토링을 한다.
기능을 추가할 때 리팩토링을 하라.
이 시점에서 리팩토링을 해야 하는 첫 번째 이유는 종종 수정해야 할 코드에 대한 이해를 돕기 위해서 이다. 수정하려는 코드를 리팩토링해서 좀 더 명확하게 하면 더 많은 것을 이해할 수 있다.
리팩토링하는 다른 이유는 기능 추가가 쉽지 않은 디자인이다. 디자인을 변경하여 기능 추가가 쉽다면 리팩토링을 하여 그것을 고치는 것이 훨씬 빠르고, 매끄럽다.
버그를 수정해야 할 때 리팩토링을 하라.
버그 리포트를 받으면, 그것은 리팩토링이 필요하다는 신호인데, 왜냐하면 버그가 있었다는 것을 몰랐을 정도로 코드가 명확하지 않았다는 뜻이기 때문이다.
코드 검토(code review)를 할 때 리팩토링을 하라.
코드 검토는 지식이 개발팀 전체로 확산되는 것을 도와주고, 경험 많은 개발자의 지식이 경험이 적은 사람들에게 전달되도록 한다. 내 코드가 나에게는 명확하지만 다른 팀원들에게는 아닐 수도 있다. 검토는 더 많은 사람들에게 유용한 아이디어를 제안할 기회를 준다. 리팩토링은 다른 사람의 코드를 검토하는데 도움이 된다.
새로운 기능을 추가해야 하는데 프로그램의 코드가 새로운 기능을 추가하기 쉽도록 구조화되어 있지 않은 경우에는 먼저 리팩토링을 해서 프로그램에 기능을 추가하기 쉽게 하고, 그 다음에 기능을 추가한다.
리팩토링을 시작하기 전에 견고한 테스트 세트를 가지고 있는지 확인하라. 이 테스트는 자체 검사여야 한다.
리팩토링은 작은 단계로 나누어 프로그램을 변경한다. 실수를 하게 되더라도 쉽게 버그를 찾을 수 있다.
데이터 베이스
객체 데이터베이스는 도움도 되지만 방해도 된다. 일부 객체지향 데이터베이스는 한 객체에서 다른 객체로의 자동 마이그레이션을 지원한다. 이것은 노력을 줄여주기는 하지만 여전히 마이그레이션을 하는 동안의 시간을 보상해주지는 않는다. 마이그레이션이 자동이 아닌 경우는 직접 해야 하는데, 이는 많은 노력이 필요하다. 이런 경우에는 클래스의 데이터 구조에 대한 변경에 주의해야 한다. 동작을 옮기는 것은 여전히 자유지만, 필드를 옮기는 것에는 더욱 주의해야 한다. 데이터가 옮겨진 경우 그 데이터가 있는 것처럼 보이게 하기 위해 접근자를 사용할 필요가 있다.
인터페이스 변경
많은 리팩토링이 인터페이스를 변경하기 때문이다. 만약 메소드를 사용하는 모든 코드에 접근할 수 있다면 메소드 이름을 바꾸는 것은 문제되지 않는다. 메소드가 public 이더라도, 메소드를 호출하는 부분을 찾아서 변경할 수 있다면 메소드 이름을 바꿀 수 있다. 그러나, 인터페이스를 사용하는 코드를 찾을 수 없거나, 변경할 수 없는 경우라면 문제가 된다. 이런 겨우, 인터페이스는 공표된 인터페이스가 된다. 인터페이스가 공표되고 나면, 더 이상 안전하게 인터페이스를 수정할 수 없다. 공표된 인터페이스를 변경하는 경우, 적어도 해당 인터페이스를 사용하는 쪽에서 이 변경에 대응할 수 있는 기회를 가질 때까지는, 예전의 인터페이스와 새로운 인터페이스를 모두 유지해야 한다. 예전의 인터페이스가 새로운 인터페이스를 호출하도록 하면 된다.
리팩토링이 어려운 디자인 변경
한 디자인에서 다른 디자인으로 리팩토링을 할 수 있나 묻고, 그것이 쉽다면 가강 간단한 디자인을 고른다. 간단하게 리팩토링을 할 수 있는 방법이 보이지 않으면, 디자인에 더 많은 노력을 기울인다.
언제 리팩토링을 하지 말아야 하는가?
리팩토링을 하면 안 될 때가 있다. 대표적인 예가 코드를 처음부터 다시 작성해야 할 때이다. 기존의 코드가 너무 엉망이라 리팩토링을 할 수 있다 할지라도 처음부터 다시 시작하는 것이 더 쉬울 경우도 있다.
리팩토링을 피해야 하는 또 다른 경우는 마감일에 가까울 때이다. 이 시점에서 리팩토링으로 얻는 생산성 향상은 마감일이 지난 다음에 나타날 것이고, 그때는 너무 늦을 것이다.
리팩토링은 확실히 소프트웨어를 더 느리게 할 것이지만, 반면에 소프트웨어에 대한 퍼포먼스 튜닝을 더 쉽게 할 수 있도록 만든다. 엄밀한 실시간 환경을 제외한 모든 영역에서, 빠른 소프트웨어를 만드는 비결은 먼저 튜닝할 수 있는 소프트웨어를 만들고, 그 다음에 충분한 속도를 얻을 수 있도록 튜닝하는 것이다.
중복된 코드(Duplicated Code)
만약 한 곳 이상에서 중복된 코드 구조가 나타난다면, 그것을 합쳐서 프로그램을 개선할 수 있다.
긴 메소드(Long Method)
최적의 상태로 가장 오래 살아 남는 객체 프로그램은 메소드의 길이가 짧다. 긴 프로시저는 이해하기가 더 어렵다. 짧은 메소드로 된 코드를 이해하기 쉽게 하려면 무엇보다도 이름을 잘 지어야 한다. 메소드 이름을 잘 지어놓으면 메소드의 내용을 들여다 볼 필요가 없다. 메소드는 그것을 어떻게 처리하는지 보다는 그 코드의 의도를 잘 나타내는 이름을 지어야 한다.
거대한 클래스(Large Class)
클래스 하나가 너무 많은 일을 하려 할 때는 보통 지나치게 많은 인스턴스 변수가 나타난다. 클래스가 지나치게 많은 인스턴스 변수를 갖는 경우, 중복된 코드가 존재할 확률이 높다.
긴 파라미터 리스트(Long Parameter List)
긴 파리미터 리스트는 이해하기도 어렵고, 일관성이 없거나 사용하기 어려울 뿐만 아니라, 다른 데이터가 필요할 때마다 계속 고쳐야 하기 때문에, 파라미터 리스트는 짧은 것이 좋다. 새로운 데이터를 얻기 위해 몇 번 메소드 호출만 하면 되므로, 대부분의 변경은 객체를 넘김으로써 없어진다.
확산적 변경(Divergent Change)
확산적 변경은 한 클래스가 다른 이유로 인해 다른 방법으로 자주 변경되는 경우에 발생한다. 만약 어떤 클래스를 보고는 "새로운 데이터베이스를 추가할 때마다 항상 이 세 개의 메소드를 수정해야 하는군"하고 말한다면, 하나보다는 두개의 객체로 만드는 것이 더 좋은 상황에 있는 것이다. 이런 변화를 다룰 때는 하나의 클래스를 수정하는 선에서 끝나야 하며, 새로운 클래스는 그 변화를 반영하는 것이어야 한다.
산탄총 수술(Shotgun Surgery)
산탄총 수술은 확산적 변경과 비슷하지만 정 반대이다. 변경을 할 때마다 많은 클래스를 조금씩 수정해야 한다면 산탄총 수술의 냄새를 풍기고 있는 것이다. 변경해야 할 것이 여기저기 널려있다면, 찾기도 어렵고, 변경해야 할 중요한 사항을 빼먹기도 쉽다.
기능에 대한 욕심(Feature Envy)
객체의 가장 중요한 요점은 데이터와 데이터를 사용하는 프로세스를 하나로 묶는 기술이다. 메소드가 자신이 속한 클래스보다 다른 클래스에 관심을 가지는 경우 그 메소드는 분명히 다른 곳에 있고 싶은 것이고, 따라서 그 메소드를 이동시킨다. 가장 기본적인 규칙은 같이 변하는 것을 모으는 것이다. 데이터와 그 데이터를 이용하는 동작은 보통 같이 변하지만, 예외도 있다.
데이터 덩어리(Data Clump)
함께 몰려다니는 데이터의 무리는 그들 자신의 객체로 만들어져야 한다.
기본 타입에 대한 강박관념(Primitive Obsession)
대부분의 프로그래밍 환경은 두 종류의 데이터를 갖고 있다. 레코드 타입은 데이터를 구조화하여 의미 있는 그룹으로 만들 수 있게 한다. 기본 타입(primitive type)은 벽돌과 같다. 객체의 유용한 점 중의 하나는 기본 타입과 레코드 타입의 경계를 흐리게 하거나 아예 없애버린 다는 것이다.
Switch 문(Switch Statements)
객체지향 코드의 가장 명확한 특징 중 하나는 switch문이 비교적 적게 쓰인다는 것이다. switch문의 문제는 본질적으로 중복된다는 것이다. 객체지향 개념 중 다형성이 이런 문제를 다루는 훌륭한 방법을 제공한다. switch 문을 볼 때면 항상 다형성을 생각해야 한다. 만약 하나의 메소드에만 영향을 미치는 몇 개의 경우가 있다면, 굳이 바꿀 필요가 없다.
평행 상속 구조(Parallel Inheritance Hierachies)
평행 상속 구조는 실제로 산탄총 수술의 특별한 경우이다. 이런 경우 한 클래스의 서브클래스를 만들면, 다른 곳에도 모두 서브클래스를 만들어 주어야 한다. 한쪽 상속 구조에서 클래스 이름의 접두어가 다른 쪽 상속 구조의 클래스 이름의 접두어와 같은 경우에 이 냄새를 인식할 수 있다. 중복을 제거하는 일반적인 전략은 한쪽 상속 구조의 인스턴스가 다른 쪽 구조의 인스턴스를 참조하도록 만드는 것이다.
게으른 클래스(Lazy Class)
클래스를 생성할 때마다 그것을 유지하고, 이해하기 위한 비용이 발생한다. 이 비용을 감당할만큼 충분한 일을 하지 않는 클래스는 삭제되어야 한다.
추측성 일반화(Speculative Generality)
사람들이 "언젠가 이런 종류의 일을 처리하기 위해 기능이 필요한데..."라고 말하면서, 필요하지도 않은 것을 처리하기 위해 모든 종류의 갈고리와 특별한 상자를 원할 때 이 냄새를 맡을 수 있다. 그 결과 코드는 더욱 더 이해하기 어려워지고, 유지보수하기 힘들어진다. 만약 이 모든 장치가 이용된다면, 존재할 가치가 있을 것이다. 그렇지 않으면, 아니다. 사용되지 않는 것은 방해가 될 뿐이므로, 제거하라.
임시 필드(Temporary Field)
때로는 어떤 객체 안의 인스턴스 변수가 특정 상황에서만 세팅되는 경우가 있다. 이런 코드는 이해하기 어려운데, 왜냐하면 보통은 객체의 모든 변수가 값을 가지고 있을 거라고 기대하기 때문이다. 사용되지 않는 것처럼 보이는 변수가 왜 있는지를 이해하려고 하는 것은 매우 짜증나는 일이다. 불쌍한 고아 변수들을 위한 새로운 클래스를 만들고 그 변수를 사용하는 모든 코드를 새로 만든 클래스에 넣는다.
메시지 체인(Message Chains)
클라이언트가 어떤 객체를 얻기 위해 다른 객체에 물어보고, 다른 객체는 다시 또 다른 객체에 물어보고, 그 객체는 다시 다른 객체에 물어보고... 이런 경우 메세지 체인을 볼 수 있다. 이런 식으로 진행되는 것은 클라이언트가 클래스 구조와 결합되어 있다는 것을 뜻한다. 중간의 어떤 관계가 변한다면, 클라이언트 코드도 변경되어야 한다.
미들 맨(Middle Man)
객체의 주요 특징 중의 하나가 캡슐화이고 캡슐화는 보통 위임(delegation)과 함께 사용된다. 그러나 이것이 너무 지나칠 때가 있다.
부적절한 친밀(Inappropriate Intimacy)
때로는 클래스가 지나치게 친밀하게 되어 서로 사적인 부분을 파고드느라 너무 많은 시간을 소모할 수 있다. 지나치게 친밀한 클래스는 서로 떼어 놓아야 한다.
다른 인터페이스를 가진 대체 클래스(Alternative Classes with Different Interface)
같은 작업을 하지만 다른 시그너처(signature)를 가지는 메소드에 대해서는 Rename Method를 사용하라. 종종 이것만으로는 부족할 때도 있다. 이럴 때는 클래스가 여전히 충분한 작업을 하지 않는 경우이다. 프로토콜이 같아질 때까지 Move Method를 이용하여 동작을 이동시켜라. 너무 많은 코드를 옮겨야 할 때에는 목적을 이루기 위해 Extract Superclass를 사용할 수 있다.
불완전한 라이브러리 클래스(Incomplete Library Class)
클래스 라이브러리를 만드는 사람이라고 모든 것을 아는 것은 아니다. 문제는 라이브러리가 종종 나쁜 형태이고, 원하는 것을 하기 위해 라이브러리 클래스를 수정하는 것은 거의 불가능하다는 것이다.
데이터 클래스(Data Class)
필드와 각 필드에 대한 get/set 메소드만 가지고, 다른 것은 아무것도 없는 클래스도 있다. 이런 클래스는 멍청하게 데이터만 저장하고 거의 대부분 다른 클래스에 의해 조작된다. get/set 메소드가 다른 클래스에서 사용되는지를 찾아보고, 동작을 데이터 클래스로 옮기기를 시도한다.
거부된 유산(Refused Bequest)
서브클래스는 메소드와 데이터를 그 부모 클래스로부터 상속 받는다. 그러나 만약 서브클래스가 그들에게 주어진 것을 원치 않는다거나 필요하지 않는다면 어떻게 될까? 이런 훌륭한 선물 중에서 단지 필요한 쳐 가지만 고른다. 전통적인 관점에서 이것은 클래스 상속 구조가 잘못 되었다는 것을 뜻한다. 만약 서브클래스가 동작은 재사용하지만 수퍼클래스의 인터페이스를 지원하는 것은 원치 않는다면 거부된 유산의 냄새는 훨씬 더 강해진다. 구현을 거부하는 것은 상관하지 않지만, 인터페이스를 거부하는 것은 심각한 문제이다.
주석(Comments)
주석이 잔뜩 붙어 있는 코드를 보면 코드가 서투르기 때문에 주석이 있는 경우가 많다.
주석을 써야 할 것 같은 생각이 들면, 먼저 코드를 리팩토링 하여 주석이 불필요하도록 하라.
주석을 사용하기 좋을 때는 무엇을 해야 할지를 모를 때이다. 주석은 무엇이 진행되고 있는지를 설명할 뿐 아니라, 불확실한 부분을 표시할 수도 있다. 어떤 것을 왜 그렇게 했는지를 표시하는 데도 주석이 좋다. 특히 잊어버리기 쉬운 것이라면, 이런 정보는 나중에 수정할 때 도움이 된다.
냄새표
| 냄새 | 리팩토링 |
|---|---|
| 다른 인터페이스를 가지는 대체 클래스 | Rename Method, Move Method |
| 주석 | Extract Method, Introduce Assertion |
| 데이터 클래스 | Move Method, Encapsulate Field, Encapsulate Collection |
| 데이터 덩어리 | Extract Class, Introduce Parameter Object, Preserve Whole Object |
| 확산적 변경 | Extract Class |
| 중복된 코드 | Extract Method, Extract Class, Pull Up Method, Form Template Method |
| 기능에 대한 욕심 | Move Method, Move Field, Extract Method |
| 부적절한 친밀 | Move Method, Move Field, Change Bidirectional Association to Unidirectional, Replace Inheritance with Delegation, Hide Delegate |
| 불완전한 라이브러리 클래스 | Introduce Foreign Method, Introduce Local Extension |
| 거대한 클래스 | Extract Class, Extract Subclass, Extract Interface, Replace Data Value with Object |
| 게이른 클래스 | Inline Class, Collapse Hierarchy |
| 긴 메소드 | Extract Method, Replace Temp with Query, Replace Method with Method Object, Decompose Conditional |
| 긴 파라미터 리스트 | Replace Parameter with Method, Introduce Parametr Object, Preserve Whole Object |
| 메시지 체인 | Hide Delegate |
| 미들 맨 | Remove Middle Man, Inline Method, Replace Delegation with Inferitance |
| 평행 상속 구조 | Move Method, Move Field |
| 기본 타입에 대한 강박관념 | Replace Data Value with Object, Extract Class, Introduce Parameter Object, Replace Array with Object, Replace Type Code with Class, Replace Type Code with Subclasses, Replace Type Code with State/Strategy |
| 거부된 유산 | Replace Inheritance with Delegation |
| 산탄총 수술 | Move Method, Move Field, Inline Class |
| 추측성 일반화 | Collapse Hierarchy, Inline Class, Remove Parameter, Rename Method |
| Switch 문 | Replace Conditional with Polymorphism, Replace Type with Subclasses, Replace Type Code with State/Strategy, Replace Parameter with Explicit Method, Introduce Null Object |
| 임시 필드 | Extract Class, Introduce Null Object |
리팩토링을 하고자 할 때 견고한 테스트는 없어서는 안 될 필수조건이다.
자체 테스트 코드의 가치
대부분의 프로그래머는 디버깅을 하는데 대부분의 시간을 보낸다. 프로그래머라면 누구나 버그 하나를 찾아내는데 하루 종일(또는 그 이상) 걸렸던 경험을 이야기 할 수 있을 것이다. 보통 버그를 수정하는 것은 금방 할 수 있지만, 버그를 찾는 것은 악몽과 같을 수 있다.
클래스는 그 자신의 테스트를 포함하고 있어야 한다.
컴퓨터가 그 테스트를 스스로 하도록 할 수 있다.
모든 테스트를 완전히 자동화하고, 테스트가 자신의 결과를 스스로 확인하게 하라.
테스트를 작성하기 좋은 시점 가운데 하나는 프로그래밍을 시작하기 전이다. 테스트를 작성함으로써 기능을 추가하기 위해서 무엇을 해야 할지를 알 수 있기 때문이다. 또한 구현보다는 인터페이스에 집중할 수 있다. 또한 이것은 코딩을 완료할 명확한 지점(테스트 통과하는 시점)을 갖게 되는 것을 의미한다. 리팩토링을 원한다면 테스트를 작성해야 한다. 리팩토링을 하기 전에 먼저 자체 테스트 코드를 만들어야 한다. 테스트가 쉬운 프레임워크 내에서 별도의 테스트 클래스를 만든다.
테스트를 자주 실행시켜라. 컴파일 할 때마다 테스트를 하고, 적어도 하루에 한번 모든 테스트를 실행시켜라.
리팩토링을 할 때는 작업하고 있는 코드만 몇 가지 테스트를 실행시켜야 한다. 왜냐하면 테스트는 빨라야 하기 때문이다. 그렇지 않으면 테스트 때문에 작업 속도가 느려질 테고, 테스트를 실행시키고 싶은 마음이 사라질 것이다.
테스트를 작성할 때 종종 고의로 테스트가 실패하게 만든다. 기존의 코드에서 테스트가 실패하도록 수정하거나 또는 부정확한 값을 넣는다. 이렇게 하는 이유는 테스트가 실제로 실행되고, 또 테스트가 내가 기대하는 부분을 테스트 하는 지를 확인하기 위해서다. 프레임워크는 테스트 실패만 잡아내는 것이 아니라, 에러(기대하지 않았던 예외)도 잡아낸다. 실패와 에러는 구별하는 것이 좋다. 왜냐하면 이들은 서로 다르게 나타나고, 디버깅 프로세스도 다르기 때문이다.
단위 테스트와 기능 테스트(unit and functional tests)
단위 테스트는 범위가 매우 한정된다. 각각의 테스트 클래스는 하나의 패키지 내에서 작동한다. 다른 패키지에 대한 인터페이스는 테스트하지만 그 이상은 잘 작동하는 것으로 간주한다.
기능 테스트는 소프트웨어 전체가 제대로 작동하는 지를 확인하기 위해 작성된다. 기능 테스트는 사용자에게 확실한 품질을 제공하지만 프로그래머의 생산성 같은 것은 상관하지 않는다.
리팩토링을 하는 경우에는 단위 테스트에 의지한다.
테스트 추가하기
테스트는 문제가 생길만한 곳에 집중되어야 한다. 요점은 잘못된 염려가 가장 큰 영역에 테스트를 하는 것이다. 이런 식으로 하면 적은 노력으로 큰 효과를 얻을 수 있다.
완전한 테스트를 실행시키지 않는 것보다는 불완전한 테스트라도 작성하고 실행시키는 것이 더 낫다.
뭔가 잘못되었을 때에는 경계 조건을 생각하고 테스트를 경계 조건에 집중하라.
뭔가 잘못되리라 예상될 때 적절한 예외(execption)가 발생하는지 테스트 하는 것을 잊지 마라.
테스트로 모든 버그를 잡을 수 없다는 걱정 때문에 대부분의 버그를 잡을 수 있는 테스트를 작성하는 것을 멈추지 마라.
리팩토링을 할 때 많은 경우 메소드나 필드, 클래스를 참조하는 부분을 찾아야 한다. 이런 작업을 할 때는 컴퓨터의 도움을 받는 것이 좋다. 컴퓨터를 사용하면 실수를 할 확률을 줄일 수 있고, 눈으로 코드를 보는 것보다 훨씬 빨리 찾을 수 있다.
무턱대고 찾기/바꾸기를 사용하면 안 된다. 정말 바꾸기를 원하는 부분인지 확인하기 위해서 각각의 참조를 직접 점검해야 한다.
강력한 타입 체크 언어에서는, 컴파일러의 도움을 받아 참조하는 부분을 사냥할 수 있다. 예전의 기능을 삭제하고 컴파일러가 죽은 참조를 찾게 할 수 있다. 이와 같은 방법이 좋은 점은 컴파일러가 모든 죽은 참조를 찾아준다는 것이다. 그러나 여기에는 다음과 같은 문제가 있다.
첫째, 상속 구조에서 어떤 기능이 두 번 이상 선언된 경우에는 컴파일러가 식별할 수 없을 것이다. 이것은 여러 번 오버라이드 된 메소드를 볼 때 특히 그렇다. 상속 구조에서 작업을 할 때는 텍스트 검색을 사용하여 작업하고 있는 메소드가 다른 클래스에도 정의되어 있는지 확인해야 한다.
두 번째 문제는, 컴파일러가 너무 느려서 효과적으로 작업할 수 없을 수도 있다는 것이다. 이런 경우에는 텍스트 검색을 먼저 하는 것이 좋다.
세 번째 문제는 컴파일러가 리플렉션(실행시간 클래스의 정보를 알아낼 수 있다) API의 사용을 잡아낼 수 없다는 것이다. 이것이 리플렉션을 사용할 때 주의해야 하는 이유 중의 하나이다. 만약 시스템에서 리플렉션이 사용된다면 원하는 부분을 찾기 위해 텍스트 검색을 이용해야 하고, 테스트를 할 때도 좀 더 주의해야 한다.
리팩토링의 많은 부분이 메소드를 정리해서 코드를 적절하게 포장하는 것이다. 대부분의 경우 문제는 지나치게 긴 메소드에서 나온다. 긴 메소드는 많은 정보를 가지고 있는 데다가, 보통 이것저것 붙어있는 복잡한 로직에 의해 많은 정보가 묻혀버리기 쉽기 때문에 여러 가지 문제를 유발한다.
그룹으로 함께 묶을 수 있는 코드 조각이 있으면, 코드의 목적이 잘 드러나도록 메소드의 이름을 지어 별도의 메소드로 뽑아낸다.
![]()
![]()
![]()
void printOwing(double amount) {
동기
지나치게 긴 메소드를 보거나, 또는 목적을 이해하기 위해서 주석이 필요한 코드를 보면 그 부분을 하나의 메소드로 뽑아낸다.
이해하기 쉬운 이름으로 된 메소드는 다음과 같은 장점이 있다. 첫째, 메소드가 잘게 쪼개져 있을 때 다른 메소드에서 사용될 확률이 높아진다. 둘째, 고수준의 메소드를 볼 때 일련의 주석을 읽는 것 같은 느낌이 들도록 할 수 있다. 또한, 메소드가 잘게 쪼개져 있을 때 오버라이드 하는 것도 훨씬 쉽다. 작은 메소드는 실제로 이름을 잘 지었을 때만 그 진가가 드러나므로, 이름을 지을 때 주의해야 한다.
절차
메소드를 새로 만들고, 의도를 잘 나타낼 수 있도록 이름을 정한다.
원래 메소드에서 뽑아내고자 하는 부분의 코드를 복사하여 새 메소드로 옮긴다.
원래 메소드에서 사용되고 있는 지역변수가 뽑아낸 코드에 있는지 확인한다. 이런 지역변수는 새로운 메소드의 지역변수나 파라미터가 된다.
뽑아낸 코드 내에서만 사용되는 임시변수가 있는지 본다. 있다면 새로 만든 메소드의 임시변수로 선언한다.
뽑아낸 코드 내에서 지역변수의 값이 수정되는지 본다. 만약 하나의 지역변수만 수정된다면, 뽑아낸 코드를 질의(query)로 보고, 수정된 결과를 관련된 변수에 대입할 수 있는지 본다. 이렇게 하는 것이 이상하거나, 값이 수정되는 지역변수가 두개 이상 있다면 쉽게 메소드로 추출할 수 없는 경우이다.
뽑아낸 코드에서 읽기만 하는 변수는 새 메소드의 파라미터로 넘긴다.
지역변수와 관련된 사항을 다룬 후에는 컴파일을 한다.
원래 메소드에서 뽑아낸 코드 부분은 새로 만든 메소드를 호출하도록 바꾼다.
컴파일과 테스트를 한다.
예제
지역변수가 없는 경우 : 단순한 경우로 코드를 잘라서 새 메소드로 붙여넣고, 원래 코드는 새 메소드를 호출하도록 바꾸기만 하면 된다.
지역변수가 포함되어 있는 경우 : 지역변수가 포함된 경우에서 가장 쉬운 것은 변수가 읽히기만 하고 값이 변하지는 않는 경우이다. 이런 경우는 변수를 파라미터로 넘길 수 있다.
지역변수에 다른 값을 여러 번 대입하는 경우 : 임시변수에 값을 대입하는 경우는 두 가지가 있다. 임시변수가 뽑아낸 코드 안에서만 사용될 때와 임시변수가 뽑아낸 코드 외부에서도 사용되는 경우이다. 앞의 경우는 간단하다. 이럴 때는 뽑아낸 코드로 임시변수를 옮길 수 있다. 두 번째 경우에는 만약 그 변수가 뽑아낸 코드 이후의 부분에서 사용되지 않는다면 앞에서의 경우와 같이 바꿀 수 있다. 만약 뽑아낸 코드 이후 부분에서 사용된다면, 뽑아낸 코드에서 임시변수의 바뀐 값을 리턴하도록 수정해야 한다. 만약 두개 이상의 값이 리턴되어야 하는 경우 가장 좋은 선택은 뽑아낼 코드를 다르게 선택하는 것이다. 가능한 메소드가 하나의 리턴값을 가지는 것이 좋다.
메소드 몸체가 메소드의 이름 만큼이나 명확할 때는, 호출하는 곳에 메소드의 몸체를 넣고, 메소드를 삭제하라.
![]()
![]()
동기
메소드의 몸체가 메소드의 이름 만큼이나 명확할 때가 있다. 또는 메소드의 몸체를 메소드의 이름 만큼 명확하게 리팩토링 할 수도 있다. 이런 경우에는 해당 메소드를 제거해야 한다.
Inline Method는 메소드가 잘못 나뉘어져 있을 때에도 사용할 수 있다. 이런 경우 나누어져 있는 메소드를 다시 합쳐 하나의 큰 메소드로 만든 다음, 메소드를 다시 추출한다. 너무 많은 인디력션이 사용되어 모든 메소드가 단순히 다른 메소드에 위임을 하고 있어, 그 인디렉션 속에서 길을 잃을 염려가 있을 때도, Inline Method를 사용한다.
절차
메소드가 다형성을 가지고 있지않은지 확인한다.( 서브클래스에서 오버라이드하고 있는 메소드에는 적용하지 않는다.)
메소드를 호출하고 있는 부분을 모두 찾는다.
각각의 메소드 호출을 메소드 몸체로 바꾼다.
컴파일과 테스트를 한다.
메소드 정의를 제거한다.
위의 절차만 봐서는 Inline Method는 간단하게 보인다. 그러나 일반적으로는 그리 간단하지 않다. 재귀가 사용되는 경우나 리턴 포인트가 여러 곳인 경우등 복잡한 경우에는 이 리팩토링을 하지 않는 것이 좋다.
간단한 수식의 결과값을 가지는 임시변수가 있고, 그 임시변수가 다른 리팩토링을 하는데 방해가 된다면, 이 임시변수를 참조하는 부분을 모두 원래의 수식으로 바꾸라.
![]()
동기
대부분의 경우 Inline Temp는 Replace Temp with Query의 한 부분으로 사용된다. 따라서 진짜 동기는 그 쪽에 있다. Inline Temp가 자신의 목적으로 사용되는 유일한 경우는 메소드 호출의 결과값이 임시변수에 대입되는 경우이다.
절차
임시변수를 final(상수화)로 선언한 다음 컴파일 한다.( 이것은 임시 변수에 값이 단 한번만 대입되고 있는지를 확인하기 위한 것이다.)
임시변수를 참조하고 있는 곳을 모두 찾아 대입문의 우변에 있는 수식으로 바꾼다.
각각의 변경에 대해 컴파일과 테스트를 한다.
임시변수의 선언과 대입문을 제거한다.
컴파일과 테스트를 한다.
어떤 수식의 결과값을 저장하기 위해서 임시변수를 사용하고 있다면, 수식을 뽑아내서 메소드로 만들고, 임시변수를 참조하는 곳을 찾아 모두 메소드 호출로 바꾼다. 새로 만든 메소드는 다른 메소드에서도 사용될 수 있다.
![]()
![]()
![]()
동기
임시변수를 질의 메소드로 바꿈으로써 클래스 내의 어떤 메소드도 임시변수에 사용될 정보를 얻을 수 있다. 또한 이것은 클래스의 코드가 더 깔끔해지도록 한다. 지역변수는 메소드의 추출을 어렵게 하기 때문에 가능한 많은 지역변수를 질의 메소드로 바꾸는 것이 좋다.
이 리팩토링을 적용하는데 있어 가장 간단한 경우는 임시변수에 값이 한번만 대입되고, 대입문을 만드는 수식이 부작용을 초래하지 않는 경우이다. 만약 임시변수가 어떤 결과를 모으는 경우( 루프를 돌면서 덧셈을 하는 경우와 같이 ) 질의 메소드 안으로 몇몇 로직을 복사할 필요가 있다.
절차
간단한 경우에 대한 절차는 다음과 같다.
임시변수에 값이 한번만 대입되는지 확인한다.
임시변수를 final로 선언한다.
컴파일 한다. ( 임시변수에 값이 한번만 대입되는지 확인한다. )
대입문의 우변을 메소드로 추출한다. ( 처음에는 메소드를 private로 선언한다. 나중에 다른 곳에서도 사용하는 것이 좋을 것 같으면 그 때 쉽게 접근 권한을 바꿀 수 있다. 추출된 메소드에 부작용이 없는지 확인한다. )
컴파일과 테스트를 한다.
Inline Temp를 적용한다.
복잡한 수식이 있는 경우에는, 수식의 결과나 또는 수식의 일부에 자신의 목적을 잘 설명하는 이름으로 된 임시변수를 사용하라.
![]()
![]()
동기
수식은 매우 복잡해져 알아보기가 어려워질 수 있다. 이런 경우 임시변수가 수식을 좀 더 다루기 쉽게 나누는데 도움이 될 수 있다. 특히 조건문에서 각각의 조건의 뜻을 잘 설명하는 이름의 변수로 만들어 사용할 때 유용하다. 다른 경우로는 긴 알고리즘에서 각 단계의 계산 결과를 잘 지어진 이름의 임시변수로 설명할 수 있다.
절차
final 변수를 선언하고, 복잡한 수식의 일부를 이 변수에 대입한다.
원래의 복잡한 수식에서, 임시변수에 대입한 수식을 임시변수로 바꾼다.
컴파일과 테스트를 한다.
수식의 다른 부분에 대해서도 위의 작업을 반복한다.
루프 안에 있는 변수나 collecting temporary variable도 아닌 임시변수에 값을 여러 번 대입하는 경우에는, 각각의 대입에 대해서 따로따로 임시변수를 만들어라.
![]()
![]()
동기
어떤 변수든 여러 가지 용도로 사용되는 경우에는 각각의 용도에 대해 따로 변수를 사용하도록 바꾸어야 한다. 왜냐하면 하나의 임시변수를 두 가지 용도로 사용하면 코도를 보는 사람이 매우 혼란스러울 수 있다.
절차
임시변수가 처음 선언된 곳과 임시변수에 값이 처음 대입된 곳에서 변수의 이름을 바꾼다.
새로 만든 임시변수를 final로 선언한다.
임시변수에 두 번째로 대입하는 곳의 직전까지 원래 임시변수를 참조하는 곳을 모두 바꾼다.
임시변수에 두 번째로 대입하는 곳에서 변수를 선언한다.
컴파일과 테스트를 한다.
각 단계(임시변수가 선언되는 곳에서부터 시작하여)를 반복한다. 그리고 임시변수에 다음으로 대입하는 곳까지 참조를 바꾼다.
파라미터에 값을 대입하는 코드가 있으면, 대신 임시변수를 사용하도록 하라.
![]()
![]()
동기
파라미터로 객체를 넘긴 다음 파라미터에 다른 값을 대입하는 것은 파라미터가 다른 객체를 참조하게 하는 것을 뜻한다. 이것은 일단 명확히지가 않고, 값에 의한 전달과 참조에 의한 전달을 혼동하게 한다. 자바에서는 값에 의한 전달만 사용된다. 값에 의한 전달에서는 파라미터에 어떤 변경을 가하더라도 호출하는 루틴 쪽에는 반영되지 않는다.
이 규칙은 출력 파라미터를 사용하는 다른 언어에까지 적용할 필요는 없다. 하지만 출력 파라미터는 가능한 적게 사용하는 것이 좋다.
절차
파라미터를 위한 임시변수를 만든다.
파라미터에 값을 대입한 코드 이후에서 파라미터에 대한 참조를 임시변수로 바꾼다.
파라미터에 대입하는 값을 임시변수에 대입하도록 바꾼다.
컴파일과 테스트를 한다.
긴 메소드가 있는데, 지역변수 때문에 Extract Method를 적용할 수 없는 경우에는, 메소드를 그 자신을 위한 객체로 바꿔서 모든 지역변수가 그 객체의 필드가 되도록 한다. 이렇게 하면 메소드를 같은 객체 안의 여러 메소드로 분해할 수 있다.
![]()
![]()

동기
지역변수는 메소드를 분해할 때 어려움을 준다. 즉 지역변수가 많으면 분해가 어려워질 수 있다. 쪼개야 하는 메소드를 쪼갤 수 없는 경우가 생기면 도구상자의 깊숙한 부분에서 메소드 객체를 꺼내 사용한다. Replace Method with Method Object를 사용하는 것은 이런 모든 지역변수를 메소드 객체의 필드로 바꿔버린다. 그런 다음에 이 새로운 객체에 Extract Method를 사용하여 원래의 메소드를 분해할 수 있다.
절차
메소드의 이름을 따서 새로운 클래스를 만든다.
새로운 클래스에 원래 메소드가 있던 객체(소스 객체)를 보관하기 위한 final 필드를 하나 만들고, 메소드에서 사용되는 임시변수와 파라미터를 위한 필드를 만들어준다.
새로운 클래스에 소스 객체와 파라미터를 취하는 생성자를 만들어준다.
새로운 클래스에 compute라는 이름의 메소드를 만들어준다.
원래의 메소드를 compute 메소드로 복사한다. 원래의 객체에 있는 메소드를 사용하는 경우에는 소스 객체 필드를 사용하도록 바꾼다.
컴파일 한다.
새로운 클래스의 객체를 만들고 원래의 메소드를 새로 만든 객체의 compute 메소드를 호출하도록 바꾼다.
알고리즘을 보다 명확한 것으로 바꾸고 싶을 때는, 메소드의 몸체를 새로운 알고리즘으로 바꾼다.
![]()
![]()
동기
어떤 것을 할 때건 한 가지 이상의 방법이 있게 마련이다. 어떤 것을 할 때 더 명확한 방법을 찾게 되면, 복잡한 것을 명확한 것으로 바꾸어야 한다.
절차
대체 알고리즘을 준비한다. 적용하여 컴파일한다.
알고리즘을 테스트한다. 만약 결과가 같다면 작업은 끝난 것이다.
만약 결과가 같지 않다면, 테스트에서 비교하기 위해 예전의 알고리즘을 사용하여 디버깅한다.
객체 디자인에서 가장 기본이 되는 것 중의 하나는 책임을 어디에 둘지를 결정하는 것이다. 클래스는 종종 너무 많은 책임으로 비대해진다. 이런 경우에 책임의 일부를 떼어 놓는다.
메소드가 자신이 정의된 클래스보다 다른 클래스의 기능을 더 많이 사용하고 있다면, 이 메소드를 가장 많이 사용하고 있는 클래스에 비슷한 몸체를 가진 새로운 메소드를 만들어라. 그리고 이 전 메소드는 간단한 위임으로 바꾸거나 완전히 제거하라.

동기
메소드를 옮기는 것은 리팩토링에서 가장 중요하고 기본이 되는 것이다. 클래스가 너무 많은 동작을 가지고 있거나, 다른 클래스와 공동으로 일하는 부분이 너무 많아서 단단히 결합되어 있을 때 메소드를 옮긴다. 메소드를 옮김으로써 클래스를 더 간단하게 할 수 있고 클래스는 맡고 있는 책임에 대해 더욱 명확한 구현을 가질 수 있게 된다.
절차
소스 클래스에 정의된 소스 메소드에 의해 사용되는 모든 부분을 조사한다.
소스 클래스의 서브 클래스나 수퍼클래스에서 옮기려고 하는 메소드에 대한 다른 선언이 있는지 확인한다. (만약 다른 선언이 있다면, 다형성이 타겟 클래스에서도 역시 표현될 수 있는 경우에만 해당 메소드를 옮길 수 있을 것이다.)
타겟 클래스에 메소드를 정의한다.
소스 메소드에서 타겟 메소드로 코드를 복사한다. 그리고 그 메소드가 타겟 클래스에서 동작하도록 적절히 수정한다.
타겟 클래스를 컴파일한다.
소스 클래스에서 적절한 타겟 객체를 참조하는 방법을 결정한다.
소스 메소드를 위임 메소드로 바꾼다.
컴파일, 테스트를 한다.
소스 메소드를 제거할지 위임 메소드로 남겨둘지를 결정한다.
소스 메소드를 제거한다면 소스 메소드를 참조하고 있는 부분을 타겟 메소드를 참조하도록 수정한다.
컴파일, 테스트를 한다.
필드가 자신이 정의된 클래스보다 다른 클래스에 의해서 더 많이 사용되고 있다면, 타겟 클래스에 새로운 필드를 만들고 기존 필드를 사용하고 있는 모든 부분을 변경하라.

동기
시스템을 개발함에 따라 새로운 클래스가 필요하고 책임을 이리 저리 옮겨야 할 필요가 생긴다. 어떤 필드가 자신이 속한 클래스보다 다른 클래스의 메소드에서 더 많이 사용되고 있는 것을 보면 그 필드를 옮기는 것을 고려한다.
절차
필드가 public으로 선언되어 있으면 Encapsulate Field를 사용한다.
컴파일, 테스트를 한다.
타겟 클래스에 필드와 그 필드에 대한 get/set 메소드를 만든다.
타겟 클래스를 컴파일한다.
소스 클래스에서 타겟 객체를 참조하는 방법을 결정한다.
소스 클래스에 있는 필드를 제거한다.
소스 필드를 참조하고 있는 모든 부분을 타겟 클래스에 있는 적당한 메소드를 참조하도록 바꾼다.
컴파일, 테스트를 한다.
두 개의 클래스가 해야 할 일을 하나의 클래스가 하고 있는 경우, 새로운 클래스를 만들어서 관련 있는 필드와 메소드를 예전 클래스에서 새로운 클래스로 옮겨라.

동기
클래스는 책임이 점점 커지고 늘어남에 따라 지나치게 복잡해진다. 그 클래스를 분리할 방법을 생각하고, 클래스를 분리해야 한다. 데이터의 부분 집합과 메소드의 부분 집합이 같이 몰려다니는 것은 별도의 클래스로 분리할 수 있다는 좋은 신호다. 보통 같이 변하거나 특별히 서로에게 의존적인 데이터의 부분 집합 또한 별도의 클래스로 분리할 수 있다는 좋은 신호이다. 개발의 후반부에 종종 나타나는 신호중의 하나는 클래스가 서브타입이 되는 방법이다.
절차
클래스의 책임(기능)을 어떻게 나눌지를 결정하라.
분리된 책임을 떠맡을 새로운 클래스를 만든다.
이전 클래스에서 새로 만든 클래스에 대한 링크를 만든다.
옮기고자 하는 각각의 필드에 대해 Move Field를 사용한다.
각각의 필드를 옮길 때마다 컴파일, 테스트를 한다.
Move Method를 사용해서 이전 클래스에서 새로 만든 클래스로 메소드를 옮긴다. 저수준 메소드(호출하기 보다는 호출되는 메소드)부터 시작해서 점점 고수준의 메소드에 적용한다.
각각의 메소드를 옮길 때마다 컴파일, 테스트를 한다.
각 클래스를 검토하고, 인터페이스를 줄인다.
새로운 클래스를 공개할지 결정한다. 새로운 클래스를 공개하기로 결정했다면, 참조 객체로 드러낼지 또는 불변성 값 객체로 드러낼지를 결정한다.
클래스가 하는 일이 많지 않은 경우에는, 그 클래스에 있는 모든 변수와 메소드를 다른 클래스로 옮기고 그 클래스를 제거하라.

동기
클래스가 더 이상 제 몫을 하지 못하고 더 이상 존재할 필요가 없다면 Inline Class를 사용한다. 존재할 필요가 없는 클래스를 가장 많이 사용하는 것처럼 보이는 클래스를 하나 골라서, 그 클래스로 합친다.
절차
흡수하는 클래스에 소스 클래스의 public 필드와 메소드를 선언한다.
소스 클래스를 참조하고 있는 모든 부분을 흡수하는 클래스를 참조하도록 변경한다.
컴파일, 테스트를 한다.
Move Method와 Move Field를 사용하여, 소스 클래스에 있는 모든 변수와 메소드를 흡수하는 클래스로 옮긴다.
짧고 간단한 장레식을 거행한다.
클라이언트가 객체의 위임 클래스를 직접 호출하고 싶은 경우, 서버에 메소드를 만들어서 대리객체(delegate)*를 숨겨라.

동기
캡슐화는 객체에서 가장 중요한 개념 가운데 하나이다. 캡슐화가 되어 있는 경우에는 어떤 것이 변경되었을 때 시스템의 다른 부분이 영향을 덜 받으므로, 결과적으로 변경을 좀더 쉽게 할 수 있게 한다.
만약 클라이언트가 서버 객체의 필드에 들어있는 객체에 정의된 메소드를 호출한다면, 클라이언트는 대리객체(delegate)에 대해서 알아야 한다. 대리객체가 바뀌면 클라이언트 또한 바뀌어야 할 것이다. 이와 같은 경우에 여러분은 서버객체에 간단한 위임 메소드를 두어 위임을 숨김으로서 이런 종속성을 제거할 수 있다.
간단한 위임
절차
대리객체 각각의 메소드에 대해, 서버에서 간단한 위임 메소드를 만든다.
클라이언트가 서버를 호출하도록 바꾼다.
각각의 메소드를 알맞게 바꾸고 나서 컴파일, 테스트를 한다.
어떤 클라이언트도 더 이상 대리객체에 접근할 필요가 없다면, 서버 클래스에서 대리객체에 대한 접근자를 제거한다.
컴파일, 테스트를 한다.
클래스가 간단한 위임을 너무 많이 하고 있는 경우에는, 클라이언트가 대리객체를 직접 호출하도록 해라.

동기
Hide Delegate를 사용할 때 그만한 대가를 치러야 한다. 클라이언트가 대리 객체의 새로운 메소드를 사용하려 할 때마다 서버 클래스에 간단한 위임 메소드를 추가해야 하는 것이다. 따라서 새로운 메소드를 추가하려면 추가 비용이 들게 된다. 서버 클래스는 단지 미들맨에 지나지 않게 되는데, 아마도 이때가 클라이언트로 하여금 대리객체를 직접 호출하도록 해야 할 때일 것이다.
절차
대리객체에 대한 접근자를 만든다.
서버 클래스에 있는 위임 메소드를 사용하는 각각의 클라이언트에 대해 클라이언트가 대리객체의 메소드를 호출하도록 바꾸고 서버 클래스에 있는 메소드를 제거한다.
각각의 메소드에 대한 작업을 마칠 때마다 컴파일, 테스트를 한다.
사용하고 있는 서버 클래스에 부가적인 메소드가 필요하지만 클래스를 수정할 수 없는 경우에는, 첫 번째 인자로 서버 클래스의 인스턴스를 받는 메소드를 클라이언트에 만들어라.
![]()
![]()
동기
꼭 필요하지만 클래스가 제공하지 않는 서비스가 있을 경우 만약 소스 코드를 변경할 수 있다면 이러한 서비스를 제공하는 메소드를 추가할 수 있다. 그러나 소스 코드를 변경할 수 없다면 부족한 메소드를 클라이언트 쪽에 만들어야 한다.
만약 클라이언트 클래스에서 필요한 메소드를 여러 번 사용한다면 새로 만드는 메소드를 외래 메소드로 만들어서 이 메소드가 실제로는 서버 클래스에 있어야 하는 메소드라는 것을 명확하게 나타낼 수 있다.
외래 메소드는 임시 방편이다. 만약 할 수 있다면, 외래 메소드를 그들이 원래 있어야 하는 위치로 옮기는 것을 시도해 봐라. 코드 소유권이 문제가 된다면 외래 메소드를 서버 클래스의 소유자에게 보내고 그 소유자에게 당신을 위해 그 메소드를 구현해 달라고 요청하라.
절차
필요한 작업을 하는 메소드를 클라이언트 클래스에 만든다. ( 그 메소드는 클라이언트 클래스의 어떤 부분에도 접근해서는 안된다. 값이 필요하다면 값을 파라미터로 넘겨야 한다. )
첫 번째 파라미터로 서버 클래스의 인스턴스를 받도록 한다.
메소드에 "외래 메소드, 원래는 서버 클래스에 있어야 한다."와 같은 주석을 달아 놓는다.
사용하고 있는 서버 클래스에 여러 개의 메소드를 추가할 필요가 있지만 서버 클래스를 수정할 수 없는 경우, 필요한 추가 메소드를 포함하는 새로운 클래스를 만들어라. 이 확장 클래스를 원래 클래스의 서브클래스 또는 래퍼(wrapper) 클래스로 만들어라.

동기
소스 코드를 수정할 수 없는 경우, 적당한 장소에 필요한 메소드를 같이 모아 둘 필요가 있다. 일반적인 객체지향 기술인 서브클래싱과 래핑은 이런 작업을 하는 명확한 방법이다. 여기에서 서브클래스 또는 래퍼클래스를 local extension이라 부른다.
local extension은 별도의 클래스이지만, 확장하고 있는 클래스의 서브타입이다. 즉 이 클래스는 원래 클래스가 할 수 있는 모든 기능을 지원할 뿐만 아니라 이외의 기능도 가지고 있다는 것을 뜻한다. 원래 클래스를 사용하는 것 대신에, local extension 인스턴스를 만들어서 사용할 수 있다.
래퍼를 사용하는 것은 local extension변경된 사항이 원래 객체에 영향을 미칠 수 있게 하고, 원래 객체를 통해 변경된 사항은 래퍼에 영향을 미치게 한다.
절차
원래 클래스의 서브클래스나 래퍼 클래스로 확장 클래스를 만든다.
변환 생성자를 확장 클래스에 추가한다. ( 생성자는 원래 클래스를 인자로 받는다. 서브클래스 버전은 적당한 수퍼클래스 생성자를 호출한다. 래퍼를 사용할 경우에는 대리객체에 대한 필드를 파라미터로 설정한다. )
새로운 기능을 확장 클래스에 추가한다.
필요한 곳에서 원래 클래스를 확장 클래스로 대체한다.
이 클래스에 대해 정의된 외래 메소드를 모두 확장 클래스로 옮겨라.
데이터를 좀더 쉽게 다루기 위한 여러 가지 리팩토링.
필드에 직접 접근하고 있는데 필드에 대한 결합이 이상해지면, 그 필드에 대한 get/set 메소드를 만들고 항상 이 메소드를 사용하여 필드에 접근하라.
![]()
![]()
동기
간접 접근 방식(indirect variable access)의 장점은 서브클래스에서 정보를 얻는 메소드를 오버라이드 하여, 데이터를 관리하는데 있어 lazy initialization(사용할 필요가 있을 때 값을 초기화한다)과 같은 보다 많은 융통성을 제공할 수 있다는 것이다.
Self Encapsulate Field를 사용해야 하는 가장 중요한 때는 수퍼클래스에 있는 필드에 접근하지만 이 변수 접근을 서브클래스에서 계산되는 값으로 오버라이드 하고 싶을 때이다. 필드를 자체 캡슐화 하는 것은 첫 번째 단계일 뿐이다. 이렇게 한 다음에는 get/set 메소드를 필요한 대로 오버라이드 할 수 있다.
절차
필드에 대한 get/set 메소드를 만든다.
해당 필드가 참조되는 곳을 모두 찾아 get/set 메소드를 사용하도록 바꾼다.
필드를 private로 한다.
모든 참조를 수정했는지 다시 확인한다.
컴파일과 테스트를 한다.
추가적인 데이터나 동작을 필요로 하는 데이터 아이템이 있을 때는, 데이터 아이템을 객체로 바꾸어라.

동기
종종 개발 초기 단계에서는 간단한 사실을 간단한 데이터 아이템으로 표현하도록 한다. 개발이 진행될수록 이런 간단한 데이터 아이템이 더 이상 간단하지 않다는 것을 알게 된다. 한두 개의 아이템에 대해서는 아이템을 포함하고 있는 객체에 메소드를 추가할 수 있겠지만, 곧 코드는 중복과 기능에 대한 욕심의 냄새를 풍기게 된다. 이런 냄새가 나기 시작하면 데이터 값을 객체로 바꾸어야 한다.
절차
데이터 값에 대한 클래스를 만든다. 그리고 여기에 소스 클래스의 값과 같은 타입으로 필드를 만들어 final로 선언한다. get 메소드와 데이터 값의 필드를 파라미터로 취하는 생성자를 추가한다.
컴파일한다.
소스 클래스 필드의 타입을 새로운 클래스로 바꾼다.
소스 클래스의 get 메소드를 새로운 클래스의 get 메소드를 호출하도록 바꾼다.
만약 필드가 소스 클래스의 생성자에서 사용되면, 새로운 클래스의 생성자를 사용하여 필드에 값을 할당한다.
get 메소드에서 새로운 클래스의 인스턴스를 생성하도록 바꾼다.
컴파일, 테스트를 한다.
이제 새로운 객체에 대해 Change Value to Reference를 사용할 필요가 있을지도 모른다.
동일한 인스턴스를 여러 개 가지고 있는 클래스가 있고 여러 개의 동일한 인스턴스를 하나의 객체로 바꾸고 싶으면, 그 객체를 참조 객체로 바꾸어라.

동기
참조 객체를 쓸지 값 객체를 쓸지 결정하는 것이 항상 명확한 것은 아니다. 때로는 약간의 불변성 데이터를 가지는 간단한 값으로 시작한다. 그리고 나중에는 변경 가능한 데이터를 두고, 변경이 그 객체를 참조하고 있는 모든 곳으로 전파되기를 바란다. 이 시점이 객체를 참조 객체로 바꾸어야 할 때이다.
절차
Replace Constructor with Factory Method를 사용한다.
컴파일과 테스트를 한다.
어떤 객체가 참조 객체에 대한 접근을 제공하는 책임을 질 것인지 결정한다.
객체를 미리 생성해둘지 필요한 때에 생성할지를 결정한다.
팩토리 메소드가 참조 객체를 리턴하도록 수정한다.
컴파일, 테스트를 한다.
작고, 불변성(immutable)이고, 관리하기가 어려운 참조 객체가 있는 경우, 그것을 값 객체로 바꾸어라.

동기
참조 객체로 작업하는 것이 복잡해지면 참조에서 값으로 바꿀 이유가 될 수 있다. 값 객체의 중요한 특징중의 하나는 불변성이어야 한다는 것이다. 하나에 대서 질의를 호출하면 언제나 같은 결과를 얻어야 한다. 만약 이렇게 된다면 같은 것을 나타내는 객체가 여러 개 있더라도 문제될 것이 없다. 값 객체는 특히 분산 및 컨커런트 시스템에서 유용하다.
절차
바꿀 객체가 immutable인지 또는 immutable이 될 수 있는지 확인한다.( 만약 객체가 immutable이 될 수 없다면, 이 리팩토링을 포기해야 한다. )
equals 메소드와 hashCode 메소드를 만들어라.
컴파일, 테스트한다.
팩토리 메소드를 제거하고, 생성자를 publicc으로 하는 것을 고려하라.
예제
Currency 클래스로 시작하자.
코드를 저장하고, 리턴하는 것이 이 클래스가 하는 일의 전부다. 참조 객체이므로 인스턴스를 얻기 위해서는 다음과 같이 한다.
Currency는 인스턴스 리스트를 유지한다. 인스턴스를 만들기 위해 단순히 생성자를 사용 할 수는 없다.(이것이 생성자가 private인 이유이다.)
이것을 값 객체로 바꾸기 위해서는 먼저 이 객체가 immutable인지를 확인하는 것이 중요하다. 만약 immutable이 아니면, 바꾸지 않는 것이 좋다.
이 경우는 객체가 immutable이므로, 다음으로 해야 할 일은 equals 메소드를 정의하는 것이다.
equals 메소드를 정의하면 hashCode 메소드도 정의해야 한다. 간ㄷ나한 방법은 equals 메소드에서 사용된 모든 필드의 해시 코드를 취해서 XOR(^) 연산을 하는 것이다. 여기서는 하나의 필드만 사용되고 있으므로 간단하다.
두 메소드를 정의했으면, 이제 컴파일과 테스트를 할 수 있다. 위의 두 메소드를 모두 정의하지 않으면 해시 코드를 사용하는 Hashtable, HashSet 또는 HashMap과 같은 컬렉션이 이상하게 동작할 것이다.
이제 같은 Currency 객체를 원하는 만큼 만들 수 있다. 클래스 내의 모든 컨트롤러 관련 동작과 팩토리 메소드를 제거하고, 그냥 생성자를 사용할 수 있다. 따라서 생성자를 public으로 만든다.
배열의 특정 요소가 다른 뜻을 가지고 있다면, 배열을 각각의 요소에 대한 필드를 가지는 객체로 바꿔라.
![]()
![]()
동기
배열은 데이터를 조직하는 일반적인 구조다. 그러나 배열은 비슷한 객체가 어떤 순서대로 모여 있는 경우에 대해서만 사용되어야 한다. 그러나 때로는 다른 종류의 객체를 포함한 배열을 보게 된다. 배열의 첫 번째 요소는 사람 이름이라는 식의 약속은 기억하기 어렵다. 객체를 사용하면 필드의 이름이나 메소드에 이런 정보를 전달하도록 할 수 있기 때문에, 이런 것을 하나하나 기억할 필요도 없고, 주석이 항상 최신이기를 바랄 필요도 없다. 또한 정보를 캡슐화하고 Move Method를 사용하여 객체에 동작을 추가할 수도 있다.
절차
배열에 포함된 정보를 나타낼 새로운 클래스르 만든다. 여기에 배열을 위한 public 필드를 만든다.
해당 배열을 사용하는 부분을 모두 새로운 클래스를 사용하도록 바꾼다.
컴파일, 테스트를 한다.
배열의 요소에 get/set 메소드를 하나씩 추가하고 각 요소의 목적을 잘 나타내도록 메소드의 이름을 정한다. 클라이언트 코드가 이 메소드를 사용하게끔 수정한다. 하나씩 바꿀 때마다 컴파일, 테스트를 한다.
배열에 접근하는 코드가 모두 메소드를 통하도록 바꾸었으면, 배열을 private으로 만든다.
컴파일 한다.
배열의 각 요소에 대해 필드를 만들고, get/set 메소드에서 이 필드를 사용하도록 수정한다.
하나씩 바꿀 때마다 컴파일과 테스트를 한다.
배열의 모든 요소를 필드로 바꾼 후에, 배열을 제거한다.
GUI 컨트롤에서만 사용 가능한 도메인 데이터가 있고, 도메인 메소드에서 접근이 필요한 경우, 그 데이터를 도메인 객체로 복사하고, 옵저버(observer)를 두어 두 데이터를 동기화하라.

동기
계층화가 잘 된 시스템에서는 사용자 인터페이스를 다루는 코드와 비즈니스 로직을 다루는 코드가 분리되어 있다. 동작은 쉽게 분리될 수 있을지 모르지만, 데이터는 그렇지 않다. 도메인 모델에 있는 데이터와 같은 데이터가 GUI 컨트롤에 내장될 필요가 있다. 사용자 인터페이스 프레임워크는 모델-뷰-컨트롤러(MVC)를 비롯하여, 이 데이터와 모든 것을 동기화하기 위한 메커니즘을 제공하기 위해 다계층 시스템을 사용했다.
만약 비즈니스 로직이 사용자 인터페이스와 섞여 있는 2-티어로 개발된 코드를 보게 되면 인터페이스와 동작을 분리해야 한다. 이 작업은 대부분 메소드를 분해하고, 이동하는 것과 관련된다. 그러나 데이터에 대해서는, 단순히 옮기기만 하면 되는 것이 아니라, 데이터를 중복시키고 동기화하는 메커니즘을 제공해야 한다.
절차
프리젠테이션 클래스를 도메인 클래스의 오브저버[Gang fo Four]로 만든다.( 아직도 도메인 클래스가 없다면, 지금 만든다. 프리젠테이션 클래스에 도메인 클래스로의 링크가 없다면, 프리젠테이션 클래스의 필드에 도메인 클래스를 넣는다.)
GUI 클래스 내의 도메인 데이터에 Self Encapsulate Field를 사용한다.
컴파일, 테스트를 한다.
이벤트 핸들러 안에 있는 get 메소드에 컴포넌트를 현재 값으로 업데이트하는 코드를 추가한다.( 컴포넌트의 현재 값은 컴포넌트에 직접 접근하여 알아낸다. 이벤트 핸들러에 컴포넌트의 현재 값에 기반하여 그 값을 업데이트하는 메소드를 넣는다. 물론 현재의 값으로 다시 그 값을 설정하고 있으므로 불필요한 작업이다. 그러나 set 메소드를 이용하여 그곳에서 어떤 동작이라도 실행시킬 수 있다. 이 작업을 할 때 컴포넌트의 get 메소드를 이용하면 안 된다. 컴포넌트에 대한 직접 접근 방법을 사용하라. 나중에 get 메소드는 도메인에서 값을 가져올 것이고, set 메소드가 실행되기 전까지는 값을 변경하지 않을 것이다. 이벤트 처리 메커니즘이 테스트 코드에 의해 실행되는지 확인하라. )
컴파일, 테스트를 한다.
도메인 클래스에 데이터와 접근자 메소드를 정의한다.( 도메인의 set 메소드가 반드시 오브저버 패턴의 통보 메커니즘을 작동하도록 하라. 도메인에 프리젠테이션에 있는 것과 같은 데이터 타입을 사용하라. 데이터 타입은 나중에 리팩토링을 하여 바꾼다. )
접근자가 도메인 필드로 접근하도록 재지정한다.
오브저버의 update 메소드가 도메인 필드의 데이터를 GUI 컴포넌트로 복사하도록 수정한다.
컴파일, 테스트를 한다.
각각 서로의 기능을 필요로 하는 클래스가 있는데, 링크가 한쪽 방향으로만 되어 있는 경우, 반대 방향으로 포인터를 추가하고, 수정자(modifier)가 양쪽 세트를 모두 업데이트 하게 변경하라.
동기
두 개의 클래스 중 하나가 다른 하나를 참조하게 한 경우가 있을 수 있다. 시간이 지날수록, 두 클래스 중 참조되는 클래스를 사용하는 클라이언트 코드에서 다른 클래스를 알아내야 할 경우가 생길 수 있다. 이것은 결과적으로 포인터의 반대 방향을 알아내야 하는 것이다. 포인터는 단방향 링크이므로 이것은 불가능하다. 이런 경우에는 종종 역 포인터로 불리는 양방향 참조를 사용해야 한다. 역 포인터를 사용하는데 익숙하지 않다면, 포인터가 꼬일 수 있다. 그러나 일단 익숙해지면, 그렇게 복잡한 것도 아니다.
절차
역 포인터를 위한 필드를 추가한다.
어떤 클래스가 이 연관을 제어할 것인지 결정한다.
이 연관을 제어하지 않는 쪽에 헬퍼 메소드를 만든다. 이 메소드의 이름이 제한된 용법을 명확하게 나타내도록 정한다.
연관을 제어하는 쪽에 수정자가 있으면, 역 포인터를 업데이트하도록 수정한다.
만약 수정자가 제어되는 쪽에 있다면, 제어하는 쪽에 제어 메소드를 만들고, 수정자에서 이 메소드를 호출하도록 한다.
서로 링크를 가지는 두 개의 클래스에서 한 쪽이 다른 한쪽을 더 이상 필요로 하지 않을 때는 불필요한 링크를 제거하라.

동기
양방향 연관은 편리하기는 하지만, 비용이 따른다. 양방향 링크를 유지해야 하고, 객체가 적절히 생성/제거되는지를 확인해야 하는 등의 복잡성이 증가하기 때문이다. 많은 프로그래머들이 양방향 연관을 자연스럽게 다루지 못하므로, 양방향 연관은 종종 에러의 원인이 된다.
양방향 연관은 반드시 필요한 곳에만 사용해야 한다. 제 몫을 하지 못하는 양방향 연관을 발견하면 즉시 불필요한 한쪽 링크를 끊어라.
절차
삭제하고자 하는 포인터에 대한 필드를 읽고 있는 곳을 모두 조사하여, 삭제가 가능한지 확인한다.( 필드를 직접 읽는 메소드와 그 메소드를 호출하는 다른 메소드도 살펴봐야 한다. )
만약 클라이언트 코드에서 get메소드를 사용해야 한다면, get 메소드에 Self Encapsulate Field를 사용하고, Substitute Algorithm을 적용한 후 컴파일, 테스트를 한다.
만약 클라이언트 코드에서 get메소드를 필요로 하지 않으면, 포인터 필드를 사용하는 부분을 찾아 필드의 객체를 다른 방법으로 얻을 수 있도록 한다. 각각의 변경에 대해서 컴파일, 테스트를 한다.
필드를 읽는 부분을 모두 제거한 후에, 필드를 업데이트 하는 코드와 필드를 제거한다.
컴파일, 테스트를 한다.
특별한 의미를 가지는 숫자 리터럴이 있으면, 상수를 만들고, 의미를 잘 나타내도록 이름을 지은 다음, 숫자를 상수로 바꾸어라.
![]()
![]()
![]()
동기
매직 넘버는 프로그래밍에 있어서 가장 오래된 해악 가운데 하나이다. 이것은 특히 논리적으로 같은 숫자를 여러 곳에서 사용해야 하는 경우에 고약하다. 만약 숫자 값이 바뀐다면, 변경을 적용하는 것은 악몽이다. 값을 바꾸지 않는 경우라 하더라도 그 의미를 이해하는데 어려움이 있다.
절차
상수를 선언하고 그 값을 매직 넘버의 값으로 설정한다.
매직 넘버가 사용되는 곳을 모두 찾는다.
매직 넘버가 상수의 의미와 맞는지 보고, 의미가 맞으면 상수로 바꾼다.
컴파일한다.
모든 매직 넘버를 바꾼 후에 컴파일, 테스트를 한다. 이때 모든 동작이 바뀌기 전과 동일해야 한다.
public 필드가 있는 경우, 그 필드를 private으로 만들고, 접근자를 제공하라.
![]()
![]()
![]()
동기
객체 지향의 중요한 교리중의 하나가 캡슐화 또는 데이터 은폐이다. 객체 지향에서는 데이터를 절대로 public으로 하지 말라고 한다. 만약 데이터를 public으로 하면, 데이터를 가지고 있는 객체가 알지 못하는 사이에 다른 객체가 데이터 값에 접근하고 변경할 수 있게 된다. 데이터와 그 데이터를 사용하는 동작이 같이 묶여 있으면 변경할 코드가 프로그램 여기저기에 흩어져 있는 것이 아니라 한 곳에 모여있게 되므로 변경하기가 쉬워진다.
절차
필드에 대한 get/set 메소드를 만든다.
클래스 밖에서 그 필드를 참조하는 모든 클라이언트 코드를 찾는다. 만약 클라이언트 코드에서 값을 이용한다면, 필드에 대한 참조를 get 메소드 호출로 바꾼다. 만약 클라이언트 코드가 값을 변경하고 있다면, set 메소드 호출로 바꾼다.
각각의 변경에 대해서 컴파일, 테스트를 한다.
클라이언트 코드를 모두 수정했으면, 필드를 private으로 만든다.
컴파일, 테스트를 한다.
컬렉션을 리턴하는 메소드가 있으면, 그 메소드가 읽기전용 뷰(read-only view)를 리턴하도록 만들고, add/remove 메소드를 제공하라.
동기
종종 클래스가 컬렉션의 인스턴스를 포함하고 있을 때가 있다. 이 컬렉션은 배열이거나 List, Set, Vector 등일 것이고, 컬렉션에 대한 get/set 메소드를 가지고 있는 경우가 많다.
그러나 컬렉션은 다른 종류의 데이터를 다룰 때와는 약간 다른 프로토콜을 사용해야 한다. get 메소드가 컬렉션 객체 자체를 리턴하면 안 되는데, 왜냐하면 이것은 클라이언트 코드가 컬렉션을 가지고 있는 클래스가 알지 못하는 사이에 컬렉션의 내용을 조작할 수 있기 때문이다. 또한 컬렉션에 대한 set 메소드가 있으면 안 된다. 대신 컬렉션에 요소를 추가, 삭제하는 오퍼레이션이 있어야 한다.
절차
(컬렉션을 포함하고 있는 클래스에) 컬렉션에 대한 add/remove 메소드를 추가한다.
필드를 빈 컬렉션으로 초기화 한다.
컴파일한다.
set 메소드를 호출하는 코드를 찾는다. set 메소드를 add/remove 오퍼레이션을 사용하도록 수정하거나, 또는 클라이언트 코드가 set 메소드 대신 이 오퍼레이션을 호출하도록 수정한다.
컴파일, 테스트를 한다.
get 메소드를 사용하여 컬렉션을 수정하는 코드를 찾아서 add/remove 메소드를 사용하도록 수정한다. 각각의 변경에 대해 컴파일, 테스트를 한다.
get 메소드를 사용하여 컬렉션을 수정하는 코드를 모두 바꾸었으면, get 메소드가 컬렉션의 읽기전용 뷰를 리턴하도록 수정한다.( 자바2 에서는 수정할 수 없는 컬렉션 뷰가 있다. 자바 1.1에서는 컬렉션의 복사본을 리턴해야 한다. )
컴파일, 테스트를 한다.
get 메소드를 사용하는 코드를 찾는다. 호스트 객체(컬렉션을 가지고 있는 객체)에 있어야 하는 코드가 있는지 본다. Extract Method와 Move Method를 사용하여 코드를 호스트 객체로 옮긴다.
자바 2에서는 작업이 끝났다. 그러나 자바 1.1에서는 클라이언트 코드가 Enumeration을 사용하는 것이 좋다. 다음과 같이 Enumeration을 제공한다.
현재의 get 메소드의 이름을 바꾸고, Enumeration을 리턴하는 새로운 get 메소드를 추가한다. 예전의 get 메소드를 사용하는 코드를 찾아 새로운 메소드를 사용하도록 수정한다.
컴파일, 테스트를 한다.
전통적인 프로그래밍 환경에서의 레코드 구조에 대한 인터페이스가 필요한 경우, 그 레코드를 위한 데이터 객체를 만들어라.
동기
레코드 구조는 프로그래밍 환경의 일반적인 특징이다. 이것을 객체지향 프로그램에 가져와야 하는 여러 가지 이유가 있다. 레거시 프로그램을 복사하는 경우도 있을 수 있고, 전통적인 프로그래밍 API에서 사용되는 구조적 레코드 또는 데이터베이스 레코드와 통신하는 경우도 있을 수 있다. 이런 경우 이런 외부 요소를 다루는 인터페이스 클래스를 만들면 편리하다. 클래스가 외부 레코드처럼 보이도록 만드는 것은 아주 쉽다. 나중에 다른 필드나 메소드를 이 클래스 안으로 옮길 수 있다.
절차
레코드를 표현할 클래스를 만든다.
클래스에 private 필드를 추가하고, 각각의 데이터 아이템에 대한 get/set 메소드를 만든다.
클래스의 동작에 영향을 미치지 않는 숫자로 된 타입 코드(numeric type code)가 있으면, 숫자를 클래스로 바꾸어라.

동기
숫자를 클래스로 바꾼다면, 컴파일러는 클래스에 대해서 타입 체크를 할 수 있다. 이 클래스에 팩토리 메소드를 제공함으로써, 유효한 인스턴스만이 생성되는지, 이런 인스턴스가 적절한 객체로 전달되는지를 정적으로 확인할 수 있다.
그러나 Replace Type Code with Class를 하기 전에, 타입 코드에 대한 다른 대안에 대해서도 생각해봐야 한다. 타입 코드가 순수한 데이터일 때에만 타입 코드를 클래스로 바꿀 수 있다. 즉, 타입 코드가 switch 문에 사용되어 다른 동작을 유발하거나 하면 안 된다. 자바에서는 switch 문에 임의의 클래스는 쓸 수 없고 정수형만 쓸 수 있기 때문에, 이런 경우 타입 코드를 클래스로 바꾸는 것은 실패할 것이다. 더 중요한 것은, 모든 switch 문은 Replace Conditional with Polymorphism을 사용하여 제거되어야 하다는 것이다.
절차
타입 코드를 위한 새로운 클래스를 만든다.
소스 클래스에서 새로 만든 클래스를 사용하도록 구현을 수정한다.
컴파일, 테스트를 한다.
소스 클래스에서 코드를 사용하는 각각의 메소드에 대해, 새로 만든 클래스를 사용하는 새로운 메소드를 만든다.
소스 클래스의 클라이언트 코드를 하나씩 수정하여 새로운 인터페이스를 사용하도록 한다.
각각의 클라이언트 코드를 수정할 때마다 컴파일과 테스트를 한다.
코드를 사용하는 기존의 인터페이스와, 코드에 대한 static 변수 선언을 제거한다.
컴파일, 테스트를 한다.
클래스의 동작에 영향을 미치는 변경 불가능한 타입 코드가 있다면, 타입 코드를 서브클래스로 바꾸어라.

동기
클래스의 동작에 영향을 미치지 않는 타입 코드가 있다면, Replace Type Code with Class를 사용할 수 있다. 그러나 타입 코드가 동작에 영향을 미치는 경우에는 다형성을 사용하여 여러 가지 동작을 처리하도록 하는 것이 가장 좋다. Replace Type Code with Subclasses의 장점은 변화하기 쉬운 동작에 대한 정보를 클래스의 클라이언트 코드에서 클래스 자체로 옮긴다는 것이다. 만약 새로운 동작이 추가되면, 단지 서브클래스를 추가하기만 하면 된다. 다형성을 사용하지 않는다면 모든 조건문을 찾아서 변경해주어야 한다. 따라서 이 리팩토링은 동작이 계속해서 변경되는 경우에 유용하다.
절차
타입 코드를 자체 캡슐화한다.
각각의 타입 코드에 대해 서브클래스를 생성한다. 서브클래스에서 타입 코드를 얻는 get 메소드를 오버라이드하여 관련된 값을 리턴하도록 한다.
각각의 타입 코드를 서브클래스로 바꾸고 난 후에 컴파일, 테스트를 한다.
수퍼클래스에서 타입 코드를 제거한다. 타입 코드에 대한 접근자를 abstract로 한다.
컴파일, 테스틀 한다.
클래스의 동작에 영향을 미치는 타입 코드가 있지만 서브클래싱을 할 수 없을 때는, 타입 코드를 스테이트 객체로 바꾸어라.
동기
이 리팩토링은 Replace Type Code with Subclasses와 비슷하지만, 객체의 존속기간 동안 타입 코드가 바뀌거나 또는 다른 이유로 서브클래싱을 할 수 없는 경우에도 사용될 수 있다. 그리고, 스테이트 또는 스트레티지 패턴 중 하나를 사용한다.
절차
타입 코드를 자체 캡슐화한다.
새로운 클래스를 만들고, 타입 코드의 목적을 잘 나타내도록 이름을 정한다.( 이것이 스테이트 객체이다. )
각각의 코드에 대해서 스테이트 객체의 서브클래스를 추가한다.
스테이스 객체에 타입 코드를 리턴하는 추상 질의 메소드를 만든다.
스테이트 객체의 서브클래스에서 적절한 타입 코드를 리턴하도록 질의 메소드를 오버라이드 한다.
컴파일한다.
원래의 클래스에 새로 만든 스테이트 객체를 위한 필드를 만든다.
원래의 클래스에 있는 질의 메소드가 스테이트 객체로 작업을 위임하도록 조정한다.
원래의 클래스에서 타입 코드 set 메소드가 적절한 스테이트 객체 서브클래스 인스턴스를 할당하도록 조정한다.
컴파일, 테스트를 한다.
상수 데이터를 리턴하는 메소드만 다른 서브클래스가 있으면, 그 메소드를 수퍼클래스의 필드로 바꾸고 서브클래스를 제거하라.

동기
보통 기능을 추가하거나, 동작의 변화를 허용하기 위해서 서브클래스를 만든다. 동작 변화의 한 형태는 상수 메소드이다. 상수 메소드는 하드 코딩된 값을 리턴하는 메소드이다. 이것은 접근자가 서로 다른 값을 리턴하는 서브클래스가 있는 경우에 매우 유용하다. 수퍼클래스에 접근자를 선언하고, 서브클래스에서 다른 값을 리턴하도록 구현한다.
상수 메소드가 유용하기는 하지만, 상수 메소드만을 포함하고 있는 서브클래스는 존재할 가치가 있을 만큼 충분한 일을 하는 것이 아니다. 이런 서브클래스는 수퍼클래스에 필드를 추가하고 완전히 삭제할 수 있다. 이렇게 하면 서브클래스를 사용하면서 생기는 여러 복잡성을 제거할 수 있다.
절차
서브클래스에 Replace Constructor with Factory Method를 사용한다.
서브클래스를 참조하는 코드는 수퍼클래스를 참조하도록 수정한다.
각각의 상수 메소드에 대한 필드를 수퍼클래스에 선언한다.
이 필드들을 초기화하기 위해, 수퍼클래스에 protected 생성자를 만든다.
새로운 수퍼클래스 생성자를 호출하도록 서브클래스 생성자를 수정하거나 추가한다.
컴파일, 테스트를 한다.
수퍼클래스에서 각각의 상수 메소드가 필드의 값을 리턴하도록 하고, 서브클래스에서 상수 메소드를 제거한다.
각각의 메소드를 제거할 때마다 컴파일, 테스트를 한다.
서브클래스의 모든 메소드가 제거되었으면, Inline Method를 사용하여 생성자를 수퍼클래스의 팩토리 메소드 안으로 넣는다.
컴파일, 테스트를 한다.
서브클래스를 제거한다.
컴파일, 테스트를 한다.
불필요한 서브클래스가 모두 사라질 때까지 각각의 서브클래스에 대해 Inline Method를 사용하여 생성자를 수퍼클래스의 팩토리 메소드 안으로 넣고, 서브클래스를 제거한다.
조건 논리는 복잡해지기 쉬우믈, 조건 논리를 단순화하는 데 쓸 수 있는 여러 가지 리팩토링.
객체지향 프로그램은 종종 조건부 동작의 상당 부분이 다형성에 의해서 처리되기 때문에 절차적 프로그램보다 조건부 동작을 덜 가지고 있다. 다형성은 호출부(caller)에서 조건부 동작에 대해 알 필요가 없기 때문에 좋고, 따라서 조건을 확장하는 것이 쉽다. 그 결과, 객체지향 프로그램은 switch(case) 문을 거의 가지고 있지 않다.
복잡한 조건문(if-then-else)이 있는 경우, 조건, then 부분, 그리고 else 부분에서 메소드를 추출하라.
![]()
![]()
![]()
동기
일반적으로 프로그램에서 가장 복잡한 부분 가운데 하나가 복잡한 조건 논리이다. 어떤 큰 코드 블록을 가지고 작업할 때, 그 코드를 분해하고 코드 덩어리를 의도에 맞는 이름을 가진 메소드 호출로 바꿔서 여러분의 의도를 좀더 명확하게 할 수 있다. 조건절에서 각각의 다른 조건 부분에 대해 이와 같은 작업을 함으로써 여러분은 좀더 많은 이익을 얻을 수 있다.
절차
조건을 하나의 메소드로 뽑아낸다.
then 부분과 else 부분을 각각의 메소드로 뽑아낸다.
같은 결과를 초래하는 일련의 조건 테스트가 있는 경우, 그것을 하나의 조건 식으로 결합하여 뽑아내라.
![]()
![]()
![]()
동기
때때로 각각 검사하는 조건은 다르지만 동일한 결과를 초래하는 조건 검사가 반복되는 것을 보게 된다. 이런 것을 보면 AND와 OR을 사용하여 하나의 결과를 가지는 하나의 조건 검사로 통합해야 한다.
조건을 통합하는 것이 중요한 두 가지 이유가 있다. 첫째, 여러 개의 검사를 OR로 연결하여 실제로는 하나의 검사를 하고 있다는 것을 보여줌으로써 검사를 좀더 명확하게 한다. 두 번째 이유는 이 리팩토링이 종종 Extract Method를 시작할 수 있는 환경을 만들어 준다는 것이다.
조건문을 통합하는 것을 지지하는 이유는 또한 그것을 하지 말아야 하는 이유와도 같다. 그 검사가 정말로 독립적이고 하나의 검사로 생각되지 말아야 한다면, 이 리팩토링을 하지 말라.
절차
부작용을 가지고 있는 조건문이 있는지 확인한다.
여러 개의 조건문을 논리 연산자를 사용하는 하나의 조건문으로 대체한다.
컴파일, 테스트를 한다.
조건에 대해 Extract Method를 사용하는 것을 고려한다.
동일한 코드 조각이 조건문의 모든 분기 안에 있는 경우, 동일한 코드를 조건문 밖으로 옮겨라.
![]()
![]()
![]()
동기
때때로 조건문의 모든 조건 구간 안에서 실행 되는 동일한 코드를 보게 된다. 이런 경우 이 코드를 조건문 밖으로 옮겨야 한다. 이것은 변하는 것과 변하지 않는 것을 좀더 명확하게 한다.
절차
조건에 상관없이 동일하게 실행 되는 코드를 확인한다.
공통으로 사용되는 코드가 시작 부분에 있다면, 그 코드를 조건문 앞으로 옮긴다.
공통으로 사용되는 코드가 끝 부분에 있다면, 그 코드를 조건문 뒤로 옮긴다.
공통으로 사용되는 코드가 중간 부분에 있다면, 그 코드의 앞 또는 뒤에 있는 코드와 위치를 바꿀 수 있는지를 살펴본다. 만약 가능하다면, 공통으로 사용하는 코드를 앞쪽이나 뒤쪽으로 옮길 수 있다. 그리고 나서 공통으로 사용하는 코드가 시작 부분 또는 끝부분에 있는 경우와 같은 방법으로 옮길 수 있다.
만약 한 문장보다 많다면, 여러분은 그 코드를 메소드로 추출해야 한다.
일련의 boolean 식에서 컨트롤 플래그 역할을 하는 변수가 있는 경우, break 또는 return 을 대신 사용하라.
동기
일련의 조건식이 있을 때, 종종 루프를 빠져 나올 때를 결정하기 위해 사용되는 컨트롤 플래그를 보게 된다. 이런 컨트롤 플래그는 유용하기보다는 골칫거리이다.
절차
컨트롤 플래그를 처리하는 명확한 방법은 자바에 있는 break 또는 continue를 사용하는 것이다.
논리문 밖으로 나오도록 하는 컨트롤 플래그의 값을 찾는다.
논리문 밖으로 나오도록, 컨트롤 플래그에 값을 설정하던 부분을 찾아 break나 continue 문으로 바꾼다.
각각의 변경에 대해 컴파일, 테스트를 한다.
break나 continue를 사용하지 않는 다른 접근 방법은 다음과 같다.
로직을 메소드로 뽑아낸다.
논리문 밖으로 나오도록 하는 컨트롤 플래그의 값을 찾는다.
논리문 밖으로 나오도록 컨트롤 플래그에 값을 설정하던 부분을 return으로 바꾼다.
각각의 변경에 대해 컴파일, 테스트를 한다.
메소드가 정상적인 실행 경로를 불명확하게 하는 조건 동작을 가지고 있는 경우, 모든 특별한 경우에 대해서 보호절(guard clause)을 사용하라.
![]()
![]()
![]()
동기
두 가지 형태의 조건문이 있다. 첫 번째 형태는 어느 한쪽 코스가 정상적인 동작 부분인지를 검사하는 것이다. 두 번째 형태는 조건문의 한 경우가 정상적인 동작을 나타내고 다른 한 경우는 비정상적인 동작을 나타내는 상황이다.
이런 종류의 조건문은 다른 의도를 가지고 있고, 이런 의도는 코드에 나타나야 한다. 둘 다 정상적인 동작 부분이라면, if와 else를 가진 조건을 사용하라. 조건이 예외적인 조건이라면, 조건을 검사하고 true면 리턴하라. 이런 종류의 검사는 종종 보호절이라고 불린다. 보호절은 "이것은 드문 경우이고, 만약에 이 경우가 일어난다면, 뭔가를 하고 밖으로 나가라."고 말하는 것이다.
절차
각각의 검사에 보호절을 넣어라.( 그 보호절은 리턴 하거나 예외를 발생시킨다.)
각각의 검사 부분을 보호절로 바꾼 후 컴파일, 테스트를 한다.( 모든 보호절이 동일한 결과를 산출한다면, Consolidate Conditional Expressions를 사용하라. )
객체의 타입에 따라 다른 동작을 선택하는 조건문을 가지고 있는 경우, 조건문의 각 부분을 서브클래스에 있는 오버라이딩 메소드로 옮겨라. 그리고 원래 메소드를 abstract로 만들어라.
![]()
![]()
![]()
동기
다형성은 많은 이점을 제공한다. 가장 큰 이점은 동일한 조건 집합이 프로그램의 많은 곳에 나타날 때 생긴다. 만약 새로운 타입을 추가하고 싶다면, 모든 조건문을 찾아서 업데이트해야 한다. 그러나 서브클래스를 사용한다면, 단지 새로운 서브클래스를 만들고 적당한 메소드를 제공하면 된다. 클래스를 사용하는 클라이언트는 그 서브클래스에 대해서 알 필요가 없고, 이는 여러분의 시스템에서 종속성을 줄이고 업데이트하는 것을 쉽게 한다.
절차
이 리팩토링을 적용하기 위해서는 먼저 필요한 상속 구조를 가져야 한다. 상속구조를 가지고 있지 않다면, 만들어야 한다.
조건문이 좀더 큰 메소드의 한 부분이라면, 그 조건문 부분을 취해서 Extract Method를 사용한다.
필요하다면 Move Method를 사용하여 조건문을 상속 구조의 제일 위에 위치시킨다.
서브클래스 중에 하나를 선택하여 조건문 메소드를 오버라이드 하는 서브클래스 메소드를 만든다. 조건문에서 필요한 부분을 서브클래스에 복사하고 적절히 수정한다.
컴파일, 테스트를 한다.
조건문에서 복사한 부분을 제거한다.
컴파일, 테스트를 한다.
조건문의 모든 조건 부분이 서브클래스 메소드로 바뀔 때까지 이 작업을 계속한다.
수퍼클래스 메소드를 abstract로 만든다.
null 체크를 반복적으로 하고 있다면, null 값을 null 객체로 대체하라.
![]()
![]()
![]()
동기
다형성의 진가는, 객체에 타입을 묻고 그 답을 기초로 하여 어떤 동작을 호출하는 대신에 단지 그 동작을 호출하는 것이다. 그 객체는 타입에 따라서 적절한 동작을 한다. 이렇게 하기에 덜 직관적인 곳 중의 하나가 필드에 null 값을 가지고 있는 곳이다.
절차
소스 클래스에 서브클래스를 만들어서 소스 클래스의 null 버전 역할을 하도록 한다. 소스 클래스와 null 클래스에 isNull 메소드를 만든다. 이 메소드는 소스 클래스에서는 false를, null 클래스에는 true를 리턴해야 한다. ( inNull 메소드를 정의하고 있는 명시적인 Nullable 인터페이스를 만드는 것이 유용할 수도 있다. )
컴파일한다.
소스 객체를 요구했을 때 null을 리턴할 수 있는 곳을 모두 찾아, null 대신 null 객체를 리턴하도록 바꾼다.
소스 타입의 변수가 null과 비교되는 곳을 찾아, isNull 메소드를 호출하도록 바꾼다.
컴파일, 테스트를 한다.
클라이언트가 null이 아니면 오퍼레이션을 호출하고, null 이면 다른 동작을 하는 경우를 찾는다.
이런 각각의 경우에 대해서, null 클래스에 있는 메소드를 오버라이드하여 클라이언트에서 null인 경우에 실행되는 동작을 하게끔 한다.
오버라이된 동작을 사용하는 것에 대한 조건감사(null 체크)를 제거하고, 컴파일과 테스트를 한다.
코드의 한 부분이 프로그램의 상태에 대하여 어떤 것을 가정하고 있으면, assertion을 써서 가정을 명시되게(explicit) 만들어라.
![]()
동기
종종 코드의 특성 부분은 어떤 조건이 참일 때만 동작한다. 이런 가정은 종종 명확히 기술되어 있지 않고, 알고리즘을 살펴봐야만 이해할 수 있다. 때때로 이러한 가정은 주석으로 기술되어 있기도 하다. 더 나은 방법은 assertion을 써서 그 가정이 명시되게 하는 것이다.
assertion은 항상 참이라고 가정되는 조건문이다. assertion이 실패하면 프로그래머가 실수를 했다는 것을 뜻한다. 따라서 assertion 실패는 항상 비검사 예외를 발생해야 한다. assertion은 결코 시스템의 다른 부분에서 사용되면 안 된다. 실제로 assertion은 일반적으로 완성된 코드에서는 제거된다. 따라서 어떤 것이 assertion이라고 표시해 두는 것은 중요하다.
절차
어떤 조건이 참이라고 가정하고 있는 때, 그것을 기술하는 assertion을 추가한다.( assertion 동작을 하는데 쓸 수 있는 assert 클래스를 만들어라. )
assertion의 남용에 주의하라. 코드 부분에 대하여 참이라고 생각하는 곳을 확인하기 위해 모든 곳에 assertion을 사용하지는 않기 바란다. 참이 될 필요가 있는 것들을 검사하기 위해서만 assertion을 사용해야 한다. assertion에서 중복되는 코드에 주의하라.
객체는 모두 인터페이스에 관한 것이다. 메소드의 이름을 바꾸는 것은 가장 간단하고 중요한 것이다. 좋은 인터페이스는 단지 그것이 보여야 할 만큼만 보여주고 그 이상은 보여주지 않는다.
메소드의 이름이 그 목적을 드러내지 못하고 있다면, 메소드의 이름을 바꿔라.

동기
코드는 첫째로 사람이 읽기 좋아야 하고, 컴퓨터는 그 다음이라는 것을 기억하라. 사람이 읽기 쉬우려면 좋은 이름을 가지고 있어야 한다.
절차
메소드 시그너처가 수퍼클래스 또는 서브클래스에서 구현되고 있는지를 살펴본다. 만약 그렇다면, 각각 구현된 부분에 대하여 이들 단계를 수행한다.
새로운 이름을 가진 새로운 메소드를 선언한다. 원래 메소드 몸체를 새로운 이름의 메소드로 복사하고 적절히 수정한다.
컴파일한다.( 원래 메소드는 새로 만든 메소드를 호출하도록 변경한다. )
원래 메소드를 참조하는 부분이 적다면 이 단계를 생략해도 무방하다.
컴파일, 테스트를 한다.
원래 메소드를 참조하고 있는 모든 부분을 찾아서 새로운 메소드를 참조하도록 수정한다. 각각의 변경에 대해 컴파일, 테스트를 한다.
원래 메소드를 제거한다.( 원래 메소드가 인터페이스의 일부분이어서 없앨 수 없다면, 그 메소드를 그대로 남겨두고 deprecated 되었다고 표시해 둔다. )
컴파일, 테스트를 한다.
어떤 메소드가 그 메소드를 호출하는 부분에서 더 많은 정보를 필요로 한다면, 이 정보를 넘길 수 있는 객체에 대한 파라미터를 추가하라.

동기
메소드를 변경해야 하는데, 변경하려면 이전에는 필요하지 않았던 정보가 필요하다. 그래서 파라미터를 추가한다. 이 리팩토링은 가능하다면 다른 대안을 사용하는 것이 더 좋은데, 왜냐하면 파라미터의 길이를 증가시키지 않기 때문이다. 긴 파라미터 리스트는 기억하기 어렵고 종종 데이터 덩어리를 필요로 하기 때문에 안 좋다.
절차
메소드 시그너처가 수퍼클래스 또는 서브클래스에서 구현되고 있는지를 살펴 본다. 만약 그렇다면, 각각 구현된 부분에 대하여 이들 단계를 수행한다.
추가된 파라미터를 가지는 새로운 메소드를 선언한다. 원래 메소드의 몸체를 새로운 메소드로 복사한다.
컴파일 한다.
원래 메소드에서 새로 만든 메소드를 호출하도록 변경한다.
컴파일, 테스트를 한다.
원래 메소드를 참조하고 있는 모든 부분을 찾아서 새로운 메소드를 참조하도록 수정한다. 각각을 변경한 후 컴파일, 테스트를 한다.
원래 메소드를 제거한다.
컴파일, 테스트를 한다.
파라미터가 메소드 몸체에서 더 이상 사용되지 않는다면, 그 파라미터를 제거하라.
동기
파라미터는 필요로 하는 정보를 나타낸다. 다른 값은 다른 결과를 산출한다. 메소드를 사용하는 사람은 어떤 값을 넘겨줄지에 대해 고민해야 한다. 불필요한 파라미터를 제거하지 않음으로 인해, 그 메소드를 사용하는 모든 사람들이 일을 더 많이 하게 만든다.
값을 리턴할 뿐만 아니라 객체의 상태도 변경하는 메소드를 가지고 있는 경우, 두 개의 메소드를 만들어서 하나는 값을 리턴하는 역할을 하고, 하나는 객체의 상태를 변경하는 역할을 하게 하라.

동기
값을 리턴하는 동시에 부작용도 가지고 있는 메소드를 발견한다면, 질의하는 부분과 변경하는 부분을 분리해야 한다.
일반적인 최적화 방법은 질의 결과 값을 필드에 캐쉬해서 반복 되는 질의가 좀더 빨리 처리되도록 하는 것이다. 비록 이것이 캐쉬를 가진 객체의 상태를 바꾼다 할지라도, 그 변경은 눈에 띄지 않는다. 어떤 반복되는 질의라도 각각의 질의에서는 항상 같은 결과를 리턴할 것이다.
몇몇 메소드가 메소드 몸체에 다른 값을 포함하고 있는 것을 제외하고는 비슷한 일을 하고 있다면, 다른 값을 파라미터로 넘겨 받는 하나의 메소드를 만들어라.

동기
여러 개의 메소드를 달라지는 값을 파라미터로 처리하는 하나의 메소드로 대체함으로써 일을 간단하게 할 수 있다. 이와 같이 바꾸면 중복된 코드가 없어지고 유연성이 좋아지는데, 왜냐하면 파라미터를 추가하는 것으로 다른 변종을 다룰 수 있기 때문이다.
파라미터의 값에 따라서 다른 코드를 실행하는 메소드가 있다면, 각각의 파라미터 값에 대한 별도의 메소드를 만들어라.
![]()
동기
이 리팩토링을 적용하면 조건에 따른 행동을 피할 뿐만 아니라 컴파일 할 때 확인을 할 수 있다. 또한 인터페이스가 좀더 명확해진다. 파라미터로 어떤 일을 할지 결정하는 경우, 그 메소드를 사용하는 모든 프로그래머는 클래스에서 그 메소드를 살펴봐야 할뿐만 아니라 유효한 파라미터 값을 결정해야 한다. 유효한 파라미터 값은 문서화가 제대로 되어 있지 않은 경우가 많다.
파라미터 값들이 많이 변할 것 같다면 이 리팩토링을 사용하지 말아야 한다. 이런 경우 간단한 set 메소드를 사용하여 파라미터로 값을 넘겨 필드에 설정한다.
어떤 객체에서 여러 개의 값을 얻은 다음 메소드를 호출하면서 파라미터로 넘기고 있다면, 대신 그 객체를 파라미터로 넘겨라.
![]()
동기
이런 상황은 객체가 어떤 메소드를 호출할 때, 한 객체에서 얻은 여러 개의 데이터 값을 파라미터로 넘겨 줄 때 발생한다. 이 방법의 문제점은 만약 호출된 객체가 나중에 새로운 데이터 값을 필요로 한다면, 이 메소들 호출하고 있는 모든 부분을 찾아서 변경해야 한다는 것이다. 여러 개의 데이터를 넘겨주는 대신에 객체를 넘겨줌으로써 이런 문제점을 피할 수 있다. 이렇게 하면 호출된 객체는 무엇을 원하든지 간에 넘겨 받은 객체에게 요청할 수 있다.
이 리팩토링은 파라미터 리스트가 변경에 유연하게 대처할 수 있도록 하는 것 이외에도, 종종 코드를 좀더 읽기 좋게 만든다.
나쁜 점도 있다. 여러분이 값으로 전달할 때, 호출된 객체는 값에 종속성을 가지고 있지만, 그 값을 추출한 객체에 대해서는 어떠한 종속성도 가지고 있지 않다. 파라미터로 객체를 넘기는 것은 파라미터로 넘겨진 객체와 호출된 객체사이에 종속성을 만든다. 만약 이것이 여러분의 종속성 구조를 나쁘게 만든다면, 이 리팩토링을 사용하지 마라.
객체가 메소드를 호출한 다음, 결과를 다른 메소드에 대한 파라미터로 넘기고 있다. 수신자(Receiver-파라미터를 넘겨 받는 메소드) 또한 이 메소드를 호출할 수 있다면, 그 파라미터를 제거하고 수신자가 그 메소드를 호출하도록 하라.
![]()
동기
어떤 메소드가 파라미터로 넘겨 받는 값을 다른 방법을 사용해서 얻을 수 있다면, 그렇게 해야 한다. 긴 파라미터 리스트는 이해하기 어려우므로, 파라미터 리스트를 가능한 짧게 줄여야 한다.
자연스럽게 몰려다니는 파라미터 그룹을 가지고 있다면, 그것들을 객체로 바꾸어라.

동기
이 리팩토링은 파라미터 리스트 길이를 줄이기 때문에 유용한데, 긴 파라미터 리스트는 이해하기 어렵기 때문이다. 새로운 객체에 정의된 접근자는 코드를 좀더 일관되게 만들고 이는 다시 코드를 전보다 이해하기 쉽고 수정하기 쉽게 만든다.
어떤 필드가 객체 생성시에 값이 정해지고 그 이후에는 변경되지 않아야 한다면, 그 필드 값을 설정하는 모든 메소드를 제거하라.

동기
이 리팩토링을 사용하면 여러분의 의도는 명확해지고 그 필드 값이 변할지도 모르는 가능성이 없어지게 된다.
메소드가 다른 클래스에서 사용되지 않는다면, 그 메소드를 private으로 만들어라.

객체를 생성할 때 단순히 생성하는 것 이외에 다른 작업도 하고 있다면, 생성자를 팩토리 메소드로 대체하라.
![]()
동기
이 리팩토링을 사용해야 하는 가장 명확한 때는 타입 코드를 서브클래스로 대체할 때이다. 여러분은 타입 코드 값을 가지고 생성되는 객체를 가지고 있었지만, 이제는 서브클래스를 필요로 하는 객체를 가지고 있다. 어떤 서브클래스를 사용할지는 타입 코드에 달려 있다. 그러나 생성자는 단지 요청된 객체의 인스턴스만 리턴할 수 있다. 따라서 생성자를 팩토리 메소드로 대체할 필요가 있다.
메소드가 그 호출부에서 다운캐스트 될 필요가 있는 객체를 리턴하고 있다면, 다운캐스트 하는 것을 메소드 안으로 옮겨라.
![]()
동기
다운캐스팅은 필요악일지도 모른다. 그러나 가능하면 다운캐스팅을 적게 사용해야 한다. 만약 메소드에서 어떤 값을 리턴하고 있는데, 여러분이 메소드 시그너처에 있는 것보다 리턴 타입을 좀더 구체적으로 알고 있다면, 여러분은 불필요한 작업을 클라이언트에서 하도록 하는 것이다. 클라이언트에서 이러한 다운캐스팅을 하도록 하는 것보다는 가장 구체적인 타입을 항상 제공해야 한다.
메소드가 에러를 나타내는 특별한 코드를 가지고 있다면, 대신 예외를 던져라.
![]()
동기
예외는 일반적인 처리 과정과 예외 처리 과정을 명확하게 분리하기 때문에 좋다. 이는 프로그램을 이해하기 쉽게 만든다.
호출부에서 먼저 검사할 수 있는 조건에 대해 예외를 던지고 있다면, 호출부가 먼저 검사하도록 바꿔라.
![]()
동기
예외는 과도하게 사용될 수 있고, 그렇다면 예외를 사용해서 얻게 되는 즐거움이 없어진다. 예외는 예외적인 동작(예상치 못한 에러)에 대해서 사용되어야 한다. 예외가 조건 테스트를 대신하는 역할을 하면 안 된다. 호출부에서 동작을 호출하기 전에 조건을 테스트할 수 있다면 테스트를 제공해야 하고, 호출부에서 그것을 사용해야 한다.
일반화(generalization)는 하나의 리팩토링 군을 만드는데, 주로 상속 구조에서 메소드를 옮기는 것을 다룬다.
두 서브클래스가 동일한 필드를 가지고 있다면, 그 필드를 수퍼클래스로 옮겨라.

동기
이 리팩토링을 사용하면 두 가지 점에서 중복을 없앤다. 중복된 데이터 정의를 없애고, 그 필드를 사용하는 동작을 서브클래스에서 수퍼클래스로 옮기는 것이 가능하다.
동일한 일을 하는 메소드를 여러 서브클래스에서 가지고 있다면, 이 메소드를 수퍼클래스로 옮겨라.

동기
중복된 동작을 제거하는 것은 중요하다. 중복된 부분이 있는 경우에는 한 쪽은 바꿨는데 다른 쪽은 그대로 놓아 두는 위험에 처하게 된다. 일반적으로 중복된 부분을 찾는 것은 어렵다.
서브클래스들이 대부분 동일한 몸체를 가진 생성자를 가지고 있다면, 수퍼클래스에 생성자를 만들고 서브클래스 메소드에서 이것을 호출하라.
![]()
동기
만약 공통된 동작을 가지는 메소드를 서브클래스에서 발견한다면, 가장 먼저 떠올리는 생각은 공통된 동작을 메소드로 뽑아내서 그 메소드를 수퍼클래스로 옮기는 것이다. 그러나 생성자에서는 공통되는 동작이 생성이다. 이런 경우에는 서브클래스에 의해서 호출되는 수퍼클래스 생성자가 필요하다. 많은 경우 이것은 생성자의 몸체이다. 여기서는 Pull Up Method를 사용할 수 없는데, 왜냐하면 생성자를 상속할 수 없기 때문이다.
수퍼클래스에 있는 동작이 서브클래스 중 일부에만 관련되어 있다면, 그 동작을 관려된 서브클래스로 옮겨라.

동기
동작이 특정한 서브클래스에서만 있는 것이 타당한 경우 사용한다.
어떤 필드가 일부 서브클래스에 의해서만 사용되고 있다면, 그 필드를 관련된 서브클래스로 옮겨라.

동기
어떤 필드를 수퍼클래스에서는 필요로 하지 않지만, 서브클래스에서 필요로 할 때는 이 방법을 사용하라.
어떤 클래스가 일부 인스턴스에 의해서만 사용되는 기능을 가지고 있다면, 기능의 부분집합을 담당하는 서브클래스를 만들어라.

동기
주로 어떤 클래스가 그 클래스의 모든 인스턴스가 아닌 일부 인스턴스에 의해서만 사용되는 기능을 가지고 있다는 것을 깨닫게 될 때 사용한다. Extract Subclass의 대안으로는 Extract Class이 있다. 이것은 위임과 상속 사이의 선택이다. 보통 Extract Subclass가 적용하기는 좀 더 간단하지만, 제약을 가지고 있다. 일단 객체가 생성된 후에는 객체에서 클래스에 기초한 동작을 변경할 수 없다. 반면에 Extract Class를 적용하면 단순히 다른 컴포넌트를 플러그인 해서 클래스에 기초한 동작을 변경할 수 있다. 또한 단지 서브클래스만 사용해서 일련의 변형된 것들을 나타낼 수 있다. 다양한 방법으로 변하는 클래스를 원한다면, 그것들 중 하나를 제외하고는 모든 것에 대해서 위임을 사용해야 한다.
비슷한 메소드와 필드를 가진 두 개의 클래스가 있다면, 수퍼클래스를 만들어서 공통된 메소드와 필드를 수퍼클래스로 옮겨라.
동기
중복된 코드의 한 형태, 동일한 방법 또는 서로 다른 방법으로 비슷한 일을 하는 두개의 클래스가 존재하는 것이다. 객체는 상속을 통해 이런 상황을 단순화하는 고유의 메커니즘을 제공한다. 그러나 종종 여러분이 어떤 클래스들을 만들 때까지 공통성을 알아차리지 못할 때가 있고, 이 경우에 상속 구조를 나중에 만들어야 한다.
여러 클라이언트가 한 클래스 인터페이스의 동일한 부분 집합을 사용하고 있거나, 두 클래스가 공통된 인터페이스를 가지는 부분이 있다면, 그 부분 집합을 인터페이스로 뽑아내라.

동기
Extract Interface는 공통된 코드가 아니라, 공통된 인터페이스만 가지고 나갈 수 있다. Extract Interface를 사용하는 것은 냄새가 나는 중복된 코드를 초래할 수 있다. 여러분은 Extract Class를 사용해서 동작을 컴포넌트에 두고 컴포넌트에 위임하도록 함으로써 이 문제점을 감소시킬 수 있다. 공통된 동작이 많다면, Extract Superclass가 좀 더 간단하지만, 단지 하나의 수퍼클래스만 가지게 된다.
클래스가 서로 다른 상황에서 별개의 역할을 가지고 있을 때는 언제나 인터페이스를 사용하는 것이 좋다. 각 역할에 Extract Interface를 사용하라. 또 다른 편리한 경우는, 여러분이 한 클래스의 밖으로 나가는 인터페이스, 즉 그 클래스가 서버에 하는 오퍼레이션 같은 것을 기술하고 싶은 경우이다. 만약 나중에 다른 종류의 서버를 허용하고 싶으면, 단지 인터페이스를 구현하면 된다.
각각의 서브클래스에, 동일한 순서로 비슷한 단계를 행하지만 단계가 완전히 같지는 않은 두 메소드가 있다면, 그 단계를 동일한 시그너처를 가진 메소드로 만들어라. 이렇게 하면 원래의 두 메소드는 서로 같아지므로, 수퍼클래스로 올릴 수 있다.

동기
상속은 중복된 동작을 제거하는 데 있어서 강력한 도구이다. 여러 서브클래스에 비슷한 메소드가 있는 것을 볼 때마다, 우리는 이들 메소드를 합쳐서 수퍼클래스에 두기를 원한다. 그러나, 만약 이들 메소드가 완전히 똑같지 않다면 어떻게 하겠는가? 우리는 여전히 가능하면 모든 중복된 코드를 줄이면서 본질적인 차이점은 남겨둘 필요가 있다.
일반적인 경우는 두 메소드가 대체로 비슷하게 보이는 단계를 같은 순서로 수행하지만, 각각의 단계는 동일하지 않은 경우이다. 이런 경우에 순서는 수퍼클래스로 옮기고, 다형성을 사용해서 각각의 단계가 자신의 일을 다른 방법으로 처리하도록 할 수 있다. 이런 종류의 메소드를 템플릿 메소드라고 부른다.
서브클래스가 수퍼클래스 인터페이스의 일부분만 사용하거나 또는 데이터를 상속 받고 싶지 않은 경우, 수퍼클래스를 위한 필드를 만들고 메소드들이 수퍼클래스에 위임하도록 변경한 후 상속 관계를 제거한다.

동기
종종 어떤 클래스를 상속하여 시작하지만, 수퍼클래스에 있는 오퍼레이션의 상당 부분이 서브클래스에서는 별로 적절하지 않다는 것을 알게 된다. 이런 경우 여러분은 그 클래스가 하는 일을 제대로 반영하지 못하는 인터페이스를 가지고 있다는 것이다. 또는 서브클래스에 적합하지 않은 상당량의 데이터를 상속하고 있다는 것을 발견할 수도 있다. 또는 서브클래스에서 의미 없는 수퍼클래스 메소드들이 protected로 되어 있는 것을 발견할지도 모른다.
상속 대신 위임을 사용하면, 위임되는 클래스의 단지 일부분만 사용하고 있다는 것을 명확하게 한다. 여러분은 인터페이스의 어느 면을 사용하고 어느 면을 무시할지를 제어해야 한다. 이런 작업을 하는데 드는 비용은 여분의 위임 메소드를 만드는 것인데, 작성하기는 지루하지만 너무 간단해서 잘못될 수가 없다.
위임을 사용하고 있는데, 전체 인터페이스에 대해 간단한 위임을 자주 작성하고 있다면, 위임하는 클래스를 대리객체의 서브클래스로 만들어라.

동기
만약 자신이 대리객체의 모든 메소드를 사용하고 있고 이런 간단한 위임 메소드를 작성하는 데 넌더리가 난다면, 매우 쉽게 상속을 사용하도록 바꿀 수 있다.
여기서 명심해야 할 사항이 몇 가지가 있다. 만약 위임하고 있는 클래스의 모든 메소드를 사용하지 않고 있다면 Replace Delegation with Ingeritance를 사용하지 말아야 하는데 왜냐하면 서브클래스는 항상 수퍼클래스의 인터페이스를 따라야 하기 때문이다. 주의해야 할 또 다른 상황은 대리객체가 둘 이상의 객체에 의해 공유되고 있고 가변성일 때이다. 이런 경우에는 더 이상 데이터를 공유하지 못하기 때문에 위임을 상속으로 대체할 수 없다. 데이터 공유는 상속으로 돌릴 수 있는 책임이 아니다. 객체가 불변성일 때는 데이터 공유는 문제가 아닌데, 왜냐하면 단순히 복사할 수 있고 아무도 그 사실을 모르기 때문이다.
대규모 리팩토링을 할 때는 아주 많은 변경을 위해 방향을 설정해야 한다. 팀 전체가 대규모 리팩토링이 진쟁 중이라는 사실을 인지하고 있어야 하고, 그들의 작업을 적절히 맞추어야 한다.
두 가지 작업을 한번에 처리하는 상속 구조가 있는 경우, 두개의 상속 구조를 만들고 하나가 다른 하나를 호출하도록 위임(delegation)을 사용하라.

동기
엉켜있는 상속 구조는 프로그래머에게는 파멸의 원인이 되는 코드의 중복을 야기하므로 문제가 된다. 어떤 문제를 풀기 위한 전략이 흩어지게 되어 변경을 더욱 어렵게 한다. 마침내 소스 코드는 이해하기 어려운 지경에 이른다.
하나의 상속 구조에서 두 가지 작업을 하는 경우는 쉽게 찾아낼 수 있다. 만약 상속 구조의 특정 레벨에 있는 클래스들이 같은 형용사로 시작하는 이름의 서브클래스를 각각 가지고 있다면, 아마도 하나의 상속 구조에서 두 가지 작업을 하고 있을 것이다.
절차적 스타일(procedural style)로 작성된 코드가 있으면, 데이터 레코드를 객체로 바꾸고, 동작을 쪼개서 객체로 옮겨라.

동기
여러분은 종종 좀더 객체지향적으로 바뀌어야 하는 프로시저 같은 코드의 문제를 보게 될 것이다. 이런 상황이 대표적인 것은 데이터가 거의 없는 클래스에 있는 아주 긴 절차적 메소드와, 단지 접근자만 가지고 있는 바보 같은 데이터 객체를 들 수 있다. 만약 여러분이 순수한 절차적 프로그램을 (객체지향으로)변환하고 있다면, 이런 것조차도 없을 수 있다. 그러나 시작하기에는 안성맞춤이다.
우리는 데이터는 거의(또는 전혀) 없고 동작만 있는 객체를 가져서는 안 된다고 말하는 것이 아니다. 우리는 동작이 변할 필요가 있을 때는 종종 작은 스트레티지 객체를 사용한다. 그러나 이런 절차적 객체는 보통 작다. 그리고 융통성을 위해서 필요한 특별한 경우에만 사용된다.
도메인 로직을 포함하고 있는 GUI 클래스를 가지고 있다면, 도메인 로직을 분리하여 도메인 클래스를 만들어라.

동기
여러분은 사람들이 객체에 대해서 이야기할 때면 항상 모델-뷰-컨트롤러(MVC)에 대해서 이야기 하는 것을 들을 것이다. 이 아이디어는 스몰토크-80에서 그래픽 사용자 인터페이스와 도메인 객체와의 관계의 기초가 된다.
MVC에서 가장 중요한 개념은 사용자 인터페이스 코드(뷰, 요즘은 종종 프리젠테이션으로 불림)와 도메인 로직(모델)을 분리하는 것이다. 프리젠테이션 클래스는 사용자 인터페이스를 다루는 데 필요한 로직만 가지고 있다. 도메인 객체는 인터페이스와는 관계없는 비즈니스 로직만을 포함한다. 이는 프로그램의 복잡한 두 부분을 좀더 쉽게 수정할 수 있는 조각으로 분리한다. 또한 동일한 비즈니스 로직에 대해 여러 가지 프리젠테이션을 가질 수 있도록 한다.
너무 많은 작업을 하거나 또는 부분적으로라도 많은 조건문이 있는 클래스에 대해서는, 각각의 서브클래스가 특정 작업을 담당하도록 클래스의 상속 구조를 만들어라.

동기
점진적 디자인에서는, 한 클래스가 처음에는 하나의 아이디어를 구현하도록 했는데, 나중에 보니 실제로는 두세 개 또는 열 개의 아이디어를 구현하고 있다는 것을 깨닫게 되는 일이 비일비재하다. 처음에는 클래스를 간단하게 만든다. 며칠 후 또는 몇주 후 여러분은 플래그를 하나 추가해서 몇 가지 테스트를 하면 그 클래스를 다른 경우에도 사용할 수 있다는 것을 알게 되며 한달 후에 이런 기회를 또 갖게 된다. 일년 후에는 플래그와 조건문이 여기저기 퍼져 코드가 엉망이 된다.
여러 목적으로 쓰이는 클래스를 보게 되면, 각각의 기능을 분리하는 것을 생각하는데 이 여러 가지 기능을 분리시키는 데는 전략이 필요하다. 이 전략은 조건 로직이 객체의 존속 기간 동안 변하지 않고 남아 있을 때만 적용 가능하다. 만약 그렇지 않으면 아마도 각각의 경우를 분리하기 전에 Extract Class를 사용해야 할 것이다.
Extract Hierarchy가 하루 만에 끝날 수 있는 리팩토링이 아니라는 사실에 실망하지 않기를 바란다. 엉클어진 디자인의 매듭을 푸는 데는 몇 주 혹은 몇 달이 걸릴 수도 있다. 쉽고 명확한 단계에 대한 작업을 한 다음 휴힉을 취하라. 그리고 며칠 동안 눈에 보이는 생산적인 일을 하라. 뭔가 다른 것을 배웠을 때 돌아와서 다시 쉽고 명확한 몇 가지 작업을 하라.