Обработка ошибок без использования блока try-catch, используя эффективный kotlin способ - PullRequest
5 голосов
/ 03 марта 2020

Kotlin 1.3.61

Я читал книгу Effective Kotlin by Marcin Moskala. И нашел пункт об обработке ошибок интересным, так как он не рекомендует использовать блок try-catch и вместо этого пользовательский класс обработчика

Цитата из книги:

Использование такой обработки ошибок не только более эффективнее, чем блок try-catch, но часто также более прост в использовании и более явный

Однако существуют случаи, когда try-catch невозможно избежать. У меня есть следующий фрагмент

class Branding {

        fun createFormattedText(status: String, description: String): ResultHandler<PricingModel> {
            return try {
                val product = description.format(status)
                val listProducts = listOf(1, 2, 3)

                ResultHandler.Success(PricingModel(product, listProducts))
            }
            catch (exception: IllegalFormatException) {
                ResultHandler.Failure(Throwable(exception))
            }
        }
    }

    class PricingModel(val name: String, products: List<Int>)

Так что description.format(status) сгенерирует исключение, если он не сможет отформатировать

Это мой класс HandlerResult, и то, что рекомендует книга:

sealed class ResultHandler<out T> {
    class Success<out T>(val result: T) : ResultHandler<T>()
    class Failure(val throwable: Throwable) : ResultHandler<Nothing>()
}

class FormatParsingException: Exception()

И как я использую их в своем коде:

fun main() {
    val branding = Branding()
    val brand = branding.createFormattedText("status", "$%/4ed")

    when(brand) {
        is Success -> println(brand.result.name)
        is Failure -> println(brand.throwable.message)
    }
}

У меня вопрос. Это один из тех случаев, когда нельзя избежать try-catch. Или я все еще мог бы вернуть Отказ, если формат должен был потерпеть неудачу, не используя try-catch?

Ответы [ 2 ]

6 голосов
/ 03 марта 2020

Я не читал эту книгу, но, по-моему, автор считает, что в ваших функциях publi c лучше использовать обертку для результатов запечатанного класса, чем исключение, а не то, что вам следует избегать использования try-catch на третьей -party или stdlib функции, которые могут бросить.

В вашем случае у вас есть , чтобы использовать catch для преобразования исключения в результат. Я бы сказал, что ваш код полностью реализует совет автора. Вы не можете не сказать, что вызываемая вами функция выставляет исключение, требующее обработки, но вы можете преобразовать это в лучшую парадигму в собственной подписи функции c.

Kotlin отменил с проверенными исключениями в основном из-за проблем, которые они создают. Весь стек вызовов должен быть осведомлен о типах исключений в своих сигнатурах, даже если промежуточные функции не заботятся об обработке ошибок, поэтому это очень плохая инкапсуляция и незначительные изменения API могут иметь огромный волновой эффект. Но удаление отмеченных исключений создает проблему для программиста, который, возможно, забывает (или не знает, что им нужно) обработать возможную ошибку.

Запечатанный класс-оболочка результата может решить обе проблемы. Каждая функция в стеке вызовов может выбрать, передавать ли весь результат назад или перехватывать ошибки, и какие типы ошибок перехватывать. Если они просто передают это, им не нужно знать о возможных видах ошибок. Ни одна из сигнатур функций не должна быть изменена, если изменяются типы ошибок. И у программиста нет ничего, с чем они могут забыть справиться. Либо они просто передают весь результат, не глядя на него, либо заменяют ошибку значением по умолчанию, либо они могут фактически реагировать на определенные c типы ошибок.

3 голосов
/ 12 марта 2020

Вы можете избежать попытки перехвата, используя Kotlin встроенный Результат класс и код. (За кулисами у вас есть попытка поймать - см. источник ).

fun createFormattedText(status: String, description: String): ResultHandler<PricingModel> {
    runCatching {
        val product = description.format(status)
        val listProducts = listOf(1, 2, 3)
        ResultHandler.Success(PricingModel(product, listProducts))
    }.getOrElse {
        ResultHandler.Failure(it)
    }
}

Топи c главы в книге «Предпочитать ноль или результат неудачи, когда возможно отсутствие результата », поэтому, если вас не волнует исключение, вы можете сделать это:

fun createFormattedText(status: String, description: String): PricingModel? {
    runCatching {
        val product = description.format(status)
        val listProducts = listOf(1, 2, 3)
        PricingModel(product, listProducts)
    }.getOrNull()
}

Для отладки / ведения журнала это также будет работать:

fun createFormattedText(status: String, description: String): PricingModel? {
    runCatching {
        val product = description.format(status)
        val listProducts = listOf(1, 2, 3)
        PricingModel(product, listProducts)
    }.onFailure {
        log("Something wrong with $it")
    }.getOrNull()
}

К сожалению, вы не можете заменить свой ResultHandler на Kotlins Result - потому что Result не может использоваться в качестве возвращаемого типа. Я нашел этот пост , объясняющий причины, а также обходной путь, надеюсь, это поможет.

В качестве альтернативы вы можете создать собственную функцию расширения для вашего ResultHandler и перенести обработку исключений под капот:

public inline fun <R> runCatching(block: () -> R): ResultHandler<R> {
    return try {
        ResultHandler.Success(block())
    } catch (e: Throwable) {
        ResultHandler.Failure(e)
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...