클린 코드
클린 코드란 무엇일까? 글쓰기로 예를 들어보겠습니다. 누군가 써놓은 글이 의미를 알 수 없고 난잡한 글일 때 그 글을 이해하기 어려울 것이다. 코드도 결국엔 글이다. 이해하기 어려운 글(코드)은 사람들에게 오해를 불러 일으킬 수 있다. 난잡하고 요상한 코드는 이 코드가 무엇을 의미하고 뭘 하고 싶은지 알 수 없게 한다.
그렇기에 우리는 코드를 작성할 때 읽기 쉬운 방향으로 변경하도록 노력해야한다.
도서 Clean Code에 이런 말이 있다.
"유지보수 하기 쉽게 코드를 작성하고 코드에 의도를 담아라. 6개월 뒤에 그 코드를 다시 보는 사람은 아주 높은 확률로 바로 너다."
지금 나에게만 정리되어 보이고, 내 눈에만 잘 읽히는 코드는 아무 의미가 없다.
우리는 코드를 단순히 동작하게 작성하는 것에서 남이 봐도 이해할 수 있도록 작성하는 것으로 코드를 바라보는 관점을 변경해야 한다.
조건과 탈출
조건문을 작성할 때에는 명확한 조건을 선택해야 한다. 명확한 조건을 선택하는 여러가지 방법들에 대해 살펴보겠습니다.
1. 긍정 조건 사용하기
다음 조건문 예시를 보면 부정 값을 조건문에 사용하고 있고, 조건문에서도 not을 주어 처리하고 있다. 부정의 부정이라 조건문을 잘못 해석할 여지가 다분하다.
//bad
const isNotBoy = false;
//good
const isBoy = true;
// ! 표기가 잘보이지 않고 부정의 부정이라 조건문을 잘못 해석할 여지가 다분함
if(!isNotBoy){}
if(isBoy){}그렇다면 꼭 부정 값을 사용해야 할 때에는?
조건문(if) 사용하기 직전에 긍정 값으로 한 번 변경해주는 것이 좋다.
const isBoy = !isNotBoy // if 문 바로 상단에서 긍정 조건으로 변경
if(isBoy){}2. 조건문에 이름 붙이기
조건문을 작성할 때 조건만을 나열하여 사용하지 말고, 어떤 조건인지 한 번 명시해준 뒤에 작성해보자. 가독성이 훨씬 좋아지고, 몇년 뒤에 보더라도 알아 볼 수 있을 것이다.
// bad
if(age >= 13 && age <= 19){
/* 실행 코드 ... */
}
// good
const 청소년 = age >= 13 && age <= 19;
if(청소년){
/* 실행 코드 ... */
}
3. 함수를 통해 조건 사용하기
만약 아주 복잡한 조건문이고, 조건이 추후에도 변경이 될 여지가 있다면 다음과 같이 함수로 분리하여 조건 체크를 할 수 있도록 작성해보자. 확장성과 가독성 둘 다 증가한다.
//bad
if(age >= 13
&& age <= 19
&& address === "서울시 어디 어디"
&& school === "서울 어디 학교"
){
/* 실행 코드 ... */
// 그리고 또 다른 조건이 추가될 수도 있음
}
// good
const checkDataValid = ({age, address, school}) => {
let result true;
/* 비즈니스 로직 */
return result;
}
if(checkDataValid(age, address, school)) {
/* 실행 코드 ... */
}4. 조건문의 성능을 신경쓰지 말기
10개의 조건을 2개로 줄인다면 성능상으로 얼마나 이득이 있을까? 티도 나지 않을 만큼 매우 적은 차이이다.
따라서 조건문을 압축하고 성능을 신경쓰다가 너무나 압축된 조건문을 다시 해석하는데에 더 많은 시간을 써야할 가능성이 높다.
즉, 조건문을 최적화하기보다 명확하고 읽기 편하게 작성하는것이 더 좋다.
5. 조건, 반복 depth 지옥
조건문을 작성할 때 가장 주의할 점 중 하나는 바로 깊이(Deep)이다. 이중(if 안에 if), 삼중, N 중 중첩문을 가장 기피해야 한다. 가능하다면 최대 깊이가 1인 분기문을 작성하는 것이 좋다.
이 때 사용하는 패턴이 Guard Clause인데, early return 이라는 이름으로 더 익숙한 패턴이다.
이름에서 알 수 있듯 조건을 판별하여 거짓이라면 예외(혹은 반환) 처리하여 더 이상 다음 단계가 실행되지 않도록 사전에 차단하는 방식이다.
// bad
async function guardClause(userId) {
const user = await getUser(userId);
if (user) {
const orders = await getOrders(user);
if (orders && orders.length > 0) {
const order = orders.find(order => order.value > 100);
if (order) {
// 실제 처리할 로직
console.log(`Found an order from user ${user.id} with value greater than 100: ${order.id}`);
}
}
}
}
// good
async function guardClause(userId) {
const user = await getUser(userId);
if (!user) return;
const orders = await getOrders(user);
if (!orders || orders.length === 0) return;
const order = orders.find(order => order.value > 100);
if (!order) return;
// 실제 처리할 로직
console.log(`Found an order from user ${user.id} with value greater than 100: ${order.id}`);
}코드의 기능은 똑같지만 아래 예시가 더 보기 쉽고 편할 것이다.
Early return 주의할 점
-
함수의 일관성이 떨어질 수 있다.
Early return은 먼저 예외(혹은 반환) 처리를 함으로써 함수의 흐름을 분산시킬 수 있기 때문에 함수의 일관성을 해칠 수 있다. 따라서 함수를 읽는 사람이 어떤 조건에서 반환이 일어나는지를 정확히 이해하고 추적할 수 있어야 한다. -
명확하게 종료되지 않는다.
이름에서 알 수 있듯이 return문 뒤에 다른 비즈니스 로직이 있을 수 있다. 단순히return;만 작성할 경우 함수는undefined를 반환하게 된다. void 함수일 때는 문제가 없지만 필요한 반환값이 존재할 경우 오류가 발생할 수 있다. 이 때 초기값을 할당함으로써 해결할 수 있다.
function calculateTotalPrice(items) {
// 초기값 설정
let totalPrice = 0;
if (!items || items.length === 0) {
return totalPrice; // 그냥 return;이 아닌 초기값 반환
}
items.forEach(item => {
totalPrice += item.price * item.quantity;
});
return totalPrice;
}위 예시처럼 초기값을 설정하여 반환하지 않을 경우 함수를 호출하는 측에서 따로 undefined값에 대한 처리를 해주어야 한다.
무분별하게 early return을 사용하지 않도록 return을 작성할 때는 신중하게 하는 것이 좋다.
6. Object Mapping 방식
조건문을 효과적으로 줄일 수 있는 방식으로 Object Mapping 방식을 사용해 볼 수 있다.
import img1 from '...'
import img2 from '...'
import img3 from '...'
import img4 from '...'
let imgSrc;
if(imgId === 1){imgSrc = img1}
if(imgId === 2){imgSrc = img2}
if(imgId === 3){imgSrc = img3}
if(imgId === 4){imgSrc = img4}
//imgMapper.js
export const imageMapper = {
1:img1,
2:img2,
3:img3,
4:img4,
}
// 사용처
let imgSrc = imgMapper[imgId];정리
코드를 작성할 때 가장 조심해야할 부분이 조건문이라고 생각한다. 정말 사소한 실수로 막대한 손해를 볼 수 있기 때문이다. 따라서 이런 실수를 발생시키지 않으려면 버그가 발생하지 않도록 명확한 조건문을 작성해야 하고, 버그가 발생하더라도 빠르게 추적하여 해결할 수 있어야 한다. 즉, 조건문을 작성 할 때에는 웬만하면 성능보다는 가독성을 최우선으로 챙길 수 있도록 합시다.
클린코드
#1 [클린코드 - 의미 있는 변수명]
#2 현재 글 - [클린코드 - 조건과 탈출]
#3 [클린코드 - 코드 가독성]