코드 조각을 찾아 무슨 일을 하는지 파악한 다음, 독립된 함수로 추출하고 목적에 맞는 이름을 붙인다.
언제 독립된 함수로 묶어야 할지에 관한 의견이 분분함
함수의 목적이 눈에 확 들어오고, 본문 코드(목적을 이루기 위해 구체적으로 수행하는 일)에 대해서는 더 이상 신경 쓸 필요가 없다.
함수명으로 함수의 목적을 나타내고, 그 목적을 이루기 위한 구현코드를 분리한다.
function printOwing(invoice) {
printBanner();
let outstanding = calculateOutstanding();
// 세부 사항 출력
console.log(`고객명 : ${invoice.customer}`);
console.log(`채무액 : ${outstanding}`);
}
function printOwing(invoice) {
printBanner();
let outstanding = calculateOutstanding();
printDetails(outstanding);
function printDetails(outstanding){
console.log(`고객명 : ${invoice.customer}`);
console.log(`채무액 : ${outstanding}`);
}
}
함수 본문이 이름만큼 명확한 경우 또는 함수 본문 코드를 이름만큼 깔끔하게 리팩터링가능하다면 함수를 제거하고 인라인으로 기술한다.
함수 추출하기와 반대되는 개념
잘못 추출한 함수들을 다시 인라인한다.(과도한 함수추출) 유용한 것만 남기고 나머지는 제거해야 한다.
// 등급
function getRating(driver) {
return moreThanFiveLateDeliveries(driver) ? 2 : 1;
}
****
function moreThanFiveLateDeliveries(driver) {
return driver.numberOfLateDeliveries > 5;
}
function getRating(driver) {
return (driver.numberOfLateDeliveries > 5) ? 2 : 1;
}
표현식이 너무 복잡하여 이해하기 어려울 경우, 지역 변수를 활용하여 표현식을 쪼개 관리
로직을 구성하는 단계마다 이름을 붙여 목적을 명확하게 드러낼 수 있다.
// 가격(price) = 기본 가격 - 수량 할인 + 배송비
return order.quantity * order.itemPrice -
Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 +
Math.min(order.quantity * order.itemPrice * 0.1, 100);
const basePrice = order.quantity * order.itemPrice;
const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05
const shipping = Math.min(basePrice * 0.1, 100);
return basePrice - quantityDiscount + shipping;
변수 이름이 원래 표현식과 다를 바가 없을 때
let basePrice = anOrder.basePrice;
return (basePrice > 1000);
return anOrder.basePrice > 1000;
함수는 잘 정의하면 새로운 부분을 추가하기가 쉬워지지만, 잘못 정의하면 지속적인 방해요인이 될 수 있다. 함수의 존재가 방해요인이 될때 수정을 생각해봐야 한다.
// 함수 이름이 너무 축약되어 있음
function circum(radius) {...}
function circumference(radius) {...}
지불 기한(30일 기준)이 넘었는지 판단하는 함수 존재할 때 매개변수를 어떤것으로 설정할 것인가?
private boolean 지불기한이지났는지체크(지불객체) {
return baseDate + 30 < 지불객체.마감일;
}
private boolean 지불기한이지났는지체크(마감일) {
return baseDate + 30 < 마감일;
}
// 로직이 복잡해진다면?
private boolean 지불기한이지났는지체크(지불객체) {
if(지불객체.유예기간연장여부) ...
...
...
return baseDate + 30 < 지불객체.마감일;
}
// 메서드 시그니처가 변경됨
private boolean 지불기한이지났는지체크(마감일, 유예기간, ...) {
....
return baseDate + 30 < 마감일;
}
데이터의 사용 범위가 넓을수록 절절히 캡슐화하자
let defaultOwner = {firstName: "마틴", lastName: "파울러"};
let defaultOwner = {firstName: "마틴", lastName: "파울러"};
export function defaultOwner() {return defaultOwner;} // 접근
export function setDefaultOwner(arg) {defaultOwnerData = arg;} // 갱신
명확한 프로그램의 핵심은 이름짓기!
let a = height * width;
let area = height * width;
한 줄짜리 담다식에서 사용하는 변수는 맥락으로부터 변수의 목적을 명확히 알 수 있어 한 글자로 된 이름을 짓기도 함
데이터 항목 여러 개가 공통적으로 매개변수로 사용될 경우 데이터 무리를 데이터 구조 하나로 모아주자.
function amountInvoiced(startDate, endDate) {...}
function amountReceived(startDate, endDate) {...}
function amountOverdue(startDate, endDate) {...}
// 범위(range)라는 개념은 객체 하나로 묶어 표현하는 게 나은 대표적인 예
function amountInvoiced(aDateRange) {...}
function amountReceived(aDateRange) {...}
function amountOverdue(aDateRange) {...}
데이터 구조에 담길 데이터에 공통으로 적용되는 동작을 추출해서 함수로 사용한다면 문제를 훨씬 간결하게 표현할 수 있다.
// 측정
function readingsOutsideRange(station, range) {
return station.readings
.filter(r => r.temp < range.min || r.temp > range.max);
}
function readingsOutsideRange(station, range) {
return station.readings
.filter(r => !range.contains(r.temp));
}
// NumberRange
contains(args) { return (arg >= this.min && arg <= this.max);}
공통 데이터를 중심으로 긴밀하게 엮여 작동하는 함수 무리는 하나의 클래스로 묶자
function base(aReading) {...}
function taxableCharge(aReading) {...}
function calculateBaseCharge(aReading) {...}
class Reading {
base() {...}
taxableCharge() {...}
calculateBaseCharge() {...}
}
클래스 : 데이터와 함수를 하나의 공유 환경으로 묶은 후, 다른 프로그램 요소와 어우러질 수 있도록 그중 일부를 외부에 제공한다. (캡슐화)
// 측정값 기록
reading = {custmer: "ivan", quantity: 10, month: 5, year: 2017};
// client1
const aReading = acquireReading();
const baseCharge = baseRate(aReading.month, aReading.year) * aReading.quantity; // 기본요금
// client2
const aReading = acquireReading();
const base = baseRate(aReading.month, aReading.year) * aReading.quantity;
const taxableCharge = Math.max(0, base - taxThreshold(aReading.year)); // 면세
// client3
const aReading = acquireReading();
const basicChargeAmount = calculateBaseCharge(aReading);
function calculateBaseCharge(aReading) {
return baseRate(aReading.month, aReading.year) * aReading.quantity;
}
// 함수들이 공유하는 공통 데이터를 캡슐화하고 메소드를 추가하자.
class Reading {
constructor(data) {
this._customer = data.customer;
this._quantity = data.quantity;
this._month = data.month;
this._year = data.year;
}
get customer() {return this._customer;}
get quantity() {return this._quantity;}
get month() {return this._month;}
get year() {return this._year;}
// 함수들을 데이터 처리 코드 가까이에 둔다.
get baseCharge() {
return baseRate(this.month, this.year) * this.quantity;
}
get taxableCharge() {
return Math.max(0, this.baseCharge - taxThreshold(reading.year));
}
}
// client1
const rawReading = acquireReading();
const aReading = new Reading(rawReading);
const basicChargeAmount = aReading.baseCharge;
파생 데이터 모두 필요한 시점에 계산되게 만들었으니 저장된 데이터를 갱신하더라도 문제가 생길 일이 없다.
⇒ 가변적인 데이터를 사용할때 (데이터 갱신 가능성이 있을 경우) 클래스로 묶어두면 큰 도움이된다.
데이터를 입력받아서 여러 가지 정보를 도출하는 로직이 반복되는 경우 고려해보자
function base(aReading) {...}
function taxableCharge(aReading) {...}
function enrichReading(argReading) {
const aReading = _.cloentDeep(argReading);
aReading.baseCharge = base(aReading);
aReading.taxableCharge = taxableCharge(aReading);
return aReading;
}
갱신을 일관된 장소에서 처리할 수 있고 로직 중복도 막을 수 있다.
rawReading = {custmer: "ivan", quantity: 10, month: 5, year: 2017};
// 변환함수로 묶기 - 원본 데이터가 갱신되는 경우 사용 x, 일관성이 깨질 수 있음 // 불변 또는 읽기전용 문맥
aReading = enrichReading(rawReading);
...
aReading.quantity = aReading.quantity + 1;
...
pay(aReading.baseCharge); // quantity 11 ?
// 클래스로 묶기 - 원본 데이터가 갱신되는 경우 사용 o
aReading = new Reading(rawReading);
...
aReading.quantity = aReading.quantity + 1;
....
pay(aReading.baseCharge);
서로 다른 두 대상을 한꺼번에 다루는 코드를 발견하면, 각각 별개 모듈로 나누는 방법을 모색하자.
orderData = "필통-0 2"
priceList = ["1000", "2000", ...]
const orderData = orderString.split(/\s+/);
const productPrice = priceList[orderData[0].split("-")[1]];
const orderPrice = parseInt(orderData[1]) * productPrice;
const orderRecord = parseOrder(order);
const orderPrice = price(orderRecord, priceList);
function parseOrder(aString) {
const values = aString.split(/\s+/);
return ({
productID: values[0].split("-")[1],
quantity: parseInt(values[1])
});
}
function price(order, priceList) {
return order.quantity * priceList[order.productID];
}
코드를 수정해야 할 때 두 대상을 동시에 생각할 필요 없이 하나에만 집중하기 위함
동작을 연이은 단계로 쪼개자
입력이 처리 로직에 적합하지 않은 상태로 들어오는 경우
컴파일러
출처: https://cornswrold.tistory.com/550 [평범한개발자노트:티스토리]