티스토리 뷰

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

이런 API 를 이해하고 사용하기 쉽게 만드는 일은 중요한 동시에 어렵다. 그래서 API를 개선하는 방법을 새로 깨달을 때마다 그에 맞게 리팩터링을해야 한다.

 

  • 데이터 갱신과 조회가 섞여 있다면
    • 11.1 질의 함수와 변경 함수 분리하기
  • 값 하나 때문에 여러개로 나뉜 함수들은
    • 11.2 함수 매개변수화하기
  • 매개변수가 그저 함수의 동작 모드를 전환하는 용도로만 쓰이면
    • 11.3 프래그 인수 제거하기
  • 데이터 구조가 함수 사이를 건너다니면서 필요 이상으로 분해될 떄
    • 11.4 객체 통째로 넘기기
  • 무언가를 매개변수로 건네 피호출 함수와 호출 함수 중 어디서 판단할지에 관해서는 불변의 진리는 없으니 상황이 바뀔때마다
    • 11.5 질의 함수로 바꾸기
    • 11.6 질의 함수를 매개변수로 바꾸기
  • 객체가 되도록 불변이길 원하므로 기회가 될 때 마다
    • 11.7 세터 제거하기
  • 새로운 객체를 만들어 반환하려 할 때 일반적인 생성자의 능력만으로 부족한 경우
    • 11.8 생성자를 팩터리 함수로 바꾸기
  • 수많은 데이터를 받는 복잡한 함수를 잘게 쪼개는 문제를 다룰때
    • 11.9 함수를 명령으로 바꾸기
  • 함수를 단순화하여 명령 객체가 더는 필요 없어지면
    • 11.10 명령을 함수로 바꾸기
  • 함수 안에서 데이터가 수정됐음르 확실히 알리려면
    • 11.11 수정된 값 반환하기
  • 오류 코드에 의존하는 과거 방식 코드는
    • 11.12 오류 코드를 예외로 바꾸기
  • 예외는 올바른 상황에만 정확하게 적용해야 한다. 문제가 되는 조건을 함수 호출 전에 검사할 수 있다면
    • 11.13 예외를 사전 확인으로 바꾸기

 

 

11.1 질의 함수와 변경 함수 분리하기 (Separate Query From Modifier)

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));
}

배경

  • 외부에서 관찰할 수 있는 겉보기 부수효과(observable side effect)가 전혀 없이 값을 반환해주는 함수를 추구해야 한다
  • 겉보기 부수효과란?
    • 외부 세계 영향을 주는 네트워크 통신, 입출력 변경, 데이터 변형 등을 의미
  • 보통 질의 함수에 부수효과가 포함된 경우 제거하는게 일반적이다

효과

  • 이런 함수는 호출부에서 부수효과를 신경쓸 필요가 없어 사용하기 편하고 테스트하기 쉽다

적용시점

  • 겉보기 부수효과가 존재하는 함수가 있는 경우

 

 

11.2 함수 매개변수화하기 (Parameterize Function)

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);
}

배경

  • 두 함수의 로직이 비슷하고 리터럴 값만 다른 경우 매개변수로 만들어 함수를 하나로 합칠 수 있다

효과

  • 유사한 함수의 중복을 제거한다
  • 매개변수 값만 바꿔서 여러 곳에서 쓸 수 있으니 함수의 유용성이 커진다

적용시점

  • 두 함수의 로직이 비슷하고 리터럴 값만 다른 경우

 

 

11.3 플래그 인수 제거하기 (Remove Flag Argument)

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 객체 통째로 넘기기 (Preserve Whole Object)

before

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

after

  if (aPlan.withinRange(aRoom.daysTempRange)) 

배경

  • 레코드에서 값 두어 개를 가져와 인수로 넘기는 경우 레코드를 통채로 넘기는게 변화에 대응하기 쉽다
  • 한편, 객체의 특정 기능을 사용하는 코드가 많다면, 그 기능만 따로 묶어서 클래스로 추출(7.5) 하라는 신호일 수 있다

