Refactoring이 동작하는 매커니즘을 아는 것 만큼 중요한 것은, 언제 Refactoring을 적용할까 하는 것이다. 여기서 딜레마가 온다. 어떻게 인스턴스 변수를 삭제하거나 클래스 계증구조를 만드는가를 표현하는 것은 쉽다. 그건 사소한 문제들이다. 하지만 언제 이러한 것들을 해야 할 것인지 표현하는 것은 쉽지 않다. 나는 (여기서의 I는 Martin Fowler) 프로그래밍 미학이라는 모호한 표현으로 얼버무리지 않고 좀 더 확실한 것을 원했다. 내가 이 문제로 Kent Beck 을 방문했을 때 그는 "언제" 를 설명하기 위해서 "Smell" 이라는 표현을 사용했다. 우리는 많은 코드들을 보았고, 그것들을 보면서 Refactoring이 적용가능한 어떤 구조를 발견했다. 여기에서 우리는 Refactoring이 적용가능한 아주 정확한 척도를 제공하려고는 하지 않을 것이다. 경험상, 어떠한 측정도구들도 숙련된 인간의 직관의 경쟁상대가 될 수는 없었다. 우리가 하려는 것은 Refactoring에 의해 해결될 수 있는 문제들이 있는 몇몇 부분을 지적하려는 것이다. 어떠한 Refactoring을 해야 할 지 확신할 수 없을때 이 부분을 읽어라. 정확하게 똑같은 Smell을 발견할 순 없더라도 Refactoring에 대한 올바른 방향을 가리켜 줄 지침이 될 것이다. == Duplicated Code == 중복코드 코드 * 같은 클래스내에 2개이상의 메소드들에 중복코드 존재시 - ExtractMethod * 두개 이상 서브클래스 내 중복코드시 - ExtractMethod 한뒤 PullUpField * 코드는 비슷하지만 똑같지는 않은 경우 - 비슷한 부분에 대해서 ExtractMethod * 다른 알고리즘 내에서 같은 일을 하는 메소드 - SubstituteAlgorithm * 두개이상의 연관없는 클래스 내의 중복코드 - ExtractClass ExtractMethod, ExtractClass, PullUpMethod, FormTemplateMethod == Long Method == 코드의 길이가 긴 메소드 * 대부분의 경우에 대해서 - ExtractMethod * ExtractMethod 하는중 parameter를 많이 넘겨야 하거나, 임시변수를 많이 사용하게 되는 경우 - ReplaceTempWithQuery, IntroduceParameterObject, PreserveWholeObject, ReplaceMethodWithMethodObject * 조건 & 반복문 - DecomposeConditional ExtractMethod, ReplaceTempWithQuery, ReplaceMethodWithMethodObject, DecomposeConditional == Large Class == 너무 하는 일이 많은 큰 클래스 * 수많은 변수들 - ExtractClass, ExtractSubclass * 꼭 항상 사용되지는 않는 인스턴스 변수들 - ExtractClass, ExtractSubclass * GUI 클래스에서 데이터부가 중복될때 - DuplicateObservedData * AWT -> Swing Component로 바꿀때 - DuplicateObservedData ExtractClass, ExtractSubclass, ExtraceInterface, ReplaceDataValueWithObject == Long Parameter List == Parameter 인자가 많은 함수. 이해하기 힘들고, 사용하기 어렵다. * When you can get the data in one parameter by making a request of an object you already know about - ReplaceParameterWithMethod * to take a bunch of data gleaned from an object and replace it with the object itself - PreserveWholeObject * logic을 가지지 않는 여러개의 data item을 가지는 경우 - IntroduceParameterObject ReplaceParameterWithMethod, IntroduceParameterObject, PreserveWholeObject == Divergent Change == 하나의 클래스가 각각 다른 이유들로 인해서 다른 방식으로 자주 변경될 때. 다른 클래스들이 바뀔 때마다 매번 수정되는 부분. ex) * 새 데이터베이스가 생길때마다 3개의 메소드를 바꿔야 한다. * 바뀌어야 하는 경우들을 명확하게 한뒤 ExtractClass 하여 하나의 클래스에 모은다. ExtractClass == Shotgun Surgery == 하나의 변화에 의해 다른 여러 클래스들의 변경 필요시 * 바뀌는 부분들에 대해 MoveMethod, MoveField 하여 하나의 클래스에 넣는다. (없으면 새로 하나 클래스 생성할것) * 모든 행위들의 묶음을 가지기 위해 - InlineClass * Divergent Change - one class that suffers many kinds of changes * shotgun surgery - one change that alters many classes MoveMethod, MoveField, InlineClass == Feature Envy == 어떤 메서드가 자신이 속해 있는 클래스의 데이터가 아닌 다른 클래스의 데이터들을 필요로 할 때. * StrategyPattern, VisitorPattern, DelegationPattern MoveMethod, MoveField, ExtractMethod == Data Clumps == 클래스의 변수선언 필드나 함수 프로토타입 같은 곳에 있는, 항상 같이 몰려다니는 데이터 아이템들 * ExtractClass, IntroduceParameterObject or PreserveWholeObject * 처음에 Data Clump들을 ExtractClass했을때 Field 들 묶음으로 보일 수도 있지만 너무 걱정할 필요는 없다. ExtractClass, IntroduceParameterObject, PreserveWholeObject == Primitive Obsession == 기본 데이터형 : Class 에 대해 ReplaceValueWithObject, ExtraceClass, IntroduceParameterObject, ReplaceArrayWithObject, ReplaceTypeCodeWithClass, ReplaceTypeCodeWithSubclasses, ["ReplaceTypeCodeWithState/Strategy"] == Switch Statements == 길이가 긴 Switch-Case 문. * 대부분의 경우 - polymorphism으로 해결 가능 * switch-case 부분을 ExtractMethod 한 뒤, polymorphism이 필요한 class에 MoveMethod 한다. 그리고 나서 ReplaceTypeCodeWithSubclasses 나 ["ReplaceTypeCodeWithState/Strategy"] 를 할 것을 결정한다. 상속구조를 정의할 수 있을때에는 ReplaceConditionalWithPolyMorphism 한다. * polymorphism을 이용하기에는 너무 작아 오히려 cost가 더 드는 경우 - ReplaceParameterWithExplicitmethods * 조건 case 에 null 이 있는 경우 - IntroduceNullObject ReplaceConditionalWithPolymorphism, ReplaceTypeCodeWithSubclasses, ["ReplaceTypeCodeWithState/Strategy"], ReplaceParameterWithExplicitMethods, IntroduceNullObject == Parallel Inheritance Hierarchies == 병렬 상속 구조. shotgun surgery의 특별 케이스가 된다. -_-a MoveMethod, MoveField == Lazy Class == 거의 쓸모없는 클래스. * 별로 사용하지 않는 subclass들 - CollapseHierarchy * 거의 쓸모없는 컴포넌트들 - InlineClass InlineClass, CollapseHierarchy == Speculative Generality == 일어날 가능성이 거의 없는 일까지 다 대비한 필요없는 코드. 모든 기능들과 절차들은 제대로 이용되었을때 쓸모있다. * 추상클래스들이 별로 하는 일이 없을때 - CollapseHierarchy * 불필요한 Delegation - InlineClass * 사용하지 않는 parameter들을 가진 메소드 - RemoveParameter * 추상적인 두리뭉실한 메소드 이름 -_-; - RenameMethod 로 지상으로 내려오도록 하라는.. --; CollapseHierarchy, InlineClass, RemoveParameter, RenameMethod == Temporary Field == 특별한 상황에서만 세팅되는 변수를 가진 객체. ExtractClass, IntroduceNullObject == Message Chains == 객체를 부르고 그 객체가 다른 객체를 부르고, 그 다른 객체는 또 또 다른 객체를 부르고.. -- HideDelegate == Middle Man == delegation 의 남용. RemoveMiddleMan, InlineMethod, ReplaceDelegationWithInheritance == Inappropriate Intimacy == 사적인 부분(?)에 대해서 지나치게 관심을 기울이는 위험한(?) 클래스들. MoveMethod, MoveField, ChangeBidirectionalAssociationsToUnidirectional, ReplaceInheritanceWithDelegation, HideDelegation == Alternative Classes with Different Interfaces == 같은 일을 하지만 다른 signature를 가진 메서드들. RenameMethod, MoveMethod == Incomplete Library Class == 라이브러리에서 제공하는 메서드들이 불충분할 때. IntroduceForeignMethod, IntroduceLocalExtension == Data Class == 필드, getter, setter만을 가진 어린아이와 같은 클래스. MoveMethod, EncapsulateField, EncapsulateCollection == Refused Bequest == 서브클래스가 부모의 behavior는 재사용하나 부모의 인터페이스를 지원하기를 원하지는 않을 때. ReplaceInheritanceWithDelegation == Comments == 나쁜 냄새를 가리기 위한 방향제로 사용되는 주석. --; ---- 주석을 쓸 필요가 있다는 느낌이 들때 일단 코드를 리펙토링 하면 주석을 많이 쓸 필요가 없음을 알게 된다. ---- 주석을 이용할 좋은 시기는 도대체 무엇을 해야 할 지 모르겠을 때 이다. 무엇을 할 것인지 주석으로 먼저 서술함으로서 주석은 프로그래머가 무엇을 해야 할 지 확신할 수 없을 때 좋은 지침서가 된다. 주석은 ' ''왜'' 당신이 이것을 하는가' 를 말하기 위한 좋은 장소이다. ExtractMethod, IntroduceAssertion ---- 전에 JuNe 형이 최한기의 신기통을 언급하면서 Metaphor 로서 'Smell' 이 잘 맞아떨어짐을 이야기하던게 생각. '냄새란 일단 그 자체로 악취를 풍길 뿐만 아니라, 밖으로 점차적으로 퍼지고, 사람에게 배어들 수 있으며, 사람에게 배어들고 나면 그 사람이 냄새에 대해 인식을 하지 못한다.'. Smell 에 민감한 사람들은 작은 Refactoring 도 잘 해낼 수 있다. -- ["1002"] ---- ["Refactoring"]