0Pricing
Kotlin Academy · Урок

Повторный выброс и связывание исключений

Выборочно выбрасывайте исключения повторно и оборачивайте ошибки нижнего уровня, сохраняя их причины.

«Повторный выброс и связывание исключений» — бесплатный урок Kotlin Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Kotlin Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Kotlin Academy содержит 4 уроков всего.

Повторный выброс и оборачивание

Иногда исключение перехватывают только для того, чтобы записать его в журнал, обернуть или преобразовать, прежде чем позволить более конкретному исключению распространиться дальше. Kotlin делает такой подход идиоматичным.

Простой повторный выброс

Чтобы повторно выбросить исключение, просто снова выполните throw e — распространение продолжится до следующого обработчика.

fun main() {
    try {
        try { "x".toInt() }
        catch (e: NumberFormatException) {
            println("logging: ${e.message}")
            throw e // re-throw original
        }
    } catch (e: NumberFormatException) {
        println("outer caught: ${e.message}")
    }
}

Оборачивание в собственное исключение

Передайте исходное исключение как cause при создании нового, чтобы сохранить цепочку причин.

class ParseException(msg: String, cause: Throwable) : RuntimeException(msg, cause)
fun main() {
    try {
        try { "xyz".toInt() }
        catch (e: NumberFormatException) {
            throw ParseException("could not parse user input", e)
        }
    } catch (e: ParseException) {
        println("got: ${e.message}")
        println("cause: ${e.cause?.message}")
    }
}

Изучение цепочки причин

Свойство cause ссылается на предыдущее исключение. Пройдите по цепочке, чтобы вывести полную исходную причину.

fun main() {
    val e = RuntimeException("top",
        RuntimeException("middle",
            RuntimeException("root")))
    var current: Throwable? = e
    while (current != null) {
        println(current.message)
        current = current.cause
    }
}

Выборочный повторный выброс

Перехватите общий тип, а затем повторно выбросьте исключение, если оно НЕ соответствует ожидаемому случаю.

fun parseRequired(s: String): Int {
    try {
        return s.toInt()
    } catch (e: NumberFormatException) {
        if (s.isBlank()) return 0
        throw e // unexpected — let it propagate
    }
}
fun main() {
    println(parseRequired("42"))
    println(parseRequired(""))
    // parseRequired("abc") // throws
}

Добавление контекста

Оберните исключение в исключение предметной области, чтобы добавить контекст, которого не было в исходном (входные данные, идентификатор запроса и так далее).

class UploadError(msg: String, cause: Throwable) : RuntimeException(msg, cause)
fun upload(file: String) {
    try { throw RuntimeException("disk full") }
    catch (e: Throwable) { throw UploadError("uploading $file failed", e) }
}
fun main() {
    try { upload("photo.jpg") }
    catch (e: UploadError) { println("${e.message} <- ${e.cause?.message}") }
}

Не теряйте трассировку стека

При оборачивании всегда передавайте исходное исключение как cause — никогда не создавайте просто новое исключение с тем же сообщением, но без причины. Трассировка стека — ваш главный помощник при отладке.

class Wrapped(m: String, cause: Throwable) : RuntimeException(m, cause)
fun main() {
    try {
        try { error("inner") }
        catch (e: Throwable) { throw Wrapped("outer", e) }
    } catch (e: Wrapped) {
        println("message: ${e.message}")
        println("cause: ${e.cause?.message}")
    }
}

Повторный выброс без оборачивания

Если вам нужно только записать исключение в журнал, просто выполните throw e после вызова журналирования — оборачивание не требуется.

fun risky() {
    try { throw IllegalStateException("oops") }
    catch (e: IllegalStateException) {
        println("Logging before rethrow: ${e.message}")
        throw e
    }
}
fun main() {
    try { risky() }
    catch (e: IllegalStateException) { println("outer: ${e.message}") }
}

finally для очистки при повторном выбросе

Используйте finally, чтобы очистить ресурсы даже при повторном выбросе исключения.

fun process() {
    try {
        throw RuntimeException("failure")
    } finally {
        println("cleanup ran")
    }
}
fun main() {
    try { process() }
    catch (e: RuntimeException) { println("caught: ${e.message}") }
}

Подавленные исключения

Если сам блок finally выбрасывает исключение, исходное исключение по умолчанию подавляется. Используйте addSuppressed, чтобы сохранить оба исключения.

fun main() {
    val primary = RuntimeException("primary")
    val secondary = RuntimeException("from cleanup")
    primary.addSuppressed(secondary)
    println(primary.suppressed.joinToString { it.message ?: "?" })
}

Антипаттерн: подавление с последующим выбросом другого исключения

Перехват одного исключения и выброс несвязанного нового исключения без причины уничтожает информацию, необходимую для отладки. Всегда передавайте cause.

// BAD
// catch (e: IOException) { throw RuntimeException("error") }
// GOOD
// catch (e: IOException) { throw RuntimeException("error reading config", e) }
fun main() { println("Pass cause when re-throwing!") }

Быстрая проверка

Когда Вы оборачиваете исключение в новое, что нужно сделать, чтобы сохранить исходную трассировку стека?

Итоги

Для повторного выброса используйте throw e. Для оборачивания выбросьте новое исключение с cause = e. Всегда сохраняйте цепочку причин — никогда не создавайте оболочку без причины. Используйте finally для очистки, а addSuppressed — если сама очистка завершилась ошибкой.

Часто задаваемые вопросы

Урок «Повторный выброс и связывание исключений» бесплатный?

Да — полный текст урока «Повторный выброс и связывание исключений» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Kotlin Academy, подпишись на CoddyKit PRO. Курс Kotlin Academy содержит 4 уроков всего.

Чему я научусь в уроке «Повторный выброс и связывание исключений»?

Выборочно выбрасывайте исключения повторно и оборачивайте ошибки нижнего уровня, сохраняя их причины. Ты практикуешь Kotlin Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Kotlin Academy?

Предыдущий опыт не требуется. Kotlin Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Повторный выброс и связывание исключений»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Kotlin Academy?

Да. Каждый урок Kotlin Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. try/catch/finally как выражение
  2. Создание пользовательских классов исключений
  3. runCatching и Result
  4. Повторный выброс и связывание исключений
← Назад к Kotlin Academy