Исключения и обработка ошибок
В Kotlin все исключения непроверяемые, try-catch является выражением, а для функционального стиля есть runCatching и тип Result.
Суть: грамотная обработка ошибок отличает учебный код от продакшен-кода; Kotlin даёт для этого как классический try-catch, так и удобный функциональный Result.
В отличие от Java, в Kotlin нет проверяемых исключений: компилятор не заставляет объявлять, какие исключения может бросить функция. Это упрощает код, но возлагает ответственность за обработку на разработчика. Базовый инструмент — try-catch-finally, и, как многое в Kotlin, try — это выражение, возвращающее значение.
fun parseOrZero(text: String): Int {
return try {
text.toInt() // может бросить NumberFormatException
} catch (e: NumberFormatException) {
0 // значение по умолчанию при ошибке
}
}
println(parseOrZero("42")) // 42
println(parseOrZero("abc")) // 0Бросаем свои исключения
Бросить исключение можно через throw. Часто используют IllegalArgumentException для неверных аргументов. Есть удобные функции-проверки: require бросает исключение, если условие ложно, а check — для проверки состояния.
fun withdraw(balance: Int, amount: Int): Int {
require(amount > 0) { "Сумма должна быть положительной" }
require(amount <= balance) { "Недостаточно средств" }
return balance - amount
}
println(withdraw(100, 30)) // 70
// withdraw(100, 200) // IllegalArgumentException: Недостаточно средствКак работает под капотом
Для функционального стиля Kotlin предлагает runCatching: он выполняет блок и оборачивает результат в тип Result — либо успех со значением, либо неудачу с исключением. Это удобно, когда ошибку нужно передать дальше как данные, а не как прерывание потока. На Result навешивают onSuccess, onFailure, getOrElse, getOrNull.
val result = runCatching { "123".toInt() }
result
.onSuccess { println("Успех: $it") }
.onFailure { println("Ошибка: ${it.message}") }
val value = runCatching { "x".toInt() }.getOrElse { -1 }
println(value) // -1 runCatching { ... } --> Result
|
+-----------------+-----------------+
v v
Success(value) Failure(exception)
| |
getOrNull() = value getOrNull() = nullЧастые ошибки
Глотать исключения молча. Пустой catch {} прячет проблему, и баг проявится позже в непонятном месте. Минимум — залогируйте ошибку.
Ловить слишком общий тип. catch (e: Exception) перехватывает всё подряд, включая то, что вы не ожидали. Ловите конкретные типы.
Использовать исключения для управления потоком. Исключения — для исключительных ситуаций, а не для обычных ветвлений; для ожидаемых случаев лучше nullable или Result.
Best practices
- Ловите конкретные типы исключений, а не общий Exception.
- Используйте
require/checkдля проверки аргументов и состояния. - Для ошибок как данных применяйте
runCatchingиResult. - Никогда не оставляйте catch пустым — как минимум логируйте.
Логику безопасного парсинга с запасным значением легко повторить на Python. Запустите врезку.
# Аналог try-catch с значением по умолчанию из Kotlin
def parse_or_zero(text):
try:
return int(text)
except ValueError:
return 0
for s in ['42', 'abc', '-7', '3.14']:
print(repr(s), '->', parse_or_zero(s))Попробуй сам ▶ — добавьте обработку дробных чисел или своё значение по умолчанию. В Kotlin это был бы try { text.toInt() } catch (...) { ... }.
Закрепим главное
Держите в голове границу между ожидаемым и исключительным. Если отсутствие значения — нормальный сценарий (пользователь не найден, поле не заполнено), выражайте это через nullable-тип или Result, а не через брошенное исключение. Исключения дороги и сбивают линейность кода, поэтому их место — действительно нештатные ситуации: оборванная сеть, повреждённые данные, нарушенный инвариант. Эта дисциплина делает поток управления честным: по сигнатуре функции видно, что она может не вернуть значение.
Второй ориентир пригодится в Android напрямую. Сетевой и файловый код падает регулярно — это норма, а не редкость. Поэтому ещё до написания UI продумывайте, как ошибка превратится в состояние экрана: вспомните sealed UiState с веткой Error. Связка «поймали исключение в слое данных — превратили в состояние ошибки — показали пользователю понятное сообщение» отличает приложение, которое переживает плохую сеть, от приложения, которое на ней падает.
Итог: Kotlin даёт гибкую модель ошибок — от классического try-catch-выражения до функционального Result. Грамотная обработка ошибок особенно важна в сетевом коде Android, к которому мы придём в последнем разделе.