티스토리 뷰

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

ㅅException이 Throw여부에 따라 각각 어떤 코드를 실행하고 싶을때가 있죠.

try-catch(-finally)로 구현하기 힘들때가 있습니다.

try-catch(-finally)는
Exception이 Throw되었을때의 처리(또는 throw여부에 관계없이 처리) 는 구현할 수 있어도 throw 되지 않았을때의 처리를 구현하기는 어렵습니다.

예를 들어 생각해봅시다.

일단 Exception이 throw 되는것을 생각하지 않아도 되는 케이스가 있습니다.

fun noException() {
    val myValue =
            getMyValue() // 값을 취득한다
                    .also {
                        onMySuccess(it) // 그 다음에 onMySuccess 를 호출한다
                        onMyFinally() // 마지막에 onMyFinally 를 호출한다
                    }

    setMyValue(myValue)
}

여기서 getMyValue()에 예외처리를 한다고 생각해 봅시다.
Exception이 throw된 경우에는 onMyFailure 함수를 호출하여 임의의 값을 취득하도록 합시다
간단하게 구현하자면 이렇게 됩니다.

fun useTryCatch1() {
    val myValue =
            try {
                getMyValue() // getMyValue()는 Exception을 throw할 가능성이 있다.
                        .also { onMySuccess(it) } // Exception이 Throw되지 않는다면 onMySuccess를 호출한다.
            } catch (e: MyException) {
                onMyFailure(e) // Exception이 Throw된다면 onMyFailure 를 호출하여, 임의의 값을 취득한다.
            } finally {
                onMyFinally() // 마지막으로 onMyFinally 를 호출한다.
            }

    setMyValue(myValue)
}

 

하지만 위와 같은 방식은 좋은 구현이 아닙니다.  try의 내부에 관계없는 onMySuccess함수도 호출하고 있기때문입니다.
혹시 실수로 onMySuccess 가 Exception을 발생시켰을 경우에 무시되어 이슈를 발견하기 어렵게 만들 수 있습니다.

onMySuccess 를 try 구문의 밖으로 내보냅시다.

fun useTryCatch2() {
    val myValue =
            run {
                try {
                    getMyValue()
                } catch (e: Throwable) {
                    return@run onMyFailure(e)
                }.also {
                    onMySuccess(it)
                }
            }.also {
                onMyFinally()
            }

    setMyValue(myValue)
}

…너무 복잡해졌네요.

runCatching 함수를 써봅시다.

그러면 try-catch 대신에 runCatching 함수를 사용해봅시다.

fun useRunCatching() {
    val myValue =
            runCatching {
                getMyValue()
            }.fold(
                    onSuccess = { it.also { onMySuccess(it) } },
                    onFailure = { onMyFailure(it) }
            ).also {
                onMyFinally()
            }

    setMyValue(myValue)
}

심플하죠. 
try-catch-finally 에 Exception이 발생하지 않았을때의 처리구문이 추가된 것같은 알기 쉬운 구조입니다.

Result 클래스 — runCatching 함수의 반환형

runCatching 함수는 주어진 블록을 실행하여  Result 형태의 오브젝트를 반환합니다.

val result: Result<MyValue> = runCatching { MyValue() }

만약 블록내에서 throw 하더라도 runCatching 함수는 해당 Exception을 밖으로 throw하지 않습니다.

블록을 실행한 결과인 반환값이나 Exception을 runCatching 함수가 반환한  Result 오브젝트로부터 취득하는 것이 가능합니다

Result 클래스 맴버

블록의 실행이 성공하면 (블록내에서 예외처리가 발생하지 않았을때)  isSuccess 변수를 확인하여 알 수 있습니다.
역으로 블록의 실행이 실패했다면 isFailure 변수를 통해 확인 할 수 있습니다.

if (result.isSuccess) { println("성공") }
if (result.isFailure) { println("실패") }

 

블록의 반환값을 얻기 위해선 getOrNull 함수를 사용합니다.
블록내에서 예외가 발생한 경우에는 null이 반환됩니다.

블록내에서 throw된 exception을 얻기 위해선 exceptionOrNull 함수를 사용합니다.
블록내에서 예외가 발생하지 않은 경우에는 null이 반환됩니다.

result.getOrNull()
        ?.also { println("반환값은 $it 입니다.") }
        ?: println("Exception이 발생했습니다.")
result.exceptionOrNull()
        ?.also { println("Exception은  $it 입니다.") }
        ?: println("반환값이 존재합니다.")

Result 클래스의 확장함수

Result 클래스의 맴버는 위에 서술한 4가지뿐입니다.
하지만 확장 함수가 다수 존재하고 있어서 매우 편리합니다.

  • get계열 함수

get 계열 함수는 블록 실행이 성공했을때는 그 결과를 반환합니다.  실패했을때는 동작이 함수마다 다릅니다.

getOrThrow
 블록내에서 예외가 발생하면 그대로 throw합니다
