본문 바로가기
카테고리 없음

else를 쓰지 않기로 했다 - 조건문을 줄이면 코드가 살아난다

by 냉국이 2026. 3. 17.
728x90

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개월 뒤에 열었을 때, "이게 뭐지?"가 아니라 "아 이렇게 했구나"가 나온다면, 그 코드는 잘 쓴 코드다.


함께 읽으면 좋은 글

300x250

댓글