본론으로 들어가기 앞서서 제어문에 대해서 알아보자.
제어문 (control flow statements)
프로그램의 순차적인 흐름을 제어해야 할 때 사용하는 실행문을 제어문이라고 한다.
제어문에는 조건문, 반복문 등이 포함된다.
우리는 코드를 작성할 때 제어문을 상당히 많이 사용한다. 그 중에서 다양한 조건을 설정할 수 있도록 if-else 문을 중첩하여 사용할 일이 아주 많다. 자주 사용하는 만큼 제대로 알고, 좋은 성능과 가독성을 챙기면 더욱 좋겠다 생각하여 if-else문을 더욱 좋은 성능과 가독성을 얻을 수 있는 코드작성 방법을 포스팅해본다. 총 4가지의 방법을 알아보려하는데 2개의 포스팅으로 나누어서 작성하려고 한다. 이유는 내 머리에 다 안들어오기 때문임.
조건문의 성능과 가독성을 위한 포스팅이므로 예시 코드들의 조건들에 대한 예외처리는 그냥 넘어가주세영~
1. 긴 조건식은 함수로 분리
아래 코드를 보자. 뭐.. num이 1인지 아닌지를 구별하는 조건문이다.
if (num === 1) {
console.log("정답입니다.");
} else if (num !== 1) {
console.log("오답입니다.");
}
위의 코드를 봤을 때는 그렇게 가독성이 안좋아보이진 않는다. 하지만 그것은 조건식이 간단했을 때만 들 수 있는 생각이다. 조건식을 좀더 길게 만들어보자.
const now = new Date();
const year = now.getFullYear();
if (now > new Date(year, 3, 2) && now < new Date(year, 6, 17)) {
console.log("1학기입니다.");
} else if (now > new Date(year, 9, 1) && now < new Date(year, 12, 27)) {
console.log("2학기입니다.");
}
위의 코드는 현재의 날짜를 구해서 대학교에서 1학기인지 2학기인지를 구하는 조건문이다. 처음 코드의 간단한 조건식보다는 확실히 난잡하고 가독성이 떨어진다. 그럼 조건식을 함수로 만들어서 조건식을 심플하게 만들어보자.
const now = new Date();
const isFirstSemester = (now) => {
const year = now.getFullYear();
return now > new Date(year, 3, 2) && now < new Date(year, 6, 17);
};
const isSecondSemester = (now) => {
const year = now.getFullYear();
return now > new Date(year, 9, 1) && now < new Date(year, 12, 27);
};
if (isFirstSemester(now)) {
console.log("1학기입니다.");
} else if (isSecondSemester(now)) {
console.log("2학기입니다.");
}
1학기의 조건, 2학기의 조건을 각각 함수의 return문으로 넣어주고, if문의 조건식에 함수 호출문을 넣어주어 반환되는 boolean값을 통해 조건문이 동작되게 함으로써 코드를 모듈화 하여 가독성을 좋게해준다.
2. Early Return 기법
Early Return은 조건문에서 먼저 return이 가능한 부분을 분리하여 조건문의 초반인 if문 내에서 return하여 함수를 미리 종료할 수 있는 기법이다. 쉽게 말하면, 딱 명시되어서 먼저 return 가능한 부분을 위로 꺼내서 가능한 함수를 빠르게 종료시키고 가독성을 좋게 만드는 것이다. 코드를 통해 예를 들어보겠다. orderProcess() 함수에 주문상태, 결제상태, 재고상태를 확인하는 중첩된 조건문을 만들어보자.
const orderProcess = (orderStatus, paymentStatus, inventoryStatus) => {
let result = "";
let count = 0;
// 1. 주문상태 확인
if (orderStatus === false) {
// 2. 결제상태 확인
if (paymentStatus === true) {
// 3. 재고상태 확인
if (inventoryStatus === 0) {
storeInventory(); // 재고채우는 로직
result = "재고를 채웠습니다.";
} else {
completeOrder(); // 주문완료하는 로직
result = "주문이 완료되었습니다.";
}
} else {
throw new Error("결제가 완료되지 않았습니다!");
}
} else {
result = "이미 주문이 완료되었습니다.";
}
result += ` (주문요청 횟수 ${++count}번)`;
return result;
};
위처럼 조건이 계속 중첩해서 들어가기 때문에 코드 가독성이 좋지않다. 그럼 Early Return기법을 통해 좀더 좋은 성능과 가독성을 위해 최적화를 해보자.
1. if문 다음에 나오는 공통적인 절차를 첫번째 분기점 내부에 넣어준다.
이게 몬말임..? 싶을수도 있지만 코드를 통해서 보면, 이해가 될 것이다.
const orderProcess = (orderStatus, paymentStatus, inventoryStatus) => {
let result = "";
let count = 0;
if (orderStatus === false) {
if (paymentStatus === true) {
if (inventoryStatus === 0) {
storeInventory(); // 재고 채우는 로직
result = "재고를 채웠습니다.";
} else {
completeOrder(); // 주문 완료시키는 로직
result = "주문이 완료되었습니다.";
}
} else {
throw new Error("결제가 완료되지 않았습니다!");
}
result += ` (주문요청 횟수 ${++count}번)`; // 공통된 절차
return result; // 공통된 절차
} else {
result = "이미 주문이 완료되었습니다.";
result += ` (주문요청 횟수 ${++count}번)`; // 공통된 절차
return result; // 공통된 절차
}
};
위처럼 조건에 상관없이 공통적인 절차를 첫번째 분기점(코드 레벨의 첫 if문)인 orderStatus === false 조건문의 if블럭, else블럭에 각각 넣어준다는 것이다.
2. 분기점에서 짧은 절차부터 실행할 수 있도록 if문을 반전 시켜준다.
원본코드를 보면, else문보다 if문이 코드절차가 더 긴 것을 알 수 있다. 따라서, 절차가 더 짧은 else 블럭 코드부분을 if 블럭에 넣어주는 식으로 if블럭과 else블럭의 코드를 반전시키는 것이다. 이걸 어떻게 하냐? if(orderStatus === false) 이였던 것을 if(orderStatus === true) 로 바꿔주면 코드를 반전시킬 수 있다.
const orderProcess = (orderStatus, paymentStatus, inventoryStatus) => {
let result = "";
let count = 0;
if (orderStatus === true) {
result = "이미 주문이 완료되었습니다.";
result += ` (주문요청 횟수 ${++count}번)`; // 공통된 절차
return result; // 공통된 절차
} else {
if (paymentStatus === true) {
if (inventoryStatus === 0) {
storeInventory(); // 재고 채우는 로직
result = "재고를 채웠습니다.";
} else {
completeOrder(); // 주문 완료시키는 로직
result = "주문이 완료되었습니다.";
}
} else {
throw new Error("결제가 완료되지 않았습니다!");
}
result += ` (주문요청 횟수 ${++count}번)`; // 공통된 절차
return result; // 공통된 절차
}
};
요론식으로 바꿔주는 것이다.
3. 첫번째 분기의 else를 제거해준다.
이번 단계를 통해 if문의 중첩 한개가 제거된다.
const orderProcess = (orderStatus, paymentStatus, inventoryStatus) => {
let result = "";
let count = 0;
if (orderStatus === true) {
result = "이미 주문이 완료되었습니다.";
result += ` (주문요청 횟수 ${++count}번)`; // 공통된 절차
return result; // 공통된 절차
} // 이곳의 else를 제거해준 것이다.
if (paymentStatus === true) {
if (inventoryStatus === 0) {
storeInventory(); // 재고 채우는 로직
result = "재고를 채웠습니다.";
} else {
completeOrder(); // 주문 완료시키는 로직
result = "주문이 완료되었습니다.";
}
} else {
throw new Error("결제가 완료되지 않았습니다!");
}
result += ` (주문요청 횟수 ${++count}번)`; // 공통된 절차
return result; // 공통된 절차
};
4. 조건문의 분기마다 1~3번까지의 방법을 똑같이 반복해준다.
const orderProcess = (orderStatus, paymentStatus, inventoryStatus) => {
let result = "";
let count = 0;
if (orderStatus === true) {
result = "이미 주문이 완료되었습니다.";
result += ` (주문요청 횟수 ${++count}번)`; // 공통된 절차
return result; // 공통된 절차
}
if (paymentStatus === true) {
if (inventoryStatus === 0) {
storeInventory(); // 재고 채우는 로직
result = "재고를 채웠습니다.";
} else {
completeOrder(); // 주문 완료시키는 로직
result = "주문이 완료되었습니다.";
}
result += ` (주문요청 횟수 ${++count}번)`; // 공통된 절차
return result; // 공통된 절차
} else {
throw new Error("결제가 완료되지 않았습니다!");
}
};
위의 1번방법과 똑같이 공통된 절차를 중첩조건문의 첫번째 분기점인 paymentStatus === true 조건문의 if블럭, else블럭에 각각 넣어주는데, 위 예제의 else블럭은 에러를 던져주기 때문에 return이 필요없기 때문에 if블럭에만 넣어주고 else블럭에서는 생략해준다.
const orderProcess = (orderStatus, paymentStatus, inventoryStatus) => {
let result = "";
let count = 0;
if (orderStatus === true) {
result = "이미 주문이 완료되었습니다.";
result += ` (주문요청 횟수 ${++count}번)`; // 공통된 절차
return result; // 공통된 절차
}
if (paymentStatus === false) {
throw new Error("결제가 완료되지 않았습니다!");
} else {
if (inventoryStatus === 0) {
storeInventory(); // 재고 채우는 로직
result = "재고를 채웠습니다.";
} else {
completeOrder(); // 주문 완료시키는 로직
result = "주문이 완료되었습니다.";
}
result += ` (주문요청 횟수 ${++count}번)`; // 공통된 절차
return result; // 공통된 절차
}
};
위의 2번방법과 똑같이 분기점에서 짧은 절차부터 실행시킬 수 있게 if(paymentStatus === true) 이였던 것을 if(paymentStatus === false) 로 바꿔주고 if블럭과 else블럭의 코드를 반전시켜주었다.
const orderProcess = (orderStatus, paymentStatus, inventoryStatus) => {
let result = "";
let count = 0;
if (orderStatus === true) {
result = "이미 주문이 완료되었습니다.";
result += ` (주문요청 횟수 ${++count}번)`; // 공통된 절차
return result; // 공통된 절차
}
if (paymentStatus === false) {
throw new Error("결제가 완료되지 않았습니다!");
}
if (inventoryStatus === 0) {
storeInventory(); // 재고 채우는 로직
result = "재고를 채웠습니다.";
} else {
completeOrder(); // 주문 완료시키는 로직
result = "주문이 완료되었습니다.";
}
result += ` (주문요청 횟수 ${++count}번)`; // 공통된 절차
return result; // 공통된 절차
};
위의 3번방법과 똑같이 else를 제거해줌으로써 중첩조건문의 중첩 하나를 제거해준다.
리팩토링 후 최종 코드
const orderProcess = (orderStatus, paymentStatus, inventoryStatus) => {
let result = "";
let count = 0;
if (orderStatus === true) {
result = "이미 주문이 완료되었습니다.";
result += ` (주문요청 횟수 ${++count}번)`;
return result;
}
if (paymentStatus === false) {
throw new Error("결제가 완료되지 않았습니다!");
}
if (inventoryStatus === 0) {
storeInventory();
result = "재고를 채웠습니다.";
result += ` (주문요청 횟수 ${++count}번)`;
return result;
}
completeOrder();
result = "주문이 완료되었습니다.";
result += ` (주문요청 횟수 ${++count}번)`;
return result;
};
Early Return 기법은 이러한 방법으로 중첩 조건문들을 하나씩 꺼내면서 조건문들이 중첩되지 않고 하나의 레벨에서 각각 조건들을 판별할 수 있도록 만들어주는 것이다. 초기의 코드보다 조건들을 분리하여 생각할 수 있기 때문에 더욱 로직파악하기가 쉬워진다. 아주 쓸모있는 방법이니 자주 연습해보자!! 다른 방법들은 다음 포스팅에서 계속..