효과

  • 더 다양한 데이터를 사용하도록 바뀌어도 매개변수 목록은 수정할 필요가 없다
  • 매개변수 목록이 짧아져 함수 사용법을 이해하기 쉬워진다

적용시점

  • 레코드의 값을 두개 이상 넘기는 함수인 경우
  • 단, 함수가 레코드에 의존하기를 원치 않을때는 적용하지 않는다

 

 

11.5 매개변수를 질의 함수로 바꾸기 (Replace Parameter with Query)

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 질의 함수를 매개변수로 바꾸기 (Replace Query with Parameter)

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 세터 제거하기 (Remove Setting Method)

before

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

after

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

 

배경

  • 세터 메서드가 있다고 함은 필드가 수정될 수 있다는 뜻이다
  • 하지만, 세터가 아닌 다른 방식으로 필드를 제어하는 경우에는 안전하게 제거해야 한다

효과

  • 객체의 의도(객체 생성, 필드 변경 방식)가 명백해지고 의도치 않게 변경될 가능성이 봉쇄된다

적용시점

  • 생성자 스크립트(생성자에서 직접 필드에 값을 세팅하는 방식)를 사용해서 객체를 완성하는 형태일 때

 

 

11.8 생성자를 팩터리 함수로 바꾸기 (Replace Constructor with Factory Function)

before

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

after

leadEngineer = createEngineer(document.leadEngineer);

 

배경

  • 기본 생성자는 반드시 그 인스턴스를 반환해야 하므로 서브클래스 등을 반환 시키거나 함수 이름을 변경할 수 없다

효과

  • 팩터리 함수를 제약없이 생성하고 다양한 처리를 수행할 수 있다

적용시점

  • 기본 생성자 이상의 동작을 구현하고 싶을 때

 

 

11.9 함수를 명령으로 바꾸기 (Replace Function with Command)

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 명령을 함수로 바꾸기 (Replace Command with Function)

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 수정된 값 반환하기 (Return Modified Value)

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 오류 코드를 예외로 바꾸기 (Replace Error Code With Exception)

before

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

after

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

 

배경

  • 예전에는 오류 코드를 사용하는게 보편적이었으나, 이 경우 오류 코드를 매번 검사해서 콜스택으로 전파하거나 직접 처리하는 처리가 필요하다
  • 예외는 프로그래밍 언어에서 제공하는 독립적인 오류 처리 메커니즘이다
  • 오류가 발견되면 예외를 던지고 적절한 예외 핸들러를 찾을 때까지 콜스택을 타고 위로 전파되어 오류 코드를 일일이 검사하지 않아도 되어 편리하다

효과

  • 오류 발생에 따른 복잡한 상황에 대처하는 코드를 작성하거나 읽을 일이 없게 해준다

적용시점

  • 예외는 프로그램의 정상 동작 범주에 들지 않는 오류를 나타낼 때만 쓰여야 한다
  • 괜찮은 경험 법칙으로는 예외를 던지는 코드를 프로그램 종료 코드로 바꿔도 프로그램이 여전히 정상 동작할지를 따져보는 것이다
  • 정상 동작하지 않을 것 같다면 예외 대신 오류를 검출하여 프로그램을 정상 흐름으로 되돌리게끔 처리해야 한다

 

 

11.13 예외를 사전확인으로 바꾸기 (Replace Exception with Precheck)

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];
}

배경

  • 예외라는 개념은 프로그래밍 언어의 발전에 의미 있는 한걸음이었다
  • 하지만 좋은 것들이 늘 그렇듯, 예외도 과용되곤 한다. 예외는 '뜻밖의 오류' 라는, 말 그대로 예외적으로 동작할 때만 쓰여야 한다
  • 함수 수행 시 문제가 될 수 있는 조건을 함수 호출 전에 검사할 수 있다면, 예외를 던지는 대신 호출하는곳에서 조건을 검사해야 한다

효과

  • 예외가 정말로 의미있는 부분에서만 사용될 수 있도록 한다

적용시점

  • 예외 대신 사전확인이 가능할 때

 

 

참고
http://www.yes24.com/Product/Goods/89649360
https://refactoring.com/catalog

댓글