(Android/안드로이드) runCatching vs try-catch 비교 분석

✨ 개요

실무에서 언제 무엇을 쓰는 게 맞을까?


1. 요약


2. 개념 차이: “문(Statement)” vs “값(Expression)”

2-1. try-catch 제어문에 가까움(명시적 흐름)

try {
    risky()
    doNext()
} catch (e: Exception) {
    handle(e)
}

2-2. runCatching: 결과를 값(Result)으로 감쌈(함수형 스타일)

val result = runCatching { risky() }

3. runCatching이 빛나는 포인트: 체이닝과 조합

3-1. 성공/실패 후속 처리 체이닝

runCatching { api.getUser() }
    .onSuccess { user -> cache.save(user) }
    .onFailure { e -> logger(e) }

3.2 기본값 제공

val port = runCatching { config.readPort() }
    .getOrElse { 8080 }

3.3 fold로 한 번에 분기

val text = runCatching { readFile() }.fold(
    onSuccess = { it },
    onFailure = { "fallback" }
)

이런 “값 기반 흐름”은 try-catch보다 깔끔한 경우가 많습니다.


4. try-catch가 더 안전한 포인트: 흐름 제어/가독성/정책

4-1. 조기 return, break/continue 필요할 때

fun load(): Data {
    return try {
        repo.load()
    } catch (e: IOException) {
        return Data.Empty // 명시적
    }
}

4-2. “어떤 예외를 잡을지”가 중요한 경우

try {
    parser.parse(input)
} catch (e: JsonDataException) {
    // JSON 구조 문제만 처리
} catch (e: IOException) {
    // 네트워크/파일 IO 문제만 처리
}

5. 가장 중요한 실무 리스크: runCatching은 “예외 삼키기”가 쉽다

runCatching { risky() } /// XXX

runCatching { risky() }
    .onFailure { e -> Log.e("TAG", "failed", e) } /// OOO

6. Coroutine에서 특히 주의: CancellationException

// 위험 예시
lifecycleScope.launch {
    runCatching { delay(1000) } // 취소 예외를 잡아버릴 수 있음
}

// 권장 패턴
runCatching { delay(1000) }
    .onFailure { e ->
        if (e is CancellationException) throw e
        Log.e("TAG", "error", e)
    }

// try-catch
try {
    delay(1000)
} catch (e: CancellationException) {
    throw e
} catch (e: Exception) {
    Log.e("TAG", "error", e)
}


Related Posts