SlideShare ist ein Scribd-Unternehmen logo
1 von 54
Downloaden Sie, um offline zu lesen
Lösungsorientierte
Fehlerbehandlung
Thomas Aglassinger
http://www.roskakori.at
https://github.com/roskakori/talks
Agenda
1.Begriffserklärung: Fehler
2.Darstellung von Fehler in Python
3.Lösungsorientierter Ansatz
4.Fehlermeldungen
5.Ähnliche Fehler gruppieren
6.Programmvorlage
7.Empfehlungen
Begriffserklärung: Fehler
Was ist ein Fehler?
Ein Fehler ist die
Abweichung des Ist-Stands von der Erwartung
Herausforderungen für Entwickler
● Was ist die Erwartung?
→ notwendig, um Fehler erkennen zu können
● Was soll Programm im Fehlerfalle machen?
● Wer kann den Fehler beheben?
● Wie kann Programm bei der Korrektur
unterstützen?
Fehler in Python
Wozu Beispiele in Python?
● Leicht verständlich und gut lesbar
● Kompakter Code
● Weitgehend englische Sätze
● Prozedural und objektorientiert nutzbar
Hinweis: Beispiele i.d.R. sowohl in Python 2 als auch 3
lauffähig, tw. mit geringfügigen Anpassungen für Python 3
Fehler in Python
● Darstellung über Exceptions
● Durchgängige Verwendung in Standard-Bibliothek
● Vorteil: nicht unabsichtlich ignorierbar
● Bewährt in vielen anderen Programmiersprachen
(Java, C#, ...)
● alternative Ansätze (hier nicht näher betrachtet):
● spezielle oder zusätzliche Rückgabewerte (zB go)
● globale Fehlervariablen (zB errno in C)
● Spezielle Sprachkonstrukte (zB „on error goto“ in
Basic)
Fehler erkennen
● Erkennen mit if und einer Fehlerbedingung
● Aufzeigen mit raise und einer Fehlermeldung
● Fehler führt zum Abbruch der Routine
height)
# Actual processing would happen here.
pass
Fehler abfangen und ausgeben
● Aufruf des möglicherweise fehlschlagenden Codes mit try
● Bestimmte Fehler abfangen mit except
● Ausgabe: height is -3 but must be greater than 0
try:
processSomething(-3)
except ValueError as error:
print(error)
vgl. C#, Java:
catch
statt except
Ressourcen immer freigeben (1)
● Mit finally: sowohl bei Erfolg als auch Fehler
'some.txt', 'rb')
processData(inputFile)
finally:
Ressourcen immer freigeben (2)
● Mit with-Statement:
as inputFile:
● Voraussetzung: verwendete Klasse implementiert
Context Manager
→ hat Wissen darüber, was wie auf zu räumen ist
vgl. C#:
using
Ressourcen immer freigeben (3a)
● Mit eigenem Context Manager
● Schritt 1: Definition von __enter__() und __exit__()
class SocketClient():
'''Provide a ``socket`` and automatically close it when done.'''
def __init__(self, host, port):
self.socket = socket.create_connection((host, port))
return self
self.socket.shutdown(socket.SHUT_RDWR)
self.socket.close()
Ressourcen immer freigeben (3b)
● Mit eigenem Context Manager
● Schritt 2: Aufruf wie zuvor über with-Statement
'www.python.org', 80) as
pythonOrg.clientSocket.sendall(
'GET /index.html HTTP/1.0n'
+ 'Host: www.python.orgn'
+'n')
reply = pythonOrg.clientSocket.recv(64)
print(reply)
Ressourcen immer freigeben (4b)
● Mit eigenem Context Manager
● Schritt 2: Aufruf wie zuvor über with-Statement
with SocketClient('www.python.org', 80) as
pythonOrg:
pythonOrg.clientSocket.sendall(
'GET /index.html HTTP/1.0n'
+ 'Host: www.python.orgn'
+'n')
reply = pythonOrg.clientSocket.recv(64)
print(reply)
Ergebnis von
__enter__().
Aufruf von
__exit__().
Aufruf von
__init__().
Ressourcen freigeben (5)
● Nicht verwenden: __del__()
● Aufruf erfolgt durch Garbage Collector
● Nicht vorhersagbar wann → Bindet Ressource
unnötig lange
● Wenn Exception während __del__(): nur Warnung
in Log, Aufrufer bekommt nichts davon mit
● Daher nicht vorhersagbares Verhalten
→ Anwender glaubt, alles hat funktioniert
→ Entwickler kann Fehler schwer reproduzieren
● Anwendung: Python-interne Aufräumarbeiten
vgl. Java:
dispose()
Fehler erkennen mit assert (1)
● Beispiel von zuvor
def processSomething(height):
if height <= 0:
raise ValueError(
'height must be greater than 0')
def processSomething(height):
assert height > 0,
'height must be greater than 0'
● Als Assertion:
Fehler erkennen mit assert (2)
● Wenn Bedingung verletzt:
wie raise AssertionError('...')
● Deaktivieren von assert mittels Aufruf über:
$ python -O xxx.py
(Buchstabe „großes O“, nicht Ziffer „0“)
● Von assert aufgerufene Funktionen dürfen keine
Seiteneffekte haben → sonst unterschiedliches
Programmverhalten je nachdem ob -O gesetzt
● Frage: wann raise und wann assert?
→ Antwort folgt
Zusammenfassung
● Fehler erkennen mit raise und assert
● Fehler abfangen mit try und except
● Aufräumarbeiten: finally, with und
Context Manager
● Nicht verwenden: __del__()
Lösungsorienterter Ansatz
zur Fehlerbehandlung
Grundprinzipen
● Im Zentrum der Überlegungen steht die
Beseitigung des Fehlers (Lösung) und nicht
der Fehler selbst
● Klare Zuständigkeiten zwischen Entwickler
und Anwender
● Hilfreiche Fehlermeldungen
● Fehlerbedingungen und -meldung aus
Programmcode ableitbar
Zuständigkeiten
● Entwickler: Umsetzung des Programms zur
● Verarbeitung der Daten und Eingaben des
Anwenders
● Liefern des gewünschten Ergebnisses
● Anwender:
● Bereitstellen von Eingaben und Daten zur
Verarbeitung durch das Programm
● Bereitstellen einer Umgebung, in der das
Programm ausführbar ist (ggf. über Administrator)
Nutzung von assert
● Fehlererkennung: aus internen Programmzustand
● Lösung: Änderung des Programms
● Zielgruppe für Fehlermeldungen: Entwickler
● Klare Zuständigkeit beim Aufruf von Routinen:
muss Aufrufer oder Routine auf
Fehlerbedingungen reagieren?
● Besonders nützlich zur Prüfung von übergebenen
Parametern („preconditon“)
● Dient als „ausführbare“ Dokumentation
Nutzung von raise
● Fehlererkennung: aus Daten und in Umgebung
● Lösung:
● Daten: korrekte und vollständige Eingabe
● Umgebung: Dateien, Netzwerk, Berechtigungen, …
● Fehler erst zur Laufzeit erkennbar
● Zielgruppe für Fehlermeldungen: Anwender
Fehlermeldungen
Anforderungen
● In Literatur oft: unklare Richtlinien („hilfreich“,
„verständlich“, ...)
● In Praxis oft: Beschreibung, was falsch ist (z.B.
„ungültiges Datum“)
● Lösungsorientierter Zugang:
● Beschreibung des Ist-Zustands und des Soll-Zustands
● Beschreibung der Maßnahmen, die zur Korrektur zu
setzen sind
● Beschreibung oder Darstellung des Zusammenhangs,
in dem der Fehler aufgetreten ist
Ableiten der Fehlermeldung aus
Programmcode
● Allgemein:
if height <= 0:
raise ValueError(
'height is %d but must be greater than 0' % height)
● Konkret:
if actual != expected:
raise SomeError('<actual> must be
<expected>')
Darstellung des Zusammenhangs
● Bei raise: Anführen von Name und Wert des
Ist-Zustands (z.B. „height is -3“)
● Bei except: ursprüngliche Fehlermeldung
beibehalten und ergänzen:
● Beschreiben der Herkunft der Fehlerursache (zB
Name und Position in Eingabedatei, Feldname in
Formular, markieren in Benutzeroberfläche, ...)
● Beschreiben der Aktion, die aufgrund des Fehlers
nicht durchführbar ist
Beispiel: Fehler erkennende Routine
def processSomething(height):
if height <= 0:
raise ValueError('height must be greater than 0')
# Actual processing would happen here.
pass
def processSomething(height):
if height <= 0:
raise ValueError('height must be greater than 0')
# Actual processing would happen here.
pass
Beispiel: Fehler berichtender Code
def processAllThings(dataFile):
try:
# Process all heights read from `dataFile`.
lineNumber = 1
for line in dataFile:
except ValueError as error:
print('cannot process %s, line %d: %s' %
Beispiel für Ausgabe im Fehlerfall:
cannot process some.txt, line 17:
height is -3 but must be greater than 0
Wo Fehlermeldung ausgeben?
● Bei GUI oder Web-Anwendung:
● Bei Eingaben: Feld hervorheben und Meldung unter
dem betroffenem Formularfeld
● In eigenem Fehlerdialog oder auf Fehlerseite
● Zusätzlich in Log für spätere Nachvollziehbarkeit
● Bei Services: in Log
● Bei Befehlszeilenwerkzeugen: in Konsole auf
stderr
Ausgabe in Log-Datei
● Mit Standard Modul logging:
http://docs.python.org/2/library/logging.html
● Mehrere Stufen zur Bewertung der Meldung, u.a.:
● Info – Informationen, welche Aktionen gesetzt werden
● Error – Fehlermeldungen
● Exception – Fehlermeldung und Stack Trace
● Debug – zusätzliche interne Detailinformationen;
interessant für Entwickler und während Fehleranalysen
● Ausgabe auf Datei, Console, Netzwerk-Socket, ...
Logging auf stderr
● Auch für Befehlzeilenanwendungen nutzbar
import
def processData(dataPath):
_log.info(u'read "%s"', dataPath)
with open(dataPath, 'rb') as dataFile:
# Here we would actually process the data.
pass
if __name__ == '__main__':
Logging auf stderr
● Was passiert im angeführten Beispiel, wenn die
Datei data.txt nicht auffindbar ist?
Logging auf stderr
$ python somelog.py
INFO:some:read "data.txt"
Traceback (most recent call last):
File "somelog.py", line 15, in <module>
processData('data.txt')
File "somelog.py", line 8, in processData
with open(dataPath, 'rb') as dataFile:
IOError: [Errno 2] No such file or directory: 'data.txt'
$ echo $?
1
Logging auf stderr
● Kein eigener Code für Fehlerbehandlung
→ kein Aufwand für Entwickler
● Auch im Fehlerfalle Schließen der Datei
→ effiziente Nutzung der Ressourcen
● Anzeige der I/O-Fehlermeldung
→ Anwender kann Fehlermeldung nachgehen
● Exit Code 1
→ etwaiges aufrufendes Shell-Script kann Fehler
erkennen
● Nachteil: Stack Trace für Anwender verwirrend und
auch nicht notwending, um Fehler zu beheben
Lösungsorientierte Nutzung
von Exception-Hierarchien
Exception Hierarchie
● Exceptions sind Klassen
http://docs.python.org/2/library/exceptions#exception-hierarchy
● Gruppierung von „ähnlichen“ Fehlern über
Vererbungs-Hierarchie
● Ein try kann mehrere excepts haben
● Über Reihenfolge können verschiedene Fehler
unterschiedlich behandelt werden
Lösungsorientiere Nutzung
● Vom Entwickler lösbar: AssertionError
● Vom Anwender lösbar: EnvironmentError
→ Dateien, Netzwerk, Berechtigungen
● Situationsabhängig vom Entwickler oder
Anwender lösbar: restliche Exception wie
LookupError, ArithmeticError, ValueError, …
→ hier ist Präzisierung durch Entwickler
erforderlich
Alle anderen vom Anwender
behebaren Fehler
● Mit except abfangen und umwandeln in
eigene Exception, die klar als vom Anwender
behebbar definiert ist
● Beispiel: DataError
● Programm kann diese gleich wie
EnvironmentError behandeln
● Mit if … raise selbst erkannte Fehler können
gleich zu DataError führen
Beispiel DataError
● Für fehlerhafte Daten aus Eingangsdatei:
class DataError(Exception):
pass
raise DataError('height is %d but must be greater than 0' 
% height)
Umwandeln einer Exception in
DataError
Fehler abfangen und Meldung übernehmen:
try:
# Process all heights read from `dataFile`.
for lineNumber, line in enumerate(dataFile,
start=1):
processSomething(long(line))
% (
dataFile.name, lineNumber, error))
Umwandeln einer Exception in
DataError
● In Python 3: Stack Trace erhalten mit Exception Chaining:
try:
# Process all heights read from `dataFile`.
for lineNumber, line in enumerate(dataFile,
start=1):
processSomething(long(line))
except ValueError as error:
raise DataError('file %s, line %d' % (
● Ursprüngliche Exception und Fehlermeldung ist in __cause__ ersichtlich
→ „gesamte“ Fehlermeldung zusammenbaubar
● Stack Trace enthält zuerst den ursprünglichen ValueError und
anschließend den verketteten DataError
Alle anderen vom Entwickler
behebaren Fehler
● Sind nun über „alles andere aber kein
DataError“ erkennbar
● Behandlung wie AssertionError
Programmvorlage
Vorlage für Programm
● Nutzt logging
● Nutzt Parser für
Befehlszeilenoptionen
● Vom Anwender
behebbare Fehler über
log.error()
● Vom Entwickler
behebbare Fehler über
log.exception()
● Setzt Exit Code 0 oder 1
def main(arguments=None):
if arguments is None:
arguments = sys.argv
# Exit code: 0=success, >0=error.
exitCode = 1
# Process arguments. In case of errors, report them and
exit.
parser = optparse.OptionParser(usage='process some
report')
parser.add_option("-o", "--out", dest="targetPath",
help="write report to FILE", metavar="FILE")
options, others = parser.parse_args(arguments)
if len(others) < 1:
# Note: parser.error() raises SystemExit.
parser.error('input files must be specified')
try:
_process(options, others)
exitCode = 0 # Success!
except KeyboardInterrupt:
_log.error('stopped as requested by user')
except (DataError, EnvironmentError) as error:
_log.error(error)
except Exception as error:
_log.exception(error)
return exitCode
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
sys.exit(main())
Von mir zu
implementieren
Durchführen des Hauptteils
exitCode = 1
…
try:
_process(options, others)
exitCode = 0 # Success!
except KeyboardInterrupt:
_log.error('stopped as requested by user')
except (DataError, EnvironmentError) as error:
_log.error(error)
except Exception as error:
_log.exception(error)
return exitCode
Von mir zu
implementieren
Empfehlungen für
except und raise
Wann except verwenden? (1)
● Ganz „außen“ in __main__ bzw. main()
● Bei GUI-Anwendungen: um abgeschlossene
Benutzeraktionen (action pattern)
● Zum Umwandeln von Exceptions in DataError
● Zum Umwandeln von Fehlern und gültige
Zustände
→ z.B. bei LookupError einen Defaultwert
verwenden
Wann except verwenden? (2)
● Insgesamt: selten und gezielt
● Wenig Aufwand für Entwickler
● Fehlerbehandlung i.d.R. Trivial:
1.Zusammenräumen (with, finally, ...)
2.Routine abbrechen (raise oder aufgetretene
Exception delegieren)
3.Aufrufer entscheidet, was zu tun ist
● Vorteile: Leicht wartbarer, kompakter Code mit
wenig Einrückebenen
Wann raise verwenden?
● Konsequente Namenskonventionen für Routinen:
● Prozeduren: „mach etwas“
Beispiel: sort(liste) → sortiert Liste, ändert Original
● Funktionen: „etwas“ gemäß dem gelieferten
Beispiel: sorted(liste) → liefert sortierte Kopie einer
Liste, Original bleibt unverändert
● Falls nicht möglich, das beschriebene „etwas“ zu
machen oder liefern: raise
● Damit klare und einfache Definition von
Fehlerbedingungen: alles, was daran hindert, „etwas“
zu machen
Zusammenfassung
Lösungsorientierte
Fehlerbehandlung (1)
● gezielte Nutzung der vorhandene Python-
Mechanismen
● Unterscheidung: Wer kann Fehler beheben?
● Anwender zur Laufzeit: Daten, Umgebung
→ EnvironmentError, DataError
● Entwickler während Umsetzung: Programm
→ Assertions und Rest
● Zusammenräumen mit with, finally und
Context Manager (nicht mit __del__())
Lösungsorientierte
Fehlerbehandlung (2)
● Fehlerbehandlung im Programm:
● Mit if … raise neue Fehler erkennen
● Mit raise bereits erkannte Fehler meist einfach
weiterleiten
● An einigen wenigen stellen mit except abfangen
und Meldung ausgeben
● Schema für gute Fehlermeldung:
→ beschreibt die Lösung statt den Fehler
cannot do <some task>:
<something> is <actual> but must be <expected>

Weitere ähnliche Inhalte

Ähnlich wie Lösungsorientierte Fehlerbehandlung

Puppet - Module entwickeln - Von der Planung bis zur Umsetzung
Puppet - Module entwickeln - Von der Planung bis zur UmsetzungPuppet - Module entwickeln - Von der Planung bis zur Umsetzung
Puppet - Module entwickeln - Von der Planung bis zur Umsetzung
inovex GmbH
 
Schulung C++ Boost Bibliotheken
Schulung C++ Boost BibliothekenSchulung C++ Boost Bibliotheken
Schulung C++ Boost Bibliotheken
tutego
 
EntwicklerCamp 2014 - DOTS reloaded
EntwicklerCamp 2014 - DOTS reloadedEntwicklerCamp 2014 - DOTS reloaded
EntwicklerCamp 2014 - DOTS reloaded
René Winkelmeyer
 
Qualitätssicherung in Webprojekten
Qualitätssicherung in WebprojektenQualitätssicherung in Webprojekten
Qualitätssicherung in Webprojekten
Sebastian Springer
 

Ähnlich wie Lösungsorientierte Fehlerbehandlung (20)

REST Problems
REST ProblemsREST Problems
REST Problems
 
OSMC 2014: Plugin Entwicklung für Einsteiger | Alexander Wirt
OSMC 2014: Plugin Entwicklung für Einsteiger | Alexander WirtOSMC 2014: Plugin Entwicklung für Einsteiger | Alexander Wirt
OSMC 2014: Plugin Entwicklung für Einsteiger | Alexander Wirt
 
C/ C++ for Notes & Domino Developers
C/ C++ for Notes & Domino DevelopersC/ C++ for Notes & Domino Developers
C/ C++ for Notes & Domino Developers
 
Abläufe mit PHP und Phing automatisieren
Abläufe mit PHP und Phing automatisierenAbläufe mit PHP und Phing automatisieren
Abläufe mit PHP und Phing automatisieren
 
GitLab: CI-Pipelines | PHP Usergroup Hamburg 20.03.2018
GitLab: CI-Pipelines | PHP Usergroup Hamburg 20.03.2018GitLab: CI-Pipelines | PHP Usergroup Hamburg 20.03.2018
GitLab: CI-Pipelines | PHP Usergroup Hamburg 20.03.2018
 
Die Macht der Zahlen
Die Macht der ZahlenDie Macht der Zahlen
Die Macht der Zahlen
 
Softwaremonitoring mit prometheus
Softwaremonitoring mit prometheusSoftwaremonitoring mit prometheus
Softwaremonitoring mit prometheus
 
Cloud Migration mit KI: der Turbo
Cloud Migration mit KI: der Turbo Cloud Migration mit KI: der Turbo
Cloud Migration mit KI: der Turbo
 
Puppet - Module entwickeln - Von der Planung bis zur Umsetzung
Puppet - Module entwickeln - Von der Planung bis zur UmsetzungPuppet - Module entwickeln - Von der Planung bis zur Umsetzung
Puppet - Module entwickeln - Von der Planung bis zur Umsetzung
 
JMeter + ELK - Event-basierte Daten != Logfiles in ELK
JMeter + ELK - Event-basierte Daten != Logfiles in ELKJMeter + ELK - Event-basierte Daten != Logfiles in ELK
JMeter + ELK - Event-basierte Daten != Logfiles in ELK
 
Schulung C++ Boost Bibliotheken
Schulung C++ Boost BibliothekenSchulung C++ Boost Bibliotheken
Schulung C++ Boost Bibliotheken
 
EntwicklerCamp 2014 - DOTS reloaded
EntwicklerCamp 2014 - DOTS reloadedEntwicklerCamp 2014 - DOTS reloaded
EntwicklerCamp 2014 - DOTS reloaded
 
Kuck mal, Node.js! Einstieg für .NET Entwickler mit Visual Studio Code und Ty...
Kuck mal, Node.js! Einstieg für .NET Entwickler mit Visual Studio Code und Ty...Kuck mal, Node.js! Einstieg für .NET Entwickler mit Visual Studio Code und Ty...
Kuck mal, Node.js! Einstieg für .NET Entwickler mit Visual Studio Code und Ty...
 
Rex - Infrastruktur als Code
Rex - Infrastruktur als CodeRex - Infrastruktur als Code
Rex - Infrastruktur als Code
 
Software Entwicklung im Team
Software Entwicklung im TeamSoftware Entwicklung im Team
Software Entwicklung im Team
 
System-Management-Trio: Zentrale Verwaltung mit facter, puppet und augeas
System-Management-Trio: Zentrale Verwaltung mit facter, puppet und augeasSystem-Management-Trio: Zentrale Verwaltung mit facter, puppet und augeas
System-Management-Trio: Zentrale Verwaltung mit facter, puppet und augeas
 
Java und Python - Das Beste aus beiden Welten nutzen
Java und Python - Das Beste aus beiden Welten nutzenJava und Python - Das Beste aus beiden Welten nutzen
Java und Python - Das Beste aus beiden Welten nutzen
 
High performance mit PHP
High performance mit PHPHigh performance mit PHP
High performance mit PHP
 
Qualitätssicherung in Webprojekten
Qualitätssicherung in WebprojektenQualitätssicherung in Webprojekten
Qualitätssicherung in Webprojekten
 
Web Entwicklung mit PHP - Teil 3 Beta
Web Entwicklung mit PHP - Teil 3 BetaWeb Entwicklung mit PHP - Teil 3 Beta
Web Entwicklung mit PHP - Teil 3 Beta
 

Mehr von roskakori

Mehr von roskakori (18)

Expanding skill sets - Broaden your perspective on design
Expanding skill sets - Broaden your perspective on designExpanding skill sets - Broaden your perspective on design
Expanding skill sets - Broaden your perspective on design
 
Django trifft Flutter
Django trifft FlutterDjango trifft Flutter
Django trifft Flutter
 
Multiple django applications on a single server with nginx
Multiple django applications on a single server with nginxMultiple django applications on a single server with nginx
Multiple django applications on a single server with nginx
 
Helpful pre commit hooks for Python and Django
Helpful pre commit hooks for Python and DjangoHelpful pre commit hooks for Python and Django
Helpful pre commit hooks for Python and Django
 
Startmeeting Interessengruppe NLP NLU Graz
Startmeeting Interessengruppe NLP NLU GrazStartmeeting Interessengruppe NLP NLU Graz
Startmeeting Interessengruppe NLP NLU Graz
 
Helpful logging with python
Helpful logging with pythonHelpful logging with python
Helpful logging with python
 
Helpful logging with Java
Helpful logging with JavaHelpful logging with Java
Helpful logging with Java
 
Einführung in Kommunikation und Konfliktmanagement für Software-Entwickler
Einführung in Kommunikation und Konfliktmanagement für Software-EntwicklerEinführung in Kommunikation und Konfliktmanagement für Software-Entwickler
Einführung in Kommunikation und Konfliktmanagement für Software-Entwickler
 
Analyzing natural language feedback using python
Analyzing natural language feedback using pythonAnalyzing natural language feedback using python
Analyzing natural language feedback using python
 
Microsoft SQL Server with Linux and Docker
Microsoft SQL Server with Linux and DockerMicrosoft SQL Server with Linux and Docker
Microsoft SQL Server with Linux and Docker
 
Migration to Python 3 in Finance
Migration to Python 3 in FinanceMigration to Python 3 in Finance
Migration to Python 3 in Finance
 
Introduction to pygments
Introduction to pygmentsIntroduction to pygments
Introduction to pygments
 
XML namespaces and XPath with Python
XML namespaces and XPath with PythonXML namespaces and XPath with Python
XML namespaces and XPath with Python
 
Erste-Hilfekasten für Unicode mit Python
Erste-Hilfekasten für Unicode mit PythonErste-Hilfekasten für Unicode mit Python
Erste-Hilfekasten für Unicode mit Python
 
Introduction to trader bots with Python
Introduction to trader bots with PythonIntroduction to trader bots with Python
Introduction to trader bots with Python
 
Open source projects with python
Open source projects with pythonOpen source projects with python
Open source projects with python
 
Python builds mit ant
Python builds mit antPython builds mit ant
Python builds mit ant
 
Kanban zur Abwicklung von Reporting-Anforderungen
Kanban zur Abwicklung von Reporting-AnforderungenKanban zur Abwicklung von Reporting-Anforderungen
Kanban zur Abwicklung von Reporting-Anforderungen
 

Lösungsorientierte Fehlerbehandlung

  • 2. Agenda 1.Begriffserklärung: Fehler 2.Darstellung von Fehler in Python 3.Lösungsorientierter Ansatz 4.Fehlermeldungen 5.Ähnliche Fehler gruppieren 6.Programmvorlage 7.Empfehlungen
  • 4. Was ist ein Fehler? Ein Fehler ist die Abweichung des Ist-Stands von der Erwartung
  • 5. Herausforderungen für Entwickler ● Was ist die Erwartung? → notwendig, um Fehler erkennen zu können ● Was soll Programm im Fehlerfalle machen? ● Wer kann den Fehler beheben? ● Wie kann Programm bei der Korrektur unterstützen?
  • 7. Wozu Beispiele in Python? ● Leicht verständlich und gut lesbar ● Kompakter Code ● Weitgehend englische Sätze ● Prozedural und objektorientiert nutzbar Hinweis: Beispiele i.d.R. sowohl in Python 2 als auch 3 lauffähig, tw. mit geringfügigen Anpassungen für Python 3
  • 8. Fehler in Python ● Darstellung über Exceptions ● Durchgängige Verwendung in Standard-Bibliothek ● Vorteil: nicht unabsichtlich ignorierbar ● Bewährt in vielen anderen Programmiersprachen (Java, C#, ...) ● alternative Ansätze (hier nicht näher betrachtet): ● spezielle oder zusätzliche Rückgabewerte (zB go) ● globale Fehlervariablen (zB errno in C) ● Spezielle Sprachkonstrukte (zB „on error goto“ in Basic)
  • 9. Fehler erkennen ● Erkennen mit if und einer Fehlerbedingung ● Aufzeigen mit raise und einer Fehlermeldung ● Fehler führt zum Abbruch der Routine height) # Actual processing would happen here. pass
  • 10. Fehler abfangen und ausgeben ● Aufruf des möglicherweise fehlschlagenden Codes mit try ● Bestimmte Fehler abfangen mit except ● Ausgabe: height is -3 but must be greater than 0 try: processSomething(-3) except ValueError as error: print(error) vgl. C#, Java: catch statt except
  • 11. Ressourcen immer freigeben (1) ● Mit finally: sowohl bei Erfolg als auch Fehler 'some.txt', 'rb') processData(inputFile) finally:
  • 12. Ressourcen immer freigeben (2) ● Mit with-Statement: as inputFile: ● Voraussetzung: verwendete Klasse implementiert Context Manager → hat Wissen darüber, was wie auf zu räumen ist vgl. C#: using
  • 13. Ressourcen immer freigeben (3a) ● Mit eigenem Context Manager ● Schritt 1: Definition von __enter__() und __exit__() class SocketClient(): '''Provide a ``socket`` and automatically close it when done.''' def __init__(self, host, port): self.socket = socket.create_connection((host, port)) return self self.socket.shutdown(socket.SHUT_RDWR) self.socket.close()
  • 14. Ressourcen immer freigeben (3b) ● Mit eigenem Context Manager ● Schritt 2: Aufruf wie zuvor über with-Statement 'www.python.org', 80) as pythonOrg.clientSocket.sendall( 'GET /index.html HTTP/1.0n' + 'Host: www.python.orgn' +'n') reply = pythonOrg.clientSocket.recv(64) print(reply)
  • 15. Ressourcen immer freigeben (4b) ● Mit eigenem Context Manager ● Schritt 2: Aufruf wie zuvor über with-Statement with SocketClient('www.python.org', 80) as pythonOrg: pythonOrg.clientSocket.sendall( 'GET /index.html HTTP/1.0n' + 'Host: www.python.orgn' +'n') reply = pythonOrg.clientSocket.recv(64) print(reply) Ergebnis von __enter__(). Aufruf von __exit__(). Aufruf von __init__().
  • 16. Ressourcen freigeben (5) ● Nicht verwenden: __del__() ● Aufruf erfolgt durch Garbage Collector ● Nicht vorhersagbar wann → Bindet Ressource unnötig lange ● Wenn Exception während __del__(): nur Warnung in Log, Aufrufer bekommt nichts davon mit ● Daher nicht vorhersagbares Verhalten → Anwender glaubt, alles hat funktioniert → Entwickler kann Fehler schwer reproduzieren ● Anwendung: Python-interne Aufräumarbeiten vgl. Java: dispose()
  • 17. Fehler erkennen mit assert (1) ● Beispiel von zuvor def processSomething(height): if height <= 0: raise ValueError( 'height must be greater than 0') def processSomething(height): assert height > 0, 'height must be greater than 0' ● Als Assertion:
  • 18. Fehler erkennen mit assert (2) ● Wenn Bedingung verletzt: wie raise AssertionError('...') ● Deaktivieren von assert mittels Aufruf über: $ python -O xxx.py (Buchstabe „großes O“, nicht Ziffer „0“) ● Von assert aufgerufene Funktionen dürfen keine Seiteneffekte haben → sonst unterschiedliches Programmverhalten je nachdem ob -O gesetzt ● Frage: wann raise und wann assert? → Antwort folgt
  • 19. Zusammenfassung ● Fehler erkennen mit raise und assert ● Fehler abfangen mit try und except ● Aufräumarbeiten: finally, with und Context Manager ● Nicht verwenden: __del__()
  • 21. Grundprinzipen ● Im Zentrum der Überlegungen steht die Beseitigung des Fehlers (Lösung) und nicht der Fehler selbst ● Klare Zuständigkeiten zwischen Entwickler und Anwender ● Hilfreiche Fehlermeldungen ● Fehlerbedingungen und -meldung aus Programmcode ableitbar
  • 22. Zuständigkeiten ● Entwickler: Umsetzung des Programms zur ● Verarbeitung der Daten und Eingaben des Anwenders ● Liefern des gewünschten Ergebnisses ● Anwender: ● Bereitstellen von Eingaben und Daten zur Verarbeitung durch das Programm ● Bereitstellen einer Umgebung, in der das Programm ausführbar ist (ggf. über Administrator)
  • 23. Nutzung von assert ● Fehlererkennung: aus internen Programmzustand ● Lösung: Änderung des Programms ● Zielgruppe für Fehlermeldungen: Entwickler ● Klare Zuständigkeit beim Aufruf von Routinen: muss Aufrufer oder Routine auf Fehlerbedingungen reagieren? ● Besonders nützlich zur Prüfung von übergebenen Parametern („preconditon“) ● Dient als „ausführbare“ Dokumentation
  • 24. Nutzung von raise ● Fehlererkennung: aus Daten und in Umgebung ● Lösung: ● Daten: korrekte und vollständige Eingabe ● Umgebung: Dateien, Netzwerk, Berechtigungen, … ● Fehler erst zur Laufzeit erkennbar ● Zielgruppe für Fehlermeldungen: Anwender
  • 26. Anforderungen ● In Literatur oft: unklare Richtlinien („hilfreich“, „verständlich“, ...) ● In Praxis oft: Beschreibung, was falsch ist (z.B. „ungültiges Datum“) ● Lösungsorientierter Zugang: ● Beschreibung des Ist-Zustands und des Soll-Zustands ● Beschreibung der Maßnahmen, die zur Korrektur zu setzen sind ● Beschreibung oder Darstellung des Zusammenhangs, in dem der Fehler aufgetreten ist
  • 27. Ableiten der Fehlermeldung aus Programmcode ● Allgemein: if height <= 0: raise ValueError( 'height is %d but must be greater than 0' % height) ● Konkret: if actual != expected: raise SomeError('<actual> must be <expected>')
  • 28. Darstellung des Zusammenhangs ● Bei raise: Anführen von Name und Wert des Ist-Zustands (z.B. „height is -3“) ● Bei except: ursprüngliche Fehlermeldung beibehalten und ergänzen: ● Beschreiben der Herkunft der Fehlerursache (zB Name und Position in Eingabedatei, Feldname in Formular, markieren in Benutzeroberfläche, ...) ● Beschreiben der Aktion, die aufgrund des Fehlers nicht durchführbar ist
  • 29. Beispiel: Fehler erkennende Routine def processSomething(height): if height <= 0: raise ValueError('height must be greater than 0') # Actual processing would happen here. pass def processSomething(height): if height <= 0: raise ValueError('height must be greater than 0') # Actual processing would happen here. pass
  • 30. Beispiel: Fehler berichtender Code def processAllThings(dataFile): try: # Process all heights read from `dataFile`. lineNumber = 1 for line in dataFile: except ValueError as error: print('cannot process %s, line %d: %s' % Beispiel für Ausgabe im Fehlerfall: cannot process some.txt, line 17: height is -3 but must be greater than 0
  • 31. Wo Fehlermeldung ausgeben? ● Bei GUI oder Web-Anwendung: ● Bei Eingaben: Feld hervorheben und Meldung unter dem betroffenem Formularfeld ● In eigenem Fehlerdialog oder auf Fehlerseite ● Zusätzlich in Log für spätere Nachvollziehbarkeit ● Bei Services: in Log ● Bei Befehlszeilenwerkzeugen: in Konsole auf stderr
  • 32. Ausgabe in Log-Datei ● Mit Standard Modul logging: http://docs.python.org/2/library/logging.html ● Mehrere Stufen zur Bewertung der Meldung, u.a.: ● Info – Informationen, welche Aktionen gesetzt werden ● Error – Fehlermeldungen ● Exception – Fehlermeldung und Stack Trace ● Debug – zusätzliche interne Detailinformationen; interessant für Entwickler und während Fehleranalysen ● Ausgabe auf Datei, Console, Netzwerk-Socket, ...
  • 33. Logging auf stderr ● Auch für Befehlzeilenanwendungen nutzbar import def processData(dataPath): _log.info(u'read "%s"', dataPath) with open(dataPath, 'rb') as dataFile: # Here we would actually process the data. pass if __name__ == '__main__':
  • 34. Logging auf stderr ● Was passiert im angeführten Beispiel, wenn die Datei data.txt nicht auffindbar ist?
  • 35. Logging auf stderr $ python somelog.py INFO:some:read "data.txt" Traceback (most recent call last): File "somelog.py", line 15, in <module> processData('data.txt') File "somelog.py", line 8, in processData with open(dataPath, 'rb') as dataFile: IOError: [Errno 2] No such file or directory: 'data.txt' $ echo $? 1
  • 36. Logging auf stderr ● Kein eigener Code für Fehlerbehandlung → kein Aufwand für Entwickler ● Auch im Fehlerfalle Schließen der Datei → effiziente Nutzung der Ressourcen ● Anzeige der I/O-Fehlermeldung → Anwender kann Fehlermeldung nachgehen ● Exit Code 1 → etwaiges aufrufendes Shell-Script kann Fehler erkennen ● Nachteil: Stack Trace für Anwender verwirrend und auch nicht notwending, um Fehler zu beheben
  • 38. Exception Hierarchie ● Exceptions sind Klassen http://docs.python.org/2/library/exceptions#exception-hierarchy ● Gruppierung von „ähnlichen“ Fehlern über Vererbungs-Hierarchie ● Ein try kann mehrere excepts haben ● Über Reihenfolge können verschiedene Fehler unterschiedlich behandelt werden
  • 39. Lösungsorientiere Nutzung ● Vom Entwickler lösbar: AssertionError ● Vom Anwender lösbar: EnvironmentError → Dateien, Netzwerk, Berechtigungen ● Situationsabhängig vom Entwickler oder Anwender lösbar: restliche Exception wie LookupError, ArithmeticError, ValueError, … → hier ist Präzisierung durch Entwickler erforderlich
  • 40. Alle anderen vom Anwender behebaren Fehler ● Mit except abfangen und umwandeln in eigene Exception, die klar als vom Anwender behebbar definiert ist ● Beispiel: DataError ● Programm kann diese gleich wie EnvironmentError behandeln ● Mit if … raise selbst erkannte Fehler können gleich zu DataError führen
  • 41. Beispiel DataError ● Für fehlerhafte Daten aus Eingangsdatei: class DataError(Exception): pass raise DataError('height is %d but must be greater than 0' % height)
  • 42. Umwandeln einer Exception in DataError Fehler abfangen und Meldung übernehmen: try: # Process all heights read from `dataFile`. for lineNumber, line in enumerate(dataFile, start=1): processSomething(long(line)) % ( dataFile.name, lineNumber, error))
  • 43. Umwandeln einer Exception in DataError ● In Python 3: Stack Trace erhalten mit Exception Chaining: try: # Process all heights read from `dataFile`. for lineNumber, line in enumerate(dataFile, start=1): processSomething(long(line)) except ValueError as error: raise DataError('file %s, line %d' % ( ● Ursprüngliche Exception und Fehlermeldung ist in __cause__ ersichtlich → „gesamte“ Fehlermeldung zusammenbaubar ● Stack Trace enthält zuerst den ursprünglichen ValueError und anschließend den verketteten DataError
  • 44. Alle anderen vom Entwickler behebaren Fehler ● Sind nun über „alles andere aber kein DataError“ erkennbar ● Behandlung wie AssertionError
  • 46. Vorlage für Programm ● Nutzt logging ● Nutzt Parser für Befehlszeilenoptionen ● Vom Anwender behebbare Fehler über log.error() ● Vom Entwickler behebbare Fehler über log.exception() ● Setzt Exit Code 0 oder 1 def main(arguments=None): if arguments is None: arguments = sys.argv # Exit code: 0=success, >0=error. exitCode = 1 # Process arguments. In case of errors, report them and exit. parser = optparse.OptionParser(usage='process some report') parser.add_option("-o", "--out", dest="targetPath", help="write report to FILE", metavar="FILE") options, others = parser.parse_args(arguments) if len(others) < 1: # Note: parser.error() raises SystemExit. parser.error('input files must be specified') try: _process(options, others) exitCode = 0 # Success! except KeyboardInterrupt: _log.error('stopped as requested by user') except (DataError, EnvironmentError) as error: _log.error(error) except Exception as error: _log.exception(error) return exitCode if __name__ == "__main__": logging.basicConfig(level=logging.INFO) sys.exit(main()) Von mir zu implementieren
  • 47. Durchführen des Hauptteils exitCode = 1 … try: _process(options, others) exitCode = 0 # Success! except KeyboardInterrupt: _log.error('stopped as requested by user') except (DataError, EnvironmentError) as error: _log.error(error) except Exception as error: _log.exception(error) return exitCode Von mir zu implementieren
  • 49. Wann except verwenden? (1) ● Ganz „außen“ in __main__ bzw. main() ● Bei GUI-Anwendungen: um abgeschlossene Benutzeraktionen (action pattern) ● Zum Umwandeln von Exceptions in DataError ● Zum Umwandeln von Fehlern und gültige Zustände → z.B. bei LookupError einen Defaultwert verwenden
  • 50. Wann except verwenden? (2) ● Insgesamt: selten und gezielt ● Wenig Aufwand für Entwickler ● Fehlerbehandlung i.d.R. Trivial: 1.Zusammenräumen (with, finally, ...) 2.Routine abbrechen (raise oder aufgetretene Exception delegieren) 3.Aufrufer entscheidet, was zu tun ist ● Vorteile: Leicht wartbarer, kompakter Code mit wenig Einrückebenen
  • 51. Wann raise verwenden? ● Konsequente Namenskonventionen für Routinen: ● Prozeduren: „mach etwas“ Beispiel: sort(liste) → sortiert Liste, ändert Original ● Funktionen: „etwas“ gemäß dem gelieferten Beispiel: sorted(liste) → liefert sortierte Kopie einer Liste, Original bleibt unverändert ● Falls nicht möglich, das beschriebene „etwas“ zu machen oder liefern: raise ● Damit klare und einfache Definition von Fehlerbedingungen: alles, was daran hindert, „etwas“ zu machen
  • 53. Lösungsorientierte Fehlerbehandlung (1) ● gezielte Nutzung der vorhandene Python- Mechanismen ● Unterscheidung: Wer kann Fehler beheben? ● Anwender zur Laufzeit: Daten, Umgebung → EnvironmentError, DataError ● Entwickler während Umsetzung: Programm → Assertions und Rest ● Zusammenräumen mit with, finally und Context Manager (nicht mit __del__())
  • 54. Lösungsorientierte Fehlerbehandlung (2) ● Fehlerbehandlung im Programm: ● Mit if … raise neue Fehler erkennen ● Mit raise bereits erkannte Fehler meist einfach weiterleiten ● An einigen wenigen stellen mit except abfangen und Meldung ausgeben ● Schema für gute Fehlermeldung: → beschreibt die Lösung statt den Fehler cannot do <some task>: <something> is <actual> but must be <expected>