티스토리 뷰

IS & Audit/Tool & Tips

Refactoring with Java

Auditories 2012. 4. 17. 15:20

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.

 

 

최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함