Throw/Throws — какво е, оператори за хвърляне на изключения и как работят в мобилната разработка

Автор: IT Sectr Публикувано: 2026-05-26 Време за четене: 9 мин

Throw и Throws са механизми за хвърляне и деклариране на изключения в езиците за програмиране. Операторът throw прекъсва нормалното изпълнение на функция и предава обекта за грешка нагоре по стека от извиквания. Ключовата дума throws в сигнатурата на функция предупреждава извикващата страна за възможността от грешка, правейки кода предвидим. Според Kotlin Documentation (2026), throw в Kotlin е израз (expression), а не инструкция, което позволява използването му в when-блокове и elvis оператора.

Основни неща

  • Throw — оператор, прекъсващ изпълнението на функция и предаващ обекта за грешка нагоре по стека от извиквания
  • Throws — ключова дума в сигнатурата на функция, обявяваща, че функцията може да хвърли изключение
  • В Swift функцията се маркира с throws, извикването изисква try/try?/try!, а throw приема всеки тип, имплементиращ протокола Error
  • В Kotlin throw е израз от тип Nothing, throws отсъства (всички изключения са unchecked)
  • Потребителски грешки се създават чрез enum (Swift) или sealed class (Kotlin) за удобство при обработка в catch

Какво са Throw и Throws?

Throw е оператор, който генерира изключение в точката на изпълнение на програмата. Когато се срещне throw, текущият поток на изпълнение незабавно се прекъсва и управлението се предава на най-близкия манипулатор catch в стека от извиквания. Ако не бъде намерен манипулатор, приложението завършва аварийно. Throw в мобилната разработка се използва за сигнализиране на грешки, които не могат да бъдат обработени на текущото ниво на абстракция — например, грешен отговор от сървър, липса на мрежа или невалидни аргументи.

Throws е модификатор в сигнатурата на функция (основно в Swift), който обявява, че функцията може да хвърли грешка. Това е част от механизма за checked errors в Swift: извикващата страна е длъжна да обработи грешката чрез do-catch, try?, try! или да маркира своята функция с throws за по-нататъшно предаване (propagation). В Kotlin и Java throws също съществува, но Kotlin го счита за излишен — всички изключения в Kotlin са unchecked, тоест могат да останат необработени без синтактично принуждение. Според Apple Swift Documentation (2026), throws в Swift е единственият начин за изрично деклариране на възможността за грешка в типа на функцията, което прави API договорите прозрачни за разработчика.

Разликата между throw и throws е фундаментална: throw е действие (хвърляне на изключение по време на изпълнение), throws е декларация (compile-time договор). Функция без throws не може да използва throw — компилаторът на Swift ще даде грешка. Функция с throws може да не използва throw — това е позволено, но безсмислено. Това разделение прави throw/throws мощен инструмент за проектиране на API, където договорът за грешка е видим в сигнатурата на функцията преди нейното извикване.

Throw в Swift

В Swift операторът throw приема всеки тип, имплементиращ протокола Error. Най-често това е enum с case-ове за различни видове грешки. Swift не поддържа checked exceptions в Java-стил — вместо това типът на функцията се маркира с throws, а обработката се възлага на извикващата страна. Това прави throw в Swift по-гъвкав, но и по-отговорен за разработчика.

Протокол Error и типове грешки

Всеки тип, съответстващ на протокола Error, може да бъде хвърлен чрез throw. Най-често разработчиците използват enum с case-ове без асоциирани стойности (за прости грешки) или с associated values (за предаване на контекст). Swift не изисква грешката да бъде enum — могат да се използват struct или class, имплементиращи Error, но enum е за предпочитане благодарение на exhaustive switch от страна на манипулатора. Компилаторът проверява дали всички case-ове са обработени в do-catch.

swift
enum AuthError: Error {
    case invalidCredentials
    case tokenExpired
    case accountLocked(remainingMinutes: Int)
}

func login(username: String, password: String) throws -> Session {
    guard isValid(username) else {
        throw AuthError.invalidCredentials
    }
    let response = try api.authenticate(username, password)
    if response.isLocked {
        throw AuthError.accountLocked(
            remainingMinutes: response.lockDuration
        )
    }
    return Session(token: response.token)
}

AuthError определя три сценария: невалидни идентификационни данни, изтекъл токен и блокиран акаунт с асоциирана стойност remainingMinutes. Функцията login е декларирана като throws — компилаторът изисква извикване чрез try. Вътре във функцията throw се използва на две места: при невалидно username и при блокиран акаунт. Асоциираната стойност accountLocked позволява предаване на конкретни данни на потребителя — колко минути да изчака до отключване. Този подход елиминира необходимостта от отделни API endpoints за проверка на статуса на блокиране.

Throw в Kotlin

В Kotlin throw е израз (expression) от тип Nothing, а не инструкция. Това означава, че throw може да се използва от дясната страна на присвояване, вътре в when-изрази и в elvis оператора ?:. Типът Nothing е специален подтип на всички типове в Kotlin, което позволява използването на throw на места, където се изисква стойност от произволен тип. Компилаторът разбира, че след throw изпълнението не продължава и не изисква клон за този случай.

