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 може да бъде израз (expression), в 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("Мрежа " + 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("Мрежа ", 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 се улавя с обекта и 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. Хващайте само типовете, които очаквате и можете да обработите. За останалите позволете разпространение нагоре. В мобилната разработка специфични 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 за глобално улавяне. Игнорирането на това правило е причина за трудно възпроизводими сривове в продукция.
Обработката на изключението не трябва да блокира потребителския интерфейс за неопределено време. Покажете на потребителя конкретно съобщение и дайте възможност за повторение на операцията. 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 µs в зависимост от дълбочината на стека. Не използвайте изключения за управление на потока на изпълнение — това е анти-модел.
В корутини използвайте 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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също