6개월 전에 내가 짠 코드를 다시 열어봤을 때, 처음 든 생각은 "이게 뭐지?"였다. 함수 하나가 4단계 들여쓰기로 가득 차 있었다. if 안에 if, 그 안에 else, 그 안에 또 if. 코드를 이해하려면 괄호를 눈으로 일일이 추적해야 했다. 내가 짠 코드인데 내가 못 읽었다.
그날부터 나는 else를 최대한 쓰지 않기로 했다.
else가 왜 문제인가
else는 본질적으로 "나머지 모든 경우"다. 명확해 보이지만 사실 가장 불명확한 구조다. if 조건이 바뀌면 else가 처리하는 범위도 조용히 바뀐다. 아무도 모르게.
Kent Beck은 이렇게 말했다. "코드는 두 번 쓴다. 한 번은 컴퓨터를 위해, 한 번은 사람을 위해." else 체인은 컴퓨터한테는 완벽하지만, 사람에게는 미로다.
실제로 이런 코드가 있다고 하자.
// ❌ Before: else 중첩
function processOrder(order) {
if (order) {
if (order.isPaid) {
if (order.items.length > 0) {
if (order.user.isVerified) {
// 실제 로직은 여기 4단계 아래에...
ship(order);
return { success: true };
} else {
return { error: '미인증 사용자' };
}
} else {
return { error: '빈 장바구니' };
}
} else {
return { error: '미결제' };
}
} else {
return { error: '주문 없음' };
}
}
이 코드를 읽으면 뇌가 괄호를 쌓기 시작한다. 닫히는 중괄호가 어느 if에 속하는지 세야 한다. 실제 로직인 ship(order)는 4단계 안에 숨어 있다.
Guard Clause — 문지기를 앞으로 빼라
해결책은 단순하다. 실패 조건을 함수 앞으로 끌어낸다. "이 경우가 아니면 바로 나가라"는 패턴이다. Guard Clause, 또는 Early Return이라고 부른다.
// ✅ After: Guard Clause
function processOrder(order) {
if (!order) return { error: '주문 없음' };
if (!order.isPaid) return { error: '미결제' };
if (!order.items.length) return { error: '빈 장바구니' };
if (!order.user.isVerified) return { error: '미인증 사용자' };
// 실제 로직은 여기, 깨끗하게
ship(order);
return { success: true };
}
같은 로직이다. 줄 수도 비슷하다. 하지만 읽는 방식이 완전히 다르다. 위에서 아래로, 순서대로 읽으면 된다. 문지기들이 앞에서 잡아주고, 통과하면 본론이 나온다.
실전에서 쓸 수 있는 4가지 원칙
2년간 코드 리뷰를 하면서 정리한 규칙들이다.
① 들여쓰기가 3단계를 넘으면 함수를 쪼개라
# 들여쓰기 3단계가 보이면 추출 신호
def handle_request(req):
if req.user:
if req.user.is_active:
if req.data:
# 여기쯤에서 함수 추출을 고려
process(req.data)
들여쓰기 depth는 인지 부하와 정비례한다. 3단계를 넘으면 "이 블록을 함수로 빼면 어떨까?"를 자문해보자.
② 조건문 안에 조건문은 대부분 개별 함수로 뺄 수 있다
// ❌ 중첩 조건
if (user.plan === 'premium' && user.credits > 0 && !user.isBanned) {
// ...
}
// ✅ 의미를 이름으로
function canUseFeature(user) {
return user.plan === 'premium' && user.credits > 0 && !user.isBanned;
}
if (canUseFeature(user)) {
// ...
}
조건 자체에 이름을 붙이면, 조건이 무엇을 의미하는지 설명하지 않아도 된다.
③ else if 체인은 대부분 객체 맵이나 switch로 대체된다
// ❌ else if 지옥
function getDiscount(tier) {
if (tier === 'bronze') return 0.05;
else if (tier === 'silver') return 0.10;
else if (tier === 'gold') return 0.20;
else if (tier === 'vip') return 0.30;
else return 0;
}
// ✅ 객체 맵
const DISCOUNT = { bronze: 0.05, silver: 0.10, gold: 0.20, vip: 0.30 };
const getDiscount = (tier) => DISCOUNT[tier] ?? 0;
else if가 5개 이상이면 거의 무조건 테이블이 낫다. 새 케이스 추가도 한 줄이 된다.
④ boolean 반환 함수에는 else가 필요 없다
# ❌ 불필요한 else
def is_valid_email(email):
if '@' in email:
return True
else:
return False
# ✅ 그냥 반환
def is_valid_email(email):
return '@' in email
코드 리뷰에서 이 패턴이 보이면 else를 지워달라고 한다. 군더더기가 없어지면 의도가 더 선명해진다.
else를 쓰지 않으면 달라지는 것
이 원칙을 3개월간 팀에 적용했을 때 생긴 변화가 있다. PR 리뷰 시간이 줄었다. "이 else가 언제 실행되는 거야?"라는 질문이 사라졌다. 신입 개발자가 기존 코드를 더 빠르게 파악했다.
코드는 실행되는 것만이 목적이 아니다. 읽히는 것도 목적이다. 그리고 읽히는 코드는, 다음에 그 코드를 수정할 사람 — 6개월 뒤의 나를 위한 배려다.
오늘 짠 코드를 6개월 뒤에 열었을 때, "이게 뭐지?"가 아니라 "아 이렇게 했구나"가 나온다면, 그 코드는 잘 쓴 코드다.
댓글