Nel mondo ideale, tutto funziona perfettamente. Ma nel mondo reale, le cose possono andare storte, e il modo in cui gestiamo queste situazioni è importante per garantire che il nostro software sia robusto e affidabile. Le eccezioni sono un concetto cruciale nella programmazione che ci permette di gestire errori e situazioni impreviste con eleganza.
Sollevare un errore, se non gestito, interrompe il programma e genera un messaggio di errore. Nella maggior parte dei casi, non vuoi che il tuo programma si interrompa quando si verifica un errore: invece, vuoi gestire l'errore e continuare a eseguire il programma.
In Crystal, le eccezioni vengono sollevate usando la parola chiave raise e possono ricevere una String o un oggetto Exception.
Se non gestita, il programma si interromperà e stamperà il messaggio di errore.
raise "This is an error"
In Crystal esistono diverse eccezioni integrate, come ArgumentError, IndexError, KeyError, IOError, SystemCallError, TypeError, ZeroDivisionError e molte altre.
Queste richiedono di passare un messaggio all'eccezione.
raise ArgumentError.new("This is an argument error")
Non vorremmo che il nostro programma si blocchi quando viene sollevata un'eccezione.
Perciò, quando sappiamo che un pezzo di codice è soggetto a errori, possiamo racchiuderlo in un blocco begin e gestire l'eccezione con un blocco rescue.
Il blocco begin segna l'inizio del codice che potrebbe sollevare un'eccezione, e il blocco rescue gestisce l'eccezione.
begin
raise "This is an error"
rescue
puts "An error occurred!"
end
Il blocco rescue può anche specificare una variabile per ottenere l'oggetto dell'eccezione.
begin
raise "This is an error"
rescue ex
puts "An error occurred: #{ex.message}"
end
Il blocco rescue può anche specificare un tipo specifico di eccezione per catturare solo quell'eccezione.
begin
raise ArgumentError.new("This is an argument error")
rescue ArgumentError
puts "An argument error occurred!"
end
# or
begin
raise ArgumentError.new("This is an argument error")
rescue ex : ArgumentError
puts "An argument error occurred: #{ex.message}"
end
Si possono usare più blocchi rescue per gestire diversi tipi di eccezioni.
Nell'esempio seguente, il primo blocco rescue catturerà un ArgumentError, e il secondo blocco rescue catturerà qualsiasi altra eccezione.
begin
raise ArgumentError.new("This is an argument error")
rescue ArgumentError
puts "An argument error occurred!"
rescue
puts "An error occurred!"
end
Il blocco begin può anche avere un blocco else, che viene eseguito se non viene sollevata alcuna eccezione.
begin
puts "No error occurred"
rescue
puts "An error occurred!"
else
puts "No error occurred"
end
Infine, c'è un blocco ensure che viene sempre eseguito, indipendentemente dal fatto che sia stata sollevata un'eccezione.
begin
raise "This is an error"
rescue
puts "An error occurred!"
ensure
puts "This is always executed"
end
Alcuni metodi hanno due versioni: una con ! e l'altra senza.
Questo può significare due cose diverse.
Una è che il metodo modifica l'oggetto, l'altra è che il metodo può sollevare un'eccezione.
Ma esiste anche un'altra convenzione riguardo al terminare un metodo con ?, menzionata nel concetto booleano.
Alcuni metodi sollevano un'eccezione per impostazione predefinita, ma hanno anche una versione che termina con ? e restituisce nil invece di sollevare un'eccezione.
Questo è l'ideale quando vuoi evitare che venga sollevato un errore. Può migliorare le prestazioni, dato che non deve creare una traccia dello stack e, se impostato correttamente, potrebbe rendere il codice più sicuro.
Puoi anche creare le tue eccezioni ereditando dalla classe Exception.
Così facendo, puoi facoltativamente sovrascrivere il metodo initialize per impostare il messaggio dell'eccezione.
Questo può essere fatto assegnando il messaggio a una variabile di istanza chiamata @message.
class MyException < Exception
def initialize
@message = "This is my exception"
end
end
raise MyException.new
Il giorno che hai aspettato così a lungo è finalmente arrivato: ora sei il fiero proprietario di una bellissima fattoria sulle Alpi.
Però non ti piace ancora alzarti troppo presto la mattina per dar da mangiare alle tue mucche.
Dato che sei un ingegnere eccellente, costruisci un distributore di cibo, il FEED-M-ALL.
L'ultima cosa che ti serve per completare il progetto è un pezzo di codice che calcoli la quantità di foraggio che ogni mucca dovrebbe ricevere. È importante che ogni mucca riceva la stessa quantità: devi evitare i conflitti. Le mucche sono molto sensibili.
Per fortuna, non devi ricavare da solo tutte le formule per calcolare le quantità di foraggio. Usi una misteriosa libreria esterna che hai trovato su internet. Dovrebbe rendere le mucche le più felici possibile. La libreria espone una classe che soddisfa la seguente interfaccia. Farai affidamento su questa nel codice che scriverai tu.
class FodderCalculator
def fodder_amount! : Number
end
def fattening_factor! : Number
end
end
Mentre lavori al tuo codice, migliorerai la gestione degli errori per renderla più robusta e più facile da correggere in seguito, quando la userai nella tua vita quotidiana in fattoria.
Per prima cosa, ti concentri sul codice necessario a calcolare la quantità di foraggio per ogni mucca.
Implementa un metodo TheFarm.divide_food che accetta come argomenti un oggetto FodderCalculator e un numero di mucche espresso come numero intero.
Per questo compito, dai per scontato che il numero di mucche passato sia sempre maggiore di zero.
Il metodo deve restituire la quantità di cibo per ogni mucca.
Per fare il calcolo, devi prima recuperare la quantità totale di foraggio per tutte le mucche.
Lo fai chiamando il metodo fodder_amount! e passandogli il numero di mucche.
Inoltre, ti serve un fattore per cui moltiplicare questa quantità.
Ottieni questo fattore chiamando il metodo fattening_factor!.
Con questi due valori e il numero di mucche, ora puoi calcolare la quantità di cibo per ogni mucca (come float64).
Se uno dei metodi che chiami restituisce un errore, l'esecuzione deve fermarsi e restituire 0.
fodder_calculator = FodderCalculator.new(50, 1.5)
TheFarm.divide_food(fodder_calculator, 5)
# => 15.0
fodder_calculator = FodderCalculator.new(50, nil)
TheFarm.divide_food(fodder_calculator, 5)
# => 0
Mentre lavoravi al primo compito qui sopra, ti sei reso conto che la libreria esterna che usi non è di qualità così alta come pensavi. Per esempio, non riesce a gestire correttamente input non validi. Vuoi aggirare questo limite aggiungendo un controllo sul valore di input nel tuo codice.
Scrivi una funzione TheFarm.validate_input_and_divide_food che ha la stessa firma di TheFarm.divide_food qui sopra.
TheFarm.divide_food e restituire il risultato di quella chiamata."Number of cows must be greater than 0".TheFarm.validate_input_and_divide_food(fodderCalculator, 5)
# => 15
TheFarm.validate_input_and_divide_food(fodderCalculator, -2)
# Error: Number of cows must be greater than 0
Controllare il numero di mucche prima di passarlo avanti è stata una buona mossa, ma non sei del tutto soddisfatto del messaggio di errore poco specifico.
Decidi di fare meglio creando una classe di errore personalizzata chiamata InvalidCowsError.
L'errore personalizzato deve contenere il numero di mucche (Int32) e un messaggio personalizzato (String), e il metodo Expection deve serializzare i dati nel seguente formato:
{number of cows} cows are invalid: {custom message}
Armato del tuo errore personalizzato, implementa una funzione TheFarm.validate_number_of_cows che accetta il numero di mucche come numero intero e restituisce un errore (oppure nil).
InvalidCowsError con il messaggio personalizzato impostato su "there are no negative cows".InvalidCowsError con il messaggio personalizzato impostato su "no cows don't need food".nil per indicare che la validazione è andata a buon fine.TheFarm.validate_number_of_cows(5)
# => nil
TheFarm.validate_number_of_cows(-5)
# => InvalidCowsError: -5 cows are invalid: there are no negative cows
Dopo il duro lavoro di preparazione di questa funzione di validazione, ti accorgi che è già sera e lasci la scrivania per goderti il tramonto sulle montagne. Rimandi a un altro giorno il compito di aggiungere davvero la nuova funzione di validazione al tuo codice.
Iscriviti a Exercism per imparare e padroneggiare Crystal con 26 concetti142 esercizi e il mentoring di persone reali, tutto gratis.