본문 바로가기
핌팩토링

11. API 리팩터링

by AsCE_hyunseung 2022. 6. 19.

11. API 리팩터링

  • 모듈과 함수는 소프트웨어를 구성하는 빌딩 블록이며, API는 이 블록들을 끼워 맞추는 연결부다.
  • 좋은 API는 데이터를 갱신하는 함수와 조회만 하는 함수를 구분한다.

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

배경

  • 외부에서 관찰할 수 있는 겉보기 부수효과가 전혀 없이 값을 반환해주는 함수를 추구해야 한다.
    • 이런 함수는 원하는 만큼 호출해도 아무 문제가 없다.
  • 겉보기 부수효과가 있는 함수와 없는 함수는 명확히 구분하는 것이 좋다.
  • 상태를 변경하는 부분과 질의하는 부분을 분리하자

절차

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

예시

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

11.2 함수 매개변수화하기

배경

  • 두 함수의 로직이 비슷하고 리터럴 값만 다르다면, 그 다른 값만 매개변수로 받아 처리하는 함수 하나로 함쳐서 중복을 없앨 수 있다.

절차

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

예시

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

11.3 플래그 인수 제거하기

배경

  • 플래그 인수: 호출되는 함수가 실행할 로직을 호출하는 쪽에서 선택하기 위해 전달하는 인수
  • 플래그 인수는 호출할 수 있는 함수들이 무엇이고 어떻게 호출해야 하는지를 이해하기 어려워지기 떄문에 사용을 지양해야한다.
  • 예를 들어
    • bookConcert(aCustomer, “premium”); 보다는 premiumBookConcert(aCustomer); 라는 코드가 훨씬 깔끔하다

절차

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

예시

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

11.4 객체 통째로 넘기기

배경

  • 하나의 레코드에서 값 두개(?) 정도를 가져와 파라미터로 넘기는 코드를 보면, 레코드를 통째로 넘기고 함수 본문에서 필요한 값을 꺼내 쓰도록 만들자
  • 레코드를 통째로 넘기면 변화에 대응하기 쉽다.
  • 그러나 함수가 레코드 자체에 의존하기를 원치 않을때는 이 리팩터링을 수행하지 않는다.

절차

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

예시

const low = aRoom.daysTempRange.low;
const high = aRoom.daysTempRange.high;
if (aPlan.withinRange(low, high))
------------------------------------------------------------
if (aPlan.withinRange(aRoom.daysTempRange))

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

배경

  • 매개변수는 함수의 동작에 변화를 줄 수 있는 일차적인 수단이다.
  • 다른 코드와 마찬가지로 이 목록에서도 중복은 피하는 게 좋으며 짧을수록 이해하기 쉽다.
  • 매개변수를 제거하면 피호출 함수에 원치 않는 의존성이 생길때는 해당 리팩터링을 실행하면 안된다.
  • 제거하려는 매개변수의 값을 다른 매개변수에 질의해서 얻을 수 있다면 안심하고 질의 함수로 바꿀 수 있다.
  • 주의사항: 대상 함수가 순수함수여야한다.

절차

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

예시

availableVacation(anEmployee, anEmployee.grade);
function availableVacation(anEmployee, grade) {
  //dsfadfsad
}
-----------------------------------
availableVacation(anEmployee);
function availableVacation(anEmployee) {
  const grade = anEmployee.grade;
  //dsfadfsad
}

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

배경

  • 전역 변수를 참조한다거나, 제거하길 원하는 원소를 참조하는 경우에는 해당 참조를 매개변수로 바꿔 해결할 수 있다.
  • 참조를 풀어내는 책임을 호출자로 옮기자.
  • 참조 투명하지 않은 원소에 접근하는 모든 함수는 참조 투명성을 잃게 되는데, 이 문제는 해당 원소를 매개변수로 바꾸면 해결된다.
  • 질의 함수를 매개변수로 바꾸면 어떤 값을 제공할지를 호출자가 알아내야 한다는 단점이 있다.

절차

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

11.7 세터 제거하기

배경

  • 세터를 제거해서 객체가 생성된 후에는 값이 바뀌면 안 된다는 뜻을 분명히 하자(값은 생성자에서만 설정하자)

절차, 예시

class Person {
    get name() {return this._name;}
    set name(arg) {this._name = arg;}
}

const martin = new Person();
martin.name = "마틴"
  1. 설정해야 할 값을 생성자에서 받지 않는다면, 그 값을 받을 매개변수를 생성자에 추가한다. 그런 다음 생성자 안에서 적절한 세터를 호출한다.
  2. class Person { constructor(name) { this.name = name; } get name() {return this._name;} set name(arg) {this._name = arg;} } const martin = new Person("마틴"); martin.name = "마틴"
  3. 생성자 밖에서 세터를 호출하는 곳을 찾아 제거하고, 대신 새로운 생성자를 사용하도록 한다.
  4. class Person { constructor(name) { this.name = name; } get name() {return this._name;} set name(arg) {this._name = arg;} } const martin = new Person("마틴");
  5. 세터 메서드를 인라인한다. 가능하다면 해당 필드를 불변으로 만든다.
  6. class Person { constructor(name) { this._name = name; } get name() {return this._name;} } const martin = new Person("마틴");

11.8 생성자를 팩터리 함수로 바꾸기

