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("Мережа " + 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("Мережа ", 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 перехоплюється із захопленням об’єкта та 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. Ловіть лише ті типи, які очікуєте та можете обробити. Для всього іншого дозвольте 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 μs залежно від глибини стеку. Не використовуйте винятки для управління потоком виконання — це антипатерн.

Як обробляти помилки в 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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