본문 바로가기
Refactoring

리팩토링 - 메서드 정리

by jayden jayden-lee 2019. 5. 16.

메서드 추출 (Extract Method)

어떤 코드를 그룹으로 묶어도 된다고 판단이 들면, 해당 코드들을 빼내어 목적을 잘 나타내는 메서드로 만들자

수정전 코드

public void printOwing(double previousAmount) {
    System.out.println("****************");
  System.out.println("*****고객 외상****")
     System.out.println("****************");

  double outstanding = previousAmount * 1.2;

  for (Order o : orders) {
    outstanding += o.getAmount();
  }

  System.out.println("name: " + name);
  System.out.println("amount: " + amount);
}

수정후 코드

public void printOwing(double previousAmount) {
  printBanner();
  double outstanding = getOutstanding(previousAmount * 1.2);
    printDetails(amout);  
}

private void printBanner() {
     System.out.println("****************");
  System.out.println("*****고객 외상****")
     System.out.println("****************");
}

private double getOutstanding(double initialValue) {
    double result = initialValue;
  for (Order o : orders) {
    result += o.getAmount();
  }
  return result;
}

private void printDetails(double amount) {
  System.out.println("name: " + name);
  System.out.println("amount: " + amount);
}

설명

메서드 추출은 가장 많이 사용되는 기법 중 하나이다. 특정 메서드가 장황한 경우에 그룹으로 묶고, 해당 그룹을 묶은 코드를 빼내어서 새로운 메서드로 작성한다. 앞에서 살펴본 코드도 printBanner, getOutstanding, printDetails 메서드를 새로 생성했다. 이렇게 메서드를 적절하게 잘게 쪼개면 여러 이점이 있다. 그리고 메서드를 쪼개고 나서 목적을 잘 표현하는 이름으로 정의해야 한다.

메서드 내용 직접 삽입 (Inline Method)

메서드 기능이 단순하며 너무 뻔한 경우에는 해당 메서드를 호출하는 곳에 메서드 내용을 직접 넣고 그 메서드는 삭제하자

수정전 코드

public int getRating() {
  return (moreThanFiveLateDeliveries()) ? 2 : 1;
}

public boolean moreThanFiveLateDeliveries() {
  return numberOfLateDeliveries > 5;
}

수정후 코드

public int getRating() {
  return (numberOfLateDeliveries > 5) ? 2 : 1;
}

설명

리팩토링 핵심은 직관적인 메서드명과 메서드를 간결하게 만드는 것이다. 메서드명에 모든 기능이 반영될 정도로 메서드 기능이 단순한 경우에는 해당 메서드를 없애야 한다.

임시변수 내용 직접 삽입 (Inline Temp)

간단한 수식을 대입받는 임시변수가 있어서 다른 리팩토링을 적용하기 힘들면 임시변수를 참조하는 부분을 수식으로 치환하자

수정전 코드

double basePrice = anOrder.basePrice();
return (basePrice > 1000);

수정후 코드

return (anOrder.basePrice() > 1000);

설명

임시변수 내용 직접 삽입은 임시변수를 메서드 호출로 전환하는 기법을 적용하는 과정에서 같이 사용하게 되는 경우가 대부분이다. 임시변수 내용 직접 사용은 임시변수를 메서드 호출로 전환을 실시해야 하는 동기라고 할 수 있다.

임시변수를 메서드 호출로 전환 (Replace Temp with Query)

수식의 결과를 저장하는 임시변수가 있는 경우에 수식을 빼내어 새로운 메서드로 생성하고 내용을 넣는다. 다른 메서드에서도 수식을 정의한 메서드를 호출이 가능해진다.

수정전 코드

double basePrice = quantity * itemPrice;
if (basePrice > 1000) {
  return basePrice * 0.95;
} else {
  return basePrice * 0.98;
}

수정후 코드

if (basePrice() > 1000) {
  return basePrice() * 0.95;
} else {
  return basePrice() * 0.98;
}

public double basePrice() {
  return quantity * itemPrice;
}

설명

임시변수는 일시적이며 적은 범위에서만 사용이 가능하다는 단점이 잇다. 임시변수를 메서드로 정의하면 클래스 내에 있는 어느 메서드에서도 해당 메서드를 접근할 수 있다. 메서드 호출로 전환하는 기법은 메서드 추출을 적용하기 전에 선행되어야 한다. 그렇지 않으면 메서드 추출이 힘들어지기 때문에 최대한 많은 변수들을 메서드 호출로 고쳐야 한다.

직관적 임시변수 사용 (Introduce Explaining Variable)

수식이 복잡할 때는 수식의 결과나 일부분을 용도에 부합하는 직관적 이름의 임시변수에 대입하자

수정전 코드

if ((platform.toUpperCase().indexOf("MAC") > -1) &&
       (browser.toUpperCase().indexOf("IE") > -1) &&
       wasInitialized() && resize > 0) {
  // 기능 코드
}

수정후 코드

final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResized = resize > 0;

if (isMacOs && isIEBrowser && wasInitialized && wasResized) {
  // 기능 코드
}

설명

수식이 복잡한 경우에 조건문에 그대로 사용하게 되면 이해하기 힘들 수 있다. 그러한 경우에는 임시변수를 적절히 사용해서 수식을 처리하기 쉽게 쪼갤 수 있다. 다만 임시변수를 무분별하게 많이 사용하면 안된다. 임시변수를 많이 사용하면 코드를 보는 사람이 이해하기 힘들수 있다.

