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 може да бъде израз (expression), в 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. Хващайте само типовете, които очаквате и можете да обработите. За останалите позволете разпространение нагоре. В мобилната разработка специфични 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 за глобално улавяне. Игнорирането на това правило е причина за трудно възпроизводими сривове в продукция.

Блокиране на 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 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също