리팩토링

2019.09.30

Chapter 6. 메서드 정리

메서드 추출

어떤 코드를 그룹으로 묶어도 되겠다고 판단될 땐 그 코드를 빼내어 목적을 잘 나타내는 직관적 이름의 메서드로 만들자

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

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

메서드 추출

메서드가 너무 길거나 코드에 주석을 달아야만 의도를 이해할 수 있을 때 사용

직관적인 이름의 간결한 메서드가 좋은 이유

  1. 메서드가 적절히 잘게 쪼개져 있으면 다른 메서드에서 쉽게 사용
  2. 상위 계층의 메서드에서 주석 같은 더 많은 정보를 읽어들일 수 있다
  3. 재정의 수월

메서드 추출

예제: 지역변수 사용 안함

void printOwing() {
  Enumeration e = orders.elements();
  double outstanding = 0.0;
  
  // 배너 출력
  System.out.println("*****************");
  System.out.println("*****고객 외상*****");
  System.out.println("*****************");
  
  // 외상액 계산
  while(e.hasMoreElements()) {
    Order each = (Order) e.nextElement();
    outstanding += each.getAmount();
  }
  
  // 세부 내역 출력
  System.out.println("고객명: " + name);
  System.out.println("외상액: " + outstanding);
}

메서드 추출

예제: 지역변수 사용 안함

void printOwing() {
  Enumeration e = orders.elements();
  double outstanding = 0.0;
  
  printBanner();
  
  // 외상액 계산
  while(e.hasMoreElements()) {
    Order each = (Order) e.nextElement();
    outstanding += each.getAmount();
  }
  
  // 세부 내역 출력
  System.out.println("고객명: " + name);
  System.out.println("외상액: " + outstanding);
}

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

메서드 추출

예제: 지역변수 사용

void printOwing() {
  Enumeration e = orders.elements();
  double outstanding = 0.0;
  
  printBanner();
  
  // 외상액 계산
  while(e.hasMoreElements()) {
    Order each = (Order) e.nextElement();
    outstanding += each.getAmount();
  }
  
  // 세부 내역 출력
  System.out.println("고객명: " + name);
  System.out.println("외상액: " + outstanding);
}

메서드 추출

예제: 지역변수 사용

void printOwing() {
  Enumeration e = orders.elements();
  double outstanding = 0.0;
  
  printBanner();
  
  // 외상액 계산
  while(e.hasMoreElements()) {
    Order each = (Order) e.nextElement();
    outstanding += each.getAmount();
  }
  
  printDetails(outstanding);
}

void printDetails(double outstanding) {
  System.out.println("고객명: " + name);
  System.out.println("외상액: " + outstanding);
}

메서드 추출

예제: 지역변수를 다시 대입

void printOwing() {
  Enumeration e = orders.elements();
  double outstanding = 0.0;
  
  printBanner();
  
  // 외상액 계산
  while(e.hasMoreElements()) {
    Order each = (Order) e.nextElement();
    outstanding += each.getAmount();
  }
  
  printDetails(outstanding);
}

메서드 추출

예제: 지역변수를 다시 대입

void printOwing() {
  printBanner();
  double outstanding = getOutstanding();
  printDetails(outstanding);
}

double getOutstanding() {
  Enumeration e = orders.elements();
  double result = 0.0;
  
  while(e.hasMoreElements()) {
    Order each = (Order) e.nextElement();
    result += each.getAmount();
  }
  return result;
}
  

메서드 추출

예제: 지역변수를 다시 대입

void printOwing(double previousAmount) {
  Enumeration e = orders.elements();
  double outstanding = previousAmount * 1.2;
  printBanner();

  while(e.hasMoreElements()) {
    Order each = (Order) e.nextElement();
    outstanding += each.getAmount();
  }
  
  printDetails(outstanding);
}
  

지역 변수에 연산이 있는 경우

메서드 추출

예제: 지역변수를 다시 대입

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

double getOutstanding(double initialValue) {
  double result = initialValue;
  Enumeration e = orders.elements();
  while(e.hasMoreElements()) {
    Order each = (Order) e.nextElement();
    result += each.getAmount();
  }
  return result;
}

지역 변수에 연산이 있는 경우

메서드 추출

예제: 지역변수를 다시 대입

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

지역 변수에 연산이 있는 경우

메서드 추출

변수를 두 개 이상 반환해야하는 경우

  1. 각기 다른 값을 하나씩 반환하는 여러 개의 메서드를 만든다
  2. 출력 매개변수 사용(비추)