배경

  • 생성자에는 제약이 있지만(ex. 생성자 이름) 팩터리 함수에는 이런 제약이 없다.
  • 팩터리 함수를 구현하는 과정에서 생성자를 호출할 수는 있지만, 원한다면 다른 무언가로 대체할 수 있다.

절차, 예시

class Employee {
    constructor(name) {
        this._name = name;
    }
    get name() {return this._name}
}

const candidate = new Employee(document.name);
  1. 팩터리 함수를 만든다. 함수의 본문에서는 원래의 생성자를 호출한다.
  2. function createEmployee(name) { return new Employee(name) }
  3. 생성자를 호출하던 코드를 팩터리 함수 호출로 바꾼다.
  4. const candidate = createEmployee(document.name);
  5. 하나씩 수정할 때마다 테스트한다.
  6. 생성자의 가시 범위가 최소가 되도록 제한한다.

11.9 함수를 명령으로 바꾸기

배경

  • 함수를 그 함수만을 위한 객체 안으로 캡슐화하면 더 유용해지는 상황이 있다. 이런 객체를 가리켜 명령 이라고 한다.
  • 명령으로 인해 유연성을 얻을 수 있지만 복잡성을 키운다는 트레이드 오프가 있다.

절차

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

예시

function score(candidate, medicalExam, scoringGuide) {
    let result = 0;
    let healthLevel = 0;
    //qwerqwerw
}
------------------------------
class Scorer {
    constructor(candidate, medicalExam, scoringGuide) {
        this._candidate = candidate;
        this._medicalExam = medicalExam;
        this._scoringGuide = scoringGuide;
    }

    execute() {
        this._result = 0;
        this._healthLevel = 0;
        //qwerqwerqwerr
    }
}

11.10 명령을 함수로 바꾸기

배경

  • 로직이 크게 복잡하지 않다면 명령은 장점보다 단점이 많으니 평범한 함수로 바꿔주는게 낫다.

절차

  1. 명령을 생성하는 코드와 명령의 실행 메서드를 호출하는 코드를 함께 함수로 추출한다.
  2. 명령의 실행 함수가 호출하는 보조 메서드들 각각을 인라인한다.
  3. 생성자의 매개변수 모두를 명령의 실행 메서드로 옮긴다.
  4. 명령의 실행 메서드에서 참조하는 필드들 대신 대응하는 매개변수를 사용하게끔 바꾼다.
  5. 생성자 호출과 명령의 실행 메서드 호출을 호출자 안으로 인라인한다.
  6. 명령 클래스를 없앤다.

예시

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

11.11 수정된 값 반환하기

배경

  • 데이터가 어떻게 수정되는지를 추적하는 일은 코드에서 이해하기 가장 어려운 부분중 하나다.
  • 변수를 갱신하는 함수라면, 수정된 값을 반환하여 호출자가 그 값을 변수에 담아두도록 한다. 이렇게 하면 데이터가 수정됨을 알려주기 쉬워진다.
  • 값 하나를 계산한다는 분명한 목적이 있느 함수들에 가장 효과적이다.

절차

  1. 함수가 수정된 값을 반환하게 하여 호출자가 그 값을 자신의 변수에 저장하게 한다.
  2. 피호출 함수 안에 반환할 값을 가리키는 새로운 변수를 선언한다.
  3. 계산이 선언과 동시에 이뤄지도록 통합한다.
  4. 피호출 함수의 변수 이름을 새 역할에 어울리도록 바꿔준다.

예시

let totalAscent = 0;
calculateAscent();

function calculateAscent() {
    for (let i = 1; i <points.length; i++) {
        const verticalCharge = points[i].elevation - points[i-1].elevation;
        totalAscent += (verticalCharge > 0) ? verticalCharge : 0;
    }
}

---------------------------------------------------------
let totalAscent = calculateAscent();

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

11.12 오류 코드를 예외로 바꾸기

배경

  • 예외는 프로그래밍 언어에서 제공하는 독립적인 오류 처리 메커니즘이다.
  • 예외는 정교한 메커니즘이지만, 대다수의 다른 정교한 메커니즘과 같이 정확하게 사용할 떄만 최고의 효과를 낸다.
  • 예외는 정확히 예상 밖의 동작일 떄만 쓰여야 한다.

절차

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

예시

if (data)
    return new ShippingRules(data);
else
    return -23;
------------------------------------
if (data)
    return new ShippingRules(data);
else
    return new OrderProcessingError(-23);

11.13 예외를 사전확인으로 바꾸기

배경

  • 예외는 말 그대로 예외적으로 동작할 때만 쓰여야한다.
  • 함수 수행 시 문제가 될 수 있는 조건을 함수 호출 전에 검사할 수 있다면, 예외를 던지는 대신 호출하는 곳에서 조건을 검사하도록 해야한다.

절차

  1. 예외를 유발하는 상황을 검사할 수 있는 조건문을 추가한다. catch블록의 코드를 조건문의 조건절 중 하나로 옮기고, 남은 try 블록의 코드를 다른 조건절로 옮긴다.
  2. try문과 catch 블록을 제거한다.

예시

 

 

'핌팩토링' 카테고리의 다른 글

12. 상속 다루기  (0) 2022.06.19
10. 조건문 로직 간소화  (0) 2022.04.28
9. 데이터 조직화  (0) 2022.04.28
8. 기능 이동  (0) 2022.04.27
7. 캡슐화  (0) 2022.04.27