[ Programming > Refactoring ]
[리팩터링] 조건부 로직 간소화
[리팩터링] 조건부 로직 간소화
조건부 로직은 자주 쓰이지만 프로그램을 복잡하게 만드는 주요 원인 중 하나다. 그래서 이번 장에서는 조건부 로직을 가독성있고 유지 보수성있게 만드는 방법을 알려주고자 한다.
1. 조건부 로직 간소화 방법
1.1 조건문 분해하기
다양한 조건에 따라 동작도 다양한 코드를 작성하면 긴 함수가 만들어진다. 그래서 조건에 따른 동작을 표현한 코드는 "무슨 일이 일어나는 지"와 "왜 일어나는 지"를 말해줘야 가독성이 좋아진다. 간단히 말하면 코드를 부위별로 분해하고, 각 부위를 의도를 살린 이름의 함수 호출로 바꾸는 작업이다.
아래는 계절이 여름이면 할인율이 달라지는 예시 코드에 조건문 분해하기를 적용한 예시다.
// 리팩터링 전 여름에 할인율이 다른 코드if(!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd)) { // 함수 추출 1 charge = quantity * plan.summerRate; // 함수 추출 2} else { charge = quantity * plan.regularRate + plan.regularServiceCharge; // 함수 추출 3}// 리팩터링 후 추상화 된 코드 => 여름이면 여름 요금을 계산하고 아니면 보통 요금을 계산한다// 어떻게 => 무엇을 왜 했는지 더 잘 들어나게 됌. (기본 리팩터링편 글에서도 "어떻게" 보다 "목적"을 더 잘 들어내야 함을 강조했었다!!)if(summer()) { // 1. 여름이면 charge = summerCharge(); // 2-1. 여름 요금 계산} else { charge = regularCharge(); // 2-2. 보통 요금 계산}function summer() {...};function summerCharge() {...};function regularCharge() {...};// 가다듬기charge = summer() ? summerCharge() : regularCharge();
1.2 조건식 통합하기
조건식 통합하기는 비교 조건이 다르지만 그 결과는 동일한 동작을 수행해야하는 경우 수행한다. 이 경우 보통 하나로 통합하여 함수로 추출하면 그 의미가 더 명확해지는 경우가 많다. 물론 독립된 검사들이라 판단되면 이 리팩터링을 해서는 안되며 만약 통합하는 경우 부수효과가 생기는지 잘 확인해야한다.
아래는 조건식 통합하는 예시 코드다.
// 장애 수당 계산 함수.function disabilityAmount(anEmployee) { if (anEmployee.seniority < 2) return 0; if (anEmployee.monthsDisabled > 12) return 0; if (anEmployee.isPartTime) return 0; ... // 아래는 장애 수당 계산 구현부}// 조건식 통합 적용 후function disabilityAmount(anEmployee) { // 장애 조건에 적합하지 않는 경우 0원 if(isNotEligibleForDisability()) return 0; function isNotEligibleForDisability() { return (anEmployee.seniority < 2) || (anEmployee.monthsDisabled > 12) || (anEmployee.isPartTime); } ... // 아래는 장애 수당 계산 구현부}
1.3 중첩 조건문을 보호 구문으로 바꾸기
조건문은 주로 두 가지 형태로 쓰인다. 참인 경로와 거짓인 경로 모두 정상 동작이거나, 한쪽만 정상 동작인 형태다.
두 형태는 의도하는 바가 서로 다르므로 그 의도가 잘 드러나야 한다. 두 경로 모두 정상 동작이면 if else를 쓰고 한쪽만 정상이면 비정상 조건을 if에서 검사하여 빠져나오는 것이 깔끔하다. 특히 한쪽이 비정상 여부만 결정하여 함수를 이어갈 지 결정하는 형태를 보호 구문(guard clause)라고 한다.
보호 구문을 사용하는 것은"이건 이 함수의 핵심이 아니니, 이 일이 일어나면 무언가 조치를 취하고 함수에서 빠져나와"라고 이야기하는 것과 같다. 이와 달리 if else절을 사용하는 것은 양 쪽 조건의 길이 모두 중요함을 의미한다.
아래는 보호 구문으로 바꾸는 예시이다.
// 진짜 의도한 일들이 거짓일 때만 작동하는 중첩 조건문 코드.function payAmount(employee) { let result; if (employee.isSeparated) { result = {amount:0, reasonCode: "SEP"}; } else { if (employee.isRetired) { result = {amount:0, reasonCode: "RET"}; } else { ... 기타 로직들 result = someFinalComputation(); } } return result;}// 리팩터링 적용 코드function payAmount(employee) { if (employee.isSeparated) return {amount:0, reasonCode: "SEP"}; if (employee.isRetired) return {amount:0, reasonCode: "RET"}; ...기타 로직들 return someFinalComputation();}
아래는 보호 구문으로 바꾸는데 일부러 조건식을 반대로 만들어 적용하는 예시다.
// 대출이 0 초과, 이자율 0초과, 기간 0초과인 경우에 계산하는 코드.function adjustedCaptial(anInstrument) { let result = 0; if (anInstrument.captial > 0) { if(anInstrument.interestRate > 0 && anInstrument.duration > 0) { result = (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor; } } return result;}// 리팩터링 후 (조건문 반대인 경우를 활용해 보호 구문으로 변경)function adjustedCapital(anInstrument) { if( anInstrument.capital <= 0 || anInstrument.interestRate <= 0 || anInstrument.duration <= 0) { return 0; } return (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;}
1.4 조건부 로직을 다형성으로 바꾸기
복잡한 조건부 로직은 해석하기 가장 힘든 대상에 속한다. 그래서 좀 더 직관적으로 구조화할 방법을 찾는게 좋다. 그리고 종종 더 높은 수준의 개념을 도입해 조건들을 분리할 수 있다. 그 방법으로 클래스와 다형성을 이용하는 방법 또는 Map 자료형을 사용하는 방법이 있다.
예를 들면 switch case 문을 사용해서 타입별로 처리 방법이 다른 경우가 있다. 이 경우 case 별로 클래스를 만들어 각 클래스가 알아서 처리하도록 만들 수 있고, 아니면 Map을 통해 각 case 별로 처리하는 함수를 만들어 줄 수도 있다.
디자인 패턴을 공부해본 사람이라면 SOLID 원칙의 OCP와 DIP를 떠올렸을 것 같다. 그런데 같은 interface를 항상 사용할 지는 알 수 없다. 기본적인 로직들을 사용하는 case문도 있지만 변형 동작을 사용하는 case들도 있기 때문이다. 그래서 변형 동작이 필요한 로직들은 또 다른 서브 클래스로 만들 필요가 있을 수 있다.(다형성의 사용)
그런데 다형성 사용은 공통 인터페이스를 활용할 수 없다면 오히려 더 복잡해지고 SOLID 원칙의 ISP를 위반하며 구현해야하는 경우가 있다. (예를 들면 어떤 함수의 파라미터로 들어가는 변수를 억지로 같은 Interface나 Class를 갖게 만드는 경우, 그게 불가능 하여 상속하여 나눠지는 경우).
이제 적용 예시를 보자.
// 새의 종에 따른 비행 속도와 깃털 상태를 알려주는 코드.function plumages(birde) { return new Map(birds.map(b => [b.name, pumage(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.numberOfCounts > 2) ? "지쳤다" : "보통이다"; case '노르웨이 제비': return (bird.voltage > 100) ? "그을렸다" : "예쁘다"; default: return "알 수 없다"; }}function airSpeedVelocity(bird) { switch (bird.type) { case '유럽 제비': return 35; case '아프리카 제비': return 40 - 2 * bird.numberOfCoconuts; case '노르웨이 제비': return (bird.isNailed) ? 0 : 10 + bird.voltage / 10; default: return null; }}
아래가 리팩터링 적용하기 위해 추가한 코드다. airSpeedVelocity와 pumage를 Bird 클래스로 묶었다.
interface Bird { // 에러를 추가해도 될 지도? get plumage() { return "알 수 없다" } get irSpeedVelocity() { return null; }}class 유럽제비 implements Bird { get plumage() { return "보통이다"; } get irSpeedVelocity() { return 35; }}... 다른 제비들
이제 위 클래스를 활용하여 이전 코드를 바꿔보자.
// 리팩터링 후 코드.function createBird(bird) { switch (bird.type) { case '유럽 제비': return new 유럽제비(bird); case '아프리카 제비': return new 아프리카제비(bird); case '노르웨이 제비': return new 노르웨이제비(bird); default: return new Bird(bird); }}function plumages(birde) { return new Map(birds.map(b => createBird(b)).map(bird => [bird.name, bird.plumage])));}function speeds(birds) { return new Map(birds.map(b => createBird(b)).map(bird => [bird.name, bird.airSpeedVelocity])));}
위 방식은 거의 똑같은 제비 객체지만 다른 부분도 있음을 표현하는 데 상속을 썼다. 하지만 보통 위 방식대로 되는 경우는 흔하지 않은 방식이다. 실제는 더 복잡할 것이다.
그래서 이번에는 변형 동작을 다형성으로 자바스크립트에서 표현하는 방법을 설명하겠다.
// 신용 평가 기관에서 선박의 항해 투자 등급을 계산하는 코드function rating(voyage, history) { // 투자 등급 const vpf = voyageProfitFactor(voyage, history); const vr = voyageRisk(voyage); const chr = captainHistoryRisk(voyage, history); if (vpf * 3 > (vr + chr * 2)) return "A"; else return "B";}function voyageRisk(voyage) { // 항해 경로 위험요소 let result = 1; if (voyage.length > 4) result += 2; if (voyage.length > 8) result += voyage.length 8; if (["china", "eastindies"].includes(voyage.zone)) result += 4; return Math.max(result, 0);}function captainHistoryRisk(voyage, history) { // 선장의 항해 이력 위험요소 let result = 1; if (history.length < 5) result += 4; result += history.filter(v => v.profit < 0).length; if (voyage.zone === "china" && hasChina(history)) result = 2; return Math.max(result, 0);}function hasChina(history) { // 중국 권유 여부 return history.some(v => "china" === v.zone);}function voyageProfitFactor(voyage, history) { // 수익 요인 let result = 2; if (voyage.zone === "china") result += 1; if (voyage.zone === "eastindies") result += 1; if (voyage.zone === "china" && hasChina(history)) { result += 3; if (history.length > 10) result += 1; if (voyage.length > 12) result += 1; if (voyage.length > 18) result = 1; } else { if (history.length > 8) result += 1; if (voyage.length > 14) result = 1; } return result;}// 호출 코드const voyage = {zone: "westindies", length: 10};const history = [ {zone: "eastindies", profit: 5}, {zone: "westindies", profit: 15}, {zone: "china", profit: 2}, {zone: "westafrica", profit: 7},];const myRating = rating(voyage, history);
위 코드를 구조화 및 추상화 해보자
위험 점수(항해 위험 요소 + 선장 위험 요소 * 2) = vr + chr * 2 ;
항해 위험 요소 = voyageRisk
voyage
선장 위험 요소 = captainHistoryRisk
voyage
history + china
수익 점수(vpf * 3 )
수익 요소 = voyageProfitFactor
voyage
history + china
투자 등급(rating)
수익 점수 > 위험 점수 = "A"
수익 점수 < 위험 점수 = "B"
투자 등급은 수익 점수와 위험 점수를 비교하여 결정되는 것을 알 수 있다. 그리고 주목할 부분은 선장이 china를 다녀본 이력이 있는지에 따라 선장 위험 요소와 수익 요소의 값이 달라지는 로직이 있다는 것이다.
// Rating을 구하는 공통 클래스를 만들어 보자function rating(voyage, history) { return new Rating(voyage, history).value;}class Rating { constructor(voyage, history) { this.history = history; this.voyage = voyage; } get value() { const vpf = this.voyageProfitFactor; const vr = this.voyageRisk; const chr = this.captainHistoryRisk; if (vpf * 3 > (vr + chr * 2)) return "A"; else return "B"; } get voyageRisk() { let result = 1; if (this.voyage.length > 4) result += 2; if (this.voyage.length > 8) result += this.voyage.length 8; if (["china", "eastindies"].includes(this.voyage.zone)) result += 4; return Math.max(result, 0); } get captainHistoryRisk() { let result = 1; if (this.history.length < 5) result += 4; result += this.history.filter(v => v.profit < 0).length; if (this.voyage.zone === "china" && this.hasChinaHistory) result = 2; return Math.max(result, 0); } get voyageProfitFactor() { let result = 2; if (this.voyage.zone === "china") result += 1; if (this.voyage.zone === "eastindies") result += 1; if (this.voyage.zone === "china" && this.hasChinaHistory) { result += 3; if (this.history.length > 10) result += 1; if (this.voyage.length > 12) result += 1; if (this.voyage.length > 18) result = 1; } else { if (this.history.length > 8) result += 1; if (this.voyage.length > 14) result = 1; } return result; } get hasChinaHistory() { return this.history.some(v => "china" === v.zone); }}
위와 같이 기본 Rating 클래스가 만들어졌다. 그런데 단순히 연관된 여러 객체를 사용하는 여러 함수를 클래스로 묶었을 뿐이다. 이제 china를 다녀와 본 경우의 Rating과 아닌경우 Rating을 분리해보자.
function createRating(voyage, history) { if (voyage.zone === "china" && history.some(v => "china" === v.zone)) return new ExperiencedChinaRating(voyage, history); else return new Rating(voyage, history);}class Rating { get captainHistoryRisk() { let result = 1; if (this.history.length < 5) result += 4; result += this.history.filter(v => v.profit < 0).length; if (this.voyage.zone === "china" && this.hasChinaHistory) result = 2; return Math.max(result, 0); } get voyageProfitFactor() { let result = 2; if (this.voyage.zone === "china") result += 1; if (this.voyage.zone === "eastindies") result += 1; result += this.voyageAndHistoryLengthFactor; return result; } get voyageAndHistoryLengthFactor() { let result = 0; if (this.history.length > 8) result += 1; if (this.voyage.length > 14) result -= 1; return result; }}class ExperiencedChinaRating extends Rating { get captainHistoryRisk() { // 왜 데코레이터 패턴이 생각날까!! const result = super.captainHistoryRisk - 2; return Math.max(result, 0); } get voyageAndHistoryLengthFactor() { let result = 0; result += 3; if (history.length > 10) result += 1; if (voyage.length > 12) result += 1; if (voyage.length > 18) result -= 1; return result; }}
이렇게 오버라이딩해서 China 항해 여부에 따라 ExperiencedChinaRating 와 Rating로 나눌 수 있게 되었다. 여기 까지가 리팩터링 끝이지만 좀 더 다듬어 보자. voyageAndHistoryLengthFactor 가 한번에 2가지 일을 하면서 악취가 나서 좀 더 분리해 보자.
class Rating { constructor(voyage, history) { this.history = history; this.voyage = voyage; } get value() { const vpf = this.voyageProfitFactor; const vr = this.voyageRisk; const chr = this.captainHistoryRisk; if (vpf * 3 > (vr + chr * 2)) return "A"; else return "B"; } get voyageRisk() { let result = 1; if (this.voyage.length > 4) result += 2; if (this.voyage.length > 8) result += this.voyage.length 8; if (["china", "eastindies"].includes(this.voyage.zone)) result += 4; return Math.max(result, 0); } get captainHistoryRisk() { let result = 1; if (this.history.length < 5) result += 4; result += this.history.filter(v => v.profit < 0).length; return Math.max(result, 0); } get voyageProfitFactor() { let result = 2; if (this.voyage.zone === "china") result += 1; if (this.voyage.zone === "eastindies") result += 1; result += this.historyLengthFactor; result += this.voyageLengthFactor; return result; } get voyageLengthFactor() { return (this.voyage.length > 14) ? 1: 0; } get historyLengthFactor() { return (this.history.length > 8) ? 1 : 0; }}class ExperiencedChinaRating extends Rating { get captainHistoryRisk() { const result = super.captainHistoryRisk - 2; return Math.max(result, 0); } get voyageLengthFactor() { let result = 0; if (this.voyage.length > 12) result += 1; if (this.voyage.length > 18) result -= 1; return result; } get historyLengthFactor() { return (this.history.length > 10) ? 1 : 0; } get voyageProfitFactor() { return super.voyageProfitFactor + 3; }}
1.5 특이 케이스 추가하기
우리는 데이터 구조의 특정 값을 확인한 후 똑같은 동작을 수행하는 코드가 곳곳에 등장하는 경우가 많다. 이러한 특수한 경우의 공통 동작을 요소 하나에 모아서 하용하는 것을 특이 케이스 패턴이라 한다.
특이 케이스는 여러 형태로 표현 가능하다
특이 케이스 표현
특이 케이스 객체에서 데이터를 읽기만 한다면 반환할 값들을 담은 리터럴 객체 형태로 만든다.
어떤 동작까지 수행해야 한다면, 필요한 메서드를 담은 객체로 만든다.
위를 캡슐화한 클래스를 반환할 수도 있다.
혹은 변환을 거쳐 데이터 구조에 추가시키는 형태도 가능하다.
이제 코드로 어느 경우를 특이 케이스로 규정하는지 보자.
/* 전기 회사에서 현장에서 고객이 거주하고 있지 않거나, 새로 이사왔거나 등의 이유로 확인이 어려운 경우가 있다. 이러한 케이스를 확인하는 코드가 여러번 생길 수 밖에 없다. */// 코드 Case 1. 고객이 누군지 모르면 "거주자"라는 이름으로 사용한다.const aCustomer = site.customer;let customerName;if(aCustomer === "미확인 고객") customerName = "거주자";else customerName = aCustomer.name;// 코드 Case 2. 고객이 누군지 모르면 기본 요금 플랜을 할당한다.const plan = (aCustomer === "미확인 고객") ? registry.billingPlans.basic : aCustomer.billingPlan;// 코드 Case 3. 고객이 누군지 알면 새로운 요금 플램을 적용한다.if (aCustomer !== "미확인 고객") aCustomer.billingPlan = newPlan;// 코드 Case 4. 고객이 누군지 모르면 작년 요금 지연납부 횟수는 0이다const weeksDeliquent = (aCustomer === "미확인 고객") ? 0 : aCustomer.paymentHistory.weeksDeliquentInLastYear;
위 코드를 보면 "미확인 고객" 에 따라 동작을 취하는 경우가 많다는 것을 알 수 있다. 우선 위 코드에서 직접 확인하는 방식들을 공통으로 빼주면 좋을 것 같다. 그리고 Customer 클래스를 상속하는 UnkownCustomer 클래스도 만들어 보자.
class Site { get customer() { return (this._customer === "미확인 고객") ? new UnkownCustomer() : this._customer; } }class Customer { get name() {return this._name;} get billingPlan() {return this._plan;} get paymentHistory() {return this._paymentHistory;} get weeksDelinquentInLastYear() {return this._weeksDeliquentInLastYear;} get isUnkown() {return false;}}class UnkownCustomer { get name() {return "미확인 거주자";} get billingPlan() {return registry.billingPlans.basic;} set billingPlan(arg) {/* BLANK */ } get paymentHistory() {return new NullPaymentHistory();} get weeksDelinquentInLastYear() {return 0;} get isUnkown() {return true;}}
이제 위 코드를 토대로 클라이언트 코드를 바꿔보자.
// 코드 Case 1. 고객이 누군지 모르면 "거주자"라는 이름으로 사용한다.const aCustomer = site.customer;let customerName = aCustomer.name;// 코드 Case 2. 고객이 누군지 모르면 기본 요금 플랜을 할당한다.const plan = aCustomer.billingPlan;// 코드 Case 3. 고객이 누군지 알면 새로운 요금 플랜을 적용한다.aCustomer.billingPlan = newPlan;// 코드 Case 4. 고객이 누군지 모르면 작년 요금 지연납부 횟수는 0이다const weeksDeliquent = aCustomer.weeksDeliquentInLastYear;
그런데 만약 어떤 Case에서는 "미확인 거주자"의 name 을 "거주자"로 사용해야 한다면 어떻게 할까?? 즉 24개의 케이스가 모두 동일하게 사용하지만 소수의 케이스만 다르게 사용하는 경우를 말한다. 이런 케이스는 아래와 같이 사용한다.
const name = aCustomer.isUnkown ? "거주자" : acustomer.name; // "미확인 거주자"가 아니라 그냥 "거주자"를 여기서만 쓰는 경우.
위의 예시처럼 단순한 값을 위해 클래스까지 동원하는 것은 과한 감이 있다. 하지만 고객 정보가 갱신될 수 있어서 클래스가 꼭 필요했다. 데이터 구조를 읽기만 한다면 클래스 대신 리터럴 객체를 사용해도 된다.
이제 데이터 갱신이 없이 읽기만 하는경우에 리터럴 객체를 사용하는 예시 코드다.
function createUnkownCustomer() { return { isUnkown: true, name: "미확인 거주자", billingPlan: registry.billingPlan, paymentHistory: { weeksDeliquentInLastYear: 0 } }}class Site { get customer() { return (this._customer === "미확인 고객") ? createUnkownCustomer() : this._customer; } }/* 이전에 소개한 4개의 케이스 중에서 3번 케이스를 제외한다면 이렇게 바꿔 사용하면 된다. 그리고 혹시 객체가 변경될 수 있으면 중첩된 것 까지 모두 freeze()해야 한다. */
마지막은 변환 함수를 이용하는 것이다. 앞의 두 예는 Class와 연관이 있지만 이번에는 단순한 레코드 구조를 사용하는 경우 방식으로 특이 케이스를 처리하겠다.
// site의 레코드 구조 - 고객 확인된 경우{ name: "애크미 보스턴", location: "Malden MA", customer: { name: "애크미 산업", billingPlan: "plan-451", paymentHistory: { weeksDelinquentInLastYear: 7 } }}// site의 레코드 구조 - 고객 미확인 경우{ name: "물류창고 15", location: "Malden MA", customer: "미확인 고객"}
// 위 레코드 구조로 클라이언트가 사용하는 Case들// Case 1. 거주자 이름 가져오기const site = acquireSiteData();const aCustomer = site.customer;let customerName;if (aCustomer === "미확인 고객") customerName = "미확인 거주자";else customerName = aCustomer.name;// Case 2. 플랜 가져오기const plane = (aCustomer === "미확인 고객") ? registry.billing.basic : aCustomer.billingPlan;// Case 3. 체납 기록const weeksDelinquent = (aCustomer === "미확인 고객") ? 0 : aCustomer.paymentHistory.weeksDelinquentInLastYear;
이제 위 레코드에 생명을 불어넣어 if 문을 없애 보겠다.
function enrichSite(rawSite) { const result = _.cloneDeep(rawSite); const unkownCustomer = { isUnkown: true, name: "미확인 거주자", billingPlan: registry.billingPlans.basic, paymentHistory: { weeksDelinquentInLastYear: 0, } }; if (isUnkown(result.customer)) result.customer = unkownCustomer; else result.customer.isUnkown = false; return result; function isUnkown(aCustomer) { return aCustomer === "미확인 고객"; }}
// 위 레코드 enrichSite 함수를 사용하는 경우// Case 1. 거주자 이름 가져오기const rawSite = acquireSiteData();const site = enrichSite(rawSite);const aCustomer = site.customer;else customerName = aCustomer.name;// Case 2. 플랜 가져오기const plane = aCustomer.billingPlan;// Case 3. 체납 기록const weeksDelinquent = aCustomer.paymentHistory.weeksDelinquentInLastYear;
1.6 제어 플래그를 탈출문으로 바꾸기
제어 플래그는 코드의 동작을 변경하는 데 사용되는 변수다. 어딘가에서 값을 계산해 제어 플래그 설정 후, 다른 어딘가에서 조건문에서 검사하는 형태로 쓰인다. 그런데 리팩터링 저자는 이런 걸 악취라고 부른다.
리팩터링 저자는 위와 같은 코드는 break이나 continue 문에 익숙하지 않거나, return이 1개 이기를 고집하는 사람들이 이렇게 만든다고 한다. 저자는 이에 return이 1개만 있어야 하는 것에 동의하지 않고, 함수는 자기 할 일이 끝나면 return으로 알리는게 좋다고 생각한다.
아래가 그 예시 코드다.
// 리팩터링 전let found = false;for (const p of people) { if(!found) { if(p === "조커") { sendAlert(); found = true; } if(p === "사루만") { sendAlert(); found = true; } }}// 리팩터링 후function checkForMiscreants(people) { for(const p of people) { if(p === "조커") { sendAlert(); return; } if(p === "사루만") { sendAlert(); return; } }}// 가다듬기function checkForMiscreants(people) { if (people.some(p => ["조커", "사루만"].includes(p)) sendAlert();}
2. 마무리
만약 리팩터링을 여기까지 글을 읽었으면 조건문을 어떻게 잘 없애고 깔끔하게 만들 수 있을지 방법을 많이 알았을 것 같다. 위의 방법들도 있지만 나는 Object Literal(Map 자료형)으로 switch 문을 없애고 사용하기도 한다 예를 들면 object["타입"](파라미터) 이런식으로 사용할 수 있다. 이런 방법 외에도 되게 많을 것 같다.
좋은 코드를 위해 더 열심히 공부해서 유지 보수성, 가독성, 확장성 좋은 유연한 코드를 만들자.