Compatibile con Xcode 8

Gestione degli errori in Swift. Do, try e catch!

La verità è che puoi provare a scrivere il codice più bello e perfetto del mondo, ma non potrai mai privarlo d’errori (San Swift da Cupertino – versetto 144, pag 10).

É matematico, hanno condotto studi a riguardo, più aumenta il numero di righe del codice, più aumentano gli errori che il codice potrebbe generare.

Ma, cos’è un errore?

No! Gli errori di cui ti parlo non sono quelli semantici, cioè quelli che nascono da un’errata scrittura del codice. Gli errori, quelli che tutti odiano e su cui vorrei puntare l’attenzione, sono quelli logici.

Per errore logico si intende quel tipo d’errore che, pur rispettando la semantica e i vincoli del codice, genera dei risultati inaspettati che potrebbero compromettere il funzionamento dell’applicazione.

L’esempio più eclatante è quello di un programma per la gestione del conto corrente che non controlla le varie operazioni svolte dall’utilizzatore. Tutti sappiamo che in un conto bancario il saldo iniziale non può, certamente, essere minore di zero. Però, se il deposito bancario viene rappresentato da un valore di tipo Double, l’applicazione è comunque predisposta ad accettare valori negativi.

Errore logico di un BankAccount:

Nell’esempio posso inserire qualsiasi tipo di valore, anche negativo, e non verrebbe generato nessun tipo d’errore. Nella realtà sappiamo che non è così.

E dove sta il problema? Con le istruzioni if else in Swift, con l’istruzione guard o con i modificatori d’accesso riesco perfettamente a risolvere.

Il problema, se si può chiamare tale, è che le istruzioni di controllo, fin ora utilizzate e pur svolgendo egregiamente questo lavoro, non sono adatte alla gestione degli errori logici. O comunque, in sistemi più complessi come quelli che vedrai nel prossimo corso quando imparerai a creare le tue prime app, vorrai che l’errore venga gestito a livello d’applicazione e non semplicemente in quello specifico istante.

Ti assicuro che non sono pazzo! La questione è trascendentale e sicuramente piacerà anche a te.

Gestione Errori in Swift

Mi spiego meglio.

A tutti è capitato che, provando ad inserire un valore errato in un campo di testo, l’applicazione risponda con un opportuno messaggio: “Ehi belloccio/a i caratteri speciali non sono ammessi, o ancora: “Non puoi utilizzare frasi razziste“. E sicuramente ti sarà anche capitato che l’applicazione venisse chiusa a seguito di qualche operazioni non consentita, o un numero talmente alto di operazione, da mandare in tilt l’app.

Oppure, in operazioni ancora più drastiche le applicazioni decidono di intraprendere delle azioni irreversibili: come il logout, il ban, la sospensione o addirittura la correzione dell’errore.

Ma come fa l’applicazione a rispondere diversamente ad ogni tipo d’errore? Come sceglie di comportarsi in modo diverso in base al tipo d’errore?

É proprio questo il punto! Per questo si parla di gestione degli errori in Swift.

Gestire significa poter controllare. Il controllo ti permette di far defluire correttamente l’utilizzatore nella giusta direzione anche quando ha commesso degli errori.

Per poter gestire gli errori, correttamente e in piena libertà, sono necessari degli strumenti realizzati ad hoc. Ecco perché Apple ha deciso di adottare il sistema dell’Error Handling, o gestione degli errori, con la versione 2.0 del linguaggio Swift.

Con il nuovo sistema, la gestione degli errori in Swift (o eccezioni con il linguaggio Swift), imparerai a personalizzare gli errori, a performare delle azioni che ti permetteranno di controllare la propagazione dell’errore e quindi l’inevitabile distruzione del dispositivo.

Il concetto di Eccezione

Purtroppo siamo così assuefatti dal codice che scriviamo che, non ci accorgiamo e anzi sconosciamo cosa vuol dire la parola errore.

Un altro problema deriva dall’errata conoscenza dei meccanismi di funzionamento di un computer o dispositivo portatile, infatti quando ci troviamo di fronte al computer siamo indotti a confondere il crash di un’applicazione con l’errore.

Il crash è la condizione in cui un’applicazione termina inaspettatamente il suo ciclo d’esecuzione. Quindi quando un’applicazione si chiude autonomamente e senza l’intervento umano, in gergo, dirai che è crashata. La causa di un crash, che porta un’applicazione a terminare preventivamente la sua esecuzione, si chiama errore.

In informatica e per i linguaggi di programmazione come Swift, il termine errore è sinonimo di eccezione.

