[리펙터링 2판] 11장 - API 리팩터링
도서

[리펙터링 2판] 11장 - API 리팩터링

반응형

모듈과 함수는 소프트웨어를 구성하는 빌등 블록, API는 이 블록들을 끼워 맞추는 연결부이다.

좋은 API 데이터를 갱신하는 함수와 그저 조회만 하는 함수를 명확히 구분해야 한다.

 

 

11. 1 질의 함수와 변경 함수 분리하기

내용

  • 외부에서 관찰할 수 있는 겉보기 부수효과가 전혀 없이 값을 반환해주는 함수를 추구해야한다.
  • 겉보기 부수효과는 입출력 변경이나, 데이터 값이 변형되는 경우

적용

  • 데이터 갱신과 조회 기능이 있다면 명확히 구분해야 한다.
  • 질의 함수(읽기 함수 : 값을 계산하여 리턴시키는 함수)는 부수효과가 포함된 경우에 제거한다. (명령-질의 분리)

결과

  • 부수 효과를 신경쓸 필요가 없어 사용하기 편하고 테스트하기 쉽다.

예시

Before

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

After

function totalOutstanding() {
  return customer.invoices.reduce((total, each) => each.amount + total, 0);  
}
function sendBill() {
  emailGateway.send(formatBill(customer));
}
  • 변경 함수 : 데이터를 변경하는 함수
  • 조회 함수 : 데이터를 조회하는 함수
  • 명령 함수 : 동작을 수행하는 함수
  • 질의 함수 : 연산을 통해 값을 계산하여 반환하는 함수. 질의 함수는 내외, 외부의 값을 변경시켜서 부수효과를 만들어선 안된다.

결국 간단하게 말해서는 함수에서 두 가지 이상의 기능을 넣지 말라는 것이다.

 

 

 

11. 1 질의 함수와 변경 함수 분리하기

내용

  • 외부에서 관찰할 수 있는 겉보기 부수효과가 전혀 없이 값을 반환해주는 함수를 추구해야한다.
  • 겉보기 부수효과는 입출력 변경이나, 데이터 값이 변형되는 경우

적용

  • 데이터 갱신과 조회 기능이 있다면 명확히 구분해야 한다.
  • 질의 함수(읽기 함수 : 값을 계산하여 리턴시키는 함수)는 부수효과가 포함된 경우에 제거한다. (명령-질의 분리)

결과

  • 부수 효과를 신경쓸 필요가 없어 사용하기 편하고 테스트하기 쉽다.

예시

Before

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

After

function totalOutstanding() {
  return customer.invoices.reduce((total, each) => each.amount + total, 0);  
}
function sendBill() {
  emailGateway.send(formatBill(customer));
}
  • 변경 함수 : 데이터를 변경하는 함수
  • 조회 함수 : 데이터를 조회하는 함수
  • 명령 함수 : 동작을 수행하는 함수
  • 질의 함수 : 연산을 통해 값을 계산하여 반환하는 함수. 질의 함수는 내외, 외부의 값을 변경시켜서 부수효과를 만들어선 안된다.

결국 간단하게 말해서는 함수에서 두 가지 이상의 기능을 넣지 말라는 것이다.

 

 

 

12. 2 함수 매개변수화하기

f(  ) {  }

내용

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

적용

  • 유사한 함수가 중복 되지만 매개 변수를 다르게 받고 있을 때

결과

  • 함수의 반복을 막을 수 있다.

예시

Before

function tenPercentRaise(aPerson) {
  aPerson.salary = aPerson.salary.multiply(1.1);
}
function fivePercentRaise(aPerson) {
  aPerson.salary = aPerson.salary.multiply(1.05);
}

After

function raise(aPerson, factor) {
  aPerson.salary = aPerson.salary.multiply(1 + factor);
}

 

 

 

12. 3 플래그 인수 제거하기

내용

  • 플래그 인수란 호출되는 함수가 실행할 로직을 호출하는 쪽에서 선택하기 위해 전달하는 인수.
  • 플래그 인수는 호출할 수 있는 함수들이 무엇이고 어떻게 호출해야 하는지 이해하기가 어려워 지기 때문이다.

적용

  • 플래그 인수가 있으면 함수들의 기능 차이가 잘 들러나지 않는다.
  • 플래그 인수가 둘 이상이면 함수 하나가 너무 많은 일을 처리하고 있는 신호이기 때문에 간단하게 만들 방법을 생각해야 한다.

