코틀린을 배우기 위해서 인프런에서 강의를 구매하고 코틀린과 친해지고 기본기를 다지기 위해서 공부하는 중이다. 글 내용은 예외를 다루는 방법이고 최태현님의 자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide) 강의에 소금을 조금 친 내용이다
try-catch 기본 문법
Java vs Kotlin
// Java
private int parseIntOrThrow(@NotNull String str) {
try {
return Integer.parseInt(str);
} catch(NumberFormatException e) {
throw new IllegalArgumentException(String.format("주어진 %s는 숫자가 아닙니다", str));
}
}
// Kotlin
fun parseIntOrThrow(str: String): Int {
try {
return str.toInt()
} catch (e: NumberFormatException) {
throw IllegalArgumentException("주어진 ${str}는 숫자가 아닙니다")
}
}
- 동일한 문법이지만 Kotlin이 좀 더 간결하다
try-catch가 Expression
- Kotlin에서 try-catch는 Expression이므로 값을 반환할 수 있다
fun parseIntOrThrowNull(str: String): Int? {
return try {
str.toInt()
} catch (e: NumberFormatException) {
null
}
}
- return을 한 번만 사용하여 더 간결한 코드 가능
Checked Exception vs Unchecked Exception
Java의 이원화 구조
// Checked Exception - 컴파일러가 강제 처리
public void readFile() throws IoException { } // throws 필수
// Unchecked Exception - 선택적 처리
public void divide(int a, int b) { } // RuntimeException 처리 선택
Kotlin의 단일화 구조
// 모든 예외가 Unchecked Exception
fun readFile() { } // throws 선언 불필요
fun parseNumber(str: String): Int {
return str.toInt() // numberFormatException 처리 선택적
}
Checked Exception의 문제점
- 보일러플레이트 증가: 불필요한 try-catch 코드
- throws 연쇄: 호출 스택 전체에 throws 전파
- API 진화 저해: 새로운 예외 추가 시 모든 호출부 수정
- 함수형 / 비동기 부적합: 람다, 스트림과 궁합 불량
- 의미 없는 빈 catch: 예외를 무시하는 안티패턴 유도
Java가 이렇게 나눈 의도
- Checked Exception
- 대표: IOException, SQLException 등
- 복구 가능(recoverable)한 상황을 강제로 처리하게 만듬
- 개발자가 try-catch 또는 throws로 명시적으로 다루지 않으면 컴파일조차 안 되도록 한 이유는 “파일이 없거나 네트워크 끊김처럼 외부 환경 때문에 발생하는 예외는 반드시 대응해야 한다”는 철학 때문
- “예상 가능한 문제를 무시하지 말라”는 강제 장치
- Unchecked Exception
- 대표: NullPointerException, IllegalArgumentException, IndexOutOfBoundsException 등
- 복구 불가능(프로그램 로직 오류 / 계약 위반) 상황에서 발생
- 개발자가 잘못 작성한 코드에서 나오므로 굳이 강제로 처리하게 할 필요가 없다
- “로직 오류를 잡으려고 애쓰지 말고 빨리 드라나게 하라”는 의도
- Java의 예외 구분은 실제 환경에서 발생할 수 있는 예외적 상황은 반드리 처리하도록 강제 하고, 개발자의 버그는 바로 드러나도록 놔둔다는 설계 철학에서 비롯되었다
Kotlin의 접근 방식
- 예외: 복구 불가능한 버그나 시스템 오류
- Result / sealed class: 복구 가능한 실패를 타입으로 모델링
sealed class Result<out T>
data class Success<T>(val value: T): Result<T>()
data class Failure(val error: String): Result<Nothing>()
fun safeParse(str: String): Result<Int> {
return try {
Success(str.toInt())
} catch (e: NumberFormatException) {
Failure("숫자가 아닙니다: $str")
}
}
try-with-resource vs use()
Java의 try-with-resource
public void readFile(String path) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
System.out.println(reader.readLine());
} // 자동으로 close() 호출
}
Kotlin의 use() 함수
fun readFile(path: String) {
BufferedReader(FileReader(path)).use { reader ->
println(reader.readLine())
} // 자동으로 close() 호출
}
Java는 복구 강제(Checked) + 버그 표기(Unchecked) 이원화, Kotlin은 예외 강제 없음 + 복구는 타입으로 모델
출처 – 인프런 강의 중 자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide)