Track
/
Julia
Julia
/
Esercizi
/
Sensori di fabbrica
Sensori di fabbrica

Sensori di fabbrica

Esercizio di apprendimento

Introduzione

Chi programma di solito cerca di scrivere software perfetto, e di solito fallisce.

Le cose vanno storte, inaspettatamente, e dobbiamo essere in grado di affrontarlo.

Alcuni progettisti di linguaggi ritengono che la priorità sia individuare un errore il più rapidamente possibile, poi terminare l'esecuzione con un messaggio informativo che aiuti il debug.

I linguaggi per la data science tendono ad adottare un approccio più articolato. Alcuni errori sono così gravi che la terminazione immediata è necessaria, ma spesso è meglio segnalare un problema come qualcosa da affrontare più tardi, e poi continuare l'esecuzione.

Nel concetto Nullità abbiamo visto che Julia fornisce vari segnaposto per valori problematici: missing, NaN e Inf. Se questi siano un approccio migliore rispetto alla terminazione del programma in una situazione particolare è una questione di giudizio del programmatore.

Una precisazione sulla nomenclatura prima di entrare nei dettagli: la documentazione di Julia considera le parole «error» e «exception» in gran parte intercambiabili. Anche il contenuto che segue potrebbe essere altrettanto incoerente.

Tipi di errore standard

A questo punto del percorso, avrai sicuramente visto molti messaggi di errore da Julia. Per esempio:

julia> Int(3.14)
ERROR: InexactError: Int64(3.14)

Provare a convertire un numero in virgola mobile in un intero comporta una perdita di precisione, quindi otteniamo un InexactError.

InexactError è un tipo, uno dei diversi (attualmente 25) integrati in Julia come standard. Sono tutti sottotipi di Exception:

julia> supertype(InexactError)
Exception

throw()

Alcuni dei tipi di errore standard potrebbero essere utili da generare nel codice.

Come tutti i tipi concreti, gli errori hanno costruttori. Accettano una varietà di argomenti, quindi controlla la documentazione per quello che vuoi usare.

julia> DomainError(42, "out of range")
DomainError(42, "out of range")

Per usare l'errore, avvolgi il costruttore in una funzione throw():

julia> throw(DomainError(42, "out of range"))
ERROR: DomainError with 42:
out of range

error()

Per un approccio rapido e senza troppi fronzoli, la funzione error() può essere comoda. Accetta una stringa (o i componenti di una stringa) come argomento:

julia> happy = false;
julia> happy || error("😞 something went wrong")
ERROR: 😞 something went wrong

Errori personalizzati

Creare nuovi tipi di errore è in linea di principio molto facile. Basta aggiungere un altro sottotipo di Exception:

julia> struct MyError <: Exception end

julia> throw(MyError)
ERROR: MyError

Asserzioni

L'idea di base di un'asserzione è «questa affermazione dovrebbe essere vera, quindi protesta a gran voce se è falsa». Il valore di questo sta principalmente durante il debug, dato che il codice di produzione non dovrebbe mai fallire un'asserzione.

Nel concetto Tipi abbiamo visto che possiamo aggiungere asserzioni di tipo, per esempio per controllare il tipo restituito da una funzione.

julia> 42::Number
42

julia> "two"::Number
ERROR: TypeError: in typeassert, expected Number, got a value of type String

Più in generale, la macro @assert ci permette di verificare qualsiasi espressione che produca un valore booleano:

julia> n = 22;
julia> @assert isodd(n) "n must be odd"
ERROR: AssertionError: n must be odd

try...catch

Alcuni errori sono necessariamente fatali, ma spesso ci aspettiamo che il programma si riprenda con grazia.

Per impostazione predefinita, un errore termina immediatamente la funzione corrente, e l'errore (con qualsiasi messaggio informativo) viene passato alla funzione chiamante.

Questo continua su per lo stack di chiamate, finché il codice di livello superiore termina con un messaggio di errore.

In qualsiasi momento, l'errore può essere intercettato con un blocco try...catch che prova a gestirlo.

julia> n = -1;
julia> try
           log_n = log(n)
       catch problem
           if problem isa DomainError # number out of range
               # See next section for more on @warn and @info
               @warn "you may have supplied a negative real number: $n"
               @info "trying with complex argument"
               log_n = log(Complex(n))  # fallback calculation

           elseif problem isa MethodError # no idea what n is
               @error "please supply a valid argument"
 
           else
              rethrow() # the error could be anything else
           end
      end
