반응형

iOS 개발자가 많이 하는 실수 - guard를 잘못 사용 (Early Exit Misuse)

Swift의 guard 문은 조건이 충족되지 않으면 즉시 함수 흐름을 종료하는 문법입니다.
그래서 return, break, continue, throw 중 하나가 반드시 필요하며, return을 빠뜨리는 문법적 오류는 애초에 발생할 수 없습니다.
그러나 초보 개발자는 다음과 같은 논리적 오류(Logical Bug)를 자주 겪습니다

 

1. 잘못된 이해: “guard를 쓰면 안전하다”

많은 초보자는 guard가 if let의 단순한 대체라고 생각합니다.

그러나 guard의 본질은:

조건이 안 맞으면 함수 실행을 중단한다(Early Exit)

즉, guard는 함수의 흐름을 분기시키기 때문에 잘못 쓰면 로직이 틀어짐.


2. 실무에서 자주 발생하는 잘못된 guard 사용 패턴


2-1. guard를 너무 “일찍” 사용해 로직이 실행되지 않는 문제

func load() {
    guard isLoaded else { return }   // ❌ isLoaded가 false면 항상 return
    fetchRemoteData()
}

개발자의 의도:

- “로드됐으면 fetch 한다”

실제 동작:

- 로드되기 전에는 영원히 fetchRemoteData가 호출되지 않음

해결

조건의 의미를 다시 점검하고 올바른 위치에 guard 배치.

guard !isLoaded else { return }  // 아직 안 불러왔다면 로드해야 함
fetchRemoteData()

2-2. guard 위치 때문에 함수의 중요한 로직이 실행되지 않는 문제

func configureUI() {
    guard let data = viewModel.data else { return }
    titleLabel.text = data.title

    setupBindings()  // ❌ data가 nil이면 절대 호출되지 않음
}

개발자는 “UI 구성을 모두 여기서 한다”고 생각하지만

실제로는 data가 nil일 때 UI 바인딩이 전혀 실행되지 않음.

해결

UI 흐름 자체가 data 존재 여부에 의존하는지 먼저 판단해야 함.

func configureUI() {
    setupBindings()  // 항상 실행되어야 하는 작업

    guard let data = viewModel.data else {
        titleLabel.text = "기본 제목"
        return
    }

    titleLabel.text = data.title
}

2-3. guard 조건을 과도하게 묶어서 정상 사용자가 차단되는 문제

guard let user = currentUser else { return }
guard user.isLoggedIn else { return }
guard user.isAdmin else { return }  // ❌ 관리자만 통과됨

showDashboard()

처음에는 “권한 체크” 정도로 작성했지만,

이제 일반 로그인 유저는 대시보드를 전혀 볼 수 없음.

해결

권한 체크 로직을 의미별로 분리하거나, 역할 기반 조건 분기 사용.

guard let user = currentUser else { return }
guard user.isLoggedIn else { return }

if user.isAdmin {
    showAdminDashboard()
} else {
    showUserDashboard()
}

2-4. guard return 타입을 잘못 이해한 경우 (Swift 초보 흔한 함정)

func submit() {
    guard let text = textField.text else { return false } // ❌ 컴파일 오류
}

함수의 리턴 타입이 Void인데 guard에서 return false를 사용하려고 함.

해결 1 — 함수 타입에 맞게 수정

func submit() -> Bool {
    guard let text = textField.text else { return false }
    return true
}

해결 2 — Void 함수라면 단순 return 사용

func submit() {
    guard let text = textField.text else { return }
    // ...
}

2-5. guard를 남발하여 함수 가독성을 해치는 문제

guard condition1 else { return }
guard condition2 else { return }
guard condition3 else { return }
guard condition4 else { return }
guard condition5 else { return }

// 실제 로직은 여기부터

guard는 강력한 도구지만

연속으로 여러 번 쓰이면 오히려 흐름이 복잡해짐.

해결

  • 조건을 구조화
  • 하나의 guard에서 묶음 처리
  • 또는 검증 전용 함수를 분리

예)

guard validateUser(), validateToken(), validateSession() else { return }

2-6. guard 아래에서 의미 없는 중복 체크

guard let name = user.name else { return }

// 아래에서 또 nil처럼 다룸 — 불필요한 패턴
if name.isEmpty { }

guard로 이미 nil이 아님을 보장했는데

또 Optional처럼 취급하는 잘못된 로직.

해결

  • guard 이후에는 Optional이 아닌 확정 값으로 생각하고 코드를 작성

3. guard를 잘 쓰는 핵심 원칙 (실무 기준)

  1. guard는 반드시 “실패 처리”를 담당해야 한다.
  2. 정상 흐름 코드는 guard 아래로 “평평하게(flat)” 이어져야 한다.
  3. guard 위치를 잘못 잡으면 함수 전체의 의미가 달라진다.
  4. “이 조건이 실패하면 이 함수는 더 진행할 의미가 없다”라는 상황에서만 guard 사용.
  5. guard 조건이 두 개 이상이면 반드시 조건의 의미 단위로 그룹화해야 한다.

4. 정리

  • guard는 return 누락 때문에 문제가 생기지 않는다 → 컴파일러가 막아줌
  • 진짜 문제는 “잘못된 위치, 잘못된 논리, 과도한 조기 종료(Early Exit)”
  • 예측하지 못한 시점에 함수가 멈춰 로직이 건너뛰어지는 실수가 가장 흔함
  • guard는 강력한 논리 도구이므로,
    “이곳에서 조기 종료가 발생하는 것이 정말 맞는가?”를 항상 고민해야 함

 

반응형
Posted by 까칠코더
,