임시변수가 너무 많은 경우 추출이 어려움

  1. 임시변수를 메서드 호출로 전환 기법을 실시해서 임시변수의 수를 줄인다
  2. 어떤 방법을 사용해도 메서드 추출이 어렵다면 메서드를 메서드 객체로 전환 기법 실시

메서드 내용 직접 삽입

메서드 기능이 너무 단순해서 메서드명만 봐도 너무 뻔할 땐 그 메서드 기능을 호출하는 메서드에 넣어버리고 그 메서드는 삭제하자

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

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

메서드 내용 직접 삽입

과도한 인다이렉션과 동시에 모든 메서드가 다른 메서드에 단순히 위임을 하고 있어서 코드가 지나치게 복잡할 땐 주로 메서드 내용 직접 삽입을 실시한다

임시변수 내용 직접 삽입

간단한 수식을 대입받는 임시변수로 인해 다른 리팩토링 기법 적용이 힘들 땐 그 임시변수를 참조하는 부분을 전부 수식으로 치환하자

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

임시변수 내용 직접 삽입

  • 임시변수를 메서드 호출로 전환 기법을 실시하는 도중에 병용하게 되는 경우가 태반
  • 임시변수가 메서드 추출등 다른 리팩토링에 방해가 된다면 적용
double basePrice = anOrder.basePrice();
return (basePrice > 1000);
return (anOrder.basePrice() > 1000);

임시변수를 메서드 호출로 전환

수식의 결과를 저장하는 임시변수가 있을 땐 그 수식을 빼내어 메서드로 만든 후, 임시변수 참조 부분을 전부 수식으로 교체하자.

새로 만든 메서드는 다른 메서드에서도 호출 가능하다.

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;

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

임시변수를 메서드 호출로 전환

  • 클래스 안 모든 메서드가 그 정보에 접근 가능, 코드가 깔끔해짐
  • 메서드 추출을 적용하기 전에 반드시 적용

임시변수를 메서드 호출로 전환

예제

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

임시변수를 메서드 호출로 전환

예제

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

임시변수를 메서드 호출로 전환

예제

double getPrice() {
  final int basePrice = basePrice();
  final double discountFactor;
  if(basePrice > 1000) discountFactor = 0.95;
  else discountFactor = 0.98;
  return basePrice * discountFactor;
}

private int basePrice() {
  quantity * itemPrice;
}

임시변수를 메서드 호출로 전환

예제

double getPrice() {
  final int basePrice = basePrice();
  final double discountFactor;
  if(basePrice() > 1000) discountFactor = 0.95;
  else discountFactor = 0.98;
  return basePrice() * discountFactor;
}

임시변수를 메서드 호출로 전환

예제

double getPrice() {
  final double discountFactor = discountFactor();
  return basePrice() * discountFactor;
}

private double discountFactor() {
  if(basePrice() > 1000) return 0.95;
  else return 0.98;
}

임시변수를 메서드 호출로 전환

예제

double getPrice() {
  return basePrice() * discountFactor();
}

직관적 임시변수 사용

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

if((platform.toUpperCase().indexOf("MAX") > -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) {
  // 기능 코드
}

직관적 임시변수 사용

  • 조건문에서 각 조건 절을 가져와서 그 조건의 의미를 설명할 때 사용
  • 메서드 추출을 사용하기 어려울 때 사용

직관적 임시변수 사용

예제

double price() {
  // 결제액(price) = 총 구매액(base price) -
  // 대량 구매 할인(quantity discount) + 배송비(shipping)
  return quantity * itemPrice -
        Math.max(0, quantity - 500) * itemPrice * 0.05 +
        Math.min(quantity * itemPrice * 0.1, 100.0);
}

직관적 임시변수 사용

예제

double price() {
  // 결제액(price) = 총 구매액(base price) -
  // 대량 구매 할인(quantity discount) + 배송비(shipping)
  final double basePrice = quantity * itemPrice;
  return basePrice -
        Math.max(0, quantity - 500) * itemPrice * 0.05 +
        Math.min(basePrice * 0.1, 100.0);
}

직관적 임시변수 사용

예제

double price() {
  // 결제액(price) = 총 구매액(base price) -
  // 대량 구매 할인(quantity discount) + 배송비(shipping)
  final double basePrice = quantity * itemPrice;
  final double quantityDiscount = Math.max(0, quantity - 500) * itemPrice * 0.05;
  return basePrice - quantityDiscount + Math.min(quantity * itemPrice * 0.1, 100.0);
}

직관적 임시변수 사용

예제

double price() {
  // 결제액(price) = 총 구매액(base price) -
  // 대량 구매 할인(quantity discount) + 배송비(shipping)
  final double basePrice = quantity * itemPrice;
  final double quantityDiscount = Math.max(0, quantity - 500) * itemPrice * 0.05;
  final double shipping = Math.min(basePrice * 0.1, 100.0);
  return basePrice - quantityDiscount + shipping;
}

직관적 임시변수 사용

예제: 메서드 추출

double price() {
  // 결제액(price) = 총 구매액(base price) -
  // 대량 구매 할인(quantity discount) + 배송비(shipping)
  return quantity * itemPrice -
        Math.max(0, quantity - 500) * itemPrice * 0.05 +
        Math.min(quantity * itemPrice * 0.1, 100.0);
}

직관적 임시변수 사용

예제: 메서드 추출

double price() {
  return basePrice() - quantityDiscount() + shipping();       
}

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

private double quantityDiscount() {
  return Math.max(0, quantity - 500) * itemPrice * 0.05;
}

private double shipping() {
  Math.min(basePrice() * 0.1, 100.0);
}

임시변수 분리

루프 변수나 값 누적용 임시변수가 아닌 임시변수에 여러 번 값이 대입될 땐 각 대입마다 다른 임시변수를 사용하자

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);

