Change Function Declaration

최완식·2023년 7월 24일
0

Refactoring

목록 보기
15/15
post-thumbnail

Change Function Declaration, 함수 선언 바꾸기에 대해 알아보자.

요약

코드

function circum(radius) { ... }
function circumference(radius) { ... }

배경

  • 함수는 프로그램을 작은 부분으로 나누는 주된 수단이다.
  • 함수 선언은 각 부분이 서로 맞물리는 방식을 표현한다.
  • 그러므로 실질적으로 소프트웨어 시스템의 구성 요소를 조립하는 연결부 역할을 한다.
  • 건축과 마찬가지로 소프트웨어도 이러한 연결부에 상당히 의존한다.
  • 연결부를 잘 만들어두면 새로운 부분을 추가하기가 쉬워진다.
  • 이런 연결부에서 가장 중요한 요소는 함수의 이름이다.
  • 함수 이름만 좋으면 뭘 하는 건지 단번에 알 수 있다.
  • 하지만 잘못된 이름을 봐도 그냥 두고 싶은 유혹에 빠진다. 그냥 이름이니까.
  • 하지만 그냥 둔다면 혼란을 두는 결과를 낳는다. 프로그램의 영혼을 위해서라도 이러한 속삭임에 넘어가지말자.

이름이 안나올 때 함수의 동작을 주석으로 써보자. 그 주석에서 힌트를 얻어 이름으로 대체하자.

  • 함수의 매개변수도 중요하다.
  • 매개변수를 함수가 외부 세계와 어우러지는 방식을 정의한다.
  • 어느정도 레벨의 추상화로 매개변수를 받는지에 따라 함수의 활용범위가 달라진다.
    • 클래스로 받을 것인가? String으로 받을 것인가?
  • 매개변수를 올바르게 선택하는 것은 단순히 규칙 몇개로 표현할 수 없다.
  • 정답이 없는 문제이기 때문이다.

절차

간단한 절차

  1. 매개변수를 제거하려거든 먼저 함수 본문에서 제거 대상 매개변수를 참조하는 곳은 없는지 확인한다.
  2. 메서드 선언을 원하는 형태로 바꾼다.
  3. 기존 메서드 호출문을 새로운 형태로 맞춘다.
  4. 테스트한다.
  • 변경할 게 둘 이상(매개변수 제거, 메서드 선언 바꾸기)이라면 하나씩 나눠서 하자.

마이그레이션 절차

  1. 이어지는 추출 단계를 수월하게 만들어야 한다면 함수 본문을 적절히 리팩토링한다.
  2. 함수 본문을 새로운 함수로 추출한다.
    • 이 경우 추출할 이름이 이전 함수와 이름이 같을 수 있는데, 그럴 경우 임시 이름을 선정하자.
  3. 추출한 함수에 매개변수를 추가해야 한다면 간단한 절차를 따라 추가한다.
  4. 테스트한다.
  5. 기존 함수를 인라인한다.
  6. 이름을 임시 이름에서 원래 이름으로 변경한다.
  7. 테스트한다.

따라하기

함수 이름 바꾸기(간단한 절차)

function circum(radius) {
    return 2 * Math.PI * radius;
}
function circumference(radius) {
    return 2 * Math.PI * radius;
}
  • 함수 이름을 너무 축약한 케이스이다.
  • 요즘을 IDE에서 찾아주니 이런건 금방할 수 있다.

함수 이름 바꾸기(마이그레이션 절차)

function circum(radius) {
    return 2 * Math.PI * radius;
}
function circum(radius) {
    return circumference(radius);
}

function circumference(radius) {
    return 2 * Math.PI * radius;
}
  • 이전에 사용하는 메서드인 circum의 내부 구현만을 대체하여 하위 버전 호환성을 유지했다.
  • 공개 API와 같은 경우에는 이렇게 처리해야 한다.
  • deprecated로 처리해두고, 특정 시점에 제거하는 것이 좋다.

매개변수 추가하기

  • 도서 관리 프로그램의 Book 클래스에 예약 기능이 구현되어 있다고 하자.
// Book Class
addReservation(custormer) {
    this._reservations.push(customer);
}
  • 그런데 예약시 우선순위 큐를 지원하라는 요구사항이 추가되었다.
  • 매개변수로 값을 받아 분기쳐서 넣을 예정이다.
  • 그렇다면 addReservation이 얼마나 많은 곳에서 사용하는지 확인해야 한다.
  • 적으면 그냥 다 바꾸고, 많다면 마이그레이션 절차를 사용해야 한다. 두번째 상황이라고 해보자.
// Book Class
addReservation(customer) {
    this.zz_addReservation(customer, false);
}

zz_addReservation(customer, isPriority) {
    this._reservations.push(customer);
}
  • 일단 함수를 추출해두자.
  • 같은 이름을 쓸 수 없어 임시 이름으로 붙였다.
// Book Class
addReservation(customer) {
    this.zz_addReservation(customer, false);
}

zz_addReservation(customer, isPriority) {
    this._reservations.push(customer);
}
  • 새 함수의 선언문과 호출문에 원하는 매개변수를 추가한다.
  • 다음으로는 zz_addReservation 호출부를 함수 인라인한다.

매개변수를 속성으로 바꾸기

  • 이번엔 좀더 복잡한 상황을 가정해보자
function inNewEngland(aCustomer) {
    return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(aCustomer.address.state);
}

// 호출문

const newEnglanders = someCustomers.filter(c => inNewEngland(c));
  • 이 함수는 매개변수로 받은 고객이 뉴잉글랜드에 사는지 여부를 확인한다.
  • 이 경우 customer 자체를 매개변수로 받고 있어 커플링이 심하다.
  • 주 식별 코드만 매개변수로 받는다면 의존성이 제거되어 더 넓은 문맥에서 사용가능하다.
function inNewEngland(aCustomer) {
    const stateCode = aCustomer.address.state;
    return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(stateCode);
}
  • 간단하게 리팩터링 해두고, 함수 추출하기를 사용하자.
function inNewEngland(aCustomer) {
    const stateCode = aCustomer.address.state;
    return xxNEWinNewEngland(stateCode);
}

function xxNEWinNewEngland(stateCode) {
    return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(stateCode);
}
  • 이제 변수를 다시 인라인하자.
function inNewEngland(aCustomer) {
    return xxNEWinNewEngland(aCustomer.address.state);
}

function xxNEWinNewEngland(stateCode) {
    return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(stateCode);
}
  • 함수 인라인하기를 적용하자. 그리고 새 함수로 교체하자.

Reference

profile
Goal, Plan, Execute.

1개의 댓글

comment-user-thumbnail
2023년 7월 24일

좋은 글 감사합니다.

답글 달기