Chapter11. API 리팩터링

김신영·2023년 7월 23일
0

Refactoring

목록 보기
11/12
post-thumbnail

질의 함수와 변경 함수 분리하기 (Separate Query from Modifier)

Separate Query from Modifier

image

참고

bliki: CommandQuerySeparation

절차

  1. 대상 함수를 복제하고 질의 목적에 충실한 이름을 짓는다.
  2. 새 질의 함수에서 부수효과(side effect)를 모두 제거한다.
  3. 정적 검사를 수행한다.
  4. 원래 함수를 호출하는 곳을 모두 찾아낸다. 호출하는 곳에서 반환 값을 사용한다면 질의 함수를 호출하도록 바꾸고, 원래 함수를 호출하는 코드를 바로 아래 줄에 새로 추가한다.
    • 수정할때 마다 테스트한다.
  5. 원래 함수에서 질의 관련 코드를 제거한다.
  6. 테스트한다.

예시

  • Before
    function getTotalOutstandingAndSendBill() {
      const result = customer.invoices.reduce((total, each) => each.amount + total, 0);
      sendBill();
      return result;
    }
  • After
    function totalOutstanding() {
      return customer.invoices.reduce((total, each) => each.amount + total, 0);  
    }
    function sendBill() {
      emailGateway.send(formatBill(customer));
    }

함수 매개변수화하기 (Parameterize Function)

Parameterize Function

image

절차

  1. 비슷한 함수 중 하나를 선택한다.
  2. 함수 선언 바꾸기로 리터럴들을 매개변수로 추가한다.
  3. 이 함수를 호출하는 곳 모두에 적절한 리터럴 값을 추가한다.
  4. 테스트한다.
  5. 매개변수로 받은 값을 사용하도록 함수 본문을 수정한다.
    • 수정할 때마다 테스트한다.
  6. 비슷한 다른 함수를 호출하는 코드를 찾아 매개변수화된 함수를 호출하도록 하나씩 수정한다.
    • 수정할 때마다 테스트한다.

예시

  • Before
    function tenPercentRaise(aPerson) {
      aPerson.salary = aPerson.salary.multiply(1.1);
    }
    function fivePercentRaise(aPerson) {
      aPerson.salary = aPerson.salary.multiply(1.05);
    }
  • After
    function raise(aPerson, factor) {
      aPerson.salary = aPerson.salary.multiply(1 + factor);
    }

플래그 인수 제거하기 (Remove Flag Argument)

Remove Flag Argument

image

Flag Argument

  • 호출되는 함수가 실행할 로직을 호출하는 쪽에서 선택하기 위해 전달하는 인수

절차

  1. 매개변수로 주어질 수 있는 값 각각에 대응하는 명시적 함수들을 생성한다.
    • 조건문 분해하기(Decompose Conditional) 혹은 Wrapping Function 만들기
  2. 원래 함수를 호출하는 코드들을 모두 찾아서 각 리터럴 값에 대응되는 명시적 함수를 호출하도록 수정한다.

예시

  • Before
    function setDimension(name, value) {
      if (name === "height") {
        this._height = value;
        return;
      }
      if (name === "width") {
        this._width = value;
        return;
      }
    }
  • After
    function setHeight(value) {this._height = value;}
    function setWidth (value) {this._width = value;}

객체 통째로 넘기기 (Preserve Whole Object)

Preserve Whole Object

image

절차

  1. 매개변수들을 원하는 형태로 받는 빈 함수를 만든다.
  2. 새 함수의 본문에서는 원래 함수를 호출하도록 하며, 새 매개변수와 원래 함수의 매개변수를 매핑한다.
  3. 정적 검사를 수행한다.
  4. 모든 호출자가 새 함수를 사용하게 수정한다.
    • 하나씩 수정하고, 테스트한다.
  5. 호출자를 모두 수정했다면 원래 함수를 인라인한다.
  6. 새 함수의 이름을 적절히 수정하고, 모든 호출자에 반영한다.

예시

  • Before
    const low = aRoom.daysTempRange.low;
    const high = aRoom.daysTempRange.high;
    if (aPlan.withinRange(low, high))
  • After
    if (aPlan.withinRange(aRoom.daysTempRange))

매개변수를 질의 함수로 바꾸기 (Replace Parameter with Query)

Replace Parameter with Query

image

반대 리팩터링: 질의 함수를 매개변수로 바꾸기

절차

  1. 필요하다면 대상 매개변수의 값을 계산하는 코드를 별도 함수로 추출해놓는다.
  2. 함수 본문에서 대상 매개변수로의 참조를 모두 찾아서 그 매개변수의 값을 만들어주는 표현식을 참조하도록 바꾼다.
    • 하나 수정할 때마다 테스트한다.
  3. 함수 선언 바꾸기(Change Function Declaration)로 대상 매개변수를 없앤다.

