티스토리 뷰
모듈과 함수는 소프트웨어를 구성하는 빌딩 블록이며, 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
'소프트웨어공학, CS > 리팩터링 2판' 카테고리의 다른 글
[리팩터링 2판] 3장 코드에서 나는 악취 (0) | 2021.09.04 |
---|---|
[리팩터링 2판] 12장 상속 다루기 (0) | 2021.05.25 |
[리팩터링 2판] 10장 조건부 로직 간소화 (0) | 2021.04.18 |
[리팩터링 2판] 9장 데이터 조직화 (0) | 2021.04.04 |
[리팩터링 2판] 8장 기능 이동 (2) | 2021.03.28 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- Debugging
- Debug
- 리팩토링
- HTTP
- 변경함수
- 위임
- 매개변수화
- Debug It! 실용주의 디버깅
- aws fargate
- SSL
- 박소연
- 일 잘하는 사람은 단순하게 말합니다
- https
- 제어플래그
- 코드악취
- 그림으로 공부하는 IT 인프라 구조
- 일잘러
- 디버깅
- Refactoring
- 코드스멜
- 마틴파울러
- 그림으로 배우는 HTTP & Network
- 조건부 로직
- amazon aurora
- 지시의 언어
- 안심 첫 문장
- AWS
- 리팩터링이란
- amazon vpc
- 질의함수
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
글 보관함