임시변수 분리

임시변수 하나를 두 가지 용도로 사용하면 코드를 분석하는 사람에게 혼동을 줄 수 있다

임시변수 분리

예제

double getDistanceTravelled(int time) {
  double result;
  double acc = primaryForce / mass;
  int primaryTime = Math.min(time, delay);
  result = 0.5 * acc * primaryTime * primaryTime;
  int secondaryTime = time - delay;
  if(secondaryTime > 0) {
    double primaryVel = acc * delay;
    acc = (primaryForce + secondaryForce) / mass;
    result += primaryVel * secondaryTime + 0.5 *
       acc * secondaryTime * secondaryTime;
  }
  return result;
}

임시변수 분리

예제

double getDistanceTravelled(int time) {
  double result;
  final double primaryAcc = primaryForce / mass;
  int primaryTime = Math.min(time, delay);
  result = 0.5 * primaryAcc * primaryTime * primaryTime;
  int secondaryTime = time - delay;
  if(secondaryTime > 0) {
    double primaryVel = primaryAcc * delay;
    final double secondaryAcc = (primaryForce + secondaryForce) / mass;
    result += primaryVel * secondaryTime + 0.5 *
       secondaryAcc * secondaryTime * secondaryTime;
  }
  return result;
}

매개변수로의 값 대입 제거

매개변수로 값을 대입하는 코드가 있을 땐 매개변수 대신 임시변수를 사용하게 수정하자

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;
}

매개변수로의 값 대입 제거

  • 자바는 '값을 통한 전달'만 사용하므로 매개변수에 값을 대입해서는 안된다

매개변수로의 값 대입 제거

예제

int discount(int inputVal, int quantity, int yearToDate) {
  if(inputVal > 50) inputVal -= 2;
  if(quantity > 100) inputVal -= 1;
  if(yearToDate > 1000) inputVal -= 4;
  return inputVal;
}

매개변수로의 값 대입 제거

예제

int discount(final int inputVal, final int quantity, final int yearToDate) {
  int result = inputVal;
  if(inputVal > 50) result -= 2;
  if(quantity > 100) result -= 1;
  if(yearToDate > 1000) result -= 4;
  return result;
}

메서드를 메서드 객체로 전환

지역변수 때문에 메서드 추출을 적용할 수 없는 긴 메서드가 있을 땐 그 메서드 자체를 객체로 전환해서 모든 지역변수를 객체의 필드로 만들자. 그런 다음 그 메서드를 개첵 안의 여러 메서드로 쪼개면 된다.

  • 임시변수를 메서드 호출로 전환이 어려울 때 메서드 객체로 수정한다.

메서드를 메서드 객체로 전환

예제

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 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;
  }
}

메서드를 메서드 객체로 전환

예제

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

메서드를 메서드 객체로 전환

예제

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

void importantThing() {
 if(yearToDate - importantValue1) > 100)
    importantValue2 -= 20;
}

알고리즘 전환

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

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 candidates = Arrays.asList(new String[] {"Don", "John", "Kent"});
  for(int i = 0; i < people.length; i++)
    if(candidates.contains(people[i]))
      return people[i];
  return "";
}

알고리즘 전환

  • 어떤 기능을 수행하는데 더 간단하다면 더 간단한 방법으로 교체해야 한다
  • 메서드를 최대한 잘게 쪼개야 수정 작업이 편해진다

리팩토링 - 메서드 정리

By Sungbin, Song

리팩토링 - 메서드 정리

  • 118