결과

  • 특정한 기능 하나만 수행하는 명시적 함수를 제공하여 코드가 깔끔해진다.

예시

Before

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

After

function setHeight(value) {this._height = value;}
function setWidth (value) {this._width = value;}

 

 

 

11. 4 객체 통째로 넘기기

내용

  • 레코드를 통째로 넘기면 변화에 대응하기 쉽다.

적용

  • 레코드 값을 두 개 이상 넘기는 경우
  • 함수가 레코드에 의존하지 않게 하기 위해서는 적용하지 않는다.
  • 한 객체가 제공하는 기능 중 똑같은 일부만을 사용하는 코드가 많으면, 그 기능만 따로 묶어서 클래스 추출(7.5) 신호일 수도 있다.

결과

  • 함수가 더 다양한 데이터를 사용하도록 바뀌어도 매개변수 목록은 수정할 필요가 없다.
  • 매개 변수 목록이 짧아져서 일반적으로는 함수 사용법을 이해하기 쉬워진다.
  • 레코드를 전부 넘기게 되면 로직 중복도 없앨 수 있다.

예시

Before

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

After

  if (aPlan.withinRange(aRoom.daysTempRange))

 

 

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

내용

  • 매개변수 목록에서 중복을 피하는게 좋고 짧을 수록 이해하기 쉽다.

적용

  • 피호출 함수가 '쉽게' 결정할 수 있는 값을 함수가 전달하고 있을 때
  • 피호출 함수 내부에서 결정 가능한 값을 매개변수로 건네는 것은 불필요한 작업 즉, 중복이다.

결과

  • 의미 없이 코드 복잡함을 방지한다.

예시

Before

availableVacation(anEmployee, anEmployee.grade);

