11. API 리팩터링
- 모듈과 함수는 소프트웨어를 구성하는 빌딩 블록이며, API는 이 블록들을 끼워 맞추는 연결부다.
- 좋은 API는 데이터를 갱신하는 함수와 조회만 하는 함수를 구분한다.
11.1 질의 함수와 변경 함수 분리하기
배경
- 외부에서 관찰할 수 있는 겉보기 부수효과가 전혀 없이 값을 반환해주는 함수를 추구해야 한다.
- 이런 함수는 원하는 만큼 호출해도 아무 문제가 없다.
- 겉보기 부수효과가 있는 함수와 없는 함수는 명확히 구분하는 것이 좋다.
- 상태를 변경하는 부분과 질의하는 부분을 분리하자
절차
- 대상 함수를 복제하고 질의 목적에 충실한 이름을 짓는다.
- 새 질의 함수에서 부수효과를 모두 제거한다.
- 원래 함수를 호출하는 곳을 모두 찾아낸다. 호출하는 곳에서 반환 값을 사용한다면 질의 함수를 호출하도록 바꾸고, 원래 함수를 호출하는 코드를 바로 아래 줄에 새로 추가한다.
- 원래 함수에서 질의 관련 코드를 제거한다.
예시
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 함수 매개변수화하기
배경
- 두 함수의 로직이 비슷하고 리터럴 값만 다르다면, 그 다른 값만 매개변수로 받아 처리하는 함수 하나로 함쳐서 중복을 없앨 수 있다.
절차
- 비슷한 함수 중 하나를 선택한다.
- 함수 선언 바꾸기로 리터럴들을 매개변수로 추가한다.
- 이 함수를 호출하는 곳 모두에 적절한 리터럴 값을 추가한다.
- 매개변수로 받은 값을 사용하도록 함수 본문을 수정한다.
- 비슷한 다른 함수를 호출하는 코드를 찾아 매개변수화된 함수를 호출하도록 하나씩 수정한다.
예시
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); 라는 코드가 훨씬 깔끔하다
절차
- 매개변수로 주어질 수 있는 값 각각에 대응하는 명시적 함수들을 생성한다.
- 원래 함수를 호출하는 코드들을 모두 찾아서 각 리터럴 값에 대응되는 명시적 함수를 호출하도록 수정한다.
예시
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 객체 통째로 넘기기
배경
- 하나의 레코드에서 값 두개(?) 정도를 가져와 파라미터로 넘기는 코드를 보면, 레코드를 통째로 넘기고 함수 본문에서 필요한 값을 꺼내 쓰도록 만들자
- 레코드를 통째로 넘기면 변화에 대응하기 쉽다.
- 그러나 함수가 레코드 자체에 의존하기를 원치 않을때는 이 리팩터링을 수행하지 않는다.
절차
- 매개변수들을 원하는 형태로 받는 빈 함수를 만든다.
- 새 함수의 본문에서는 원래 함수를 호출하도록 하며, 새 매개변수와 원래 함수의 매개변수를 매핑한다.
- 모든 호출자가 새 함수를 사용하게 수정한다.
- 호출자를 모두 수정했다면 원래 함수를 인라인한다.
- 새 함수의 이름을 적절히 수정하고 모든 호출자에 반영한다.
예시
const low = aRoom.daysTempRange.low;
const high = aRoom.daysTempRange.high;
if (aPlan.withinRange(low, high))
------------------------------------------------------------
if (aPlan.withinRange(aRoom.daysTempRange))
11.5 매개변수를 질의 함수로 바꾸기
배경
- 매개변수는 함수의 동작에 변화를 줄 수 있는 일차적인 수단이다.
- 다른 코드와 마찬가지로 이 목록에서도 중복은 피하는 게 좋으며 짧을수록 이해하기 쉽다.
- 매개변수를 제거하면 피호출 함수에 원치 않는 의존성이 생길때는 해당 리팩터링을 실행하면 안된다.
- 제거하려는 매개변수의 값을 다른 매개변수에 질의해서 얻을 수 있다면 안심하고 질의 함수로 바꿀 수 있다.
- 주의사항: 대상 함수가 순수함수여야한다.
절차
- 필요하다면 대상 매개변수의 값을 계산하는 코드를 별도 함수로 추출해놓는다.
- 함수 본문에서 대상 매개변수로의 참조를 모두 찾아서 그 매개변수의 값을 만들어주는 표현식을 참조하도록 바꾼다.
- 함수 선언 바꾸기로 대상 매개변수를 없앤다.
예시
availableVacation(anEmployee, anEmployee.grade);
function availableVacation(anEmployee, grade) {
//dsfadfsad
}
-----------------------------------
availableVacation(anEmployee);
function availableVacation(anEmployee) {
const grade = anEmployee.grade;
//dsfadfsad
}
11.6 질의 함수를 매개변수로 바꾸기
배경
- 전역 변수를 참조한다거나, 제거하길 원하는 원소를 참조하는 경우에는 해당 참조를 매개변수로 바꿔 해결할 수 있다.
- 참조를 풀어내는 책임을 호출자로 옮기자.
- 참조 투명하지 않은 원소에 접근하는 모든 함수는 참조 투명성을 잃게 되는데, 이 문제는 해당 원소를 매개변수로 바꾸면 해결된다.
- 질의 함수를 매개변수로 바꾸면 어떤 값을 제공할지를 호출자가 알아내야 한다는 단점이 있다.
절차
- 변수 추출하기로 질의 코드를 함수 본문의 나머지 코드와 분리한다.
- 함수 본문 중 해당 질의를 호출하지 않는 코드들을 별도 함수로 추출한다.
- 방금 만든 변수를 인라인하여 제거한다.
- 원래 함수도 인라인한다.
- 새 함수의 이름을 원래 함수의 이름으로 고쳐준다.
11.7 세터 제거하기
배경
- 세터를 제거해서 객체가 생성된 후에는 값이 바뀌면 안 된다는 뜻을 분명히 하자(값은 생성자에서만 설정하자)
절차, 예시
class Person {
get name() {return this._name;}
set name(arg) {this._name = arg;}
}
const martin = new Person();
martin.name = "마틴"
- 설정해야 할 값을 생성자에서 받지 않는다면, 그 값을 받을 매개변수를 생성자에 추가한다. 그런 다음 생성자 안에서 적절한 세터를 호출한다.
- class Person { constructor(name) { this.name = name; } get name() {return this._name;} set name(arg) {this._name = arg;} } const martin = new Person("마틴"); martin.name = "마틴"
- 생성자 밖에서 세터를 호출하는 곳을 찾아 제거하고, 대신 새로운 생성자를 사용하도록 한다.
- class Person { constructor(name) { this.name = name; } get name() {return this._name;} set name(arg) {this._name = arg;} } const martin = new Person("마틴");
- 세터 메서드를 인라인한다. 가능하다면 해당 필드를 불변으로 만든다.
- 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);
- 팩터리 함수를 만든다. 함수의 본문에서는 원래의 생성자를 호출한다.
- function createEmployee(name) { return new Employee(name) }
- 생성자를 호출하던 코드를 팩터리 함수 호출로 바꾼다.
- const candidate = createEmployee(document.name);
- 하나씩 수정할 때마다 테스트한다.
- 생성자의 가시 범위가 최소가 되도록 제한한다.
11.9 함수를 명령으로 바꾸기
배경
- 함수를 그 함수만을 위한 객체 안으로 캡슐화하면 더 유용해지는 상황이 있다. 이런 객체를 가리켜 명령 이라고 한다.
- 명령으로 인해 유연성을 얻을 수 있지만 복잡성을 키운다는 트레이드 오프가 있다.
절차
- 대상 함수의 기능을 옮길 빈 클래스를 만든다. 클래스 이름은 함수 이름에 기초에 짓는다.
- 방금 생성한 빈 클래스로 함수를 옮긴다.
- 함수의 인수들 각각은 명령의 필드로 만들어 생성자를 통해 설정할지 고민해본다.
예시
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 명령을 함수로 바꾸기
배경
- 로직이 크게 복잡하지 않다면 명령은 장점보다 단점이 많으니 평범한 함수로 바꿔주는게 낫다.
절차
- 명령을 생성하는 코드와 명령의 실행 메서드를 호출하는 코드를 함께 함수로 추출한다.
- 명령의 실행 함수가 호출하는 보조 메서드들 각각을 인라인한다.
- 생성자의 매개변수 모두를 명령의 실행 메서드로 옮긴다.
- 명령의 실행 메서드에서 참조하는 필드들 대신 대응하는 매개변수를 사용하게끔 바꾼다.
- 생성자 호출과 명령의 실행 메서드 호출을 호출자 안으로 인라인한다.
- 명령 클래스를 없앤다.
예시
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 수정된 값 반환하기
배경
- 데이터가 어떻게 수정되는지를 추적하는 일은 코드에서 이해하기 가장 어려운 부분중 하나다.
- 변수를 갱신하는 함수라면, 수정된 값을 반환하여 호출자가 그 값을 변수에 담아두도록 한다. 이렇게 하면 데이터가 수정됨을 알려주기 쉬워진다.
- 값 하나를 계산한다는 분명한 목적이 있느 함수들에 가장 효과적이다.
절차
- 함수가 수정된 값을 반환하게 하여 호출자가 그 값을 자신의 변수에 저장하게 한다.
- 피호출 함수 안에 반환할 값을 가리키는 새로운 변수를 선언한다.
- 계산이 선언과 동시에 이뤄지도록 통합한다.
- 피호출 함수의 변수 이름을 새 역할에 어울리도록 바꿔준다.
예시
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 오류 코드를 예외로 바꾸기
배경
- 예외는 프로그래밍 언어에서 제공하는 독립적인 오류 처리 메커니즘이다.
- 예외는 정교한 메커니즘이지만, 대다수의 다른 정교한 메커니즘과 같이 정확하게 사용할 떄만 최고의 효과를 낸다.
- 예외는 정확히 예상 밖의 동작일 떄만 쓰여야 한다.
절차
- 콜스택 상위에 해당 예외를 처리할 예외 핸들러를 작성한다.
- 해당 오류 코드를 대체할 예외와 그 밖의 예외를 구분할 식별 방법을 찾는다.
- catch절을 수정하여 직접 처리할 수 있는 예외는 적절히 대처하고 그렇지 않은 예외는 다시 던진다.
- 오류 코드를 반환하는 곳 모두에서 예외를 던지도록 수정한다.
- 모두 수정했다면 그 오류 코드를 콜스택 위로 전달하는 코드를 모두 제거한다.
예시
if (data)
return new ShippingRules(data);
else
return -23;
------------------------------------
if (data)
return new ShippingRules(data);
else
return new OrderProcessingError(-23);
11.13 예외를 사전확인으로 바꾸기
배경
- 예외는 말 그대로 예외적으로 동작할 때만 쓰여야한다.
- 함수 수행 시 문제가 될 수 있는 조건을 함수 호출 전에 검사할 수 있다면, 예외를 던지는 대신 호출하는 곳에서 조건을 검사하도록 해야한다.
절차
- 예외를 유발하는 상황을 검사할 수 있는 조건문을 추가한다. catch블록의 코드를 조건문의 조건절 중 하나로 옮기고, 남은 try 블록의 코드를 다른 조건절로 옮긴다.
- 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 |