728x90
- 함수가 원하는 결과를 만들어 낼 수 없을 때가 있는데 이러한 상황을 처리하는 매커니즘은 크게 다음과 같이 두 가지가 있다.
- null 또는 '실패를 나타내는 sealed 클래스(일반적으로는 Failure 라는 이름을 붙입니다)'를 리턴한다
- 예외를 throw 한다.
- 일단 예외는 정보를 전달하는 방법으로 사용해서는 안된다
- 예외는 잘못된 특별한 상황을 나타내야 하며, 처리되어야 한다
- 예외는 예외적인 상황이 발생했을 때 사용하는 것이 좋다.
- 많은 개발자가 예외가 전파되는 과정을 제대로 추적하지 못한다.
- 코틀린의 모든 예외는 unchecked 예외 입니다.따라서 사용자가 예외를 처리하지 않을 수도 있다.
- 예외는 예외적인 상황을 처리하기 위해서 만들어졌으므로 명시적인 테스트 만큼 빠르게 동작하지 않습니다.
- 일반적인 코드흐름에 비해 느리다 라는것을 의미하는듯 (if-else 같은..코드에 비해)
- 예외 생성 비용 - 예외 객체를 생성할 때, 스택 트레이스와 관련 정보를 캡처하는 과정이 필요하며, 이는 리소스 소모를 뜻한다
- try-catch 블록 내부에 코드를 배치하면, 컴파일러가 할 수 있는 최적화가 제한됩니다.
- try-catch 블록은 일반적인 실행 경로와 다른 제어 흐름을 가지고 있다
- 예외가 발생할 경우에만 catch 블록이 실행된다 이러한 동적인 실행 경로 때문에 컴파일러가 최적화 하기 어렵다
- 예외처리 코드는 일반적으로 복잡한 로직을 포함하기때문에 그만큼 복잡해 진다
- 아래는 Reulst 와같은 공용체(union type) 리턴방식 코틀린 코드
- 공용체 타입: 여러 다른 타입 중 하나를 가질 수 있는 타입을 의미
- Result -> 성공 또는 실패의 결과를 나타내는 두가지 가능한 값을 포함
fun calculateSum(a: Int, b: Int): Result<Int> = runCatching {
if (a + b > 100) throw Exception("Sum is greater than 100")
else a + b
}
또는
fun calculateSum(a: Int, b: Int): Result<Int> {
return if (a + b > 100) {
Result.failure(Exception("Sum is greater than 100"))
} else {
Result.success(a + b)
}
}
fun mainResult() {
val result = calculateSum(50, 51)
when {
result.isSuccess -> println("Sum is ${result.getOrNull()}")
result.isFailure -> println("Error: ${result.exceptionOrNull()?.message}")
}
}
- try-catch 와 runCatching 비교 코드
try {
val result = riskyOperation()
println("Operation succeeded: $result")
} catch (e: Exception) {
println("Operation failed: ${e.message}")
}
riskyOperation().runCatching {
println("Operation succeeded: $this")
}.onSuccess {
// onSuccess 블록은 runCatching 블록의 결과가 성공적일 때 실행됩니다.
// 'it'은 runCatching 블록에서 반환된 결과값입니다.
println("The result was successful: $it")
}.onFailure {
// 'it'은 발생한 예외 객체입니다.
println("Operation failed: ${it.message}")
}
- 추가적인 정보를 전달해야 한다면 sealed result 를 사용하고, 그렇지 않으면 null 을 사용하는 것이 일반적이다.
sealed class Result<out T> {
data class Success<out T>(val data: T) : Result<T>()
data class Failure(val exception: Exception) : Result<Nothing>()
}
fun calculateSum(a: Int, b: Int): Result<Int> {
return try {
Result.Success(a + b)
} catch (e: Exception) {
Result.Failure(e)
}
}
@Test
fun mainResult() {
val result = calculateSum(50, 51)
when (result) {
is Result.Success -> println("Sum is ${result.data}")
is Result.Failure -> println("Error: ${result.exception.message}")
}
}
- runCatching 과 같이 코틀린 표준 라이브러리를 써도되고, 복잡한 결과를 좀 더 세밀하게 처리하고 싶다면 sealed Result 를 만들어서 처리하는것도 괜찮다. 아래처럼 파일열기, 읽기, 변환, 저장 등의 복잡한 상황에서의 핸들링을 할 수 있는 코드를 적어봤다
sealed class ProcessResult<out T> {
data class Success<out T>(val data: T) : ProcessResult<T>()
sealed class Failure : ProcessResult<Nothing>() {
object FileNotFound : Failure()
object PermissionDenied : Failure()
data class ParsingError(val error: String) : Failure()
data class ApiError(val code: Int, val message: String) : Failure()
}
}
fun processFile(fileName: String): ProcessResult<String> {
// 파일 처리 로직 구현
return ProcessResult.Failure.FileNotFound // 예시
}
@Test
fun sealedResultTest() {
val result = processFile("example.txt")
when (result) {
is ProcessResult.Success -> println("File processed: ${result.data}")
is ProcessResult.Failure.FileNotFound -> println("File not found")
is ProcessResult.Failure.PermissionDenied -> println("Permission denied")
is ProcessResult.Failure.ParsingError -> println("Parsing error: ${result.error}")
is ProcessResult.Failure.ApiError -> println("API error ${result.code}: ${result.message}")
}
}
- 예상할 수 있는 상황과 예상할 수 없는 상황
- 특정 작업을 수행할때 예상할 수 있는 상황과 예상할 수 없는 상황이 있을 수 있다, list 컬렉션으로 예를 들어보자
- 아래의 코드와 같이
@Test
fun listTest() {
val list = listOf(1, 2, 3, 4, 5)
val get = list[0] // list.get(0)
println("get: $get")
// 리스트에서 특정 위치의 요소를 반환하지만, 요청한 인덱스가 범위를 벗어날 경우 null을 반환합니다.
val orNull = list.getOrNull(10) // list.get(10) or null
println("orNull: $orNull")
list.getOrElse(0) { 0 } // list.get(0) or 0
// Elvis 연산자 사용
val first = list.firstOrNull() ?: 0
println("first: $first")
val last = list.lastOrNull() ?: 0
println("last: $last")
// get: 1
// orNull: null
// first: 1
// last: 5
}
- nullable 을 리턴하면 안되며, null 이 발생할 수 있다는 경고를 주려면, getOrNull 등을 사용해서 무엇이 리턴되는지 예측할 수 있게 하는 것이 좋다
좋은 내용인거 같고, try-catch 말고 요즘 runCatching 을 사용하니 좀 더 나은거 같기도 하고, sealed 클래스를 Result 와 섞을 생각을 못해봤는데, 나중에 디테일한 failure 핸들링할때 사용해 볼 예정이다. 실패에 따른 메세지를 보낸다거나, 로그를 쌓는다거나 등등
728x90
'공부 > 이펙티브코틀린' 카테고리의 다른 글
아이템 9 - use 를 사용하여 리소스를 닫아라 (1) | 2024.04.07 |
---|---|
아이템 8 - 적절하게 null 을 처리하라 (0) | 2024.04.04 |
아이템 6 - 사용자 정의 오류보다는 표준 오류를 사용하라 (0) | 2024.03.31 |
아이템 5 - 예외를 활용해 코드에 제한을 걸어라 (0) | 2024.03.31 |
아이템 4 - inferred 타입으로 리턴하지 말라 (0) | 2024.03.31 |
댓글