logo
새로운 블로그로 이전하였습니다.

(리팩터링 2판) ch10.3 중첩 조건문을 보호 구문으로 바꾸기

  • 리팩터링

💭 배경

조건문은 흔히 두 가지 형태로 사용된다.

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를 제거해, 변수 하나가 두 가지 용도로 쓰이지 않게 바꿔준다.


Source

리팩터링 2판, 마틴파울러