💭 배경
조건문은 흔히 두 가지 형태로 사용된다.
1. `참일 때와 거짓일 때 모두` 정상 동작으로 이어지는 형태
2. `둘 중 하나`만 정상 동작으로 이어지는 형태
- 선자의 경우
if else
문을 사용한다. - 후자의 경우 비정상 조건을
if
에서 검사한 다음 조건이참
일 때(비정상일 때) 함수에서 빠져나오는 보호 구문을 사용한다.
중첩 조건문을 보호 구문으로 바꾸는 것의 핵심은 의도를 부각하는 것이다.
if-else
문에서if절
과else절
에 똑같은 무게를 두어 코드를 읽는 사람에게 양 갈래의 중요성이 같다는 뜻을 전달한다.- 반대로 보호 구문은 "이것이 이 함수의 핵심이 아니다. 이 일이 일어났을 때는 어떤 조치를 취한 후 함수에서 빠져나온다"는 뜻을 전달한다.
🛡️ 보호 구문 guard clause
- 함수나 메서드의 시작 부분에서 특정 조건을 검사하여 그 조건이 만족되지 않으면 즉시 함수를 종료하는 방식
- 코드의 가독성을 높이고, 중첩된 조건문을 피하는 데 유용하다.
📏 절차
1. 교체해야할 조건 중 `가장 바깥 것`을 선택하여 보호 구문으로 바꾼다.
2. 테스트한다.
3. 1 ~ 2 과정을 필요한 만큼 반복한다.
4. 모든 보호 구문이 `같은 결과`를 반환한다면 보호 구문들의 조건식을 `통합`한다.
🧑🏻🏭 리팩토링 실습
예시코드
1. 조건이 거짓
일 때 실행되는 중첩 조건문
function payAmount(employee) {
let result;
if (employee.isSeperated) { // 퇴사한 직원인가?
result = {amount: 0, reasonCode: 'SEP'};
}
else {
if (employee.isRetired) { // 은퇴한 직원인가?
result = {amount: 0, reasonCode: 'RET'};
}
else {
// 급여 계산 로직
lorem.ipsum(dolor.sitAmet);
consectetur(adipiscing).elit();
setDate.do.eiusmod = tempor.incididunt.ut(labore) && dolore(magna.aliqua);
ut.enim.ad(minim.veniam);
result = someFinalComputation();
}
}
return result;
}
- 직원 급여를 계산하는 코드이다.
- 현직 직원만 급여를 받아야 하므로 이 함수는 두 가지 조건(1. 직원의 퇴사 여부 2. 은퇴 여부)을 검사한다.
- 하지만, 중첩된 조건문때문에 중요한 로직이 제대로 보이지 않는다는 문제가 있다.
- 코드가 진짜 의도한 일은 모든 조건이
거짓
일 때만 실행되기 때문이다.
1️⃣ 최상위 조건을 보호 구문으로 변경
function payAmount(employee) {
let result;
if (employee.isSeperated) return {amount: 0, reasonCode: 'SEP'};
if (employee.isRetired) {
result = {amount: 0, reasonCode: 'RET'};
}
else {
// 급여 계산 로직
lorem.ipsum(dolor.sitAmet);
consectetur(adipiscing).elit();
setDate.do.eiusmod = tempor.incididunt.ut(labore) && dolore(magna.aliqua);
ut.enim.ad(minim.veniam);
result = someFinalComputation();
}
return result;
}
2️⃣ 변경 후 테스트
3️⃣ 다음 조건을 보호 구문으로 변경
function payAmount(employee) {
let result;
if (employee.isSeperated) return {amount: 0, reasonCode: 'SEP'};
if (employee.isRetired) return {result = {amount: 0, reasonCode: 'RET'};}
// 급여 계산 로직
lorem.ipsum(dolor.sitAmet);
consectetur(adipiscing).elit();
setDate.do.eiusmod = tempor.incididunt.ut(labore) && dolore(magna.aliqua);
ut.enim.ad(minim.veniam);
result = someFinalComputation();
return result;
}
4️⃣ 아무 역할이 없는 result 변수 삭제
function payAmount(employee) {
if (employee.isSeperated) return {amount: 0, reasonCode: 'SEP'};
if (employee.isRetired) return {result = {amount: 0, reasonCode: 'RET'};}
// 급여 계산 로직
lorem.ipsum(dolor.sitAmet);
consectetur(adipiscing).elit();
setDate.do.eiusmod = tempor.incididunt.ut(labore) && dolore(magna.aliqua);
ut.enim.ad(minim.veniam);
return someFinalComputation;
}
2. 조건이 참
일 때 실행되는 중첩 조건문
function adjustedCapital(anInstrument) {
let result = 0;
if (anInstrument.capital > 0) {
if (anInstrument.interestRate > 0 && anInstrument.duration > 0) {
result = (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;
}
}
return result;
}
- 조건이 거짓일 때는 return 되도록 조건문을 수정하면 된다.
1️⃣ 최상위 조건을 보호 구문으로 변경 및 조건을 역으로 수정
function adjustedCapital(anInstrument) {
let result = 0;
if (anInstrument.capital <= 0) return result;
if (anInstrument.interestRate > 0 && anInstrument.duration > 0) {
result = (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;
}
return result;
}
2️⃣ not 연산자 추가
function adjustedCapital(anInstrument) {
let result = 0;
if (anInstrument.capital <= 0) return result;
if (!(anInstrument.interestRate > 0 && anInstrument.duration > 0)) return result;
result = (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;
return result;
}
3️⃣ not 연산자 삭제 후 조건 수정
function adjustedCapital(anInstrument) {
let result = 0;
if (anInstrument.capital <= 0) return result;
if (anInstrument.interestRate <= 0 || anInstrument.duration <= 0)) return result;
result = (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;
return result;
}
4️⃣ 같은 결과를 내는 if문의 조건식 통합
function adjustedCapital(anInstrument) {
let result = 0;
if (anInstrument.capital <= 0
|| anInstrument.interestRate <= 0
|| anInstrument.duration <= 0) return result;
result = (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;
return result;
}
5️⃣ 두 가지 일을 하는 변수 result 쪼개기
function adjustedCapital(anInstrument) {
if (anInstrument.capital <= 0
|| anInstrument.interestRate <= 0
|| anInstrument.duration <= 0) return 0;
return (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;
}
- result 라는 변수 하나가 두 가지 용도로 사용되고 있다.
1. 보호 구문이 발동됐을 때 반환할 값 0을 가지는 용도
2. 계산의 최종 결과를 가지는 용도
→ 첫 번째 용도로 쓰이는 result를 제거해, 변수 하나가 두 가지 용도로 쓰이지 않게 바꿔준다.