조건부 로직은 프로그램을 복잡하게 만드는 주요 원흉이다.
조건문 분해하기
복잡한 조건부 로직은 프로그램을 복잡하게 만드는 가장 흔한 원횽이다.
코드를 부위별로 분해한 후 분해된 코드 덩어리들을 의도를 살린 이름의 함수 호출로 바꾸자
개요
Before
Copy if ( ! aDate .isBefore ( plan .summerStart) && ! aDate .isAfter ( plan .summerEnd)) {
charge = quantity * plan .summerRate;
} else {
charge = quantity * plan .regularRate + plan .regularServiceCharge;
}
After
Copy if ( summer ()) {
charge = summerCharge ();
} else {
charge = regularCharge ();
}
function summer () {
return ! aDate .isBefore ( plan .summerStart) && ! aDate .isAfter ( plan .summerEnd);
}
function summerCharge () {
return quantity * plan .summerRate;
}
function regularCharge () {
return quantity * plan .regularRate + plan .regularServiceCharge;
}
절차
Example
Copy //취향에 따라 3항 연산자로 변경 가능
charge = summer () ? summerCharge () : regularCharge ();
조건식 통합하기
비교하는 조건은 다르지만 결과 로직이 같다면 하나로 통합하자.
여러 조각의 조건들을 통합하면 더 명확해진다.
'무엇'이 아닌 '왜'를 말해주는 함수 추출하기로 이어질 수 있다.
함수 추출하기를 적절히 활용하여 전체를 더 이해하기 쉽게 만들어보자.
개요
Before
Copy if ( anEmployee .seniority < 2 ) return 0 ;
if ( anEmployee .monthDisabled > 12 ) return 0 ;
if ( anEmployee .isPartTime) return 0 ;
After
Copy if ( isNotEligibleForDisability ()) return 0 ;
function isNotEligibleForDisability () {
return (( anEmployee .seniority < 2 )
|| ( anEmployee .monthDisabled < 12 )
|| ( anEmployee .isPartTime));
}
절차
해당 조건식에 사이드 이펙트가 없는지 확인
사이드 이펙트가 있을 경우 질의 함수와 변경 함수 분리하기
선 적용
중첩 조건문을 보호 구문으로 바꾸기
의도를 부각하는 것이 핵심이다.
두 경로 중 한 쪽만 정상이라면 비정상 조건을 if 에서 검사한 뒤, 조건이 참(비정상)이면 함수에서 빠져나오게 하자.
개요
Before
Copy function getPayAmount () {
let result;
if (isDead)
result = deadAmount ();
else {
if (isSeparated)
result = separateAmount ();
else {
if (isRetired)
result = retiredAmount ();
else
result = normalPayAmount ();
}
}
return result;
}
After
Copy function getPayAmount () {
if (isDead) return deadAmount ();
if (isSeparated) return separateAmount ();
if (isRetired) return retiredAmount ();
return normalPayAmount ();
}
절차
교체해야 할 조건 중 가장 바깥 것을 선택하여 보호 구문으로 바꾸기
조건부 로직을 다형성으로 바꾸기
복잡한 조건부 로직은 클래스와 다형성을 이용하여 더 확실하게 분리하자.
개요
Before
Copy function plumages (birds) {
return new Map ( birds .map ((b) => [ b .name , plumage (b)]));
}
function speeds (birds) {
return new Map ( birds .map ((b) => [ b .name , airSpeedVelocity (b)]));
}
function plumage (bird) {
switch ( bird .type) {
case '유럽 제비' :
return '보통이다' ;
case '아프리카 제비' :
return bird .numberOfCoconuts > 2 ? '지쳤다' : '보통이다' ;
case '노르웨이 파랑 앵무' :
return bird .voltage > 100 ? '그을렸다' : '예쁘다' ;
default :
return '알 수 없다' ;
}
}
function airSpeedVelocity (bird) {
switch ( bird .type) {
//...
}
}
After
Copy function plumages (birds) {
return new Map (birds
.map ((b) => createBird (b))
.map ((bird) => [ bird .name , plumage (bird)]) ,
);
}
function speeds (birds) {
return new Map (birds
.map ((b) => createBird (b))
.map ((bird) => [ bird .name , bird .airSpeedVelocity]) ,
);
}
function createBird (bird) {
switch ( bird .type) {
case '유럽 제비' :
return new EuropeanSwallow (bird);
case '아프리카 제비' :
return new AfricanSwallow (bird);
case '노르웨이 파랑 앵무' :
return new NorwegianBlueParrot (bird);
default :
return new Bird (bird);
}
}
class Bird {
constructor (birdObject) {
Object .assign ( this , birdObject);
}
get plumage () { return '알 수 없다' ; }
get airSpeedVelocity () { return null ; }
}
class EuropeanSwallow extends Bird {
get plumage () { return '보통이다' ; }
get airSpeedVelocity () { return 35 ; }
}
class AfricanSwallow extends Bird {
get plumage () { return this .numberOfCoconuts > 2 ? '지쳤다' : '보통이다' ; }
get airSpeedVelocity () { return 40 - 2 * bird .numberOfCoconuts; }
}
class NorwegianBlueParrot extends Bird {
get plumage () { return this .voltage > 100 ? '그을렸다' : '예쁘다' ; }
get airSpeedVelocity () { return bird .isNailed ? 0 : 10 + bird .voltage / 10 ; }
}
절차
다형적 동작을 표현하는 클래스 만들기
적합한 인스턴스를 알아서 만들어 반환하는 Factory 함수
도 만들기
호출 코드에서 Factory 함수를 사용하도록 수정
서브 클래스 중 하나를 선택하여, 슈퍼클래스의 조건부 로직 메서드를 오버라이드하기
해당 서브클래스에 해당하는 조건절을 메서드로 복사
같은 방식으로 각 조건절을 해당 서브클래스에서 메서드로 구현하기
슈퍼클래스 메서드에는 깁노 동작 부분만 남기기
특이 케이스 추가하기
특정 값에 대해 똑같이 반응하는 코드가 여러 곳에 있다면 그 반응들을 한데로 모으자.
특이 케이스 패턴 : 특수한 경우의 공통 동작을 요소 하나에 모아서 사용하는 패턴
특이 케이스를 확인하는 코드 대부분을 단순 함수 호출로 수정 가능
개요
Before
Copy if (aCustomer === '미확인 고객' ) customerName = '거주자' ;
After
Copy class UnknownCustomer { // 자바의 경우 서브 클래스로 사용
get name () { return '거주자' ; }
}
절차
슈퍼 클래스에 특이 케이스인지를 검사
하는 속성 추가 (false 반환)
Copy class Customer {
get isUnknown () { return false ; }
// 향후 추가
get name {}
get billingPlan () {}
set billingPlan (arg) {}
get paymentHistory () {}
}
특이 케이스 전용 서브 클래스
만들기
특이 케이스인지 검사하는 속성만 포함 (true 반환)
Copy class UnknownCustomer {
get isUnknown () { return true ; }
// 향후 특이 케이스 기본값 추가
get name () { return '거주자' ; }
get paymentHistory () { return new NullPaymentHistory (); }
}
// ..
class NullPaymentHistory {
get weeksDelinquentInLastYEar () { return 0 ; }
}
클라이언트에서 특이 케이스인지 검사하는 코드를 함수로 추출
값을 직접 비교하는 코드를 추출한 함수로 수정
Copy function isUnknown (arg) {
if ( ! (arg instanceof Customer || arg === '미확인 고객' )) {
throw new Error ( `잘못된 값과 비교: < ${ arg } >` );
}
return arg === '미확인 고객' ;
}
코드에 새로운 특이 케이스 대상 추가
Copy class Site {
get customer () {
return ( this ._customer === '미확인 고객' ) ? new UnknownCustomer () : this ._customer;
}
}
특이 케이스를 검사하는 함수 본문을 특이 케이스 객체 속성을 사용
하도록 수정
Copy function isUnknown (arg) {
if ( ! (arg instanceof Customer || arg instanceof UnknownCustomer )) {
throw new Error ( `잘못된 값과 비교: < ${ arg } >` );
}
return arg .isUnknown;
}
여러 함수를 클래스 묶기
나 변환함수로 묶기
적용
특이 케이스를 처리하는 공통 동작을 새로운 요소로 옮기기
특이 케이스 검사 함수를 이용하는 곳이 남아 있다면 검사 함수 인라인
어서션 추가하기
assertion 은 프로그램이 어떤 상태임을 가정하고 실행되는지 다른 개발자에게 알려주는 훌륭한 소통 도구다.
개요
Before
Copy if ( this .discountRate) {
base = base - this .discountRate * base;
}
After
Copy assert ( this .discountRate >= 0 );
if ( this .discountRate) {
base = base - this .discountRate * base;
}
절차
참이라고 가정하는 조건이 보이면 그 조건을 명시하는 어서션 추가하기
반드시 참이어야 하는 것 혹은 프로그래머가 일으킬만한 오류에만 어서션을 사용하자.
제어 플래그를 탈출문으로 바꾸기
제어 플래그 : 코드의 동작을 변경하는 데 사용되는 변수
개요
Before
Copy for ( const p of people) {
if ( ! found) {
if (p === '조커' ) {
sendAlert ();
found = true ;
}
}
}
After
Copy for ( const p of people) {
if (p === '조커' ) {
sendAlert ();
break ;
}
}
절차
제어 플래그를 사용하는 코드를 함수로 추출
할지 고려하기
제어 플래그를 갱신하는 코드 각각을 적절한 제어문
으로 바꾸기 (매번 테스트)
제어문으로 주로 return, break, continue 사용
Last updated 11 months ago