Einfache Fehlerkontrolle#
Warum Fehlerkontrolle?#
Fakt ist, jeder macht Fehler
Code läuft beim ersten Mal nie richtig
Großteil der Entwicklungszeit wird mit Fehlersuche (Debugging) verbracht (Besonders bei Einsatz von KI -> Später mehr dazu)
“Profis” unterscheiden sich von “AnfängerInnen” dadurch, dass sie Fehler systematisch und effizient finden und beheben können
Syntaktische Fehler (Syntax Errors) sind leicht einzugrenzen (Fehlermeldung enthält Zeilennummer)
Laufzeitfehler (Runtime Errors) sind schwieriger zu finden
Programm läuft, tut aber nicht das, was es soll
Fehler fallen oft erst auf, wenn das Programm schon beim Kunden ist
Fehler vermeiden#
Programmier-Konventionen beachten
z.B. bei Namen für Variablen, Funktionen etc.
Kommentarzeilen dort, wo im Code etwas passiert
z.B. Verzweigung mit nicht offensichtlicher Bedingung
z.B. Funktionen (Zweck, Input, Output)
jede Funktion hat nur eine Funktionalität
jede Funktion einzeln & sofort testen
Wenn später Funktion verwendet wird, kann ein etwaiger Fehler dort nicht mehr sein!
d.h. kann Fehler im Programm schneller lokalisieren!
jede Funktionalität hat eigene Funktion
Programm in überschaubare Funktionen zerlegen!
nicht alles auf einmal programmieren!
Achtung: Häufiger Anfängerfehler!
Möglichst viele Fehler bewusst abfangen!
Funktions-Input auf Konsistenz prüfen!
Fehler-Abbruch, falls inkonsistent!
garantieren, dass Funktions-Output zulässig!
Try-Except#
try-Block: Code, der potentiell Fehler produziertexcept-Block: Code, der ausgeführt wird, wenn imtry-Block ein Fehler auftrittOptional:
else-Block: Code, der ausgeführt wird, wenn imtry-Block kein Fehler auftrittOptional:
finally-Block: Code, der immer ausgeführt wird, unabhängig davon, ob ein Fehler aufgetreten ist oder nicht
try:
# Code, der potentiell Fehler produziert
except:
# Code, der ausgeführt wird, wenn im try-Block ein Fehler auftritt
Einfaches Beispiel:#
Das folgende Beispiel hat keinen praktischen Nutzen, sondern soll nur die Funktionsweise von try-except demonstrieren.
def multiply(x,y):
try:
print(f"The product is {x*y}.")
except:
print("Error: Input must be a number.")
else:
print("Thanks for using our multiplication service!")
finally:
print("Goodbye!")
multiply(3,4) # Valid input
multiply("Hallo",5) # Input that does not cause an error, but probably not intended
multiply("Hallo","Welt") # Invalid input
The product is 12.
Thanks for using our multiplication service!
Goodbye!
The product is HalloHalloHalloHalloHallo.
Thanks for using our multiplication service!
Goodbye!
Error: Input must be a number.
Goodbye!
Bestimmte Fehlertypen abfangen#
Exceptionist die Basisklasse für alle eingebauten Fehler, die Python abfangen kannErlaubt die Ausgabe von Fehlertyp
def divide(a, b):
try:
result = a / b
except Exception as e:
print(f"Error: {e}")
divide(10,0)
divide(10,"Hallo")
Error: division by zero
Error: unsupported operand type(s) for /: 'int' and 'str'
Fehlertypen:
es gibt in Python viele eingebaute Fehlertypen, siehe https://docs.python.org/3/library/exceptions.html#concrete-exceptions für eine vollständige Liste
die für uns wichtigsten sind:
ZeroDivisionError: Division durch NullTypeError: Operation oder Funktion wird auf einen Wert eines unpassenden Typs angewendetValueError: Operation oder Funktion erhält Argument mit dem richtigen Typ, aber unpassendem WertIndexError: Index ist außerhalb des gültigen BereichsRuntimeError: Allgemeiner LaufzeitfehlerOverflowError: Ergebnis einer arithmetischen Operation ist zu groß, um dargestellt zu werden
def divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("Error: Division by zero is not allowed.")
except TypeError:
print("Error: Both inputs must be numbers.")
else:
print(f"The result is {result}.")
# Example calls
divide(10, 2) # Valid input
divide(10, 0) # Division by zero
divide(10, "a") # Invalid type
The result is 5.0.
Error: Division by zero is not allowed.
Error: Both inputs must be numbers.
Fehler bewusst auslösen#
raise-Anweisung: Löst einen Fehler ausNützlich um dem User zu signalisieren, dass etwas schief gelaufen ist
Besser als
print-Anweisung, da der User gezwungen wird, sich mit dem Fehler auseinanderzusetzenViel besser als das Programm einfach abstürzen zu lassen
def divide(a,b):
if not (isinstance(a, (int, float)) and isinstance(b, (int, float))):
raise TypeError("Both inputs must be numbers.")
if b == 0:
raise ValueError("Division by zero is not allowed.")
return a / b
divide(10,0) # Raises ValueError
divide(10,"Hallo") # Raises TypeError
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[4], line 8
5 raise ValueError("Division by zero is not allowed.")
6 return a / b
----> 8 divide(10,0) # Raises ValueError
9 divide(10,"Hallo") # Raises TypeError
Cell In[4], line 5, in divide(a, b)
3 raise TypeError("Both inputs must be numbers.")
4 if b == 0:
----> 5 raise ValueError("Division by zero is not allowed.")
6 return a / b
ValueError: Division by zero is not allowed.
assert-Anweisung#
assert-Anweisung überprüft, ob eine Bedingung wahr istWenn die Bedingung falsch ist, wird eine
AssertionError-Ausnahme ausgelöstNützlich für Debugging und Fehlerkontrolle
Fehler frühzeitig erkennen
assert condition, "Error message"
Beispiel: Euklidischer Algorithmus#
Stellt sicher, dass Input \(a,b\in\mathbb{N}\) erfüllt
def euklid(a,b):
# Check that a and b are natural numbers (positive integers)
assert isinstance(a, int) and isinstance(b, int) and a > 0 and b > 0, "a and b must be natural numbers (positive integers)"
# compute gcd using Euclid's algorithm
while b != 0:
a, b = b, a % b
return a
print(euklid(48, 18)) # Valid input
print(euklid(48, -18)) # Invalid input
6
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
Cell In[10], line 10
7 return a
9 print(euklid(48, 18)) # Valid input
---> 10 print(euklid(48, -18)) # Invalid input
Cell In[10], line 3, in euklid(a, b)
1 def euklid(a,b):
2 # Check that a and b are natural numbers (positive integers)
----> 3 assert isinstance(a, int) and isinstance(b, int) and a > 0 and b > 0, "a and b must be natural numbers (positive integers)"
4 # compute gcd using Euclid's algorithm
5 while b != 0:
AssertionError: a and b must be natural numbers (positive integers)
Testen von Code#
Testen ist der Prozess, ein Programm mit der Absicht auszuführen, Fehler zu finden!
Glenford Myers: Art of Software Testing (1979)
Test ist der Vergleich des Verhaltens eines Programms (Ist) mit dem erwarteten Verhalten eines Systems (Soll)
Es ist praktisch nicht möglich, alle Programmfunktionen und
alle möglichen Werte in den Eingabedaten in allen Kombinationen zu testen.d.h. Tests sind idR. unvollständig!
Probleme beim unvollständigen Testen
Tests erlauben nur das Auffinden von Fehlern
Tests können Korrektheit nicht beweisen
Fehlerursache ist durch Soll-Ist-Vergleich nicht zwangsläufig klar
Testfälle können selbst fehlerhaft sein!
Vorteile beim unvollständigen Testen
Zeitaufwand vertretbar
Tests beziehen sich idR. auf “realistischen Input”
Tests sind idR. reproduzierbar
Arten von Tests#
strukturelle Tests (für jede Funktion)
Werden alle Anweisungen ausgeführt oder gibt es toten Code?
Treten Fehler auf, wenn
if ... elsemit wahr / falsch durchlaufen werden?
funktionale Tests (für jede Fkt. und Programm)
Tut jede Funktion mit zulässigen Parametern das Richtige? (d.h. Ergebnis korrekt?)
Tut das Programm (bzw. Teilabschnitte) das Richtige? (d.h. Ergebnis korrekt?)
Werden unzulässige Parameter erkannt?
Werden Grenzfälle / Sonderfälle korrekt erkannt und liefern das Richtige?
Was passiert bei Fehleingaben, d.h. bei Fehler des Benutzers?
Funktionale Tests?#
Ziel: Tut Funktion / Programm das Richtige?
funktionale Tests brauchen Testfälle
mit bekanntem Ergebnis / Output
Was sind generische Fälle / Parameter?
Bei welchen Fällen treten Verzweigungen auf?
Möglichst viele Verzweigungen abdecken!
Welche Fälle sind kritisch?
Zahlen werden im Rechner nicht exakt dargestellt (später mehr!)
Außerdem keine exakte Arithmetik bei double! * jede double-Rechnung hat Rechenfehler!
Wo können aufgrund solcher Rechenfehler andere Ergebnisse auftreten? * z.B. Ist ein Punkt auf dem Kreisrand?
früh mit dem Testen beginnen
nach Implementierung jeder Funktion!
nicht erst dann, wenn Prg komplett fertig!
nach Code-Korrektur alle(!) Tests wiederholen
deshalb Dokumentation der Tests!
Ab jetzt in der UE stets: Wie wurde getestet?
allerdings nur inhaltlich
d.h. ohne Fehleingaben des Nutzers
Verwendung des VSCode Python Debuggers#
Debugger: Werkzeug zum Testen und Debuggen von Code
Eignet sich besonders um Laufzeitfehler zu finden
Ermöglicht das Setzen von Haltepunkten
Schrittweises Ausführen des Codes
Überwachen und Verändern von Variablenwerten
Debugger ist komfortabler als Kommentare und print-Anweisungen zum Debuggen!
Debugger starten#
Um ein Python Programm im Debugger-Modus zu starten, klicken Sie auf den kleinen Pfeil neben dem Play-Button und wählen Sie “Debug Python File”.
Haltepunkte (Breakpoints)#
Das Programm wird ganz normal durchlaufen, außer man setzt Haltepunkte (Breakpoints).
Haltenpunkte können durch Klicken links neben die Zeilennummer gesetzt werden.
Wenn Sie nun das Programm im Debugger-Modus starten, wird die Ausführung am ersten Haltenpunkt, der in der Programmausführung erreicht wird, angehalten.
Es ist möglich, mehrere Haltepunkte zu setzen. Das Programm wird dann nacheinander an jedem Haltepunkt angehalten.
Zusätzlich können Sie conditional breakpoints setzen, d.h. Haltepunkte, die nur dann ausgelöst werden, wenn eine bestimmte Bedingung erfüllt ist.
Klicken Sie dazu mit der rechten Maustaste auf den Haltepunkt und wählen Sie “Edit Breakpoint…”, oder
Klicken Sie mit der rechten Maustaste auf die Zeilennummer und wählen Sie “Add Conditional Breakpoint…”.
Als Bedingung können Sie jeden Python-Ausdruck verwenden, der zu True oder False ausgewertet wird. Der Haltepunkt wird nur ausgelöst, wenn die Bedingung True ist.
Zum Beispiel können Sie einen Haltepunkt setzen, der nur ausgelöst wird, wenn eine Variable
aeinen bestimmten Wert hat:a == 42.
Variablen überwachen#
Im Debugger können Sie die Werte von Variablen überwachen.
Die Werte der Variablen der aktuellen Zeile werden angezeigt.
Durch klicken auf
können Sie das “Variables” Fenster öffnen, in dem Sie die Werte aller Variablen sehen können.
In der “Debug Console”
können Sie während des Debuggens Python Befehle ausführen, z.B. um den Wert von Variablen zu überprüfen/ändern oder um Funktionen aufzurufen.
Programmausführung steuern#
Am oberen Rand des Debugger-Fensters finden Sie verschiedene Buttons, um die Programmausführung zu steuern:
Von links nach rechts:
Continue: Setzt die Programmausführung fort bis zum nächsten Haltepunkt.
Step Over: Führt die nächste Zeile aus. Wenn die nächste Zeile ein Funktionsaufruf ist, wird die Funktion vollständig ausgeführt und die Ausführung wird in der nächsten Zeile nach dem Funktionsaufruf fortgesetzt.
Step Into: Führt die nächste Zeile aus. Wenn die nächste Zeile ein Funktionsaufruf ist, wird die Ausführung in die erste Zeile der Funktion fortgesetzt.
Step Out: Führt den Rest der aktuellen Funktion aus und setzt die Ausführung in der Zeile nach dem Funktionsaufruf fort.
Restart: Startet das Debugging von vorne.
Stop: Beendet das Debugging.
Der VSCode Debugger bietet noch viele weitere Funktionen, die Sie in der offiziellen Dokumentation unter https://code.visualstudio.com/docs/python/debugging nachlesen können.