┌ Warning: you may have supplied a negative real number: -1
└ @ Main REPL[3]:5
[ Info: trying with complex argument
0.0 + 3.141592653589793im  # success

Nell'esempio sopra, log(n) richiede che n sia un valore reale positivo oppure un valore complesso qualsiasi. Il try ... catch intercetta i problemi con valori reali negativi, restituendo la risposta complessa corretta iπ in notazione matematica.

Se fornisci, per esempio, un argomento di tipo stringa, non c'è modo di recuperare se non chiedere all'utente di correggerlo.

Come ultima rete di sicurezza, abbiamo aggiunto rethrow() per tutto ciò che non è né DomainError né MethodError.

Nota: A volte un try...catch è quello che serve, ma evita di abusarne. Se al suo posto si può usare un blocco if...else, sarà molto più performante che catturare eccezioni.

Logging

Nota che la funzione error(), discussa sopra, non va confusa con la macro @error.

La funzione genera un'eccezione, che verrà propagata su per lo stack di chiamate a meno che non venga intercettata.

La macro @error, insieme alle sue controparti @debug, @info e @warn, fa parte del modulo Logging ed è pensata per generare messaggi informativi senza alterare il flusso del programma.

L'output va al terminale per impostazione predefinita (con colori che ne indicano la gravità), anche se in un'applicazione reale ci sono molte altre possibilità.

julia> @warn "Something looks not quite right"
┌ Warning: Something looks not quite right
└ @ Main REPL[55]:1

julia> @error "Panic!"
┌ Error: Panic!
└ @ Main REPL[56]:1

Vedi anche l'esempio precedente, sotto try...catch.

Istruzioni

Elena è la nuova responsabile qualità di una fabbrica di giornali. Dato che è appena arrivata in azienda, ha deciso di rivedere alcuni processi della fabbrica per capire cosa si potrebbe migliorare. Ha scoperto che i tecnici fanno molti controlli di qualità a mano. Vede una buona opportunità di automazione e chiede a te, sviluppatore freelance, di realizzare un software per monitorare alcune delle macchine.

1. Controlla il livello di umidità della sala

La tua prima missione è scrivere un software per monitorare il livello di umidità della sala di produzione. C'è già un sensore collegato al software dell'azienda che restituisce periodicamente la percentuale di umidità della sala.

Devi implementare una funzione nel software che lanci un errore se la percentuale di umidità è troppo alta. Se l'umidità è a un livello accettabile, verrà aggiunto un log informativo. La funzione deve chiamarsi humiditycheck e prendere la percentuale di umidità come argomento.

Dovresti interrompere l'esecuzione con un ErrorException (il messaggio esatto non è importante, ma deve contenere il livello di umidità misurato) se la percentuale supera il 70%. Altrimenti, aggiungi un log informativo con il messaggio "humidity level check passed: h%", dove h è la percentuale di umidità.

julia> humiditycheck(60)
[ Info: humidity level check passed: 60%
julia> humiditycheck(100)
ERROR: humidity check failed: 100%

2. Controlla il surriscaldamento

Elena è molto soddisfatta del tuo primo incarico e ti chiede di occuparti del monitoraggio della temperatura delle macchine. Mentre chiacchieri con un tecnico, Greg, vieni a sapere che se la temperatura di una macchina supera i 500°C, i tecnici iniziano a preoccuparsi del surriscaldamento.

La macchina è dotata di un sensore che ne misura la temperatura interna. Devi sapere che il sensore è molto sensibile e spesso si rompe. In questo caso, i tecnici dovranno sostituirlo.

Il tuo compito è implementare una funzione temperaturecheck che prende la temperatura come argomento e che aggiunge un log se va tutto bene, oppure lancia un errore se il sensore è rotto o se la macchina inizia a surriscaldarsi. Sapendo che in seguito dovrai reagire in modo diverso a seconda dell'errore, ti serve un meccanismo per distinguere i due tipi di errore.

  • Se il sensore è rotto, la temperatura sarà nothing. In questo caso, dovresti interrompere l'esecuzione con un ArgumentError (il messaggio non è importante).
  • Quando il sensore funziona, se la temperatura supera i 500°C, dovresti lanciare un DomainError che include la temperatura misurata.
  • Altrimenti va tutto bene, quindi aggiungi un log informativo con il messaggio "temperature check passed: t °C", dove t è la temperatura.
julia> temperaturecheck(nothing)
ERROR: ArgumentError: sensor is broken

julia> temperaturecheck(800)
ERROR: DomainError with 800:
"overheating detected"

julia> temperaturecheck(500)
[ Info: temperature check passed: 500 °C

3. Definisci un errore personalizzato

Per il prossimo compito, dovrai definire un errore più generale, in grado di catturare ogni caso. I dettagli dell'implementazione non sono importanti, basta che sia un errore e che si chiami MachineError. Sei libero di includere campi e messaggi come ritieni utile.

4. Monitora la macchina

Ora che la tua macchina è in grado di rilevare gli errori e hai un errore personalizzato, aggiungi una funzione wrapper che possa riportare come funziona tutto. Oltre a restituire i log delle funzioni precedenti, questo wrapper dovrà anche aggiungere log a seconda del tipo o dei tipi di errore che si verificano.

  • Controlla l'umidità e la temperatura.
  • Se il controllo dell'umidità lancia un ErrorException, va aggiunto un log di errore con il messaggio "humidity level check failed: h%", dove h è la percentuale di umidità.
  • Se il controllo della temperatura lancia un ArgumentError, va aggiunto un log di avviso con il messaggio "sensor is broken".
  • Se il controllo della temperatura lancia un DomainError, va aggiunto un log di errore con il messaggio "overheating detected: t °C", dove t è la temperatura.
  • Se uno dei due controlli fallisce, o entrambi, va lanciato un unico MachineError dopo che i log sono stati aggiunti.
  • Se va tutto bene, verranno aggiunti solo i log di humiditycheck e temperaturecheck.

Implementa una funzione machinemonitor() che prende l'umidità e la temperatura come argomenti.

julia> machinemonitor(42, 450)
[ Info: humidity level check passed: 42%
[ Info: temperature check passed: 450 °C

julia> machinemonitor(42, 550)
[ Info: humidity level check passed: 42%
┌ Error: overheating detected: 550 °C
└ @ Main # output truncated

Error: MachineError

julia> machinemonitor(82, 521)
┌ Error: humidity level check failed: 82%
└ @ Main # output truncated
┌ Error: overheating detected: 521 °C
└ @ Main # output truncated

Error: MachineError

julia> machinemonitor(42, nothing)
[ Info: humidity level check passed: 42%
┌ Warning: sensor is broken
└ @ Main # output truncated

Error: MachineError
Modifica tramite GitHub Il link si apre in una nuova finestra o scheda
Julia Exercism

Vuoi iniziare Sensori di fabbrica?

Iscriviti a Exercism per imparare e padroneggiare Julia con 35 concetti128 esercizi e il mentoring di persone reali, tutto gratis.