-
Refactoring with JavaIS & Audit/Tool & Tips 2012. 4. 17. 15:20728x90
Java Refactoring
리팩터링(refactoring): 외부에서 보는 프로그램의 동작(수행하고 난 작업 결과)은 바꾸지 않고, 프로그램의 (이해, 수정, 확장을 용이하게) 내부 구조를 개선하는 방법
코드의 악취(Bad Smells in Code): 같은 코드가 여기 저기 중복(duplicated code) 되거나 메소드가 너무 길다(Long Method), 객체지향적이지 않다(Inappropriate Intimacy) 등 코드의 이해나 수정이 어렵다는 등 22가지 소개아래 2권의 책이 Refactoring 분야의 핵심 교과서로 알려져 있음
- Martin Fowler, “Refactoring”
- Joshua Kerievsky, “Refactoring To Patterns”
적절한 이름 붙이기1. 매직넘버를 심볼릭 정수로 치환하기 (Replace Magic Number with Symbolic Constant)
• 문제: 특별한 의미를 가진 정수나 문자열의 상수가 있고, 의미가 이해하기 힘들다.
• 해법: 이 상수를 적절한 이름을 가진 심볼릭 정수로 치환하자if( 100 < input.length() ) {...}▼
if ( MAX_INPUT_LENGTH < input.length() ) {...}2. 매소드명의 변경 (Rename Method)• 문제: 메소드의 이름을 이해하기 힘들다.
• 해법: 알기 쉬운 이름으로 바꾸자addActnLsnr ▶ addActionListener
3. 설명용 변수의 도입 (Introduce Explaining Variable)• 문제: 식의 의미를 이해하기 어렵다.
• 해법: 이식을 초기화 할 때, 알기 쉽게 이름 앞에 임시변수를 사용한다.
• 주의: 메소드의 추출이 자주 사용된다. (?)if(n.equals("anonymous")&&p.equals("") || n.equals("guest")&&p.equals("quest")){...}▼
boolean isAnonymous = n.equals("anonymous")&&p.equals("");
boolean isGuest = n.equals("guest")&&p.equals("quest");
if(isAnonymous || isGuest) {...}
4. 임시변수의 분리 (Split Temporary Variable)• 문제: 임시변수를 돌려쓰고 있다.
• 해법: 각각의 목적에 맞는 별도의 임시변수를 사용하자.int tmp = right - left;
...
tmp = bottom - top▼
int width = right - left;
...
int height = bottom - top;임시변수 없애기
5. 임시변수의 인라인화 (Inline Temp)• 문제: 임시변수가 간단한 식으로 초기화 되고 후에 대입되지 않는다.
• 해법: 다른 리팩터링에 방해가 된다면 그 임시변수를 모두 식으로 치환하자.
• 주의: 이 리팩터링은 임시변수를 질문(조회, 문의)으로 치환하기의 일부로 사용되는 경우가 있다.int width = r.getWidth();
if(width > 0){...}▼
if(r.getWidth()){...}
6. 임시변수를 질문(조회, 문의)으로 치환하기 Replace Temp with Query• 문제: 의미가 있는 식을 임시변수에 대입해서 이용하고 있기 때문에 해당 메소드에서만 이 식의 값을 활용할 수 있다.
• 해법: 이 식을 조회 메소드로 추출하자void method(){
boolean center = (_p.x == 0 && _p.y == 0);
if(center){
...
}else{
...
}
...
}▼
void method(){
if(isCenter()){
}
}
boolean isCenter(){
return _p.x == 0 && _p.y == 0;
}
7. 제어 플래그의 삭제 Remove Control Flag• 문제: 루프에 제어 플래그가 사용되고 있어 읽기 어렵다.
• 해법 제어 플래그 대신 break, continue, return 을 사용하자.flag = true;
while(flag){
if(A){
flag = false;
}else {
}
}▼
while(true){
if(A){
break;
} else {
}
}8. 조건분기를 단순하게 한다. Decompose Conditional• 문제: if-then-else가 너무 복잡하다.
• 해법: 조건부, then부, else부를 알기 쉬운 메소드 호출로 하자if(sx <= px && px < ex && sy <= py && py < ey){
score = cur * ratio * bonus;
}else{
score = cur * ratio;
}▼
if(isInside()){
score = getInsideScore();
}else {
score = getOutsideScore();
}
9. 중복된 조건기술의 단편을 종합 Consolidate Duplicate Conditional Fragments• 문제: 모든 조건분기에 같은 코드가 있다.
• 해법: 이 코드를 조건분기 밖으로 이동시키자.if(command=='R'){
read();
update();
}else{
write();
update();
}▼
if(command=='R'){
read();
}else{
write();
}
update();10. 조건기술을 가드(guard)절로 치환히기 Replace Nested Conditional with Guard Clauses• 문제: if-else가 중첩이 되어 있어 정상처리가 이해하기 힘들다.
• 해법: 가드 절을 사용해서 이상처리는 빨리 return, 또는 throw 하자.boolean result = true;
if(x<0){
result = false;
}else {
if(y < 0){
result = false;
}else{
if(width <= x){
result = false;
}else {
result = compute(x,y);
}
}
}
return result;▼
if(x<0) return false;
if(y<0) return false;
if(width <= x) return false;
if(height <= y) return false;return compute(x,y);
11. 조건기술의 통합 Consolidate Conditional Expression• 문제: 결과가 같게 되는 조건판단이 여기저기 나눠져 있다.
• 해법: 조건판단을 하나로 모아서 메소드를 추출하자.
• 주의: 메소드의 추출이 잘 되지 않으면 조건기술의 통합은 하지 않는 것이 좋다.if(x<0) return false;
if (y < 0) return false;
if (width <= x) return false;
if (height <= y) return false;▼
if(x<0 || y <0 || width <=x || height <= y) return false;
if(isOutside(x,y)) return false;12. 널 오브젝트의 도입 Introduce Null Object• 문제: null 체크가 너무 많다.
• 해법: null 을 표현하는 특별한 오브젝트를 도입해서 '아무것도 하지 않음' 이라는 처리를 하게 하자.if (name != null){
name.display();
}▼
Name name = NullName.singletone();
name.display(); // NumName.display() --> 아무것도 표시하지 않음13. 조건기술을 다형성으로 치환하기 Replace Conditional with Polymorphism
• 문제: 오브젝트 타입마다 다른 동작을 하는 조건분기가 있다.
• 해법: 동작을 다형적인 메소드로 서브클래스에 이동시킨다.
• 주의: 타입코드를 서브클래스로 치환하거나 타입코드를 State/Strategy 로 치환해 둔다.
조건판단일까 예외일까
14. 오류 코드를 예외로 치환하기 Replace Error Code with Exception• 문제: 메소드가 오류 코드를 반환하고 있어 정상 처리와 오류 처리가 이해하기 힘들다.
• 해법: 오류코드 대신 예외를 사용하자.int getArea(int width, int height) {
if (width < 0 || height < 0){
return -1;
}else {
return width * height;
}
}▼
int getArea (int width, int height){
if (width < 0 || height < 0){
throw new IllegalArgumentException();
}else {
return width * height;
}
}15. 예외를 조건판단으로 치환하기 Replace Exception with Test• 문제: 예외적인 상황이 아닌데 예외를 사용하고 있다.
• 해법: 예외 대신에 조건판단을 사용하자.try{
container.add(item);
}catch(NullPointerException e){
}▼
if(container != null) {
container.add(item);
}오브젝트를 만든다.
16. 생성자를 Factory Method 로 치환하기 Replace Constructor with Factory Method• 문제: 만들고 싶은 인스턴스가 속한 실제 클래스를 클라이언트에 숨기고 싶다.
• 해법: Factory Method로 생성자를 치환하자.public Shape(){
...
}▼
private Shape(int typecode){
...
}
public static Shape create(int typecode){
return new Shape(typecode);
}
클래스나 인터페이스를 추출
17. 클래스 추출 Extract Class• 문제: 하나의 클래스가 너무 많이 책임지고 있다.
• 해법: 모여있는 필드와 메소드를 찾아내서 새로운 클래스를 추출하자.18. 클래스의 인라인화 Inline Class• 문제: 크게 중요하지 않은 일을 하고 있는 클래스가 있다.
• 해법: 해당 클래스를 삭제하고 내부 코드를 다른 클래스로 옮기자.
19. 서브 클래스의 추출 Extract Subclass• 문제: 특정 인스턴스에ㅅ만 사용되고 있는 필드나 메소드가 있다.
• 해법: 서브클래스를 만들고 그 필드나 메소드를 옮기자
20. 계층의 평탄화 Collapse Hierachy• 문제: 슈퍼클래스와 서브클래스가 대부분 같다.
• 해법: 하나의 클래스에 모으자.21. 슈퍼클래스의 추출 Extract Subclass• 문제: 여러 클래스에 공통점이 많다.
• 해법: 슈퍼클래스를 만들고 공통화 가능한 필드나 메소드를 그 곳에 옮기자
• 주의: 슈퍼클래스와 서브클래스 간에 IS-A 관계인지 확인한다.22. 인터페이스의 추출 Extract Interface• 문제: 복수의 클래스에 공통의 인터페이스(API)가 있다.
• 해법: 인터페이스를 추출하자.<<Inteface>>
Playable
play
stop pause
수정할 수 없는 클래스에 메소드를 추가
23. 외부 메소드의 도입 Introduce Foreign Method• 문제: 이용하고 있는 서브클래스에 메소드를 추가하고 싶지만 서브클래스를 수정할 수 없다.
• 해법: 그 메소드를 클라이언트 클래스의 클래스 메소드로 추가하자
• 주의: 서브클래스의 인스턴스를 인수로 전달하면 좋다. 원래 클라이언트 클래스에 넣어야만하는 메소드가 아니므로 클라이언트 클래스의 인스턴스 필드를 직접 조작하지 않도록 클래스 메소드로 한다.class Client{
void method(){
Server server = new Server();
int result = (server를 사용한 복잡한 계산)
...
}
}▼
class Client{
void method(){
int result = computeResult(new Server())
...
}
static int computeResult(Server server){
return (server를 사용한 복잡한 계산);
}
}24. 지역적 확장의 도입 Introduce Local Extension• 문제: 이용하고 있는 서버클래스에 메소드를 많이 추가하고 싶지만 서버클래스를 수정할 수 없다.
• 해법: 서버클래스의 서브클래스 또 서버클래스의 랩퍼 클래스를 만들자.캡슐화
25. 필드의 캡슐화 Encapsulate Field• 문제: 필드가 public 으로 되어 있고, 외부에서 직접 액세스된다.
• 해법: 이 필드를 private로 해서 getter 메소드와 setter 메소드(액세서)를 준비하자.class Product {
public int _price;
...
}▼
class Product {
private int _price;
public int getPrice() { return _price; }
public void setPrice(int price) { _price = price }
...
}26. 자기캡슐화 필드 Self Encapsulate Field• 문제: 코드가 필드와 직접적으로 연관되어 있어 리팩토리하기 어렵다.
• 해법: 필드의 액세스는 자기 클래스 안에 있어도 모두 getter 메소드와 setter메소드(액세서)를 통해서 하자.
• 주의: 생성자 안에서는 필드를 직접 액세스해도 좋다.27. 컬랙션의 캡슐화 Encapsulate Collection• 문제: 변경가능한 컬렉션을 반환하는 getter 메소드가 있기 때문에 외부에서 컬렉션을 직접 조작한다.
• 해법: 변경가능한 컬렉션을 반환하지 않고 전용을 추가하거나 삭제 메소드를 별도로 제공하자.
• 주의: 컬렉션을 반환하고 싶다면 읽기 전용의 컬렉션을 반환하든지 Iterator 를 반환하게만 하자.
28. 다운캐스트의 캡슐화 Encapsulate Downcast• 문제: 메소드의 리턴 값을 호출하는 쪽에서 다운캐스트하지 않으면 안된다.
• 해법: 다운캐스트를 메소드 내부에서 하자.
• 주의: 제네릭을 사용하면 다운캐스트 그 자체를 없애는 경우도 있다.book = (Book) shelf.getBook("Bible");Map map = new HashMap();
Object getBook(String name){
return map.get(name);
}▼
book = shelf.getBook("Bible");
Map map = new HashMap();
Book getBook(String name){
return (Book) map.get(name);
}(제네릭 사용)
Map<String, Book>map = new HashMap<String, Book>();
Book getBook(String name){
return map.get(name);
}29. set 메소드의 삭제 Remove Setting Method• 문제: 초기화가 끝난 후 변하지 않는 필드임에도 불구하고 setter 메소드가 있다.
• 해법: setter 메소드를 삭제하고 필드는 final 로 하자.30. 조회와 갱신의 분리 Separate Query from Modifier• 문제: 값을 조회하는 메소드가 값을 갱신하는 처리를 하고 있다.
• 해법: '값을 조회하는 메소드' 와 '값을 변경하는 메소드' 두개로 분리하자.
• 주의: 멀티스레드 프로그래밍 등에서 '조회와 갱신' 모두를 함께 실행할 필요가 있는 경우에는 두 개로 나눈 메소드가 개별적으로 외부에서 호출되지 않도록 적절히 은폐하고 동기화한 메소드를 별도로 준비해야 한다.
필드의 위치 조절
31. 필드의 이동 Move Field• 문제: 자신의 클래스보다도 다른 클래스로부터 많이 사용되는 필드가 있다.
• 해법: 많이 사용되는 클래스쪽으로 필드를 이동하자.32. 필드의 끌어내림 Push Down Field• 문제: 필드가 슈퍼클래스에 의해 선언되어 있으나 그 필드는 단지 정해진 서브클래스와 관계가 있다.
• 해법: 관계가 있는 서브클래스에 그 필드를 이동하자.33. 필드의 끌어올림 Push Up Field• 문제: 복수의 서브클래스에 같은 필드가 있다.
• 해법: 슈퍼클래스에 그 필드를 이동하자.
메소드의 위치 조절
34. 메소드의 이동 Move Method• 문제: 자신의 클래스보다도 다른 클래스로부터 많이 사용되는 메소드가 있다.
• 해법: 많이 사용되는 클래스쪽으로 메소드를 이동하자.35. 메소드의 끌어내림 Push Down Method• 문제: 메소드가 슈퍼클래스에 의해 선언되어 있으나 그 메소드는 단지 정해진 서브클래스와 관계가 있다.
• 해법: 관계가 있는 서브클래스에 그 메소드를 이동하자.36. 메소드의 끌어올림 Push Up Method• 문제: 복수의 서브클래스에 같은 메소드가 있다.
• 해법: 슈퍼클래스에 그 메소드를 이동하자.37. 생성자의 끌어올림 Pull Up Constructor Body• 문제: 여러 개의 서브클래스에 비슷한 종류의 생성자가 있다.
• 해법: 생성자의 공통부분을 슈퍼클래스의 생성자로 이동하자.class Player{
String name;
Device device;
}
class MusicPlayer extends Player{
MusicPlayer(String name, Device device){
this.name = name;
this.device = device;
}
}
class VideoPlayer extends Player{
VideoPlayer(String name, Device device){
this.name = name;
this.device = device;
prepareVideo();
}
}▼
class Player{
String name;
Device device;
Player(String name, Device device){
this.name = name;
this.device = device;
}
}
class MusicPlayer extends Player{
MusicPlayer(String name, Device device){
super(name, device);
}
}
class VideoPlayer extends Player{
VideoPlayer(String name, Device device){
super(name, device);
prepareVideo();
}
}메소드 만들기
38. 메소드의 은폐 Hide Method• 문제: 클래스 안에서만 사용하는 메소드임에도 불구하고 public 으로 되어 있다.
• 해법: private으로 하자.39. 메소드의 추출 Extract Method• 문제: 하나의 메소드가 너무 길다.
• 해법: 그룹화 가능한 코드를 추출해서 이름을 붙여 메소드를 만들자.40. 메소드의 라인화 Inline Method• 문제: 일부러 이름을 붙이지 않아도 의도가 명백한 메소드가 있다.
• 해법: 호출하는 장소에 메소드의 본체를 전개하고 메소드를 지우자.…
if (isPositive(value))
…
}
private bolean isPositive(int x){
return x > 0;
}▼
if( value > 0 )41. Assertion 도입 Introduce Assertion• 문제: 주석으로 처리하고 싶은 전제조건이 있다.
• 해법: assertion을 사용하자.void method(int value){
//여기는 value 가 참이 될 것
...
}▼
void method(int value){
assert value > 0
...
}42. 알고리즘의 치환 Substitute Algorithm• 문제: 메소드 내에서 사용되는 알고리즘을 이해하기 어렵다.
• 해법: 알고리즘을 치환하자.public boolean isValidUser(String user){
for(int i = 0; i < _validUsers.length; i++){
if(_validUsers[i].equals(user)) return true;
}
return false;
}▼
public boolean isValidUser(String user){
return Arrays.binarySearch(_validUsers, user) >= 0;
}43. 메소드를 메소드오브젝트로 치환 Replace Method with Method Object• 문제: 지역변수의 사정상 메소드의 추출이 불가능하다.
• 해법: 지역변수를 필드에 대응시킨 클래스를 만들고, 그 클래스의 인스턴스(메소드오브젝트)를 사용해서 메소드를 구현하자.
• 주의: 이 리팩터링은 꽤 트릭키한 해법이다. 적용은 신중하게class Something{
void foo(int arg){
int x = 123;
int y = 456;
int z = 790;
...
}
}▼
class Something(
void foo(int arg){
new FooMethod(this, arg, 123, 456, 789).compute();
}
}
class FooMethod {
Something something;
int arg, x, y, z;
public FooMethod(Something something, int arg, int x, int y, int z){
this.something = something;
this.arg = arg;
this.x = x;
this.y = y;
this.z = z;
}
public void compute(){
...
}
}
메소드 인수 조절
44. 인수의 삭제 Remove Parameter• 문제: 메소드의 내부에서 사용하고 있지 않은 인수가 있다.
• 해법: 사용하지 않는 인수를 삭제하자.45. 인수의 추가 Add Parameter• 문제: 메소드에 필요한 정보가 부족하다.
• 해법: 인수를 증가시켜 필요한 정보를 전달하자.
• 주의: 단지 인수를 증가시켜서는 안된다. 너무 증가해야 될 듯 싶으면 인수 오브젝트의 도입을 검토할 것double getArea(){
return _width * _height * 1.0;
}▼
double getArea(double ratio){
return _width * _height * ratio;
}46. 파라미터의 도입 삭제 Remove Assignments to Parameters• 문제: 인수에 대입하고 있다.
• 해법: 인수에 대입을 없애고 임시변수를 사용하자.void process(int value){
if(value < 0) value = 0;
...
}▼
void process(int givenValue){
int value = givenValue;
if(value < 0) value = 0;
}47. 인수를 명시적 메소드군으로 치환 Replace Parameter with Explicit Methods• 문제: 인수의 특정 값에 대해 실행되는 코드가 분리되어 있다.
• 해법: 특정 값마다 전용의 메소드를 만들자.
• 주의: 메소드의 수를 증가시키는 대신에 인수의 수를 줄인다.void setBuffer(char name, char[] buffer){
if(name == 'R') _readBuffer = buffer;
else if (name =='W') _writeBuffer = buffer;
...
}▼
void setReadBuffer(char[] buffer){
_readBuffer = buffer;
}
void setWriteBuffer(char[] buffer){
_writeBuffer = buffer;
}48. 메소드의 파라미터화 Parameterize Method• 문제: 비슷한 복수의 메소드가 값에 의존해서 다른 동작을 한다.
• 해법: 의존하는 값을 인수로 갖는 1개의 메소드로 치환하자.
• 주의: 인수의 수를 증가시키는 대신 메소드의 수가 줄고 있다.
49. 오브젝트 자체를 넘김 Preserve Whole Object• 문제: 메소드에 넘기는 인수를 준비하기 위해서 오브젝트에서 일일이 값을 얻지 않으면 안된다.
• 해법: 오브젝트 그 자체를 인수로 넘기자.
• 주의: 오브젝트를 넘기는 것으로 의존관계가 성립된다.50. 메소드에 의한 인수의 치환 Replace Parameter with Method• 문제: 메소드 A를 호출할 때 언제나 메소드 X의 리턴 값을 인수로 넘기지 않으면 안된다.
• 해법: 인수를 없애고 메소드 A의 내부에서 메소드 X를 호출하자.setPosition(getOrigin(), PosA);
...
setPosition(getOrigin(), PosB);
...
void setPosition(Point origin, Point pos){
...
}▼
setPosition(PosA);
...
setPosition(PosB);
...
void setPosition(Point pos){
Point origin = getOrigin();
...
}51. 인수 오브젝트 도입 Introduce Parameter Object• 문제: 언제나 모아서 전달되는 인수들이 있다.
• 해법: 인수들을 하나의 오브젝트에 모으자.
타입코드를 치환
52. 타입코드를 클래스로 치환하기 Replace Type Code with Class• 문제: 타입코드가 int와 같은 기본형으로 되어 있어 형체크가 되지 않는다.
• 해법: 타입코드를 나타내는 새로운 클래스를 만들자. 또는 안전한 type의 enum을 사용하자.public static final int TYPECODE_A = 0;
public static final int TYPECODE_B = 1;
public static final int TYPECODE_C = 2;▼
public static final ItemType A = new ItemType(0);
public static final ItemType B = new ItemType(1);
public static final ItemType C = new ItemType(2);53. 타입코드를 서브클래스로 치환하기 Replace Type Code with Subclasses• 문제: 타입코드마다 다른 동작이 switch문으로 분기되어 있다.
• 해법: 타입코드를 서브클래스로 치환, 다형적인 메소드를 만들자.
54. 타입코드를 State/Strategy로 치환하기 Replace Type Code with State/Stragegy• 문제: 타입코드마다 다른 동작이 switch문으로 분기되어 있고 동작이 변화한다.
• 해법: 타입코드를 나타내는 새로운 클래스를 만들고, 'State패턴' 또는 'Strategy 패턴'을 사용하자.
• 주의: 동작이 동적으로 변화할 때에는 타입코드를 서브클래스로 치환하기는 사용할 수 없다.
55. 서브클래스를 필드로 치환하기 Replace Subclass with Fields• 문제: 서브클래스들이 정수값을 반환하는 메소드밖에 가지고 있지 않다.
• 해법: 슈퍼클래스의 필드에 값을 보존시키고 서브클래스들을 삭제하자.
기존 시스템에 객체지향 도입
56. 배열을 오브젝트로 치환하기 Replace Array with Object• 문제: 다른 의미를 가진 요소가 하나의 배열에 할당되어 있다.
• 해법: 요소를 필드에 치환하고 하나의 오브젝트로 하자.57. 레코드를 데이터 클래스로 치환하기 Replace Record with Data Class• 문제: 이전 시스템의 레코드 구조를 다룰 필요가 있지만 객체지향적인 인터페이스(API)가 존재하지 않는다.
• 해법: 이 레코드 구조에 맞는 오브젝트를 만들자.58. 절차적인 설계로부터 오브젝트로 교환 Convert Procedural Design to Objects• 문제: 데이터가 많이 있고 그 데이터를 조작하는 함수가 별도로 준비되어 있다.
• 해법: 데이터마다에 클래스를 만든다. 데이터 값은 클래스 필드로 나타낸다. 데이터에 관련이 깊은 함수는 대응하는 클래스 메소드로 한다.
• 주의: 이것은 다른 리팩터링을 조합해서 하는 거대한 리팩터링이다.
값 오브젝트 또는 참조 오브젝트
59. 값에서 참조로 변경 Change Value to Reference• 문제: 하나의 값 오브젝트를 변경하면 같은 값을 가진 다른 오브젝트로 변경해야 한다.
• 해법: 같은 값을 가진 오브젝트를 하나의 참조 오브젝트로 변경하자.
• 주의: ‘값 오브젝트’란 일정이나 금액과 같이 오브젝트의 동일성보다 보존하고 있는 값 그 자체가 중요한 오브젝트이다.60. 참조에서 값으로 변경 Change Reference to Value• 문제: 단순하게 이뮤터블(불변)한 오브젝트를 참조 오브젝트로서 관리하는 것이 귀찮다.
• 해법: 참조 오브젝트를 값 오브젝트로 변경하자.
모델과 뷰
61. 관찰된 데이터의 복제 Duplicate Observed Data• 문제: 모델과 뷰가 하나의 클래스 안에 혼재되어 있다.
• 해법: 양자를 분리하고 'Observer패턴'이나 이벤트 리스너를 이용해서 동기를 얻자.
62. 프리젠테이션과 도메인의 분리 Separate Domain from Presentation• 문제: 프리젠테이션(GUI)을 다루는 클래스가 도메인 처리(비지니스 로직)도 가지고 있다.
• 해법: 비지니스 로직을 다루는 클래스를 별도로 만든다. 동기가 필요할 때는 관찰된 데이터의 복제를 이용하자.
• 주의: 이것은 다른 리팩터링을 조합해서 하는 거대한 리팩터링이다.
상속과 위임의 전환
63. 상속을 위임으로 치환 Replace inheritance with Delegation• 문제: IS-A 관계를 만족하지 않음에도 불구하고 상속이 사용되고 있다.
• 해법: 위임을 사용해서 상속을 치환하자.64. 위임을 상속으로 치환 Replace Delegation with inheritance• 문제: 위임처의 메소드 전부에 대해서 위임 메소드를 준비하고 있다.
• 해법: 위임을 하지 말고 위임처의 클래스를 슈퍼클래스로서 상속관계를 만들자.
• 주의: '상속은 최후의 무기'이므로 이 리팩터링을 적용하는 것에는 주의가 필요하다. IS-A 관계를 만족하고 있는 지를 조사한다.위임
65. 위임의 은폐 Hide Delegate• 문제: 클라이언트 클래스가 서버 클래스에 있는 위임 클래스까지 직접 이용하고 있다.
• 해법: 서버 클래스에 위임 메소드를 추가하고 클라이언트 클래스로부터 위임 클래스를 숨기자.
66. 중개자의 삭제 Remove Middle Man• 문제: 위임 메소드만의 클래스가 있다.
• 해법: 중개인을 지우자.
상속
67. Template Method의 형성 Form Template Method• 문제: 여러 개의 서브클래스에 ‘순서는 같고 내용이 다른 메소드’가 있다.
• 해법: 같은 순서의 부분을 Template Method로 해서 슈퍼클래스에 쓰자.Class App {
…
}
Class AppXXX extends App {
void excute() {
beforeXXX();
try {
excuteXXX();
} finally {
afterXXX();
}
}
}
Class AppYYY extends App {
void excute() {
beforeYYY();
try {
excuteYYY();
} finally {
afterYYY();
}
}
}▼
Class App {
void excute() {
doBefore();
try {
doExcute();
} finally {
doAfter();
}
}
protected abstract void doBefore();
protected abstract void doExecute();
protected abstract void doAfter();
…
}
Class AppXXX extends App {
@override void doBefore() { … }
@override void doExecute() { … }
@override void doAfter() { … }
}
Class AppYYY extends App {
@override void doBefore() { … }
@override void doExecute() { … }
@override void doAfter() { … }
}68. 상속의 분할 Tease Apart Inheritance• 문제: 하나의 클래스 계층이 여러 종류의 일을 하고 있다.
• 해법: 상속을 분할하고 필요한 일은 위임을 사용해서 이용하자.69. 계층의 추출 Extract Hierachy• 문제: 많은 경우 분리를 포함하고 있는 거대한 클래스가 있다.
• 해법: 서브 클래스가 각각의 경우를 담당하는 클래스 계층을 만들자.
• 주의: 이것은 다른 리팩터링을 조합시켜서 하는 거대한 리팩터링이다.
위 내용은 Fowler의 목록을 아래 책에서 요약하여 부록으로 실린 것을,
구글링으로 검색한 페이지(http://sinihong.springnote.com/pages/768916.xhtml)에서
그 내용을 가져와 누락된 몇 가지와 일부 그림(여긴 그림이 안올라가네... -.,-;;)과 코드를 보충하여 작성함.- 박건태역, 히로시 유키, "Java 언어로 배우는 리팩토링 입문", 한빛미디어, 2007
2012.04.17.
'IS & Audit > Tool & Tips' 카테고리의 다른 글
Seven Principles for Excellent Presentation (0) 2012.05.01 JTree Drag & Drop (0) 2011.05.17 CISSP 인증시험 가이드 요약 (0) 2011.01.03