function availableVacation(anEmployee, grade) {
  // calculate vacation...

After

availableVacation(anEmployee)

function availableVacation(anEmployee) {
  const grade = anEmployee.grade;
  // calculate vacation...

 

 

 

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

내용

  • 11.5절 반대의 리펙터링
  • 함수 안에서 전역 변수를 참조하거나, 제거 대상인 원소를 참조하는 경우에는 매개 변수를 바꿀 필요가 있다.

적용

  • 함수에서 참조 투명하지 않은 원소에 접근하는 경우

결과

  • 참조 투명성을 가져 매번 똑같은 결과를 내기 때문에 다루기 쉽다.

예시

Before

targetTemperature(aPlan)

function targetTemperature(aPlan) {
  currentTemperature = thermostat.currentTemperature;
  // rest of function...

After

targetTemperature(aPlan, thermostat.currentTemperature)

function targetTemperature(aPlan, currentTemperature) {
  // rest of function...

 

  • 순수함수란 동일한 인자가 주어졌을 때 항상 동일한 결과를 반환하며, 외부의 상태를 변경하지 않는 함수
  • 참조 투명성이란 똑같은 값을 매번 건내면 매번 똑같은 결과값을 내는 함수 (다른 외부 세계의 영향을 받지 않는)

모듈 개발 시, 순수 함수를 따로 구분하고, 가변 원소들을 다루는 로직으로 순수 함수를 감싸는 패턴을 활용한다.

리팩토링을 하면서 단점은 질의 함수를 매개변수로 바꾸면 어떤 값을 제공할지를 호출자가 알아내야함으로 호출자가 복잡해지므로 반대 리팩터링의 균형점을 잘 잡아야 한다.

 

 

 

11. 7 세터 제거하기

내용

  • 세터 메서드가 있는 것은 필드가 수정될 수 있다는 뜻
  • 객체 생성 후에 수정되지 않길 원하는 필드라면 세터를 제공하지 않았기에 제거하는 것이 좋다.

적용

  • 사람들이 무조건 접근 메서드를 통해서만 필드를 다루려 할 때
  • 클라이언트에서 생성 스크립트를 사용해 객체를 생성할 때

결과

  • 객체 수정하지 않겠다는 의도가 명확해지고 (객체 생성 및 필드 변경 방식), 변경될 가능성이 없어진다. 

예시

Before

class Person {
  get name() {...}
  set name(aString) {...}

After

class Person {
  get name() {...}

 

 

 

11. 8 생성자를 팩터리(Factory) 함수로 바꾸기

내용

  • 생성자는 객체를 초기화 하는 특별한 용도의 함수

적용

  • 새로운 객체를 만들어 반환하려 할 때 일반적인 생성자의 능력만으로 부족한 경우
  • 기본 생성자는 인스턴스를 반환해야 하므로 서브클래스 등을 반환 시키거나 함수 이름을 변경할 수 없다.

결과

  • 펙터리 함수는 제약 없이 생성하고 다양한 처리를 수행 가능하다.

예시

Before

leadEngineer = new Employee(document.leadEngineer, 'E');

After

leadEngineer = createEngineer(document.leadEngineer);

 

팩토리 함수란, 함수가 새로운 객체를 반환한다. (참고)

 

 

 

11. 9 함수를 명령으로 바꾸기

내용

  • 함수를 그 함수만을 위한 객체 안으로 캡슐화하면 더 유용해지는 상황인 객체를 가리켜 '명령 객체' 또는 '명령' 이라 한다. (변경함수)

적용

  • 수 많은 데이터를 받는 복잡한 함수를 잘게 쪼개는 문제를 다룰 때

결과

  • 명령은 평범한 함수 메커니즘보다 훨씬 유연하게 함수를 제어하고 표현할 수 있다.

예시

Before

  function score(candidate, medicalExam, scoringGuide) {
    let result = 0;
    let healthLevel = 0;
    // long body code
  }

After

class Scorer {
  constructor(candidate, medicalExam, scoringGuide) {
    this._candidate = candidate;
    this._medicalExam = medicalExam;
    this._scoringGuide = scoringGuide;
  }

  execute() {
    this._result = 0;
    this._healthLevel = 0;
    // long body code
  }
}

유연성은 복잡성을 키우고 얻는 대가

 

 

 

11. 10 명령을 함수로 바꾸기

내용

  • 반대 리팩터링 (11.9절)
  • 명령 객체는 로직이 복잡하지 않으면 단점이 부각되어 평범한 함수가 낫다.

적용

  • 명령 로직이 크게 복잡하지 않을 때
  • 함수를 단순화 하여 명령 객체가 더 필요 없어질 때

결과

  • 불필요 명령을 제거하여 복잡성을 줄인다.

예시

Before

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

After

function charge(customer, usage) {
  return customer.rate * usage;
}

 

 

 

11. 11 수정된 값 반환하기

내용

  • 데이터가 어떻게 수정되는지를 추적하는 일은 코드에서 이해하기 가장 어려운 부분

적용

  • 데이터가 수정되는 흐름과 코드의 흐름을 일치시기기 어려움
  • 그러므로 함수 안에서 데이터가 수정됬음을 알려야할 때 변수를 갱신하려는 함수라면 수정된 값을 반환하여 호출자가 그 값을 변수에 담아 두도록 하는 것

결과

  • 값 하나를 계산한다는 분명한 목적이 있는 함수에 효과적
  • 함수 옮기기(8.1절)의 준비작업으로 적용하기 좋은 리팩터링

예시

Before

let totalAscent = 0;
calculateAscent();

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

After

const totalAscent = calculateAscent();

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

 

 

 

11. 12 오류 코드를 예외로 바꾸기

내용

  • 예전에는 오류코드를 사용하는게 보편적, 이 경우 오류 코드를 매번 검사해서 콜스택으로 전파하거나 직접 처리가 필요하다

적용

  • 프로그램의 동작범주에 들지 않는 오류를 나타낼 때

결과

  • 오류 발생에 따른 복잡한 상황에 대처하는 코드를 작성하거나 읽을 일을 없게 만들어준다.
  • 오류가 발견되면 예외를 던지고 적절한 예외 핸들러를 찾을 때 까지 콜스택을 타고 위로 전파되어 오류코드를 일일이 검사하지 않아도 된다.

예시

Before

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

After

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

 

 

 

11. 13 예외를 사전확인으로 바꾸기

내용

  • 함수 수행시 문제가 될 수 있는 조건을 함수 호출전에 검사한다면, 예외를 던지는 대신 호출하는곳에서 조건을 검사해야 한다.

적용

  • 예외는 '뜻밖의 오류' 라는 말 그대로 예외적으로 동작할 때만 쓰여야 한다.

결과

  • try ~ catch 문을 좀 더 간결하게 표현 가능하다.

예시

Before

double getValueForPeriod (int periodNumber) {
  try {
    return values[periodNumber];
  } catch (ArrayIndexOutOfBoundsException e) {
    return 0;
  }
}

After

double getValueForPeriod (int periodNumber) {
  return (periodNumber >= values.length) ? 0 : values[periodNumber];
}

 

반응형