관리 메뉴

너와 나의 스토리

[리팩터링] Move Field 필드 옮기기 본문

개발/Refactoring

[리팩터링] Move Field 필드 옮기기

노는게제일좋아! 2021. 11. 20. 13:32
반응형

배경

  • 데이터 구조 설계에 대해 경험이 많은 사람도 초기 설계에서는 실수가 빈번하다.
    • 프로젝트를 진행할수록 문제 도메인과 데이터 구조에 대해 더 많은 것을 배우게 된다.
  • 현재 데이터 구조가 적절치 않음을 깨달으면 곧바로 수정해야 한다.
  • 필드를 옮겨야 할 상황
    • 함수에 어떤 레코드를 넘길 때마다 또 다른 레코드의 필드도 함께 넘기고 있다면 데이터 위치를 옮겨야 할 것
    • 한 레코드를 변경하려 할 때 다른 레코드의 필드까지 변경해야 하는 상황 → 필드 위치가 잘못되었다는 신호
  • 클래스를 사용하면 '필드 옮기기' 리팩터링을 수행하기 더 쉬워진다.
    • 클래스의 데이터들은 접근자 메서드들 뒤에 캡슐화되어 있으므로 클래스에 곁들여진 메서드들은 데이터를 이리저리 옮기는 작업을 쉽게 해준다. 
    • 데이터의 위치를 옮기더라도 접근자만 그에 맞게 수정하면 클라이언트 코드들은 아무 수정 없이도 동작할 것이다. 
  • 예제
    • Before
      • class Customer {
          get plan() {return this._plan;}
          get discountRate() {return this._discountRate;}
        }
    • After
      • class Customer {
          get plan() {return this._plan;}
          get discountRate() {return this.plan._discountRate;}
        }

 

 

절차

  1. 소스 필드가 캡슐화되어 있지 않다면 캡슐화한다.
  2. 테스트한다.
  3. 타깃 객체에 필드와 접근자 메서드들을 생성한다.
  4. 정적 검사를 수행한다.
  5. 소스 객체에서 타깃 객체를 참조할 수 있는지 확인한다.
  6. 접근자들이 타깃 필드를 사용하도록 수정한다
  7. 테스트한다.
  8. 소스 필드를 제거한다.
  9. 테스트한다.

 

예시

  • 고객 클래스(Customer)와 계약 클래스(CustomerContract)에서 시작
    • class Customer {
          constructor(name, discoutRate) {
              this._name = name;
              this._discountRate = discountRate;
              this._contract = new CustomerContract(dateToday());
          }
          get discountRate() { return this._discountRate; }
          becomePreferred() {
              this._discountRate += 0.03;
              // do something
          }
          applyDiscount(amount) {
              return amount.subtract(amount.multiply(this._discountRate))
          }
      }
      
      class CustomerContract {
          constructor(startDate) {
              this._startDate = startDate;
          }
      }
  • discountRate 필드를 Customer에서 CustomerContract로 옮기고 싶다고 해보자.
  • Step 1: 필드 캡슐화
    • class Customer {
          constructor(name, discoutRate) {
              this._name = name;
              this._setDiscountRate(discountRate);   // <--
              this._contract = new CustomerContract(dateToday());
          }
      
          get discountRate() { return this._discountRate; }
          _setDiscountRate(aNumber) { this._discountRate = aNumber; } // <-- 
          becomePreferred() {
              this._setDiscountRate(this.discountRate + 0.03);  // <--
              // do something
          }
          applyDiscount(amount) {
              return amount.subtract(amount.multiply(this._discountRate))
          }
      }
  • Step 3: 타깃 객체(CustomerContract) 클래스에 discountRate 필드와 접근자들을 추가한다.
    • class CustomerContract {
          constructor(startDate, discountRate) {
              this._startDate = startDate;
              this._discountRate = discountRate;
          }
      
          get discountRate() { return this._discountRate }
          set discountRate(arg) { this._discountRate = arg; }
      }


  • Step 6: Customer의 접근자들이 타깃 필드(discountRate)를 사용하도록 수정한다.
    • // Customer 클래스
      constructor(name, discoutRate) {
          this._name = name;
          this._contract = new CustomerContract(dateToday());
          this._setDiscountRate(discountRate);
      }
      
      get discountRate() { return this._contract.discountRate; }
      _setDiscountRate(aNumber) { this._contract.discountRate = aNumber; }
    • Contract 객체를 먼저 생성해야 에러가 발생하지 않는다.

 

출처:

- [리팩터링 2판]

 

 

 

 

 

 

 

 

 

반응형
Comments