Una eccezione è una condizione o evento che altera il normale flusso d’esecuzione del codice. Un esempio d’eccezione è la già citata divisione di un numero per zero, famosa per aver arresto il sistema missilistico di una nave da guerra americana.

Hai mai visto crashare il sistema operativo Windows e la celeberrima schermata blu? Non è solo un problema dei Winzoz users, anche Apple ha lo stesso problema con la schermata nera.

In ogni caso la schermata nera o blu, non è il crash del sistema in sé, bensì rappresenta la comunicazione di un errore avvenuto al sistema operativo. Infatti il sistema va realmente in crash, anzi in questo caso in riavvio, quando cliccate su uno dei bottoni o comandi presenti nella finestra.

Quindi la schermata della morte è frutto di un meccanismo dato dalla gestione degli errori. In questo caso il sistema operativo decide autonomamente di interrompere l’esecuzione di ogni attività perché soggetto ad un problema di tipo irreversibile.

Non potendo correggerlo, prima di riavviarsi, decide comunque di avvisarti dell’avvenuto problema.

Le eccezioni in Swift, quindi, rappresentano un avviso o un campanello d’allarme che scatta quando una condizione non gestita potrebbe compromettere l’esecuzione dell’algoritmo e, più avanti vedrai, dell’applicazione.

Ehi! Fai attenzione che qui stai provando a dividere per zero, potrebbe compromettere l’applicazione!

Come già ribadito ad inizio lezione, gli errori o eccezioni, non sono frutto solo di problemi di sintassi o matematici (alcuni dei quali già controllati da Xcode) bensì rientrano nella categoria tutti quegli errori che non si vedono ma che comprometterebbero ugualmente l’applicazione da un punto di vista logico.

Ricerca

Come per la nostra salute, anche le applicazioni, se non eseguono dei controlli rischiano di finire all’altro mondo.

Purtroppo il linguaggio Swift non è in grado di capire a priori quando un codice potrebbe generare un errore di tipo logico o potrebbe far ammalare l’applicazione. Per questo motivo, come per la prevenzione umana, anche il codice ha bisogno delle nostre cure e attenzioni.

Prevenire è meglio che curare, no?

La prevenzione parte da delle semplici domande:

  1. Cos’è che potrebbe generare un errore in questo codice?
  2. Ho individuato tutti i possibili fattori d’errore?
  3. Ci sono errori che non dipendono dalla natura del codice?

Partendo da questi presupposti, una volta che hai individuato le eccezioni, queste vanno catalogate.

Facciamo finta d’aver realizzato un’applicazione di chat online. L’algoritmo che creerai permetterà di inviare un messaggio di testo ad utente di una lista dei contatti. Per semplicità utilizziamo il seguente codice:

Poniti sempre delle domande anche quando l’applicazione sembra funzionare.

Per esempio l’applicazione potrebbe generare un errore perché:

  1. Il destinatario del messaggio non è presente nella lista dei contatti.
  2. Non è stato inserito un destinatario.
  3. Non è stato inserito il messaggio.
  4. Chi più ne ha, più ne metta…

Una volta individuati, come catalogo gli errori con il linguaggio Swift?

Il protocollo Error

Un errore per il linguaggio Swift è rappresentato dal protocollo Error. Quindi un’eccezione è un oggetto conforme al protocollo Error (introdotto con la versione di Swift 2.0 come ErrorType e da swift 3.0 come solo Error).

Per catalogare le possibili eccezioni si utilizza un enum conforme al protocollo Error:

Applicando la seguente definizione, agli errori trovati nel codice del paragrafo precedente, viene fuori il seguente enum:

Ottimo! a questo punto hai una bellissima struttura che ti da la possibilità di accedere velocemente ad un tipo d’errore prestabilito da te.

Ma cosa me ne faccio dell’enumerazione e come li gestisco all’interno del codice?

enum-e-protocollo-error-linguaggio-swift

Throws e throw

Dove si verificano maggiormente gli errori? O meglio, qual’è l’unica forma di interazione che ha l’utente con il programma?

Fino ad ora sei stato abituato a risolvere gli esercizi e a scriverne il codice sul Playground. Il Playground ti da la possibilità di testare il codice sia dal punto di vista dello sviluppatore che dal punto di vista dell’utilizzatore. Mi spiego meglio.

Quando realizzerai la tua prima applicazione, nel prossimo corso, ti accorgerai che l’unico sistema che ha l’utente per poter interagire con l’applicazione sono i bottoni, i campi di testo ecc ecc. Questi a loro volta sono collegati con dei metodi e funzioni di classi create ad hoc.