임시변수 분리 (Split Temporary Variable)

루프 변수 또는 값 누적용 임시변수가 아닌 임시변수에 여러 값이 대입되는 경우에는 각 대입마다 다른 임시변수를 사용하자

수정전 코드

double temp = 2 * (height + width);
System.out.println(temp);
temp = height * width;
System.out.println(temp);

수정후 코드

final double perimeter = 2 * (height + width);
System.out.println(perimeter);
final double area = height * width;
System.out.println(area);

설명

임시변수의 용도는 다양하다. 루프 변수에서 사용되는 임시변수가 있고, 값을 누적시킬 때 사용하는 임시변수가 있다. 그밖의 많은 임시변수는 긴 코드의 연산 결과를 쉽게 참조할 수 있게 저장하는 용도로 사용된다. 특히 임시변수가 여러번 값이 대입된다는 것은 여러 용도로 사용된다는 것이다. 이러한 경우에는 각 용도에 맞게 임시변수를 만들어서 사용하는 것이 좋다. 하나의 임시변수가 여러 역할을 하면 혼동을 줄 수 있기 때문이다.

매개변수로의 값 대입 제거 (Remove Assignments to Parameters)

매개변수에 값을 대입하는 코드가 있을 때는 매개변수 대신 임시변수를 사용하자

수정전 코드

int discount (int inputVal, int quantity, int yearToDate) {
  if (inputVal > 50) inputVal -= 2;
  // 기능 코드
}

수정후 코드

int discount (int inputVal, int quantity, int yearToDate) {
  int result = inputVal;
  if (inputVal > 50) result -=2;
  // 기능 코드
}

설명

전달 받은 매개변수에 다른 값을 대입하게 되면 코드의 명료성이 떨어지고 혼동을 줄 여지가 있다. 그래서 매개변수에 직접 값을 대입하는 것보다 임시변수를 사용하는 것이 일관성을 지킬 수 있고 코드를 이해하기가 더 쉬워진다.

메서드를 메서드 객체로 전환 (Replace Method with Method Object)

메서드 추출을 적용할 수 없는 긴 메서드가 있는 경우에는 메서드 자체를 객체로 전환해서 모든 지역변수를 객체의 필드로 만들자

수정전 코드

class Account {
  int gamma (int inputVal, int quantity, int yearToDate) {
    int importantValue1 = (inputVal * quantity) + delta();
    int importantValue2 = (inputVal * yearToDate) + 100;
    if ((yearToDate -importantValue1) > 100)
      importantValue2 -= 20;
    int importantValue3 = importantValue2 * 7;
    // 기타 작업
    return importantValue3 - 2 * importantValue1;
  }
}

수정후 코드

class Account {
  int gamma (int inputVal, int quantity, int yearToDate) {
    return new Gamma(this, inputVal, quantity, yearToDate).compute();
  }
}

class Gamma {
  private final Account account;
  private int inputVal;
  private int quantity;
  private int yearToDate;
  private int importantValue1;
  private int importantValue2;
  private int importantValue3;

  Gamma(Account source, int inputValArg, int quantityArg, int yearToDateArg) {
    account = source;
    inputVal = inputValArg;
    quantity = quantityArg;
    yearToDate = yearToDateArg;
  }

  int compute() {
    importantValue1 = (inputVal * quantity) + account.delta();
    importantValue2 = (inputVal * yearToDate) + 100;
    if ((yearToDate -importantValue1) > 100)
      importantValue2 -= 20;
    int importantValue3 = importantValue2 * 7;
    // 기타 작업
    return importantValue3 - 2 * importantValue1;
  }
}

설명

수정전 코드와 수정후 코드를 비교해서 보면, 기존 Account 클래스 메서드에 있는 내용을 Gamma 클래스로 옮겼다. 지역변수는 필드로 선언하고 생성자를 통해 매개변수를 받는다. 그리고 새로 생성한 Gamma 클래스에 compute 메서드를 새로 생성하고 로직을 작성한다. 그리고 Account 클래스에 gamma 메서드는 Gamma 객체를 생성하고 compute 메서드를 호출 하도록 변경한다.

알고리즘 전환 (Substitute Algorithm)

알고리즘을 더 분명한 것으로 교체해야 할 때, 해당 메서드의 내용을 새 알고리즘으로 바꾸자

수정전 코드

String foundPerson(String[] people) {
  for (int i = 0; i < people.length; i++) {
    if (people[i].equals("Don")) {
      return "Don";
    }
    if (people[i].equals("John")) {
      return "John";
    }
    if (people[i].equals("Kent")) {
      return "Kent";
    }
  }
  return "";
}

수정후 코드

String foundPerson(String[] people) {
  List<String> candidates = Arrays.asList("Don", "John", "Kent");
  for (int i = 0; i < people.length; i++) {
    if (candidates.contains(people[i]) {
      return people[i];
    }
  }
  return "";
}

설명

특정 목적을 달성하기 위한 방법은 여러가지가 있다. 알고리즘도 그 중 하나이다. 다른 방법에 비해 쉬운 방법이 있다면, 복잡한 방법을 더 간단한 방법으로 교체해야 한다.

 

'Refactoring' 카테고리의 다른 글

리팩토링 - 객체 간의 기능 이동  (0) 2019.06.06
리팩토링 - 메서드 정리  (0) 2019.05.16
리팩토링 - 리팩토링 개론  (0) 2019.05.04
리팩토링 - 코드의 구린내  (0) 2019.04.28

댓글0