예시

  • Before
    availableVacation(anEmployee, anEmployee.grade);
    
    function availableVacation(anEmployee, grade) {
      // calculate vacation...
  • After
    availableVacation(anEmployee)
    
    function availableVacation(anEmployee) {
      const grade = anEmployee.grade;
      // calculate vacation...

질의 함수를 매개변수로 바꾸기 (Replace Query with Parameter)

Replace Query with Parameter

image

반대 리팩터링: 매개변수를 질의 함수로 바꾸기

절차

  1. 변수 추출하기(Extract Variable)로 질의 코드를 함수 본문의 나머지 코드와 분리한다.
  2. 함수 본문 중 해당 질의를 호출하지 않는 코드들을 별도 함수로 추출한다. (Extract Method)
  3. 방금 만든 변수를 인라인하여 제거한다. (Inline Variable)
  4. 원래 함수도 인라인한다. (Inline Function)
  5. 새 함수의 이름을 원래 함수의 이름으로 고쳐준다.

예시

  • Before
    targetTemperature(aPlan)
    
    function targetTemperature(aPlan) {
      currentTemperature = thermostat.currentTemperature;
      // rest of function...
  • After
    targetTemperature(aPlan, thermostat.currentTemperature)
    
    function targetTemperature(aPlan, currentTemperature) {
      // rest of function...

Setter 제거하기 (Remove Setting Method)

Remove Setting Method

image

절차

  1. 설정해야 할 값을 생성자에게서 받지 않는다면 그 값을 받을 매개변수를 생성자에 추가한다. (Change Function Declaration)
  2. 생성자 밖에서 Setter를 호출하는 곳을 찾아 제거하고, 대신 새로운 생성자를 사용하도록 한다.
    • 하나 수정할 떄마다 테스트한다.
  3. Setter메서드를 인라인한다.
    • 가능하다면 해당 필드를 불변으로 만든다.
  4. 테스트한다.

예시

  • Before
    class Person {
      get name() {...}
      set name(aString) {...}
  • After
    class Person {
      get name() {...}

생성자를 팩토리 함수로 바꾸기 (Replace Constructor with Factory Function)

Replace Constructor with Factory Function

image

절차

  1. Factory 함수를 만든다.
    • Factory 함수의 본문에서는 원래의 생성자를 호출한다.
  2. 생성자를 호출하던 코드를 Factory 함수 호출로 바꾼다.
    • 하나씩 수정할 때마다 테스트한다.
  3. 생성자의 가시 범위가 최소가 되도록 제한한다.

예시

  • Before
    leadEngineer = new Employee(document.leadEngineer, 'E');
  • After
    leadEngineer = createEngineer(document.leadEngineer);

함수를 명령으로 바꾸기 (Replace Function with Command)

Replace Function with Command

image

반대 리팩터링: 명령을 함수로 바꾸기

절차

  1. 대상 함수의 기능을 옮길 빈 클래스를 만든다.
    • 클래스 이름은 함수 이름에 기초해 짓는다.
  2. 방금 생성한 빈 클래스로 함수를 옮긴다.
    • execute , call 과 같이 명령의 실행 함수에 흔히 쓰이는 이름을 택하자.
  3. 함수의 인수들 각각은 명령의 필드로 만들어 생성자를 통해 설정할지 고민해본다.

예시

  • Before
    function score(candidate, medicalExam, scoringGuide) {
      let result = 0;
      let healthLevel = 0;
      // long body code
    }
  • After
    class Scorer {
      constructor(candidate, medicalExam, scoringGuide) {
        this._candidate = candidate;
        this._medicalExam = medicalExam;
        this._scoringGuide = scoringGuide;
      }
    
      execute() {
        this._result = 0;
        this._healthLevel = 0;
        // long body code
      }
    }

명령을 함수로 바꾸기

Replace Command with Function

image

  • 로직이 크게 복잡하지 않다면, 명령 객체보다는 평범한 함수로 바꿔주는게 낫다.

반대 리팩터링: 함수를 명령으로 바꾸기

절차

  1. 명령을 생성하는 코드와 명령의 실행 메서드를 호출하는 코드를 함께 함수로 추출한다. (Extract Method)
    • 이렇게 추출한 함수가 명령을 대체할 함수다.
  2. 명령의 실행 함수가 호출하는 보조 메서드들 각각을 인라인한다. (Inline Function)
  3. 함수 선언 바꾸기를 적용하여 생성자의 매개변수 모두를 명령의 실행 메서드로 옮긴다.
  4. 명령의 실행 메서드에서 참조하는 필드들 대신 대응하는 매개변수를 사용하게끔 바꾼다.
    • 수정할 때마다 테스트한다.
  5. 생성자 호출과 명령의 실행 메서드 호출을 대체 함수 안으로 인라인한다. (Inline Function)
  6. 테스트한다.
  7. 죽은 코드 제거하기로 명령 클래스를 없앤다. (Remove Dead Code)

예시

  • Before
    class ChargeCalculator {
      constructor (customer, usage){
        this._customer = customer;
        this._usage = usage;
      }
      execute() {
        return this._customer.rate * this._usage;
      }
    }
  • After
    function charge(customer, usage) {
      return customer.rate * usage;
    }

수정된 값 반환하기 (Return Modified Value)

Return Modified Value

image

절차

  1. 함수가 수정된 값을 반환하게 하여, 호출자가 그 값을 자신의 변수에 저장하게 한다.
  2. 테스트한다 .
  3. 피호출 함수 안에 반환할 값을 가리키는 새로운 변수를 선언한다.
  4. 테스트한다.
  5. 계산이 선언과 동시에 이뤄지도록 통합한다.
    • 선언 시점에 계산 로직을 바로 실행해 대입한다.
  6. 테스트한다.
  7. 피호출 함수의 변수 이름을 새 역할에 어울리도록 바꿔준다.
    • result
  8. 테스트한다.

예시

  • Before
    let totalAscent = 0;
    calculateAscent();
    
    function calculateAscent() {
      for (let i = 1; i < points.length; i++) {
        const verticalChange = points[i].elevation - points[i-1].elevation;
        totalAscent += (verticalChange > 0) ? verticalChange : 0;
      }
    }
  • After
    const totalAscent = calculateAscent();
    
    function calculateAscent() {
      let result = 0;
      for (let i = 1; i < points.length; i++) {
        const verticalChange = points[i].elevation - points[i-1].elevation;
        result += (verticalChange > 0) ? verticalChange : 0;
      }
      return result;
    }

오류 코드를 예외로 바꾸기 (Replace Error Code with Exception)

Replace Error Code with Exception

image

  • 예외는 프로그래밍 언어에서 제공하는 독립적인 오류 처리 메커니즘이다.
    • 오류가 발견되면 예외를 던진다.
    • 적절한 예외 핸들러를 찾을 때까지 콜스택을 타고 위로 전파된다.
  • 예외를 사용하면 오류 코드를 일일이 검사하거나 오류를 식별해 콜스택 위로 던지는 일을 신경 쓰지 않아도 된다.

절차

  1. 콜스택 상위에 해당 예외를 처리할 예외 핸들러를 작성한다.
  2. 테스트한다.
  3. 해당 오류 코드를 대체할 예외와 그 밖의 예외를 구분할 식별 방법을 찾는다.
  4. 정적 검사를 수행한다.
  5. catch절을 수정하여 직접 처리할 수 있는 예외는 적절히 대처하고, 그렇지 않은 예외는 다시 던진다.
  6. 오류 코드를 반환하는 곳 모두에서 예외를 던지도록 수정한다.
    • 하나씩 수정할 때마다 테스트한다.
  7. 모두 수정했다면 그 오류 코드를 콜스택 위로 전달하는 코드를 모두 제거한다.
    • 하나씩 수정할 때마다 테스트한다.

예시

  • Before
    if (data)
      return new ShippingRules(data);
    else
      return -23;
  • After
    if (data)
      return new ShippingRules(data);
    else
      throw new OrderProcessingError(-23);

예외를 사전확인으로 바꾸기 (Replace Exception with Precheck)

Replace Exception with Precheck

image

  • 함수 수행시 문제가 될 수 있는 조건을 함수 호출 전에 검사할 수 있다면
    • 예외를 던지는 대신 호출하는 곳에서 조건을 검사하도록 해야한다.

절차

  1. 예외를 유발하는 상황을 검사할 수 있는 조건문을 추가한다.
    • catch 블록의 코드를 조건문의 조건절 중 하나로 옮긴다.
    • 남은 try 블록의 코드를 다른 조건절로 옮긴다.
  2. catch 블록에 Assertion(trap)을 추가하고 테스트한다.
  3. try문과 catch 블록을 제거한다.
  4. 테스트한다.

예시

  • Before
    double getValueForPeriod (int periodNumber) {
      try {
        return values[periodNumber];
      } catch (ArrayIndexOutOfBoundsException e) {
        return 0;
      }
    }
  • After
    double getValueForPeriod (int periodNumber) {
      return (periodNumber >= values.length) ? 0 : values[periodNumber];
    }
profile
Hello velog!

0개의 댓글