Non mi spingo oltre, però ti dico già da adesso che l’utente interagirà con la logica dell’applicazione solo ed esclusivamente grazie alle funzioni e metodi da te creati.

In base a questa considerazione, che per adesso devi prendere per buona, i metodi e le funzioni sono i primi generatori d’errore di una qualsiasi applicazione.

La prevenzione e quindi la gestione degli errori in Swift parte dal controllo dei metodi e funzioni.

Il linguaggio Swift ti permette di marcare un metodo, o funzione, che potrebbe generare delle eccezioni.

Il marcatore ha un compito ben preciso, avvisa l’applicazione e/o il compilatore che lì potrebbero nascere dei problemi: “Ehi! Fai attenzione quando esegui questo codice, potrebbe generare degli errori”.

Questo sistema permette di circoscrivere le aree possibili d’errore. Non è detto che succederà, ma se lo farà avrai uno strumento che lo controllerà.

Throws: marcare un metodo come possibile ad eccezione

Per marcare una funzione o metodo, come generatore di un probabile errore, si utilizza la parola chiave throws.

La parola chiave throws va inserita subito dopo la definizione della funzione e precisamente dopo la scrittura dei parametri d’ingresso. Se la funzione ha dei tipi di ritorno, il throws in Swift, va inserito prima della freccia (->).

Nell’esempio dell’invio dei messaggi scriverai così:

Così facendo avvisi il compilatore e in futuro l’applicazione che la funzione, non si sa quando, potrà generare un’eccezione.

Throws linguaggio swift marcare un metodo classe come passibile ad errore

Throw: sollevare un’eccezione

Sollevare un’eccezione con il linguaggio Swift significa avvisare l’applicazione della nascita d’un errore o eccezione.

Ora immagina che, all’interno di una funzione, nasca un errore. Dirai che la funzione ha sollevato un eccezione quando comunicherà la comparsa di un problema e delegherà all’esterno la gestione ed il trattamento di quella particolare eccezione.

Nel linguaggio comune, puoi tradurre “è stata sollevata un’eccezione” con: “Ehi, tu che hai chiamato la funzione! Dico proprio a te. Vedi che ho terminato l’esecuzione della funzione a causa di un errore, lascio a te il compito di gestirlo.

Nel caso dell’invio del messaggio, solleveremo un’eccezione quando tramite un controllo non verrà rispettata una delle clausole per l’invio del testo. Ad esempio, quando il destinatario non è presente nella lista solleveremo l’eccezione ErroreInvioMessaggio.destinatarioNonPresente.

Per sollevare un eccezione in Swift, devi utilizzare la parola chiave throw seguita dal tipo d’errore non controllato:

Proviamo ad applicare la definizione ad uno dei probabili errori dell’algoritmo inviaMessaggio.

Per prima cosa controlliamo che il il destinatario sia stato inserito, per semplicità immaginiamo che il non inserimento equivalga alla stringa vuota o “”. Se l’utente inserisce la stringa vuota, come parametro d’ingresso, allora la funzione solleverà l’eccezione ErroreInvioMessaggi.destinatarioNonInserito:

Quando l‘istruzione guard viene attivata, quindi quando entra nel blocco else, viene anche sollevata l’eccezione ErroreInvioMessaggio e più precisamente destinatarioNonInserito.

La throw puoi immaginarla alla stregua dell’istruzione return. 

Se il return esce tranquillamente dalla funzione, la throw esce bruscamente sollevando un’eccezione. Oltre ad uscire dalla funzione o metodo, porta con se anche il motivo dell’uscita ovvero il tipo d’eccezione sollevata.

A questo punto il codice completo dovrebbe essere questo:

throws-e-throw-gestione-errori-linguaggio-swift

condividi xcoding

Ho impiegato un po’ di tempo a scrivere questa lezione, aiutami semplicemente mettendo un mi piace o follow alle mie pagine.

[addtoany]

Grazie davvero :-)

Propagazione: try, do e catch

Cosa succede se provi a chiamare la funzione throws? Cioè, se sotto la sua definizione, provi a fare:

Il Playground ti comunica che non puoi effettuare una chiamata classica in quanto la funzione potrebbe essere soggetta a delle eccezioni. Dato che la funzione throws potrebbe sollevare delle eccezioni, quest’ultime da qualcuno andranno pur controllate.

Per controllare la chiamata ad una funzione throws, essa va ingabbia all’interno di un recinto delineato dall’istruzione do-catch. Inoltre la chiamata all’istruzione throws va preceduta dalla keyword try:

