Try-Catch è una costruzione di cattura delle eccezioni che consente di eseguire codice potenzialmente pericoloso in un blocco protetto e gestire correttamente gli errori senza terminare bruscamente il programma. Il blocco try contiene codice che può lanciare un'eccezione, catch la intercetta ed esegue la logica di ripristino. Secondo la Apple Swift Documentation (2026), il blocco finally viene eseguito indipendentemente dal fatto che sia stata lanciata o meno un'eccezione, garantendo il rilascio delle risorse.
Punti chiave
Try-Catch è una costruzione fondamentale di gestione strutturata delle eccezioni presente nella maggior parte dei linguaggi di programmazione moderni. Si compone di tre blocchi: try (tentativo di esecuzione di codice pericoloso), catch (intercettazione e gestione dell'eccezione) e finally opzionale (finalizzazione). L'idea della costruzione è separare la logica di business dalla logica di gestione degli errori, rendendo il codice più leggibile e prevedibile.
Il concetto è stato implementato per la prima volta in C++ come try/catch, poi è stato adottato da Java, C#, Swift, Kotlin, Dart, Python, JavaScript e altri linguaggi. Ogni linguaggio aggiunge le proprie caratteristiche: in Swift il blocco catch deve essere esaustivo, in Kotlin try-catch può essere un'espressione, in Dart finally è obbligatorio per le risorse di flusso. Nonostante le differenze, il principio di base è lo stesso: un errore viene gestito il più vicino possibile al punto in cui si verifica, non globalmente.
L'uso di Try-Catch è particolarmente importante nello sviluppo mobile, dove i fattori esterni — perdita di rete, risposta errata del server, memoria insufficiente — si verificano costantemente. Una corretta gestione delle eccezioni previene i crash dell'applicazione e garantisce una UX corretta: l'utente vede un messaggio di errore invece di una chiusura improvvisa. Secondo la Google Android Kotlin Style Guide (2026), ogni funzione che può lanciare un'eccezione deve gestirla tramite try-catch o dichiarare throws nella sua firma.
Il meccanismo di esecuzione di try-catch si basa sullo srotolamento dello stack (stack unwinding). Quando viene lanciata un'eccezione all'interno del blocco try tramite l'operatore throw (o a seguito di un errore di sistema), il flusso di esecuzione normale viene immediatamente interrotto. L'esecuzione si sposta di un livello superiore nello stack delle chiamate alla ricerca di un blocco catch appropriato. I linguaggi moderni cercano un catch il cui tipo corrisponda al tipo dell'eccezione lanciata utilizzando un meccanismo di corrispondenza dei tipi (type matching).
Se viene trovato un catch corrispondente, il suo corpo viene eseguito, dopodiché l'esecuzione continua dopo l'intera costruzione try-catch-finally. Se non viene trovato alcun catch, l'eccezione risale nello stack e può essere gestita a un livello superiore — fino a un gestore globale che, in un'applicazione mobile, mostra all'utente un dialogo di errore. Se l'eccezione non viene gestita da nessuna parte, l'applicazione va in crash. Ecco perché la corretta gestione di tutti i possibili tipi di eccezioni è fondamentale per la stabilità dell'applicazione.
fun readUserData(): User {
return try {
val response = api.fetchUser()
parseUser(response)
} catch (e: IOException) {
logError("Errore di rete", e)
throw AppException("Caricamento dati fallito")
} catch (e: JsonParseException) {
logError("Errore di analisi", e)
return User.default()
} finally {
closeLoadingIndicator()
}
}
Il codice prima tenta di effettuare una richiesta all'API e analizzare la risposta. Se si verifica una IOException (problema di rete), l'eccezione viene registrata e rilanciata come AppException. Se si verifica una JsonParseException — viene restituito un utente predefinito. Il blocco finally garantisce di nascondere l'indicatore di caricamento, prevenendo perdite di componenti UI sullo schermo.
In Swift, la gestione degli errori è implementata tramite il protocollo Error (precedentemente ErrorType). Qualsiasi tipo conforme a Error può essere lanciato tramite l'operatore throw. Una funzione che può lanciare un errore viene contrassegnata con la parola chiave throws nella sua firma. Chiamare tale funzione richiede il prefisso try (per try-catch esplicito), try? (risultato opzionale) o try! (esecuzione forzata senza gestione dell'errore).
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("Rete " + error.localizedDescription)
} catch {
showGenericError()
}
L'enum NetworkError implementa il protocollo Error, definendo tre casi: noConnection, serverError con un codice e timeout. La funzione fetchUser è contrassegnata con throws: prima verifica la connessione, poi esegue la richiesta e l'analisi. Nel blocco do-catch, tre catch gestiscono diversi scenari: il caso specifico noConnection, il tipo generale NetworkError e tutti gli altri errori. Ciò consente di mostrare all'utente messaggi diversi a seconda del tipo di problema.
Kotlin ha ereditato try-catch-finally da Java ma ha aggiunto una differenza importante: in Kotlin, try-catch è un'espressione (expression), non un'istruzione (statement). Ciò significa che il risultato di un blocco try o catch può essere assegnato a una variabile. L'ultima espressione nel blocco try diventa il risultato in caso di successo, l'ultima espressione nel catch — in caso di errore. Se l'errore non viene gestito da nessun catch, l'eccezione risale nello stack.
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("Rete ", e)
Result.Error(e)
}
}
Nell'esempio, sealed class Result incapsula una risposta di successo o un errore. La funzione loadData usa try-catch come espressione: in caso di successo restituisce Result.Success, in caso di HttpException o IOException — Result.Error con registrazione. Questo approccio consente al chiamante di gestire gli errori senza eccezioni — tramite un'espressione when sul tipo Result. Ciò è particolarmente conveniente in Jetpack Compose per visualizzare diversi stati dell'interfaccia utente (Loading, Success, Error) tramite StateFlow e collectAsState.
Dart supporta try-catch-finally con una sintassi simile a Java, ma con l'aggiunta di una clausola on per filtrare per tipo di eccezione senza specificare una variabile. Ciò è comodo quando l'eccezione stessa non è necessaria — solo il suo tipo è importante. Dart supporta anche un blocco catch con due parametri: l'oggetto eccezione e StackTrace, utile per registrare l'intera catena di chiamate.
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 viene catturata con l'oggetto eccezione e StackTrace per una registrazione dettagliata, poi rilanciata come AppException. FormatException viene catturata senza variabile — basta sapere che il formato della risposta non è corretto. Il blocco finally garantisce la chiusura di HttpClient, prevenendo perdite di socket. In Flutter, questo approccio è particolarmente importante per i test Widget, dove le eccezioni non gestite in State.initState causano il fallimento dell'intera sessione di test.
Anche gli sviluppatori esperti commettono errori con try-catch che causano perdite di memoria, bug nascosti o comportamenti inappropriati dell'applicazione. Esaminiamo i cinque problemi più comuni nello sviluppo mobile.
Il catch vuoto è una delle peggiori pratiche. L'eccezione viene inghiottita, l'applicazione continua a funzionare in uno stato errato e lo sviluppatore non viene mai a conoscenza del problema. Almeno registra sempre l'eccezione. In Kotlin usa catch(e: Exception) { Log.e(...) }, in Swift — catch { print($0) }. In Dart, un catch minimamente accettabile deve chiamare debugPrint o scrivere in Crashlytics.
Catturare tutte le eccezioni tramite catch (Exception e) senza distinguere per tipi nasconde errori imprevisti — NullPointerException, OutOfMemoryError, StackOverflowError. Cattura solo i tipi che ti aspetti e puoi gestire. Per tutto il resto, consenti la propagazione verso l'alto. Nello sviluppo mobile, catch specifici per IOException, TimeoutException, AuthException forniscono messaggi più significativi all'utente.
Le risorse — file, socket, cursori del database, animazioni — devono essere rilasciate in finally o in un blocco use (AutoCloseable). Gli sviluppatori spesso dimenticano di chiudere le risorse quando si verifica un'eccezione, causando perdite. In Kotlin usa .use { } per risorse Closeable, in Swift — defer { }, in Dart — await using dal pacchetto async. Il blocco finally garantisce il rilascio anche quando viene lanciata un'eccezione all'interno di catch.
Nel codice asincrono, try-catch non cattura eccezioni da altri thread. In Kotlin Coroutines usa CoroutineExceptionHandler o SupervisorJob. In Swift async/await — do-catch all'interno di Task. In Flutter — runZonedGuarded per la cattura globale. Ignorare questa regola è la causa di crash difficili da riprodurre in produzione.
La gestione delle eccezioni non deve bloccare l'interfaccia utente indefinitamente. Mostra all'utente un messaggio specifico e dagli l'opportunità di riprovare l'operazione. Una Snackbar con pulsante Retry in Kotlin/Compose, UIAlertController con azione in Swift, SnackBar con azione in Flutter — un'esperienza utente minimamente sufficiente per errori di rete o server. Evita dialoghi generici “Si è verificato un errore” senza opzioni di ripristino.
Domande frequenti
Try-catch usa eccezioni e srotolamento dello stack per la gestione degli errori, che può essere costoso in termini di prestazioni quando ci sono molti errori. Result Type è un tipo contenitore (Success o Failure) gestito tramite pattern matching senza srotolamento dello stack, più efficiente per errori previsti.
Finally è obbligatorio se il blocco try apre risorse (file, socket, cursori) che devono essere chiuse. Se non vengono aperte risorse, finally non è necessario. Nei linguaggi moderni, usa AutoCloseable/use/defer per la chiusura automatica delle risorse senza finally. Il blocco use in Kotlin e Swift sostituisce finally per oggetti Closeable.
Nel flusso normale (senza eccezioni), try-catch non influisce praticamente sulle prestazioni — la JVM e il compilatore Swift ottimizzano questo caso. Ma quando viene lanciata un'eccezione, si verifica lo srotolamento dello stack, che può richiedere 10–100 μs a seconda della profondità dello stack. Non usare le eccezioni per il controllo del flusso — è un anti-pattern.
Nelle coroutine, usa try-catch all'interno di coroutineScope o CoroutineExceptionHandler per la cattura globale. SupervisorJob impedisce la cancellazione della coroutine padre quando una coroutine figlia fallisce. Per launch usa CoroutineExceptionHandler, per async — try-catch intorno a await().
Più catch sono preferibili: il codice si legge linearmente, ogni blocco gestisce un tipo di eccezione. Un singolo catch con if-else è più difficile da mantenere ed è facile perdere un nuovo tipo di eccezione. In Swift, più catch sono obbligatori per la gestione esaustiva di enum Error, in Kotlin non ci sono restrizioni, ma la best practice è un catch separato per tipo.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche