Hibaterjedés — a hiba terjedésének mechanizmusa felfelé a hívási veremben a keletkezés helyétől a kezelőig. Amikor egy függvény nem tudja önállóan kezelni a hibát, továbbadja azt a hívó félnek kivétel (exception), throws deklaráció vagy visszatérési típus segítségével. A terjedés helyes megvalósítása kritikus a mobilalkalmazások stabilitása szempontjából: a kezeletlen vagy helytelenül továbbított hibák összeomlásokhoz vezetnek. A Apple Swift Documentation (2026) szerint az automatikus terjedés throws segítségével Swift-ben lehetővé teszi a hiba bármely szintre történő továbbítását boilerplate kód nélkül.
Főbb pontok
Hibaterjedés — a hibaobjektum továbbításának folyamata a keletkezés helyétől felfelé a hívási láncon a legközelebbi megfelelő kezelőig. Képzelje el a hívási vermet: ViewController meghívja ViewModel-t, ViewModel meghívja Repository-t, Repository meghívja API-t. Ha az API hálózati hibát ad vissza, annak át kell haladnia Repository-n és ViewModel-en keresztül a ViewController-hoz, amely üzenetet jelenít meg a felhasználónak. Minden köztes függvény eldönti: kezeli a hibát vagy továbbadja (terjeszti).
Két megközelítés létezik a terjedésre: automatikus és kézi. Az automatikus megközelítésben (Swift throws, Java ellenőrzött kivételek) a fordító kényszeríti a fejlesztőt, vagy kezelje a hibát, vagy deklarálja a terjedést az aláírásban. A kézi megközelítésben (Result típus, Kotlin Try) a hiba értékként kerül továbbításra — a fejlesztő explicit kódot ír a hiba továbbítására vagy átalakítására. A Kotlin Result Docs (2026) szerint a Result<T> Kotlin-ban nem közvetlen terjedésre szolgál függvényhatárokon keresztül — minden szinten át kell alakítani vagy kezelni, ami tudatosabbá, de terjengősebbé teszi a terjedést.
A megközelítés kiválasztása az alkalmazás architektúrájától és a nyelvtől függ. Swift-ben az automatikus terjedés uralkodik throws segítségével, Kotlin-ban — a kivételek (váratlan hibákhoz) és a Result-szerű konténerek (várható hibákhoz) keveréke. Fontos megérteni: a terjedés nem cél, hanem szükségszerűség. Az ideális architektúra minimalizálja a terjedés mélységét azáltal, hogy a lehető legalacsonyabb szinten kezeli a hibákat, ahol elegendő kontextus van a döntés meghozatalához.
Swift-ben a throws segítségével történő terjedés automatikusan történik: ha az A függvény throws-szal meghívja a B függvényt throws-szal, és A nem kezeli B hibáját do-catch-ben, a hiba automatikusan továbbításra kerül A hívó feléhez. Ez kiküszöböli a Java ellenőrzött kivételekre jellemző boilerplate kódot, ahol throws-t minden láncbeli metódusban deklarálni kell. Swift az elvet alkalmazza: „egy throws függvény a láncban = az egész lánc throws lesz, ha nem kezeljük a köztes szinteken”.
struct UserRepository {
func fetchUser(id: Int) throws -> User {
let data = try networkService.request(path: "/users/\(id)")
return try parseUser(from: data)
}
}
class UserViewModel {
let repo = UserRepository()
func loadUser(id: Int) throws -> User {
return try repo.fetchUser(id: id)
}
}
// ViewController — végső kezelő
func onButtonTap() {
let vm = UserViewModel()
do {
let user = try vm.loadUser(id: 42)
updateUI(user)
} catch {
showError("A felhasználó betöltése sikertelen")
}
}
Terjedési lánc: networkService.request -> fetchUser -> loadUser -> onButtonTap. Minden köztes függvény throws-szal van jelölve és nem tartalmaz do-catch-et — a hiba automatikusan továbbítódik felfelé. ViewController onButtonTap a végső kezelő do-catch-csel. Ha ViewModel úgy döntött volna, hogy átalakítja a hibát (más típusba csomagolja), használhatta volna a do-catch-et és egy új throw-t. Automatikus terjedés lerövidíti a kódot: Repository-nak nem kell tudnia, hogyan kezelje a hibát — ez a ViewController felelőssége, amely hozzáfér a UI-hoz az üzenet felhasználónak történő megjelenítéséhez.
Kotlin-ban a kivételek segítségével történő terjedés nem igényel throws deklarációt az aláírásban (minden kivétel ellenőrizetlen). A kivétel automatikusan felfelé halad a veremben, amíg try-catch-be nem ütközik. Az throws hiánya az aláírásban azonban implicité teszi a terjedést: a fejlesztő nem látja a függvény aláírásából, hogy az kivételt dobhat. Ez egyszerre előny (kevesebb boilerplate) és hátrány (könnyebb elfelejteni a kezelést). Kotlin ezt a problémát konvenciók és architekturális minták segítségével oldja meg, nem a nyelv által.
class UserRepository(
private val api: ApiService,
private val db: Database
) {
suspend fun getUser(id: String): User {
return try {
api.fetchUser(id)
} catch (e: IOException) {
db.getCachedUser(id) ?: throw AppException("User unavailable")
}
}
}
class UserViewModel(private val repo: UserRepository) {
private val _state = MutableStateFlow<UiState<User>>(UiState.Loading)
val state: StateFlow<UiState<User>> = _state
fun loadUser(id: String) {
viewModelScope.launch {
try {
val user = repo.getUser(id)
_state.value = UiState.Success(user)
} catch (e: AppException) {
_state.value = UiState.Error(e.message ?: "Unknown")
}
}
}
}
A Repository-ban terjedés átalakítással: IOException (hálózat nem elérhető) esetén a függvény megpróbálja lekérni a gyorsítótárazott adatokat az adatbázisból. Ha a gyorsítótár üres, AppException-t dob — a terjedés új hibával folytatódik. ViewModel elkapja az AppException-t és lefordítja UiState.Error-re — a hiba nem megy tovább, a terjedés az UI réteg szintjén befejeződik. Kotlin Coroutines sajátosságokat ad hozzá: a launch-beli kivételek automatikusan terjednek a CoroutineExceptionHandler-en keresztül, async esetén pedig csak az await() meghívásakor. Fontos ezt figyelembe venni a korutinákban történő terjedés tervezésekor — a SupervisorJob megakadályozza a szülő korutína visszavonását egy gyermek korutína hibája esetén.
Alternatíva a kivételekre — terjedés egy típus-konténeren keresztül, amely a sikert vagy a hibát értékként továbbítja. Ebben a megközelítésben a függvény nem értéket ad vissza, hanem egy csomagolást: Result<T, E> Swift-ben, Result<T> Kotlin-ban, Either<L, R> Dart-ben (az fpdart vagy dartz csomagból). A hiba nem csomagolja ki a vermet — egyszerűen a konténerben fekszik, és a következő szint dönti el, mit kezdjen vele. Ez explicitabbé és irányíthatóbbá teszi a terjedést.
data class HttpResult<out T>(
val data: T?,
val error: AppError?
) {
val isSuccess: Boolean get() = data != null
val isError: Boolean get() = error != null
}
sealed class AppError {
data class Network(val message: String) : AppError()
data class Auth(val message: String) : AppError()
}
fun fetchUser(id: String): HttpResult<User> {
return try {
val response = api.get("/users/$id")
HttpResult(data = parseUser(response), error = null)
} catch (e: IOException) {
HttpResult(data = null, error = AppError.Network("No internet"))
}
}
HttpResult<T> — egy egyszerű konténer data és error mezőkkel. A sealed class AppError meghatározza a hibatípusokat (Network, Auth). A fetchUser függvény HttpResult-t ad vissza, a terjedés nem igényli a verem kicsomagolását — a hívó fél egyszerűen ellenőrzi az isSuccess/isError értékeket. Ez a megközelítés különösen hasznos a Tiszta Architektúrában, ahol minden réteg (data, domain, presentation) átalakíthatja a hibát: IOError -> DomainError -> UiError. A konténeren keresztüli terjedés explicité és tesztelhetővé teszi ezeket az átalakításokat, ellentétben a kivételekkel, ahol az átalakítási lánc nem látható a függvények aláírásaiban.
A hibakezelés tervezésének egyik kulcsfontosságú döntése a terjedés (felfelé továbbítás) és a kezelés (itt kezelés) közötti választás. A döntés szabálya: kezeld a hibát azon a szinten, ahol elegendő kontextus van egy értelmes cselekvéshez. Ha hozzáférsz a UI-hoz — jeleníts meg üzenetet a felhasználónak. Ha hozzáférsz a gyorsítótárhoz — próbálj helyreállni. Ha egyik sincs — terjeszd tovább.
| Forgatókönyv | Művelet | Indoklás |
|---|---|---|
| Hálózati hiba a Repository-ban | Terjeszd | Repository nem tudja, hogy a felhasználó meg akarja-e ismételni a kérést |
| Értelmezési hiba a Repository-ban | Kezeld (alapérték visszaadása) | Repository ismeri a formátumot, visszaadhat tartalék értéket |
| Időtúllépés a ViewModel-ben | Kezeld (UiState.Error) | ViewModel kezeli az UiState-t, tudja hogyan fordítsa le a hibát |
| Hitelesítési hiba az Interceptor-ban | Kezeld (token frissítése) | Interceptor hozzáfér a tokenekhez és helyreállíthatja a munkamenetet |
| Ismeretlen hiba a UseCase-ben | Terjeszd | UseCase-nek nincs UI kontextusa — csak üzleti logika |
Aranyszabály: minimális terjedés, maximális kezelés az alsóbb szinteken. Ha Repository helyre tud állni a gyorsítótárból — meg kell tennie, anélkül hogy továbbítaná a hibát felfelé. Ha ViewModel meg tud jeleníteni egy Snackbar-t — jelenítse meg, anélkül hogy további kódot követelne a ViewController-től. Minden terjedési szint növeli a kapcsolódást és megnehezíti a tesztelést. A Google Android Architecture Guide (2026) szerint ajánlott minimalizálni a terjedést a réteghatárokon keresztül, sealed class UiState használatával az összes lehetséges állapot (Loading, Success, Error) megjelenítésére a ViewModel szintjén, és nem továbbítani kivételeket közvetlenül az UI rétegbe.
A helytelen terjedés a mobilalkalmazásokban nehezen megtalálható hibák forrása. Vizsgáljuk meg az öt fő problémát, amellyel a fejlesztők szembesülnek, és a megoldási módokat.
A leggyakoribb probléma: terjedés során a kivételt elkapják, naplózzák és újat dobnak az eredeti kivétel nélkül. A fejlesztő elveszíti a StackTrace-t és nem tudja megérteni, hol pontosan történt a hiba. Swift-ben használjon hibaláncolást: throw MyError(context: originalError). Kotlin-ban: throw AppException(cause = originalException). Dart-ben: throw AppException(message, originalException). Soha ne hozzon létre új kivételt az ok/alapul szolgáló hiba továbbítása nélkül.
catch (e: Exception) { /* semmi */ } — antiminta, amely ahhoz vezet, hogy az alkalmazás helytelen állapotban folytatja a működést. Ha biztos benne, hogy a hiba figyelmen kívül hagyható — adjon hozzá megjegyzést indoklással. Swift-ben opcionális figyelmen kívül hagyáshoz használja a try?-t (hiba -> nil). Kotlin-ban — Result<T>.onFailure { /* log */ }. Ne némítsa el a kivételeket naplózás nélkül.
Ha a hiba 5+ szinten megy keresztül kezelés nélkül, az architektúra felülvizsgálatot igényel. Minden terjedési szint függőség az alsóbb függvények throws aláírásától. Megoldás: használjon Failure konténereket (sealed class Result { Success, Error }) a réteghatárokon, hogy a terjedés explicit és korlátozott legyen. Minél rövidebb a terjedési lánc, annál könnyebb tesztelni és hibakeresni a kódot.
Kotlin Coroutines-ban a launch-beli kivétel alapértelmezés szerint visszavonja a szülő korutínát és az összes sibling-et (ugyanazon scope gyermekeit). Ha 10 párhuzamos feladatból egy meghiúsul, a maradék 9 visszavonásra kerül, ami gyakran nem kívánatos. Használjon SupervisorJob-ot vagy supervisorScope-ot a hibák elkülönítésére: egy child hibája nem vonja vissza a sibling-eket. ViewModelScope alapértelmezés szerint SupervisorJob-ot használ, ami Android-ban véd ez ellen a probléma ellen.
Callback-alapú API-ban a hiba gyakran a callback paramétereként kerül továbbításra. Ha a callback nem kezeli a hibát (vagy helytelenül kezeli), a terjedés implicité válik és könnyen elvész. Megoldás: migráljon async/await (Swift) vagy korutinákra (Kotlin), ahol a terjedés a szabványos try-catch mechanizmusokon keresztül működik. Ha a callback elkerülhetetlen — használjon Either<Error, T> vagy Result<T> a mindkét eset kötelező kezeléséhez.
Gyakran Ismételt Kérdések
A Throw egy egyszeri művelet, a kivétel eldobása. A Hibaterjedés a hiba több veremszinten keresztüli továbbításának teljes folyamata, throw-tól catch-ig. A terjedés magában foglalja a throw-t, az automatikus vagy kézi továbbítást köztes függvényeken keresztül és a végső kezelést. Ez egy tágabb fogalom, amely a hiba életciklusát írja le.
Használjon mock objektumokat, amelyek adott forgatókönyvekben kivételeket dobnak. Ellenőrizze, hogy a függvény helyesen terjeszti vagy kezeli a hibát assertThrows (Kotlin/JUnit) vagy XCTAssertThrowsError (Swift/XCTest) segítségével. Result-alapú terjedés esetén ellenőrizze az isSuccess/isError értékeket és az értékeket mindkét esetben.
A Result terjedés előnyösebb a várható hibákhoz (érvénytelen adatok, üzleti szabályok) egy architekturális határon belül. A kivételek jobbak a váratlan hibákhoz (hálózat elvesztése, I/O hibák), amelyeket magas szinten kell kezelni. A hibás eredmény nem szakítja meg a végrehajtási folyamatot, a kivétel megszakítja.
Kotlin Coroutines-ban a launch-beli kivétel automatikusan terjed a CoroutineScope-on keresztül a sibling-ek visszavonásával. Használjon supervisorScope-ot vagy SupervisorJob-ot az elkülönítéshez: egy korutína hibája nem vonja vissza a többit. Async esetén a hibát explicit módon kezelni kell try-catch segítségével az await() meghívásakor, különben elnyelődik.
Az ideális végső kezelő az UI réteg (ViewController, Fragment/Composable). Csak ez fér hozzá a felhasználói felülethez és tud üzenetet, Snackbar-t vagy párbeszédablakot megjeleníteni. A köztes rétegek (Repository, UseCase, ViewModel) terjesztik a hibát, szükség esetén absztraktabb domain típussá alakítva azt.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is