본문 바로가기

프로그램언어/Refactoring

[Refactoring] Extract Method

그룹으로 함께 묶을 수 있는 코드 조각이 있으면 코드의 목적이 잘 드러나도록 메소드의 이름을 지어 별도의 메소드로 뽑아낸다.

 void printOwing(double amount) {
      printBanner();

      //print details
      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);
  }

동기

  • 지나치게 긴 메소드
  • 목적을 이해하기 위해서 주석이 필요한코드
  • 짧고 이해하기 쉬운 메소드는
    • 메소드가 잘게 쪼개져 있을때 다른 메소드에서 사용될 확률이 높아진다
    • 상위 레벨(high-level)의 메소드를 볼때 일련의 주석을 읽는것 같은 느낌이 든다
    • 오버라이드 하는것도 쉽다
  • 이름을 지을때 주의하라
  • 한 메소드의 길이는 중요하지 않다
    • 메소드의 이름과 메소드 몸체의 의미적 차이
    • 뽑아내는 것이 코드를 더욱 명확하게 하면, 새로 만든 메소드의 이름이 원래 코드의 길어보다 길어져도 뽑아낸다

절차

  • 메소드를 새로 만들고, 의도를 잘 나타낼수 있도록 이름을 정한다
    • 어떻게 하는지를 나타내는 방식으로 이름을 정하지 말고, 무엇을 하는지를 나타내게 이름을 정한다.
    • 더 이해하지 쉬운 이름을 지을수 없다면 뽑아내지 않는것이 낫다.
  • 원래 메소드에서 뽑아내고자 하는 부분의 코드를 복사하여 새 메소드로 옮긴다.
  • 원래 메소드에서 사용되고 있는 지역 변수가 뽑아낸 코드에 있는지 확인한다.
    • 이런 지역변수는 새로운 메소드의 지역변수나 파라미터가 된다.
  • 뽑아낸 코드내에서만 사용되는 임시 변수가 있는지 본다
    • 있다면 새로 만든 메소드의 임시변수로 선언한다
  • 뽑아낸 코드내에서 지역 변수의 값이 수정되는지 본다
    • 하나의 지역변수만 수정된다면, 뽑아낸 코드를 질의로 보고, 수정된 결과를 관련된 변수에 대입할수 있는지 본다.
    • 이렇게 하는것이 이상하거나, 값이 수정되는 지역변수가 두개 이상 있다면 쉽게 메소드로 추출할 수 없는 경우이다.
      • Split Temporary Variable을 사용한뒤에 재시도
      • 임시 변수는 Replace Temp with Query로 제거
  • 뽑아낸 코드에서 읽기만 하는 변수는 새 메소드의 파라미터로 넘긴다.
  • 지역 변수와 관련된 사항을 다룬 후에는 컴파일 한다.
  • 원래 메소드에서 뽑아낸 코드 부분은 새로 만든 메소드를 호출하도록 바꾼다.
    • 새로 만든 메소드로 옮긴 임시 변수가 있는 경우, 그 임시 변수가 원래 메소드의 밖에서 선언 되었는지를 확인한다
    • 새로 만든 메소드에서는 선언을 해줄 필요가 없다
  • 컴파일과 테스트를 한다.

지역 변수가 없는 경우

void printOwing() {

       Enumeration e = _orders.elements();
       double outstanding = 0.0;

      // print banner
       System.out.println ("**************************");
       System.out.println ("***** Customer Owes ******");
       System.out.println ("**************************");

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

       //print details
       System.out.println ("name:" + _name);
       System.out.println ("amount" + outstanding);
   }

배너를 인쇄하는 부분을 뽑아내는 것은 쉽다. ==> Cut and Paste 후 원래 코드는 새 메소드를 호출한다.

void printOwing() {

       Enumeration e = _orders.elements();
       double outstanding = 0.0;

       printBanner();

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

       //print details
       System.out.println ("name:" + _name);
       System.out.println ("amount" + outstanding);
   }

   void printBanner() {
       // print banner
       System.out.println ("**************************");
       System.out.println ("***** Customer Owes ******");
       System.out.println ("**************************");
   }


지역 변수가 포함되어 있는 경우

  • 가장 쉬운 것은 변수가 읽히기만 하고 값이 변하지 않는 경우 ==> 그냥 파라미터만 넘긴다.
void printOwing() {

       Enumeration e = _orders.elements();
       double outstanding = 0.0;

       printBanner();

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

       //print details
       System.out.println ("name:" + _name);
       System.out.println ("amount" + outstanding);
   }
void printOwing() {

       Enumeration e = _orders.elements();
       double outstanding = 0.0;

       printBanner();

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

       printDetails(outstanding);
   }

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

  • 실제로 지역변수에 다른 값을 대입하는 경우에만 부가적인 작업이 필요

지역 변수에 다른 값을 여러번 대입하는 경우

  • 지역 변수에 다른 값을 대입하는 코드가 있는 경우에는 문제가 복잡
  • 우선 임시변수에 대해서만 생각
    • 파라미터에 다른 값을 대입하는 코드가 있다면 --> Remove Assignments to Parameters 를 적용한다.
  • 임시 코드에 값을 대입하는 경우
    • 임시 변수가 뽑아낸 코드안에서만 사용될때
      • 뽑아낸 코드로 임시 변수를 옮긴다.
    • 임시 변수가 뽑아낸 코드 외부에서도 사용되는 경우
      • 뽑아낸 코드 이후의 부분에서 사용되지 않는다면, 앞의 경우처럼 바꿀수 있다
      • 이후에서도 사용된다면, 뽑아낸 코드에서 임시 변수의 바뀐 값을 리턴하도록 수정

     void printOwing() {

        Enumeration e = _orders.elements();
        double outstanding = 0.0;

        printBanner();

        // calculate outstanding
        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 outstanding = 0.0;
        while (e.hasMoreElements()) {
            Order each = (Order) e.nextElement();
            outstanding += each.getAmount();
        }
        return outstanding;
    }

변수 e는 뽑아낸 코드 안에서만 사용되기 때문에 새 메소드로 옮길 수 있다. 변수 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();

        // calculate outstanding
        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;
    }
컴파일과 테스트를 한 후에 outstanding 변수를 초기화 하는 방법을 바꾼다

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

  • 만약 두개 이상의 값이 리턴되어야한다면
    • 가장 좋은 선택은 뽑아낼 코드를 다르게 선택하라. (하나의 값을 리턴하기를 좋아하는 저자, 잘 정리해서 각각의 다른 값에 대한 메소드를 따로 만든다.)
    • 사용중인 언어가 출력 파라미터를 지원한다면 이것을 이용한다.
  • 임시 변수가 너무 많아 코드를 뽑기 힘든 경우
    • Replace Temp with Query ==> 임시 변수를 줄인다
    • Replace Method with Method Object