2
votes

Comment créer une fonction qui avale les exceptions

inline fun <T> rest(request: () -> T): T = try {
    request()
} catch (e: HttpException) {
    val requestId = e.response().raw().request().header(REQUEST_ID_HEADER) 
    if (requestId != null) {
        Dialog(requestId, R.string.oops).show(fragmentManager, null)
    } else {
        throw e
    }
}
It should perform some REST request (in the request parameter) and if it fails, and it contained the specified HTTP header, display dialog with that header.But the compiler complains at the line with Dialog, that it doesn't return T, but Unit. But that's basicaly what I want! How can I do this?One solution that crosses my mind it to set function's return type to T? and return null, but it feel dirty doing this in Kotlin.

4 commentaires

si vous ne retournez pas T de toute façon, pourquoi le type de retour est-il T?


Vous devez décider de retourner null ou de lever une exception pour signaler tout problème au code de l'appelant. En ce moment, vous essayez de mélanger les deux approches


Quelle est la valeur de retour censée être lorsque Dialog.show est appelé? Si dans ce scénario rest ne doit rien renvoyer, son type de retour doit être T? et votre code doit renvoyer null après show invocation. Mais ... le code client doit accéder à la valeur renvoyée en utilisant appels sécurisés , n'est-ce pas?


Vous devez vous demander ce que fera le code appelant rest () au cas où la boîte de dialogue serait affichée. Il doit en quelque sorte gérer l'absence de T (une exception ou une valeur nulle), car il ne recevra pas une instance de T.


3 Réponses :


1
votes

Vous pouvez le résoudre par le type générique de Résultat :

sealed class Result<out T : Any> {
    class Success<out T : Any>(val value: T) : Result<T>()
    class Error(val exception: Exception, val requestId: String?) : Result<Nothing>()
}

inline fun <T : Any> rest(request: () -> T): Result<T> = try {
    Result.Success(request())
} catch (e: HttpException) {
    val requestId = e.response().raw().request().header(REQUEST_ID_HEADER)
    Result.Error(e, requestId)
}

private fun testRest() {
    val result = rest(::testRequest)
    when (result) {
        is Result.Success -> Log.v("__DEBUG", "success: ${result.value}")
        is Result.Error -> {
            result.requestId?.let {
                Dialog(it, R.string.oops).show(fragmentManager, null)
            } ?: run {
                throw result.exception
            }
        }
    }
}


2 commentaires

Le consommateur du résultat semble compliqué ici, pourquoi ne pas aller jusqu'au bout et traiter les 3 cas séparément à la place? J'ai essayé d'améliorer votre réponse dans la mienne.


D'accord, ça a l'air plus propre



5
votes

Renvoyer la valeur nulle n'est pas sale en soi . L'utilisation de nulls peut parfois être abusée, mais c'est un cas d'utilisation parfaitement valide pour l'utilisation de null. Kotlin vous permet d'utiliser null de manière sûre et agréable, alors n'ayez pas peur de l'utiliser!

Une autre option est de lever une exception également dans le cas où vous affichez la boîte de dialogue (que l'en-tête soit présent ou non).

Afin de choisir, vous devez vous demander ce que fera le code appelant rest () au cas où la boîte de dialogue serait affichée. Il a à gérer l'absence de T d'une manière ou d'une autre (nul ou exception). En effet, afficher une boîte de dialogue n'est pas quelque chose qui met fin à l'exécution de votre fonction.

Dernier point mais non le moindre, il existe également une option pour traiter le résultat en dehors de rest () code> méthode. Amélioration de la réponse de Taras:

sealed class Result<out T : Any> {
    class Success<out T : Any>(val value: T) : Result<T>()
    class ErrorWithId(val exception: Exception, val requestId: String) : Result<Nothing>()
    class Error(val exception: Exception) : Result<Nothing>()
}

inline fun <T : Any> rest(request: () -> T): Result<T> = try {
    Result.Success(request())
} catch (e: HttpException) {
    val requestId = e.response().raw().request().header(REQUEST_ID_HEADER)
    if (requestId != null) {
        Result.RecoverableError(e, requestId)
    } else {
        Result.Error(e)
    }
}

private fun thingCallingRest() {
    val result = rest(::testRequest)
    when (result) {
        is Result.Success -> Log.v("__DEBUG", "success: ${result.value}")
        is Result.ErrorWithId -> Dialog(result.requestId, R.string.oops).show(fragmentManager, null)
        is Result.Error -> throw result.exception
    }
}


0 commentaires

0
votes

Renvoyer null peut sembler moche au début, mais quelles sont les options?

Option 1: lever une exception

Si vous avez besoin d'informations supplémentaires sur les raisons de l'échec du code vous devriez lancer une exception.

Jetons un coup d'œil à fonction unique à titre d'exemple:

listOf<Int>().singleOrNull() ?: 1 // default element

lancera

NoSuchElementException: la liste est vide.

listOf<Int>().singleOrNull() // returns null

lancera

IllegalArgumentException: la collection contient plus d'un élément correspondant.

C'est ce que je veux dire par plus d'informations. Le type d'exception et le message donné peuvent vous fournir des moyens de décider comment continuer.

Option 2: Renvoyer null

Au cas où vous voudriez savoir s'il a échoué ou non le retour de null serait un bon moyen de le signaler. Même la bibliothèque standard de Kotlin fait cela, par exemple avec singleOrNull () .

listOf<Int>(1, 1).single { it == 1 } 

Fournir une solution de secours est également très concis en utilisant l'opérateur elvis:

listOf<Int>().single() 


0 commentaires