좋은 함수 만들기
좋은 함수 만들기 - 부작용과 거리두기
좋은 함수란 순수 함수
:::info
순수함수는 동일한 입력일 경우 항상 동일한 출력을 반환하며, 부작용이 없는 함수
:::
리팩토링 될 함수
export async function sendCompanyFees(companySellings: CompanySelling[]) {
for (const companySelling of companySellings) {
const fee = companySelling.sellingAmount * companySelling.commission;
if (fee >= 100) {
await axiosSendFee(companySelling.bankCode, fee);
}
}
Modal.open(`${companySellings.length} 개 기업들에게 송금되었습니다.`);
}
나쁜 리팩토링 사례
// 전체 기능을 관리한다
export async function sendCompanyFees(companySellings: CompanySelling[]) {
await sendFees(companySellings);
Modal.open(`${companySellings.length} 개 기업들에게 송금되었습니다.`);
}
// companySellings 만큼 sendFee를 호출한다
export async function sendFees(companySellings: CompanySelling[]) {
for (const companySelling of companySellings) {
await sendFee(companySelling);
}
}
// 100원이상인 경우 axiosSendFee를 호출한다
export async function sendFee(companySelling: CompanySelling) {
const fee = getFee(companySelling);
if (fee >= 100) {
// 100원 이상이면 계산된 금액 송금하기
await axiosSendFee(companySelling.bankCode, fee);
}
}
function getFee(companySelling: CompanySelling) {
return companySelling.sellingAmount * companySelling.commission;
}
하나의 기능만 하는 함수로 만들었지만 테스트하기 어렵다.
문제점은 크게 3가지
- 상태 검증 불가능하고 axiosSendFee 가 호출되었는지 행위 검증만 가능함
- Mocking 대상 교체되면 테스트 코드가 수정대상이 되어 버림
- 테스트 도구가 교체되면 테스트 코드가 수정되어야 함
왜 테스트 하기 어려운가?
axiosSendFee
가 함수 내부에 깊게 들어와있기 때문이다.
axiosSendFee
가 부작용함수라서 그렇다?
아무튼 테스트 하기 편하기 위해서는 부작용 함수와 순수 함수를 격리해야 한다.
좋은 리팩토링 사례
// ----------------- 부작용 함수 ----------------------------
export async function sendCompanyFees(companySellings: CompanySelling[]) {
await sendFees(companySellings);
Modal.open(`${companySellings.length} 개 기업들에게 송금되었습니다.`);
}
async function sendFees(companySellings: CompanySelling[]) {
// 전송 가능한 데이터들만 전달 받아서
const companyFees = getCompanyFees(companySellings);
// 별도의 추가 작업 없이 바로 전송만 한다.
for (const companyFee of companyFees) {
await axiosSendFee(companyFee.bankCode, companyFee.fee);
}
}
// ----------------- 순수함수 ----------------------------
export function getCompanyFees(companySellings: CompanySelling[]) {
return companySellings
.map((c) => getCompanyFee(c))
.filter((c) => isSend(c));
}
function isSend(c: { fee: number }) {
return c.fee >= 100;
}
function getCompanyFee(companySelling: CompanySelling) {
return {
fee: companySelling.sellingAmount * companySelling.commission,
bankCode: companySelling.bankCode,
};
}
최대한 순수 함수로 만들고 부작용 함수를 사용하는 작업을 최대한 지연시켰다.
순수 함수는 Mocking 없이도 기능을 테스트할 수 있다.
부작용 함수는 Mocking 테스트 혹은 E2E 테스트로 작성하면 된다.
현재의 함수가 테스트 하기가 어렵다면, 함수 설계에 대해 고민해볼 수 있는 신호가 된다.