리팩터링 | 마틴 파울러 | 한빛미디어- 교보ebook

코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기, 20여 년 만에 다시 돌아온 마틴 파울러의 리팩터링 2판 리팩터링 1판은 1999년 출간되었으며, 한국어판은 2002년 한국에 소개되었다

ebook-product.kyobobook.co.kr

개요

함수는 프로그램을 작은 부분으로 나누는 주된 수단이다.

이런 함수 선언부는 소프트웨어의 연결부 역할을 한다. 따라서 선언부를 정의할 필요가 있다.

 

가장 중요한 함수 이름이다. 이름이 좋으면 구현부를 필요가 없다.

이름은 IDE기능으로 쉽게 변경가능하니 좋은 이름이 있다면 주저하지 않고 바꾸자

 

함수 매개변수는 함수를 사용하는 문맥을 설정한다.

전화번호 포맷팅 함수가 있는데 매개변수로 사람 타입을 받아 전화번호를 포맷팅한다고 하면, 활용도가 적다. 전화번호만 인자로 받는다면 어떤 전화번호라도 활용이 가능하다.

절차

간단한 절차와 마이그레이션 절차가 있다.

간단한 절차를 최대한 사용하되 불가능하다면 마이그레이션 절차를 사용한다.

 

간단한 절차

  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()호출하는 부분을 전부 찾아 circumference() 바꾼다.

 

방식은 API같은 선언부를 고칠 없는 외부 코드 사용 부분을 리팩터링 하기 좋다.

circumference() 만들어 두고, circum() @Deprecated 같은 표시를 해두어 circumference() 호출하도록 유도를 한다. 이후 모든 클라이언트가 변경 했으면 circum() 지운다.

 

 

 

예시: 매개변수 추가하기

예약 우선순위 큐를 지원하라는 요구사항이 들어왔다.

기존 큐와 우선순위 어떤 것으로 할지 결정 매개변수를 추가해야 한다.

 

class Book{
    addReservation(customer){
        this._reservations.push(customer);
    }
}
class Book{
    addReservation(customer){
        this.zz_addReservation(customer);
    }
    //임시이름
    zz_addReservation(customer) {
        this._reservations.push(customer);
    }
}
class Book{
    addReservation(customer){
        this.zz_addReservation(customer, false);
    }
    //매개변수 추가
    zz_addReservation(customer,isPriority) {
        //자바스크립트 한정 assert 테스트
        assert(isPriority === true || isPriority === false);
        this._reservations.push(customer);
    }
}
이제 호출 부분을 전부 zz_addReservation() 변경 zz_addReservation() 함수 이름을 기존 함수 이름으로 바꾼다.
class Book{
    addReservation(customer,isPriority) {
        this._reservations.push(customer);
    }
}

 

 

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

고객이 뉴잉글랜드 살고있는지 파악하는 함수

/* 현재 문제는 매개변수를 객체로 받고 있다. 재사용성이 제한된다.*/
function inNewEngland(aCustomer){
    return ["MA","CT","ME","VT","NH","RI"].includes(aCustomer.address.state);
}
//호출부
const newEnglanders = someCustomers.filter(c=>inNewEngland(c));
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 newFunction(stateCode);
}
//임시 함수 추출
function newFunction(stateCode) {
    return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(stateCode);
}
function inNewEngland(aCustomer){
    //변수 인라인하기
    return newFunction(aCustomer.address.state);
}
function newFunction(stateCode) {
    return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(stateCode);
}
//호출부 변경
const newEnglanders = someCustomers.filter(c=>newFunction(c));
//함수 선언 바꾸기로 새함수이름을 기존함수이름으로 변경
function inNewEngland(stateCode) {
    return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(stateCode);
}
//호출부 변경
const newEnglanders = someCustomers.filter(c=>inNewEngland(c));

 

+ Recent posts