L’istruzione do-catch del linguaggio Swift puoi leggerla così: “Esegui il codice (do) e nel caso di un errore acchiappalo (catch) e gestiscilo”.

Il try, all’interno del blocco do, ti dice che il compilatore proverà (try) ad eseguire la funzione. Se la funzione solleverà un’eccezione, l’esecuzione del blocco do verrà interrotta ed il catch provvederà a gestirla.

Proviamo ad applicare la definizione ed infine a chiamare la funzione di invio messaggio:

gestione degli errori in swift. Utilizzo del do catch

Tutto il blocco do viene eseguito correttamente perché la chiamata alla funzione inviaMessaggio(destinatario:) non ha sollevato nessuna eccezione. Ne sei più che sicuro perché, altrimenti, il try l’avrebbe acchiappata e inviata al blocco catch.

Cosa succede se viene sollevata un’eccezione?

Se provi a cambiare il destinatario, ad esempio, puoi notare concretamente cosa intendo per propagazione dell’errore e gestione degli errori in Swift:

do catch ed esempio propagazione errore in swift

Alla funzione inviaMessaggio(destinatario:) ho passato come mittente il nome “tizio”. La funzione prova a cercare la presenza di “tizio” all’interno dell’array e non lo trova. Non trovandolo viene attivata l’else, dell’istruzione guard, che a sua volta solleva l’eccezione destinatarioNonPresente.

Il try, nel blocco do, capisce che è stata sollevata un’eccezione e la intercetta. A questo punto l’esecuzione del codice del blocco do, che è arrivato ad eseguire la funzione throws, viene interrotto.

L’eccezione acchiappata dal try viene spedita al blocco catch che stamperà il messaggio. La variabile error, utilizzata all’interno della print, viene creata di default dal sistema e viene utilizzata dal catch per inserirgli dentro il tipo d’errore (ergo puoi chiamarla a piacimento dentro il catch senza bisogno di dichiararla).

Lo ripeto perché voglio essere logorroico.

Quando il try stabilisce che la funzione ha sollevato un’eccezione, e lo riesce a stabilire perché fa una prova – try significa provare -, l’esecuzione viene interrotta e passata al catch (nota come nell’immagine non sono stati stampati i due print dopo il try).

do try catch linguaggio swift

Gestire ogni singolo errore

E se volessi eseguire azioni diverse o stampare messaggi diversi?

Il catch può essere specializzato, in maniera simile a quanto hai fatto con l’istruzione switch, aggiungendo più catch in base alle tue esigenze. Dopo la parola chiave catch devi far seguire il tipo d’errore da gestire:

Cioè ad ogni catch potresti creare dei messaggi ad hoc per ogni singola eccezione o anche per un’eccezione non definita, come nell’ultimo catch.

Nell’esempio della nostra funzione, si potrebbe tradurre in questo:

Anche se per il momento potrebbe sembrare banale stampare tre diversi messaggi per ogni tipo d’eccezione, questo non lo sarà quando ti troverai a sviluppare le applicazioni. Infatti nella progettazione delle applicazioni potrai anche richiedere nuovamente l’inserimento dei parametri per l’invio del messaggio e altre cose fighe che vedrai più avanti.

Funzioni di funzioni throws

Una funzione che al suo interno utilizza una funzione throws, deve essere anch’essa (la più esterna) definita come throws. La chiamata, alla funzione throws interna, deve essere preceduta dalla parola chiave try.

Questo perché, per il principio della propagazione dell’errore, quando l’eccezione viene sollevata nella funzione interna essa viene propagata alla funzione esterna che a sua volta la spedisce all’esterno.

propagazione errori linguaggio swift

Considerazioni

La gestione degli errori in Swift, o più comunemente le eccezioni del linguaggio Swift, rappresentano uno dei punti cardine della modernità dei linguaggio di programmazione orientati agli oggetti.

Quando passerai al prossimo corso, noterai come qualsiasi codice e applicazione è soggetta agli errori. Quindi avere un sistema che li possa gestire diventa di vitale importanza per la riuscita di qualsiasi progetto.

Se vuoi un consiglio e vuoi continuare ad esercitarti su questo argomento, torna indietro nelle lezioni precedenti del corso e rifai gli esercizi impostandoli dal punto di vista della gestione delle eccezioni.

Buona Programmazione!

Changelog

  • 17/11/2016 – Aggiunto il changelog. Aggiornato il tutorial per supportare Xcode 8 e Swift 3.0.

Torna a: Corso gratuito linguaggio di programmazione Swift > Programmazione ad oggetti in Swift

Start typing and press Enter to search

Istruzione guard in Swiftinterfaccia-di-xcode