Track
/
Crystal
Crystal
/
Esercizi
/
La fattoria
La fattoria

La fattoria

Esercizio di apprendimento

Introduzione

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.

Sollevare un'eccezione

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")

Gestire le eccezioni

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

Convenzione sui metodi

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.

Eccezioni personalizzate

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

Istruzioni

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.

1. Dividi il cibo in parti uguali

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

2. Controlla il numero di mucche

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.

  • Se il numero di mucche passato è maggiore di 0, la funzione deve chiamare TheFarm.divide_food e restituire il risultato di quella chiamata.
  • Se il numero di mucche è 0 o meno, la funzione deve sollevare un errore con il messaggio "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

3. Migliora la gestione degli errori

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).

  • Se il numero di mucche è minore di 0, la funzione restituisce un InvalidCowsError con il messaggio personalizzato impostato su "there are no negative cows".
  • Se il numero di mucche è 0, la funzione restituisce un InvalidCowsError con il messaggio personalizzato impostato su "no cows don't need food".
  • Altrimenti, la funzione restituisce 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.

Modifica tramite GitHub Il link si apre in una nuova finestra o scheda
Crystal Exercism

Vuoi iniziare La fattoria?

Iscriviti a Exercism per imparare e padroneggiare Crystal con 26 concetti142 esercizi e il mentoring di persone reali, tutto gratis.