[Refactoring] 리팩터링 2판 Chapter.11 part 1 (API 리팩터링)

Date:     Updated:

카테고리:

태그:

이번 챕터는 API 를 리팩터링 하는 챕터 입니다. 개발을 하면서 반복적으로 사용하는 모듈들이 있습니다. 그것들을 사용하기 쉽게 만드는것을 API 라고 합니다. 저의 API를 만들 때 철학은, 사용하는 쪽에서 쉽게 사용할 수 있어야 한다 입니다.

요즘 재밌어하는것 중에 하나가 어떤 기능을 하는 API를 만드는 것입니다. 아마 이것은 많은 개발자들이 수행하고 있을겁니다. 그래서 그런지 이번 장에서 배운 내용들이 많습니다.

질의 함수와 변경 함수 분리하기

개발자는 외부에서 관찰할 수 있는 겉보기 부수효과가 전혀 없이 값을 반환해주는 함수를 추구해야 합니다. 이런 함수는 언제 어디서든 원하는 만큼 호출해도 아무 문제가 없습니다. 한마디로 부수효과가 없기 때문에 신경쓸 부분이 줄어듭니다.

절차

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

예제

다음과 같은 함수가 있다고 생각합니다.

function getTotalOutstandingAndSendBill() {
    const result = customer.invoices.reduce((sum, each) => each.amount + sum, 0);
    sendBill(customer, result);
    return result;
}

function sendBill(customer, amount) {
    console.log(`sending bill for ${amount} to ${customer}`);
}

위 함수를 보면, 한 함수에 너무 많은 일을 하고 있음을 알 수 있습니다. 질의 함수와 상태를 변경하는 함수가 한 함수에 같이 있죠. 함수 이름도 보면 기이하게 길어지는것을 볼 수 있습니다. 또한, getTotalOutstandingAndSendBill을 호출하면 OutStanding을 가져오는 동시에 영수증도 보내주게 되어 부수효과가 나타남을 볼 수 있습니다. 해당 함수를 2개로 나누어 만듭니다.

function getTotalOutstanding() {
    return customer.invoices.reduce((sum, each) => each.amount + sum, 0);
}

function sendBill(customer) {
    emailGateway.send(formatBill(customer));
}

//클라이언트
const customer = site.customer;
const outstanding = getTotalOutstanding(customer);
sendBill(customer, outstanding);

위와같이 리팩터링하면 부수효과가 사라지고, 책임도 분리되어 깔끔한 코드가 완성되는것을 볼 수 있습니다.

플래그 인수 제거하기

플래그 인수란 호출되는 함수가 실행할 로직을 호출하는 쪽에서 선택하기 위해 전달하는 인수입니다. 가장 많이 예로 드는것이 bool값을 매개변수로 전달하는 경우 입니다.

function bookConcert(isPermium) {
    if (isPermium) {
        
    } else {
        
    }
}

위 코드처럼 짜게되면 클라이언트에서는 다음과 같이 호출해야 합니다.

const concert = bookConcert(true);

이 코드를 봤을 때, 저 true의 의미가 뭔지 한눈에 알아볼 수 없습니다. 따라서 저자는 다음과 같이 말합니다. 불리언 플래그는 코드를 읽는 이에게 뜻을 온전히 전달하지 못하기 떄문에 좋지 못하다. 함수에 전달한 true의 의미가 대체 뭐란 말인가?

저도 이것에 공감합니다. 남들이 만들어놓은 API를 가져다 쓸 때, 불리언값을 인자로 넣는 코드들이 많았습니다. 그럴때마다 이 bool 값이 무엇을 뜻하는지 알아내기 위해서 함수의 파라미터 변수명을 읽어보거나 공식 문서를 읽어야 했습니다. 이러한 불편함 때문인지 저도 API를 만들때는 가급적 플래그 변수를 쓰지 않으려고 노력합니다.

(플래그 변수에는 리터럴 string 값을 전달하는 경우나, 리터럴 enum을 전달하는 경우도 있습니다.)

그래서 불리언 값을 인수로 전달해야 할때는 그냥 함수를 2개로 나누어서 제공합니다. 하지만 다음과 같은 경우가 있을 수 있습니다. 바로 불리언 인수가 2개인 경우입니다. 이러면 벌써 경우의수가 4개가 되어 함수 4개를 만들어야 합니다. 이때는 예외적으로 플래그 변수를 써야 할 합당한 근거가 될 수 있습니다. 하지만 또 다른 관점에서 보면, 함수 하나가 너무 많은 책임을 지고 있다라고 볼 수 있습니다. 따라서 상황에 맞게 잘 처리하는 능력이 중요한것 같습니다.