Nothing-тип и неговата роля в композицията

Nothing е уникален тип в Kotlin, който е подтип на всички възможни типове. Функция, връщаща Nothing (например TODO()), никога не завършва нормално — или винаги хвърля изключение, или влиза в безкраен цикъл. Това прави throw естествен кандидат за използване на места, където се изисква стойност: elvis оператор, when без else, инициализация на променлива. Ако throw стои в клон на when, компилаторът разбира, че клонът води до Nothing и не изисква return или else за този клон.

kotlin
data class Config(val apiUrl: String, val timeoutSec: Int)

class ConfigParser {
    fun parse(json: String): Config {
        val obj = JSONObject(json)
        val url = obj.optString("apiUrl")
            ?: throw IllegalArgumentException("apiUrl is required")
        val timeout = obj.optInt("timeoutSec", 30)
        return Config(url, timeout)
    }

    fun getErrorMessage(code: Int): String {
        return when (code) {
            404 -> "Not found"
            500 -> "Server error"
            else -> throw IllegalArgumentException("Unknown code: $code")
        }
    }
}

В първия пример throw се използва в elvis оператора ?:: ако полето apiUrl липсва в JSON, изразът throw незабавно прекъсва изпълнението и хвърля IllegalArgumentException. Типът Nothing позволява на компилатора да изведе типа на дясната страна като String (elvis очаква String, throw има тип Nothing, Nothing е подтип на String). Във втория пример throw вътре в when-израз: ако кодът не съответства на нито един от известните, се хвърля изключение. Компилаторът разбира, че след throw кодът е недостижим, следователно типът на връщане на функцията String не е нарушен.

Throws в сигнатурата на функция и rethrows

В Swift throws се посочва след списъка с параметри и преди стрелката на типа за връщане. Функция с throws може да извиква само други throws-функции вътре в do-catch или с try?. Ако throws-функция не обработва грешката, тя я предава на извикващата страна. Swift също поддържа rethrows — модификатор за функции от по-висок ред, които приемат closure с throws и предават неговата грешка. rethrows означава, че функцията хвърля грешка само ако подаденото closure я е хвърлило — самата функция не генерира грешка.

swift
func mapValues<T>(
    _ array: [T],
    transform: (T) throws -> U
) rethrows -> [U] {
    var result = [U]()
    for element in array {
        result.append(try transform(element))
    }
    return result
}

// Използване с throws-closure
let parsed = try mapValues(jsonStrings) { str in
    let data = Data(str.utf8)
    return try JSONDecoder().decode(Item.self, from: data)
}

rethrows позволява на функцията mapValues да бъде гъвкава: тя приема както throws-closures, така и обикновени. Ако е подадено throws-closure, извикването на mapValues изисква try; ако е обикновено — try не е необходим. Това прави rethrows идеален за функции от по-висок ред като map, filter, reduce в стандартната библиотека на Swift. Препоръка на Apple: използвайте rethrows за API, които приемат throws-closures и единственият източник на грешка е това closure. Ако функцията може да хвърли собствена грешка, използвайте throws.

Checked vs unchecked изключения

Swift throws е по-близо до checked exceptions (като в Java) — хвърляните грешки се декларират в сигнатурата. Kotlin и Dart използват unchecked изключения — throws в сигнатурата не се изисква. Разликата е принципна: checked принуждава разработчика към обработка (по-безопасно, но по-многословно), unchecked дава свобода, но повишава риска да забравите да обработите грешка. Swift избра checked за throws, Kotlin — unchecked за всички изключения. И двата подхода имат предимства: Swift е по-надежден на езиково ниво, Kotlin е по-компактен и удобен във вериги от функционални трансформации.

Потребителски типове грешки

За структурирана обработка на грешки в мобилни приложения се препоръчва създаване на собствени типове грешки вместо използване на базови Exception или Error. В Swift за това се използва enum с протокол Error, в Kotlin — sealed class с наследяване от Throwable (или от Exception), в Dart — class с наследяване от Exception. Потребителските типове позволяват групиране на грешки по категории и предаване на асоциирани данни.

ЕзикТип грешкаОсобеност
Swiftenum: Error { ... }Асоциирани стойности, exhaustive switch в catch
Kotlinsealed class : Throwable()Data class за грешки с полета, when-израз
Dartclass implements ExceptionMessage-поле, on-клауза в catch
Javaclass extends ExceptionChecked vs unchecked, задължителен throws в сигнатурата

При проектиране на потребителски грешки следвайте правилото: една грешка — един сценарий. Не обединявайте различни причини в един тип с флаг String message — правете отделни case-ове/подкласове за всеки сценарий. Това позволява на извикващата страна да обработва всеки случай чрез pattern matching (when/switch), а не чрез сравнение на низове. В Swift това дава exhaustive checking — компилаторът ще предупреди, ако не е обработен някой case на enum NetworkError.

Пример за потребителска грешка в Kotlin

