Try-Catch: суть, конструкция перехвата исключений и как работает в мобильной разработке

Автор: IT Sectr Опубликовано: 2026-05-25 Время чтения: 9 мин

Try-Catch — конструкция перехвата исключений, позволяющая выполнять потенциально опасный код в защищённом блоке и корректно обрабатывать ошибки без аварийного завершения программы. Блок try содержит код, который может выбросить исключение, catch перехватывает его и выполняет логику восстановления. По данным Apple Swift Documentation (2026), блок finally выполняется независимо от того, было выброшено исключение или нет, гарантируя освобождение ресурсов.

Главное

  • Try-Catch — конструкция для перехвата исключений, предотвращающая аварийное завершение программы при ошибках
  • Блок try содержит код, который может выбросить исключение — выполнение прерывается при первой ошибке
  • Блок catch перехватывает исключение указанного типа и выполняет логику обработки или восстановления
  • Блок finally гарантированно выполняется после try/catch для освобождения ресурсов, например закрытия файлов
  • Вложенные try-catch позволяют обрабатывать ошибки на разных уровнях абстракции внутри одной функции

Что такое Try-Catch?

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

Механизм выполнения try-catch основан на раскрутке стека (stack unwinding). Когда внутри блока try выбрасывается исключение через оператор throw (или в результате системной ошибки), нормальный поток выполнения немедленно прерывается. Исполнение перемещается на уровень выше по стеку вызовов в поисках подходящего блока catch. Современные языки ищут catch с типом, соответствующим типу выброшенного исключения, используя механизм сопоставления типов (type matching).

Если соответствующий catch найден, выполняется его тело, после чего исполнение продолжается после всей конструкции try-catch-finally. Если catch не найден, исключение поднимается дальше по стеку и может быть обработано на более высоком уровне — вплоть до глобального обработчика, который в мобильном приложении показывает пользователю диалог с ошибкой. Если исключение не обработано нигде, приложение аварийно завершается. Именно поэтому корректная обработка всех возможных типов исключений критична для стабильности приложения.

Синтаксис базовой конструкции

kotlin
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-компонентов на экране.

Try-Catch в Swift

В Swift обработка ошибок реализована через протокол Error (раньше — ErrorType). Любой тип, соответствующий Error, может быть выброшен через оператор throw. Функция, которая может выбросить ошибку, помечается ключевым словом throws в сигнатуре. Вызов такой функции требует префикса try (для явного try-catch), try? (опциональный результат) или try! (принудительное выполнение без обработки ошибки).

swift
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 и все остальные ошибки. Это позволяет показывать пользователю разные сообщения в зависимости от типа проблемы.

Try-Catch в Kotlin

Kotlin унаследовал try-catch-finally от Java, но добавил важное отличие: в Kotlin try-catch — это выражение (expression), а не инструкция (statement). Это значит, что результат try-блока или catch-блока можно присвоить переменной. Последнее выражение в блоке try становится результатом при успехе, последнее выражение в catch — при ошибке. Если ошибка не обработана ни одним catch, исключение пробрасывается выше по стеку.

kotlin
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.

Try-Catch в Dart и Flutter

Dart поддерживает try-catch-finally с синтаксисом, похожим на Java, но с добавлением on-клаузы для фильтрации по типу исключения без указания переменной. Это удобно, когда само исключение не нужно — важен только факт его типа. Dart также поддерживает блок catch с двумя параметрами: объект исключения и StackTrace, что полезно для логирования полной цепочки вызовов.

dart
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

Даже опытные разработчики допускают ошибки при работе с try-catch, которые приводят к утечке памяти, скрытым багам или неадекватному поведению приложения. Рассмотрим пять наиболее распространённых проблем в мобильной разработке.

Пустой блок catch

Пустой catch — одна из худших практик. Исключение проглатывается, приложение продолжает работать в некорректном состоянии, а разработчик не узнаёт о проблеме. Всегда хотя бы логируйте исключение. В Kotlin используйте catch(e: Exception) { Log.e(...) }, в Swift — catch { print($0) }. В Dart минимально допустимый catch должен вызывать debugPrint или записывать в Crashlytics.

Слишком широкий catch

Перехват всех исключений через catch (Exception e) без разбора по типам скрывает неожиданные ошибки — NullPointerException, OutOfMemoryError, StackOverflowError. Ловите только те типы, которые ожидаете и можете обработать. Для всего остального allow propagation выше. В мобильной разработке специфичные catch для IOException, TimeoutException, AuthException дают более осмысленные сообщения пользователю.

Игнорирование finally

Ресурсы — файлы, сокеты, курсоры БД, анимации — должны освобождаться в 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.

Блокировка UI при исключении

Обработка исключения не должна блокировать пользовательский интерфейс на неопределённое время. Показывайте пользователю конкретное сообщение и давайте возможность повторить операцию. Snackbar с кнопкой Retry в Kotlin/Compose, UIAlertController с action в Swift, SnackBar с action в Flutter — минимально достаточный UX для ошибок сети или сервера. Избегайте общих диалогов «Произошла ошибка» без возможности восстановления.

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

Чем отличается try-catch от Result Type?

Try-catch использует исключения и раскрутку стека для обработки ошибок, что может быть затратно по производительности при большом количестве ошибок. Result Type — это тип-контейнер (Success или Failure), который обрабатывается через pattern matching без раскрутки стека, что эффективнее для ожидаемых ошибок.

Нужно ли использовать finally в каждом try-catch?

Finally обязателен, если блок try открывает ресурсы (файлы, сокеты, курсоры), которые нужно закрыть. Если ресурсы не открываются, finally не требуется. В современных языках используйте AutoCloseable/use/defer для автоматического закрытия ресурсов без finally. use-блок в Kotlin и Swift заменяет finally для Closeable-объектов.

Может ли try-catch быть медленным?

В нормальном потоке (без исключения) try-catch практически не влияет на производительность — JVM и Swift компилятор оптимизируют этот случай. Но при выбрасывании исключения происходит раскрутка стека, что может занимать 10–100 мкс в зависимости от глубины стека. Не используйте исключения для управления потоком выполнения — это антипаттерн.

Как обрабатывать ошибки в Kotlin Coroutines?

В корутинах используйте try-catch внутри coroutineScope или CoroutineExceptionHandler для глобального перехвата. SupervisorJob предотвращает отмену родительской корутины при ошибке в дочерней. Для launch используйте CoroutineExceptionHandler, для async — try-catch вокруг await().

Что лучше: множественные catch или один catch с if-else?

Множественные catch предпочтительнее: код читается линейно, каждый блок обрабатывает один тип исключения. Один catch с if-else сложнее поддерживать, легко пропустить новый тип исключения. В Swift множественные catch обязательны для исчерпывающей обработки enum Error, в Kotlin ограничений нет, но лучшая практика — отдельный catch на тип.

Итоги

  • Try-Catch — конструкция перехвата исключений с блоками try, catch и опциональным finally для гарантированного освобождения ресурсов
  • Механизм работы — раскрутка стека при выбросе исключения с поиском подходящего catch по типу ошибки
  • В Swift используется do-catch с enum Error, try? для опционального результата и try! для гарантированного успеха
  • В Kotlin try-catch — выражение, результат которого можно присвоить переменной, удобно в паре с sealed class Result
  • В Dart поддерживает on-клаузу для фильтрации по типу без переменной и finally для закрытия HttpClient
  • Типичные ошибки: пустой catch, слишком широкий перехват, игнорирование finally и отсутствие обработки в корутинах
  • Используйте try-catch для неожиданных ошибок, Result Type — для ожидаемых сценариев с возможной неудачей

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также