FoRt: Debugging Fabian Scheipl Institut für Statistik LMU München Debugging Debugging Jedes Programm hat Fehler. Debugging, also Fehlersuche und -korrektur, ist ein wichtiger und unvermeidbarer Bestandteil von Programmierung. (. . . nicht zu schweigen von der Nutzung von ‘open source’ Software die großteils von autodidaktischen Doktoranden unter Schlafentzug entwickelt wird) Effektives und effizientes debugging ist essentiell für produktives Programmieren. Ablauf 1. Beschreibung: • was läuft schief? • unter welchen Umständen / Randbedingungen? 2. Verortung: • welche Zeilen lösen den Fehler aus? • welche Zeilen verursachen den Fehler? 3. Berichtigung des Fehlers & Überprüfung der Programmänderungen: • Fehler durch Änderung behoben? • sonstiges Verhalten des Programms unverändert? Rationales & Rationelles Debugging modus operandi: We do SCIENCE! Rationales & Rationelles Debugging Auf repeat: 1. 2. 3. 4. formuliere Hypothese: “Bug wird verursacht von XY.” designe & führe Experiment aus um Hypothese zu prüfen analysiere Ergebnis immer noch fehlerhaft? zurück auf 1. Debugging Herausfinden • was kaputt ist • wo indem sukzessive mögliche Fehlerquellen eliminiert werden. 1 Rationales & rationelles Debugging Leitfragen: • Was für ein Verhalten erwarte ich? • Was für ein Verhalten beobachte ich? • Was ist der Unterschied? Dann • Hypothesenbildung (“Woran liegt’s?”) • und Bugfix (“Was muss ich ändern?”). Beschreibung 1. Fehlermeldung reproduzierbar machen • unter welchen Umständen tritt Fehler auf? • . . . auch in einem sauberen workspace? 2. Fehler eingrenzen • wie stark können sich die Inputs ändern ohne dass der Fehler verschwindet? • produzieren geänderte Inputs andere Fehler? • für welche Inputs verschwindet der Fehler? Verortung Immer ein guter erster Schritt: traceback(): Welche Funktionsaufrufe haben den Fehler ausgelöst? (setze options(deparse.max.lines=5) um Output lesbar zu halten) traceback() f <- function(a) g <- function(b) h <- function(c) i <- function(d) f(10) g(a) h(b) i(c) "a" + d Error in "a" + d : non-numeric argument to binary operator traceback() 4: 3: 2: 1: i(c) at traceback-1.R#3 h(b) at traceback-1.R#2 g(a) at traceback-1.R#1 f(10) at traceback-1.R#5 2 traceback() 4: 3: 2: 1: i(c) at traceback-1.R#3 h(b) at traceback-1.R#2 g(a) at traceback-1.R#1 f(10) at traceback-1.R#5 • Kette der calling environments • Code der per source() geladen wurde mit Dateiname#Zeilennummer Verortung andere Werkzeuge: • options(error=recover): springt in den interaktiven Debugger direkt bevor der Fehler ausgelöst wird. Höchst empfehlenswert. • debug(<function>), debugonce(<function>): interaktiver Debugger öffnet sich sobald die Funktion aufgerufen wird. undebug(<function>) um debug mode wieder zu deaktivieren. • in eigenen Skripten: Einfügen von browser()-statements. (in RStudio: Breakpoint setzen direkt im Editor) • eigener oder fremder (package) Code: trace() mit edit=TRUE, dann Einfügen von browser, print or cat, z.B. Verortung • nicht empfehlenswert: Code mit vielenvielen print, cat, etc. Befehlen “verzieren” um z.B Variablenwerte nachzuverfolgen • error=recover()-option abschalten mit # default wiederherstellen: options(error=NULL) # oder: options(error=traceback) Interaktiver Debugger • interaktive Konsole innerhalb der Funktion • führe beliebigen R Code in der Konsole im evaluation frame des Funktionsaufrufes aus. debug(f) f(10) debugging in: f(10) debug: g(a) Browse[2]> Interaktiver Debugger 3 options(error=recover) f(10) Error in "a" + d : non-numeric argument to binary operator Enter a frame number, or 0 to exit 1: 2: 3: 4: f(10) traceback-1.R#1: g(a) traceback-1.R#2: h(b) traceback-1.R#3: i(c) Selection: Interaktiver Debugger: Spezielle Befehle • n, <enter>: führe nächsten Befehl der Funktion aus. print(n) um lokale Variable n auszugeben, falls vorhanden. • c: führe den gesamten aktuellen Codeblock aus: – nützlich innerhalb von Schleifen: kein manuelles Steppen durch jede einzelne Iteration – nützlich um zu überprüfen ob Funktion komplett & korrekt durchläuft nachdem unerwünschte Variablenwerte manuell im Debugger korrigiert wurden • Q: stoppt den Debugger, beendet Funktionsauswertung und geht zurück in die normale Konsole. • sinnvoller erster Schritt im Debugger: ls.str() für Übersicht über lokale Variablen und ihre Werte Debugging workflow • • • • lies die Fehlermeldung. Echt jetzt. lies die Fehlermeldung nochmal. Denk drüber nach. noch kein Plan? Rufe traceback() immer noch kein Plan? Lies die Hilfe der Funktion die den Fehler ausgibt. Debugging workflow Immer noch kein Plan? Springe an die Stelle im Code die den Fehler auslöst um genauer nachzusehen: • • • • • setze options(error=recover, deparse.max.lines=5) lass den Code der den Fehler getriggert hat nochmal laufen wähle geeigneten frame (nicht zu niedrig im call stack, nicht zu hoch. . . ) schau den Zustand der lokalen Variablen bei Auslösung des Fehlers an: ls.str() lies den Quellcode der Funktion um zu verstehen was der Zustand der lokalen Variablen an dieser Stelle im Programm sein sollte • hilft alles nüscht? Fang von vorne an, wähle anderen frame aus dem call stack 4 Debugging: Eigener Code in RStudio Debugging: Code in (CRAN) Paketen Wie komme ich an den Quellcode von Paketen? 1. Gebe Namen der Funktion in die Konsole ein (Keine Kommentare, nicht so ganz einfach für S3/S4 Methoden und nicht-exportierte Funktionen.) 2. Lade <PAKET>.tar.gz von CRAN/R-forge/Bioconductor/etc. herunter, extrahiere lokal und durchsuche Dateien im /R-Verzeichnis 3. Gehe auf https://github.com/cran/<PAKET>, benutze Github-Suchfunktion Untersuchung unerwarteter Warnungen • konvertiere Warnungen in Fehler mit options(warn = 2) • benutze übliche debugging Werkzeuge (traceback, options(error=recover), . . . ) Modifikation Problem verstanden und verortet? Modifizierungen vorgenommen? Dann Validierung: • ursprüngliche(r) Fehler tatsächlich behoben? • modifizierter Code läuft fehlerfrei für restliche Tests durch und liefert korrekte Ergebnisse? Validierung nie vergessen, sonst häufig “Verschlimmbesserung” durch Bugfixes. Um Hilfe bitten Externe Hilfe bekommen: MREs Minimale, reproduzierbare Beispiele (minimal reproducible example (MRE)) ermöglichen es gefundene Probleme präzise an andere zu kommunizieren. Sie enthalten: • minimalen Datensatz der nötig ist um den Fehler zu reproduzieren • minimal kurzer Code der nötig ist um den Fehler zu reproduzieren und der in einem leeren Workspace mit dem gegebenen Datensatz läuft • der Output von sessionInfo() in der R-Session in der der Fehler aufgetreten ist • bei Code der Zufallsgeneratoren aufruft: ein RNG seed (also set.seed()) um Beispiel reproduzierbar zu machen Externe Hilfe bekommen: Web • • • • StackOverflow R Help Mailingliste [email protected] (wenn man gerne angeraunzt wird. . . ) Bug in einem Paket: kontaktiere maintainer & schicke ihr/ihm ein minimales, reproduzierbares Beispiel Paket auf GitHub: öffne ein issue im entsprechenden repository 5 Beliebte & Bekannte Fehler Häufige Fehlerquellen: Syntax • • • • [[ vs [ == vs = == vs all.equal, Gleitkommazahlen: 1e-380 == 0 vector vs matrix vs array – benutze [ mit drop=FALSE – schreibe vektorisierten Code • implizite Typenumwandlung: (z.B. string zu factor) Häufige Fehlerquellen: Syntax R-Spezialität: argument recycling x <- 1:3 x[1:4] ## [1] 1 2 3 NA matrix(x, 2, 2) ## Warning in matrix(x, 2, 2): data length [3] is not a sub-multiple or ## multiple of the number of rows [2] ## [,1] [,2] ## [1,] 1 3 ## [2,] 2 1 Häufige Fehlerquellen: Syntax Vermeidung & Verteidigung: • input checking: validiere Funktionsargumente (stopifnot(...), if(...) stop(...), if(missing(...)) • benutze Befehle und Funktionen die immer die selbe Art von Rückgabewert produzieren: – vapply statt sapply – [ mit drop=FALSE Häufige Fehlerquellen: Problem zwischen Stuhl und Computer • • • • falsche / ungeeignete inputs verwirrende / uneinheitliche Benamung unbenamte Funktionsargumente in der falschen Reihenfolge Übertragungsfehler: Gehirn / Papier → Code 6 Häufige Fehlerquellen: Scoping • undefinierte / falsch definierte globale Variablen • nach Kapselung von Funktionen: Nebeneffekte auf die der Code sich implizit gestützt hatte nicht mehr vorhanden (gut so. . . .) Easy Debugging Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it. — Brian Kernighan (UNIX co-creator) Easy Debugging Bugs sind unvermeidbar. Debugging ist oft frustrierend und immer zeitraubend. Code der leicht zu debuggen ist ist meistens: • weniger elegant • weniger knapp • schwieriger zu schreiben . . . das ist es trotzdem wert! Leicht zu debuggender Code • . . . hat viele Kommentare: – jede Funktion mit mindestens 3 Kommentarzeilen: Zweck, inputs, outputs – schwierige, knifflige Abschnitte: warum & wie, nicht nur was – klare Zuschreibung von fremdem Code: wann & von wo (URL) kopiert? was modifiziert? • Benamung: – Abkürzungen vermeiden – aussagekräftige, eindeutige Namen – konsistentes Schema Leicht zu debuggender Code • • • • modularer, funktionaler Code (top-down design) “reine” Funktionen don’t repeat yourself!: Kapselung teste früh, teste oft, teste alles 7 Modularer & funktionaler Code . . . macht Fehler leichter lokalisierbar: • “low-level” Funktion OK? → Bug auf höherem Abstraktionslevel • Funktion OK für künstliche inputs? → falsche / unerwartete inputs oder Fehler in einer Funktion auf niedrigerem Level Reine Funktionen Respect the Interface! Kapselung & Abtrennung von Unterfunktionen sind hilfreich: • benutze nur Variablen die als Argumente übergeben wurden – keine versteckten Abhängigkeiten! – zB von globalen Variablen in Elternumgebungen des execution frame o.ä. • Benutzung von Variablen die in der closure der Funktion liegen kann sehr eleganten und knappen Code produzieren, ist aber meist schwer zu durchschauen und zu debuggen • <<- ist pfuibäh! 8
© Copyright 2024 ExpyDoc