kotlin
sealed class NetworkError(val message: String) : Throwable(message) {
    data class Timeout(val durationMs: Long) :
        NetworkError("Request timed out after ${durationMs}ms")
    data class HttpError(val code: Int, val body: String?) :
        NetworkError("HTTP $code")
    data class NoConnection(val cause: IOException) :
        NetworkError("No internet connection")
}

Sealed class NetworkError наследява Throwable (стандартния тип изключения в Kotlin). Всеки подклас е data class със собствени полета: Timeout съдържа продължителност на таймаута в милисекунди, HttpError съдържа код и тяло на отговора, NoConnection съдържа оригиналния IOException. Такъв дизайн позволява обработка на всяка грешка чрез when с изчерпателно изброяване (когато добавите нов подклас, компилаторът ще принуди актуализиране на всички when-изрази).

Try, try? и try! в Swift

Swift предлага три варианта за извикване на throws-функции, всеки със свой договор за сигурност. try е стандартният начин: изисква do-catch или намиране вътре в throws-функция. try? преобразува грешката в nil — резултатът става опционален, при грешка се връща nil, типът се променя от T на T?. try! е принудително изпълнение без обработка: ако се хвърли грешка, приложението крашва. Използвайте try! само когато сте абсолютно сигурни, че грешката е невъзможна (например, предварително валидни данни).

swift
let configPath = Bundle.main.path(forResource: "config", ofType: "json")!

// try? — опционален резултат
let data = try? Data(contentsOf: URL(fileURLWithPath: configPath))
let json = try? JSONSerialization.jsonObject(with: data ?? Data())

// try! — гарантиран успех (само когато сте сигурни)
let decoder = JSONDecoder()
let defaultConfig = try! decoder.decode(
    Config.self,
    from: Config.defaultJSON
)

// try — стандартна обработка
do {
    let user = try fetchUser()
    showUser(user)
} catch let error as NetworkError {
    showRetryAlert(error.message)
}

В примера try! се използва за предварително валиден JSON, вграден в bundle на приложението — грешка при декодиране е невъзможна при коректно издание. try? се прилага за четене на конфигурационен файл — ако файлът липсва или е повреден, приложението използва стойности по подразбиране, вместо да крашне. try в do-catch се използва за мрежови заявки, където грешката е очаквана и изисква реакция от потребителя. Препоръка: избягвайте try! в production код — използвайте го само за константни данни, проверени по време на компилация.

Често задавани въпроси

Каква е разликата между throw и throws в Swift?

Throw е оператор, който хвърля грешка по време на изпълнение на програмата, прекъсвайки потока. Throws е модификатор на сигнатурата на функция, обявяващ, че функцията може да хвърли грешка. Функция без throws не може да използва throw. Throws е compile-time договор, throw е runtime действие.

Защо в Kotlin няма throws?

Kotlin следва философията на unchecked exceptions: всички изключения могат да останат необработени без синтактично принуждение. Разработчиците на Kotlin смятат, че throws в Java води до излишни try-catch блокове и игнориране на checked exceptions чрез празни catch. Nothing-типът на Kotlin позволява използването на throw като израз, замествайки throws по по-гъвкав начин.

Кога да използваме try! в Swift?

try! е допустим само когато сте абсолютно сигурни, че грешката е невъзможна: предварително валиден JSON от bundle, константни данни, коректни URL схеми. В production код try! е изключение, а не правило. try? е за предпочитане за опционални сценарии със стойност по подразбиране, а try с do-catch — за задължителна обработка на грешки.

Какво е rethrows в Swift?

Rethrows е модификатор за функции, приемащи throws-closures. Функция с rethrows хвърля грешка само ако подаденото closure я е хвърлило. Това позволява на функции от по-висок ред (map, filter) да работят както с throws, така и с non-throws closures без принудителен try от извикващата страна.

Може ли да се хвърли грешка вътре в catch блок?

Да, вътре в catch можете да използвате throw за предаване на грешката нагоре по стека, обвивайки я в друг тип или добавяйки контекст. Това се нарича error chaining или rethrow. В Swift за това е достатъчно още едно throw вътре в catch, в Kotlin — throw вътре в catch-блок. Блокът finally се изпълнява преди предаването на управлението нататък.

Обобщение

  • Throw — оператор за хвърляне на изключение, прекъсващ текущия поток и предаващ обекта за грешка нагоре по стека
  • Throws — compile-time декларация в сигнатурата на функция за възможност от грешка, задължителна в Swift, отсъства в Kotlin
  • В Swift throw приема enum: Error, функцията се маркира с throws, извиква се чрез try / try? / try!
  • В Kotlin throw е израз от тип Nothing, позволяващ използване в when, elvis оператор и присвоявания
  • Rethrows в Swift позволява на функции от по-висок ред да предават грешки само от подадени closures
  • Потребителски грешки се създават чрез enum (Swift) или sealed class (Kotlin) с асоциирани стойности за детайлна информация
  • Проектирайте типове грешки по принципа "един case — един сценарий" за удобство при обработка чрез pattern matching

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

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

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

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