getOrDefault
 실행이 실패하면 디폴트 값을 반환합니다.
getOrElse
 실행이 실패하면 지정된 코드를 실행합니다

val result = runCatching { "".toInt() }
try {
    result.getOrThrow()
} catch (e: Exception) {
    println("0") // > 0
}
result.getOrDefault(1).let { println(it) } // > 1
result.getOrElse { println(2) } // > 2
  • on 계열 함수

onSuccess 함수블록은 runCatching 함수 블록의 실행이 성공했을때 실행됩니다
onFailure 함수블록은 runCatching 함수 블록의 실행이 실패했을때 실행됩니다

이 함수들은 리시버의 Result 오브젝트를 그대로 반환하기때문에 체인호출이 가능합니다.

result
        .onSuccess { onMySuccess(it) }
        .onFailure { onMyFailure(it) }

이 함수들은 inline 함수이기때문에 블록 내부에서 return 하거나 throw 할수 있습니다.

fun myFun() {
    run {
        result
                .onSuccess { return@run } // 성공하면 run 블록에서 빠져나간다
                .onFailure { throw Exception() } // 실패하면 myFun 함수로부터 Exception을 throw
    }
}
  • map 계열 함수와 recover 계열 함수

아래와 같은 예외 클래스와 함수가 정의되어 있다.

class MyIntException : Exception()
class MyDoubleException : Exception()

fun throwMyIntException(): Int = throw MyIntException()
fun throwMyDoubleException(): Double = throw MyDoubleException()
  • map계열 함수

map 함수와 mapCatching 함수는 runCatching 함수의 블록을 실행한 결과를 임의의 형태의 값으로 교환합니다.

runCatching { 3.14 }
        .map { it.toInt() }
        .onSuccess { println(it) } // > 3
runCatching { 3.14 }
        .mapCatching { it.toInt() }
        .onSuccess { println(it) } // > 3

다만, 교환 처리중에 예외가 발생한 경우의 동작이 다릅니다.
map 함수는 Exception을 밖으로 throw합니다.
mapCatching 는 반환값인 Result 가 Exception을 실패 반환값으로 갖게 됩니다.

try {
    runCatching { 3.14 }
            .map { throwMyIntException() }
            .onSuccess { println(it) } // 이 람다식은 실행되지 않는다.
            .onFailure { println(it) } // 이 람다식은 실행되지 않는다.
} catch (e: MyIntException) {
    println(e) // > MyIntException
}

runCatching { 3.14 }
        .mapCatching { throwMyIntException() }
        .onFailure { println(it) } // > MyIntException

함수 recover 와 recoverCatching 는 runCatching 함수의 블록의 실행이 실패했을때 성공으로 바꿉니다

runCatching { throwMyDoubleException() }
        .recover { 3 }
        .onSuccess { println(it) } // > 3
runCatching { throwMyDoubleException() }
        .recoverCatching { 3 }
        .onSuccess { println(it) } // > 3

다만 변환중에 예외가 발생한 경우의 동작이 다릅니다.
recover 함수는 Exception을 밖으로 throw합니다.
recoverCatching 는 반환값인 Result 가 Exception을 실패 반환값으로 갖게 됩니다.

try {
    runCatching { throwMyDoubleException() }
            .recover { throwMyIntException() }
            .onSuccess { println(it) } // 이 람다식은 실행되지 않는다.
            .onFailure { println(it) } // 이 람다식은 실행되지 않는다.
} catch (e: MyIntException) {
    println(e) // > MyIntException
}

runCatching { throwMyDoubleException() }
        .recoverCatching { throwMyIntException() }
        .onFailure { println(it) } // > MyIntException
  • fold함수

fold 함수는 runCatching 함수가 성공했을 경우에는 인수 onSuccess로 주어진 동작을 수행하고, 실패했을 경우에는 인수 onFailure에 주어진 동작을 실행합니다.

위에서 소개한 함수 onSuccess 와 onFailure 를 양쪽 다 호출하는 것은 비슷하지만, 한가지 큰 차이점은 반환값입니다.

fold 함수는 성공했을 경우와 실패했을 경우의 동작이 함께 같은 형태의 반환값을 갖도록 되어 있고 그것이 fold 함수의 반환값이 됩니다.

val double = runCatching { "".toInt() }.fold(
        onSuccess = { it.toDouble() },
        onFailure = { Double.NaN }
)

try-catch 식에 반환값을 사용하는 경우에는 이 함수를 사용하게 되겠네요

주의점

Result 는 함수의 반환값으로는 사용할 수 없다.

Result 는 함수의 반환값으로는 사용할 수 없습니다

fun getResult(): Result<Int> { // 컴파일 에러가 발생합니다.
    return runCatching { 1 }
}

Result 는 함수내에서 처리합시다.

출처 : https://qiita.com/sdkei/items/030875adad33f76d1f09

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함