절차

  1. 매개변수로 주어질 수 있는 값 각각에 대응하는 명시적 함수들을 생성한다.
  2. 원래 함수를 호출하는 코드들을 모두 찾아서 각 리터럴 값에 대응되는 명시적 함수를 호출하도록 수정한다.

예제

아주 간단한 예제를 다룹니다. 어떤 값을 설정해주는 함수가 name 이라는 플래그 변수(리터럴 string)에 따라서 값을 다르게 세팅한다고 가정합니다.

function setDimension(name, value) {
    if (name == "height") {
        this.height = value;
        return;
    } else if(name == "width") {
        this.width = value;
        return;
    }
}

위와같이 함수를 만들 수 있습니다. 위 함수를 분리하면 다음과 같게 됩니다.

function setHeight(value) {
    this.height = value;
}

function setWidth(value) {
    this.width = value;
}

훨씬 직관적이고 책임이 확실한 코드가 완성되었습니다.

매개변수를 질의 함수로 바꾸기

매개변수 목록은 함수의 변동 요인을 모아놓은 곳입니다. 즉, 함수의 동작에 변화를 줄 수 있는 일차적인 수단입니다. 다른 코드와 마찬가지로 이 목록에서도 중복은 피하는게 좋으며 짧을수록 이해하기 좋습니다.

피호출 함수가 스스로 ‘쉽게’결정할 수 있는 값을 매개변수로 건네는 것도 일종의 중복 입니다. 다음과 같은 상황이 그 예 입니다.

availableVacation(anEmployee, anEmployee.grade);

function availableVacation(anEmployee, grade) {
    if (grade == "A") {
        return 30;
    } else if (grade == "B") {
        return 20;
    } else if (grade == "C") {
        return 10;
    }
}

위 코드를 보면 anEmployee에 grade라는 필드가 들어가 있는데도, 함수의 인자로 grade를 받아 중복을 일으키고 있습니다. 저 중복을 제거하면 다음과 같습니다.

availableVacation(anEmployee);

function availableVacation(anEmployee) {
    const grade = anEmployee.grade;
    if (grade == "A") {
        return 30;
    } else if (grade == "B") {
        return 20;
    } else if (grade == "C") {
        return 10;
    }
}

함수 안에서 쉽게 처리할 수 있는 인수를 없애줌으로서 호출하는쪽도 더 간소화 되었습니다. 여기서 저자의 철학이 등장합니다. 나는 습관적으로 호출하는 쪽을 간소하게 만든다. 라는 철학 입니다. 앞서 이야기했지만, API라면 이 철학을 따르는것이 좋다고 생각합니다.

절차

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

질의 함수를 매개변수로 바꾸기

해당 리팩터링은 위에서 다루었던 매개변수를 질의 함수로 바꾸기와 상반되는 내용입니다.

코드를 읽다 보면 함수 안에 두기엔 거북한 참조를 발견할 때가 있습니다. 전역변수를 참조한다거나 제거하길 원하는 원소를 참조하는 경우 여기에 속합니다. (같은 모듈 안에서 전역변수를 참조하는것은 괜찮지만 그 전역변수의 값이 달라지면서 함수에 영향을 미치면 의존성이 생기는 문제가 발생함.) 이 문제는 해당 참조를 매개변수로 바꾸어서 해결할 수 있습니다.

전역변수등 의존관계를 해제하면서 똑같은 값을 건네면 매번 똑같은 결과를 내는 함수는 다루기가 쉽습니다. 이런 성질을 참조 투명성이라고 합니다. 참조 투명성을 지키지 않는 원소에 접근하는 모든 함수는 참조 투명성을 잃게 됩니다. 이 문제는 해당 원소를 매개변수로 바꾸면 해결됩니다.

그래서 모듈을 개발할 때 순수 함수들은 따로 구분하고, 프로그램의 입출력과 기타 가변 원소들을 다루는 로직으로 순수 함수들의 겉을 감싸는 패턴을 많이 사용한다고 합니다. 그리고 이번 리팩터링을 활용하면 프로그램의 일부를 순수 함수로 바꿀 수 있으며, 결과적으로 그 부분은 테스트하거나 다루기가 쉬워집니다.

절차

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

Refactoring 카테고리 내 다른 글 보러가기

댓글 남기기