Programmiererinnen und Programmierer versuchen meist, perfekte Software zu schreiben, und scheitern meist damit.
Manchmal geht etwas schief, ganz unerwartet, und wir müssen damit umgehen können.
Manche Sprachdesigner sind der Meinung, dass es vor allem darauf ankommt, einen Fehler so schnell wie möglich zu erkennen und das Programm dann mit einer aussagekräftigen Meldung zu beenden, damit man leichter debuggen kann.
Sprachen für Data Science gehen tendenziell differenzierter vor. Manche Fehler sind so schwerwiegend, dass ein sofortiges Beenden nötig ist. Oft ist es aber besser, ein Problem als etwas zu markieren, das später behandelt wird, und die Ausführung fortzusetzen.
Im Konzept Nichts haben wir gesehen, dass Julia verschiedene Platzhalter für problematische Werte bereitstellt: missing, NaN und Inf.
Ob sie in einer bestimmten Situation besser geeignet sind als das Beenden des Programms, hängt vom Urteilsvermögen der Programmiererin oder des Programmierers ab.
Eine Anmerkung zur Terminologie, bevor wir ins Detail gehen: Die Julia-Dokumentation verwendet die Wörter „error" und „exception" weitgehend synonym. Der folgende Text ist möglicherweise genauso inkonsistent.
Bis zu diesem Punkt im Lehrplan hast du sicher schon viele Fehlermeldungen von Julia gesehen. Zum Beispiel:
julia> Int(3.14)
ERROR: InexactError: Int64(3.14)
Beim Umwandeln einer Gleitkommazahl in eine Ganzzahl geht Genauigkeit verloren, deshalb bekommen wir einen InexactError.
InexactError ist ein Typ, einer von mehreren (derzeit 25), die standardmäßig in Julia eingebaut sind.
Alle sind Untertypen von Exception:
julia> supertype(InexactError)
Exception
throw()Manche der Standard-Fehlertypen lassen sich vielleicht auch in deinem eigenen Code sinnvoll erzeugen.
Wie alle konkreten Typen haben die Fehler Konstruktoren. Sie nehmen unterschiedliche Argumente entgegen, schau also in der Dokumentation nach dem Fehler, den du verwenden möchtest.
Um den Fehler zu verwenden, packst du den Konstruktor in eine throw()-Funktion:
julia> DomainError(42, "out of range")
DomainError(42, "out of range")
julia> throw(DomainError(42, "out of range"))
ERROR: DomainError with 42:
out of range
error()Wenn es schnell gehen soll, ist die error()-Funktion praktisch.
Sie nimmt einen String (oder die Bestandteile eines Strings) als Argument entgegen:
julia> happy = false;
julia> happy || error("😞 something went wrong")
ERROR: 😞 something went wrong
Neue Fehlertypen zu erstellen ist im Prinzip sehr einfach.
Füge einfach einen weiteren Untertyp von Exception hinzu:
julia> struct MyError <: Exception end
julia> throw(MyError)
ERROR: MyError
Die Grundidee einer Assertion lautet: „Diese Aussage sollte wahr sein, also beschwere dich lautstark, wenn sie falsch ist." Der Nutzen liegt vor allem beim Debuggen, denn im Produktionscode sollte eine Assertion niemals fehlschlagen.
Im Konzept Typen haben wir gesehen, dass wir Typ-Assertions hinzufügen können, zum Beispiel um den Rückgabetyp einer Funktion zu prüfen.
julia> 42::Number
42
julia> "two"::Number
ERROR: TypeError: in typeassert, expected Number, got a value of type String
Allgemeiner gesagt können wir mit dem Makro @assert jeden Ausdruck testen, der zu einem booleschen Wert ausgewertet wird:
julia> n = 22;
julia> @assert isodd(n) "n must be odd"
ERROR: AssertionError: n must be odd
try...catch
Manche Fehler sind zwangsläufig fatal, aber oft erwarten wir, dass sich das Programm elegant davon erholt.
Standardmäßig beendet ein Fehler sofort die aktuelle Funktion, und der Fehler (mit einer eventuellen aussagekräftigen Meldung) wird an die aufrufende Funktion weitergegeben.
So geht es den Aufrufstapel hinauf, bis der Code auf oberster Ebene mit einer Fehlermeldung abbricht.
In jeder Phase kann der Fehler mit einem try...catch-Block abgefangen werden, der versucht, ihn zu behandeln.
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
Im obigen Beispiel braucht log(n), dass n entweder ein positiver reeller Wert oder ein beliebiger komplexer Wert ist.
Das try ... catch fängt Probleme mit negativen reellen Werten ab und liefert die korrekte komplexe Antwort iπ in mathematischer Schreibweise.
Wenn du zum Beispiel ein String-Argument übergibst, gibt es keine Rettung, außer die Nutzerin oder den Nutzer darum zu bitten, es zu korrigieren.
Als letzten Auffangpunkt haben wir rethrow() für alles hinzugefügt, was weder ein DomainError noch ein MethodError ist.
Hinweis: Manchmal ist ein try...catch genau das Richtige, aber bitte verwende es nicht zu oft.
Wenn stattdessen ein if...else-Block verwendet werden kann, ist er deutlich leistungsfähiger als das Abfangen von Exceptions.
Beachte, dass die oben besprochene error()-Funktion nicht mit dem Makro @error verwechselt werden darf.
Die Funktion erzeugt eine Exception, die den Aufrufstapel hinaufgereicht wird, solange sie nicht abgefangen wird.
Das Makro @error gehört zusammen mit seinen Gegenstücken @debug, @info und @warn zum Modul Logging und soll informative Meldungen erzeugen, ohne den Programmablauf zu verändern.
Die Ausgabe geht standardmäßig ans Terminal (nach Schweregrad farbcodiert), wobei es in einer echten Anwendung viele andere Möglichkeiten gibt.
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
Siehe auch das vorherige Beispiel unter try...catch.
Elena ist die neue Qualitätsmanagerin einer Zeitungsfabrik. Da sie gerade erst im Unternehmen angekommen ist, hat sie beschlossen, einige Prozesse in der Fabrik zu überprüfen, um zu sehen, was verbessert werden könnte. Sie hat herausgefunden, dass die Techniker viele Qualitätsprüfungen von Hand durchführen. Sie sieht eine gute Gelegenheit zur Automatisierung und bittet dich als freiberuflichen Entwickler, eine Software zu entwickeln, die einige der Maschinen überwacht.
Deine erste Mission ist es, eine Software zu schreiben, die die Luftfeuchtigkeit im Produktionsraum überwacht. Es gibt bereits einen Sensor, der mit der Software des Unternehmens verbunden ist und regelmäßig die Luftfeuchtigkeit des Raums in Prozent zurückgibt.
Du musst eine Funktion in der Software implementieren, die einen Fehler auslöst, wenn die Luftfeuchtigkeit zu hoch ist.
Wenn die Luftfeuchtigkeit akzeptabel ist, wird ein Info-Log hinzugefügt.
Die Funktion soll humiditycheck heißen und die Luftfeuchtigkeit in Prozent als Argument entgegennehmen.
Du sollst mit einer ErrorException abbrechen (die genaue Meldung ist nicht wichtig, muss aber den gemessenen Feuchtigkeitswert enthalten), wenn der Prozentsatz 70 % überschreitet.
Andernfalls füge ein Info-Log mit der Meldung "humidity level check passed: h%" hinzu, wobei h die Luftfeuchtigkeit in Prozent ist.
julia> humiditycheck(60)
[ Info: humidity level check passed: 60%
julia> humiditycheck(100)
ERROR: humidity check failed: 100%
Elena ist mit deiner ersten Aufgabe sehr zufrieden und bittet dich, dich um die Überwachung der Temperatur der Maschinen zu kümmern. Während du dich mit einem Techniker, Greg, unterhältst, erfährst du, dass die Techniker sich Sorgen um eine Überhitzung machen, wenn die Temperatur einer Maschine 500 °C überschreitet.
Die Maschine ist mit einem Sensor ausgestattet, der ihre Innentemperatur misst. Du solltest wissen, dass der Sensor sehr empfindlich ist und oft kaputtgeht. In diesem Fall müssen die Techniker ihn austauschen.
Deine Aufgabe ist es, eine Funktion temperaturecheck zu implementieren, die die Temperatur als Argument entgegennimmt und entweder ein Log hinzufügt, wenn alles in Ordnung ist, oder einen Fehler auslöst, wenn der Sensor defekt ist oder die Maschine zu überhitzen beginnt.
Da du später je nach Fehler unterschiedlich reagieren musst, brauchst du einen Mechanismus, um die beiden Fehlerarten zu unterscheiden.
nothing.
In diesem Fall sollst du mit einem ArgumentError abbrechen (die Meldung ist nicht wichtig).DomainError auslösen, der die gemessene Temperatur enthält."temperature check passed: t °C" hinzu, wobei t die Temperatur ist.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
Für die nächste Aufgabe musst du einen allgemeineren Auffangfehler definieren.
Die Implementierungsdetails spielen keine Rolle, außer dass es ein Fehler ist und der Name MachineError lautet.
Du kannst gerne Felder und Meldungen hinzufügen, wie du sie für hilfreich hältst.
Jetzt, wo deine Maschine Fehler erkennen kann und du einen benutzerdefinierten Maschinenfehler hast, fügst du eine Wrapper-Funktion hinzu, die melden kann, wie alles funktioniert. Über die Logs aus den vorherigen Funktionen hinaus muss dieser Wrapper auch Logs hinzufügen, je nachdem, welche Art von Fehlern auftritt.
ErrorException auslöst, soll ein Error-Log mit der Meldung "humidity level check failed: h%" hinzugefügt werden, wobei h die Luftfeuchtigkeit in Prozent ist.ArgumentError auslöst, soll ein Warn-Log mit der Meldung "sensor is broken" hinzugefügt werden.DomainError auslöst, soll ein Error-Log mit der Meldung "overheating detected: t °C" hinzugefügt werden, wobei t die Temperatur ist.MachineError ausgelöst werden.humiditycheck und temperaturecheck hinzugefügt.Implementiere eine Funktion machinemonitor(), die Luftfeuchtigkeit und Temperatur als Argumente entgegennimmt.
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
Melde dich bei Exercism an, um Julia mit 35 Konzepte128 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.