Try-Catch — конструкция перехвата исключений, позволяющая выполнять потенциально опасный код в защищённом блоке и корректно обрабатывать ошибки без аварийного завершения программы. Блок try содержит код, который может выбросить исключение, catch перехватывает его и выполняет логику восстановления. По данным Apple Swift Documentation (2026), блок finally выполняется независимо от того, было выброшено исключение или нет, гарантируя освобождение ресурсов.
Главное
Try-Catch — фундаментальная конструкция структурной обработки исключений, присутствующая в большинстве современных языков программирования. Она состоит из трёх блоков: try (попытка выполнения опасного кода), catch (перехват и обработка исключения) и необязательного finally (финализация). Идея конструкции — отделить логику бизнес-операций от логики обработки ошибок, делая код более читаемым и предсказуемым.
Концепция была впервые реализована в языке C++ как try/catch, затем заимствована Java, C#, Swift, Kotlin, Dart, Python, JavaScript и другими языками. Каждый язык добавляет свои особенности: в Swift блок catch должен быть исчерпывающим, в Kotlin try-catch может быть выражением, в Dart finally обязателен для потоковых ресурсов. Несмотря на различия, базовый принцип един: ошибка обрабатывается как можно ближе к месту возникновения, а не глобально.
Использование Try-Catch особенно актуально в мобильной разработке, где внешние факторы — потеря сети, некорректный ответ сервера, недостаток памяти — происходят постоянно. Грамотная обработка исключений предотвращает краши приложения и обеспечивает корректный UX: пользователь получает сообщение об ошибке вместо внезапного закрытия приложения. По данным Google Android Kotlin Style Guide (2026), каждая функция, которая может выбросить исключение, должна либо обработать его через try-catch, либо декларировать throws в сигнатуре.
Механизм выполнения try-catch основан на раскрутке стека (stack unwinding). Когда внутри блока try выбрасывается исключение через оператор throw (или в результате системной ошибки), нормальный поток выполнения немедленно прерывается. Исполнение перемещается на уровень выше по стеку вызовов в поисках подходящего блока catch. Современные языки ищут catch с типом, соответствующим типу выброшенного исключения, используя механизм сопоставления типов (type matching).
Если соответствующий catch найден, выполняется его тело, после чего исполнение продолжается после всей конструкции try-catch-finally. Если catch не найден, исключение поднимается дальше по стеку и может быть обработано на более высоком уровне — вплоть до глобального обработчика, который в мобильном приложении показывает пользователю диалог с ошибкой. Если исключение не обработано нигде, приложение аварийно завершается. Именно поэтому корректная обработка всех возможных типов исключений критична для стабильности приложения.
fun readUserData(): User {
return try {
val response = api.fetchUser()
parseUser(response)
} catch (e: IOException) {
logError("Сетевая ошибка", e)
throw AppException("Не удалось загрузить данные")
} catch (e: JsonParseException) {
logError("Ошибка парсинга", e)
return User.default()
} finally {
closeLoadingIndicator()
}
}
Код сначала пытается выполнить запрос к API и распарсить ответ. Если возникает IOException (проблема сети), исключение логируется и пробрасывается дальше как AppException. Если JsonParseException — возвращается пользователь по умолчанию. Блок finally гарантированно скрывает индикатор загрузки, предотвращая утечку UI-компонентов на экране.
В Swift обработка ошибок реализована через протокол Error (раньше — ErrorType). Любой тип, соответствующий Error, может быть выброшен через оператор throw. Функция, которая может выбросить ошибку, помечается ключевым словом throws в сигнатуре. Вызов такой функции требует префикса try (для явного try-catch), try? (опциональный результат) или try! (принудительное выполнение без обработки ошибки).
enum NetworkError: Error {
case noConnection
case serverError(code: Int)
case timeout
}
func fetchUser(id: Int) throws -> User {
guard isConnected() else {
throw NetworkError.noConnection
}
let data = try performRequest(path: "/users/\(id)")
return try decodeUser(from: data)
}
do {
let user = try fetchUser(id: 42)
updateUI(user)
} catch NetworkError.noConnection {
showOfflineAlert()
} catch let error as NetworkError {
showError("Network " + error.localizedDescription)
} catch {
showGenericError()
}
Enum NetworkError реализует протокол Error, определяя три кейса: noConnection, serverError с кодом и timeout. Функция fetchUser помечена throws: сначала проверяет соединение, затем выполняет запрос и парсинг. В do-catch блоке три catch обрабатывают разные сценарии: специфичный кейс noConnection, общий тип NetworkError и все остальные ошибки. Это позволяет показывать пользователю разные сообщения в зависимости от типа проблемы.
Kotlin унаследовал try-catch-finally от Java, но добавил важное отличие: в Kotlin try-catch — это выражение (expression), а не инструкция (statement). Это значит, что результат try-блока или catch-блока можно присвоить переменной. Последнее выражение в блоке try становится результатом при успехе, последнее выражение в catch — при ошибке. Если ошибка не обработана ни одним catch, исключение пробрасывается выше по стеку.
sealed class Result<out T> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Throwable) : Result<Nothing>()
}
fun loadData(): Result<List<Item>> {
return try {
val response = api.getItems()
Result.Success(response.toList())
} catch (e: HttpException) {
Log.e("HTTP ", e)
Result.Error(e)
} catch (e: IOException) {
Log.e("Network ", e)
Result.Error(e)
}
}
В примере sealed class Result оборачивает успешный ответ или ошибку. Функция loadData использует try-catch как выражение: при успехе возвращает Result.Success, при исключении HttpException или IOException — Result.Error с логированием. Такой подход позволяет вызывающей стороне обрабатывать ошибки без исключений — через when-выражение по типу Result. Это особенно удобно в Jetpack Compose для отображения разных состояний UI (Loading, Success, Error) через StateFlow и collectAsState.
Dart поддерживает try-catch-finally с синтаксисом, похожим на Java, но с добавлением on-клаузы для фильтрации по типу исключения без указания переменной. Это удобно, когда само исключение не нужно — важен только факт его типа. Dart также поддерживает блок catch с двумя параметрами: объект исключения и StackTrace, что полезно для логирования полной цепочки вызовов.
import 'dart:io';
import 'dart:convert';
class UserRepository {
Future<User> fetchUser(String id) async {
try {
final client = HttpClient();
final request = await client.getUrl(
Uri.parse('https://api.example.com/users/$id')
);
final response = await request.close();
final body = await response.transform(utf8.decoder).join();
return User.fromJson(json.decode(body));
} on SocketException catch (e, stackTrace) {
log("No internet", e, stackTrace);
throw AppException("Connection failed");
} on FormatException {
throw AppException("Invalid response format");
} finally {
client.close();
}
}
}
SocketException перехватывается с capture объекта и StackTrace для детального логирования, а затем пробрасывается как AppException. FormatException перехватывается без переменной — достаточно знать, что формат ответа некорректен. Блок finally гарантированно закрывает HttpClient, предотвращая утечку сокетов. В Flutter такой подход особенно важен для Widget тестирования, где необработанные исключения в State.initState приводят к падению всей тестовой сессии.
Даже опытные разработчики допускают ошибки при работе с try-catch, которые приводят к утечке памяти, скрытым багам или неадекватному поведению приложения. Рассмотрим пять наиболее распространённых проблем в мобильной разработке.
Пустой catch — одна из худших практик. Исключение проглатывается, приложение продолжает работать в некорректном состоянии, а разработчик не узнаёт о проблеме. Всегда хотя бы логируйте исключение. В Kotlin используйте catch(e: Exception) { Log.e(...) }, в Swift — catch { print($0) }. В Dart минимально допустимый catch должен вызывать debugPrint или записывать в Crashlytics.
Перехват всех исключений через catch (Exception e) без разбора по типам скрывает неожиданные ошибки — NullPointerException, OutOfMemoryError, StackOverflowError. Ловите только те типы, которые ожидаете и можете обработать. Для всего остального allow propagation выше. В мобильной разработке специфичные catch для IOException, TimeoutException, AuthException дают более осмысленные сообщения пользователю.
Ресурсы — файлы, сокеты, курсоры БД, анимации — должны освобождаться в finally или в use-блоке (AutoCloseable). Разработчики часто забывают закрывать ресурсы при исключении, что приводит к утечкам. В Kotlin используйте .use { } для Closeable-ресурсов, в Swift — defer { }, в Dart — await using из пакета async. Блок finally гарантирует освобождение даже при выбросе исключения внутри catch.
В асинхронном коде try-catch не перехватывает исключения из других потоков. В Kotlin Coroutines используйте CoroutineExceptionHandler или SupervisorJob. В Swift async/await — do-catch внутри Task. В Flutter — runZonedGuarded для глобального перехвата. Игнорирование этого правила — причина трудно воспроизводимых крашей в production.
Обработка исключения не должна блокировать пользовательский интерфейс на неопределённое время. Показывайте пользователю конкретное сообщение и давайте возможность повторить операцию. Snackbar с кнопкой Retry в Kotlin/Compose, UIAlertController с action в Swift, SnackBar с action в Flutter — минимально достаточный UX для ошибок сети или сервера. Избегайте общих диалогов «Произошла ошибка» без возможности восстановления.
Часто задаваемые вопросы
Try-catch использует исключения и раскрутку стека для обработки ошибок, что может быть затратно по производительности при большом количестве ошибок. Result Type — это тип-контейнер (Success или Failure), который обрабатывается через pattern matching без раскрутки стека, что эффективнее для ожидаемых ошибок.
Finally обязателен, если блок try открывает ресурсы (файлы, сокеты, курсоры), которые нужно закрыть. Если ресурсы не открываются, finally не требуется. В современных языках используйте AutoCloseable/use/defer для автоматического закрытия ресурсов без finally. use-блок в Kotlin и Swift заменяет finally для Closeable-объектов.
В нормальном потоке (без исключения) try-catch практически не влияет на производительность — JVM и Swift компилятор оптимизируют этот случай. Но при выбрасывании исключения происходит раскрутка стека, что может занимать 10–100 мкс в зависимости от глубины стека. Не используйте исключения для управления потоком выполнения — это антипаттерн.
В корутинах используйте try-catch внутри coroutineScope или CoroutineExceptionHandler для глобального перехвата. SupervisorJob предотвращает отмену родительской корутины при ошибке в дочерней. Для launch используйте CoroutineExceptionHandler, для async — try-catch вокруг await().
Множественные catch предпочтительнее: код читается линейно, каждый блок обрабатывает один тип исключения. Один catch с if-else сложнее поддерживать, легко пропустить новый тип исключения. В Swift множественные catch обязательны для исчерпывающей обработки enum Error, в Kotlin ограничений нет, но лучшая практика — отдельный catch на тип.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также