Das Standardverhalten von Perl beim Auftreten eines Fehlers ist mit der Funktion die zu beenden. Dabei wird neben einer Fehlerursache die Zeilennummer ausgegeben, welche den Fehler verursacht hat
Tritt ein Fehler in einer sehr häufig genutzten Funktion auf (z.B. Umrechnen eines Wertes), so ist es sehr schwer festzustellen, von wo und mit welchen Parametern diese Funktion aufgerufen wurde.
Beispiel:
#!/usr/bin/perl use strict; use warnings; sub divide { my ($x,$y) = @_; return $x/$y; } sub printDivision { my ($x,$y) = @_; print "$x durch $y = ".divide($x,$y)."\n"; } sub divideList { my $x = shift; my $listReference = shift; my @list = @{$listReference}; foreach my $i(@list){ printDivision($x,$i); } } my @divList1 = (22,7,16); my @divList2 = (2,5,33,50,0,7,65); divideList(100,\@divList1); # do something else ... divideList(100,\@divList2);
Zugegeben ist dies ein recht künstlich konstruiertes Beispiel, jedoch ist es nicht leicht ein möglichst einfaches und gleichzeitig realistisches Beispiel zu finden. Die Funktion divide dividiert zwei übergebene Zahlen ohne überprüfung, ob durch 0 dividiert wird. printDivision nutzt diese Funktion um das Ergebnis einer Division auszugeben. Die Funktion divideList bekommt eine Zahl und eine Referenz zu einer Liste übergeben, durch die die Zahl jeweils geteilt und das Ergebnis mit der Funktion printList ausgegeben werden soll. Schließlich wird die Funktion divideList erst mit einer Liste ohne 0 und dann mit einer Liste aufgerufen, in der eine 0 vorkommt.
Die Ausgabe des Programms ist:
100 durch 22 = 4.54545454545455 100 durch 7 = 14.2857142857143 100 durch 16 = 6.25 100 durch 2 = 50 100 durch 5 = 20 100 durch 33 = 3.03030303030303 100 durch 50 = 2 Illegal division by zero at ./test.pl line 8.
Wenn man die beiden Lisen jetzt nicht direkt so vor Augen hat (weil sie z.B. während des Ablaufs generiert werden), kann man schwer sagen, welcher Funktionsaufruf den Fehler verursacht hat. Man weiß nur, dass der Fehler in Zeile 8 lag. Auch wenn der Fehler abgefangen wird, kommen wir nicht an die Stelle, an der der falsche Parameter ins Spiel gekommen ist.
Ein Stacktrace kann da ein wenig Abhilfe schaffen. Nutzt man das Modul Carp, so kann man die Funktionen warn und die mit carp und croak überschreiben und bekommt nun zusätzlich einen Stacktrace. Dafür werden die beiden folgenden Zeilen an den Anfang des Programms eingefügt:
use Carp; $SIG{__DIE__} = sub {croak shift;};
Die Ausgabe sieht nun so aus:
100 durch 5 = 20
100 durch 10 = 10
100 durch 33 = 3.03030303030303
100 durch 50 = 2
Illegal division by zero at ./test.pl line 9.
at ./test_croak.pl line 5
main::__ANON__('Illegal division by zero at ./test_croak.pl line 9.\x{a}') called at ./test.pl line 9
main::divide(100, 0) called at ./test.pl line 15
main::printDivision(100, 0) called at ./test.pl line 22
main::divideList(100, 'ARRAY(0x87d6d28)') called at ./test.pl line 34Bei einer Warnung kann das Programm zwar noch weiter ausgeführt werden, doch sind die Ergebnisse dann in der Regel falsch (vor allem wenn die Warnung wegen einer nicht initialisierten Variablen auftritt). Daher kann es sinnvoll sein die Funktion warn mit croak zu überschreiben, so dass das Programm auch bei einer Warnung abbricht.
Noch praktischer wäre es natürlich, wenn man nun noch die Elemente der Liste kennen würde, mit deren Referenz die Funktion divideList aufgerufen wurde.
Daher habe ich ein kleines Modul entwickelt (die Grundidee stammt aus einem Perl-Buch), welches noch etwas mehr kann. Das Modul DebugMessages gibt bei einem Fehler einen Stacktrace aus, wobei die aktuellen Parameter des Aufrufs mit ausgegeben werden. Auf Wunsch werden werden auch Inhalte von Listen und Hashes ausgegeben.
Zusätzlich wird der Quelltext mit ausgegeben und zwar ab n Zeilen über der Zeile, die den Fehler erzeugt hat bis n Zeilen danach. (Die Anzahl der Zeilen n kann beliebig gewählt werden). Die Zeile mit dem Fehler wird dabei markiert.
Das Modul kann folgendermaßen eingebunden werden:
use lib "path/to/module"; use DebugMessages exit_on_warning => 1, errors => 3, warnings => 3, level => 5, verbose => 1;
Die Pfadangabe "path/to/module" muss natürlich auf das Verzeichnis zeigen, in dem die Datei DebugMessages.pm liegt.
Die einzelnen Parameter haben dabei folgende Bedeutung:
- exit_on_warning: Wenn dieser Parameter 1 ist bricht das Programm schon bei einer Warnung ab. (Default ist 0)
- errors: Anzahl der Zeilen, die jeweils vor und nach der Zeile ausgegeben werden sollen, die einen Fehler produziert hat. -1 deaktiviert DebugMessages für Fehler. (Default ist 3)
- warnings: Anzahl der Zeilen, die jeweils vor und nach der Zeile ausgegeben werden sollen, die eine Warnung produziert hat. -1 deaktiviert Default für Warnungen. (Default ist 3)
- level: Anzahl der Rekursionslevel für den Stacktrace. (Default ist 3)
- verbose: 1 zeigt den Inhalt von Listen und Hasheses in kompakter Schreibweise an. 2 Zeigt den Inhalt ausführlich an. 0 deaktiviert das Ausgeben von Listeninhalten.
Die Fehlermeldung des obigen Beispiels sieht ist nun sehr ausführlich:
100 durch 22 = 4.54545454545455
100 durch 7 = 14.2857142857143
100 durch 16 = 6.25
100 durch 2 = 50
100 durch 5 = 20
100 durch 33 = 3.03030303030303
100 durch 50 = 2
Error: Illegal division by zero at ./test.pl line 11.
0008:
0009: sub divide {
0010: my ($x,$y) = @_;
* 0011: return $x/$y;
0012: }
0013:
0014: sub divideAll {
-------------------------------------
Verbose output of parameters for call of main::divide
100
0
-------------------------------------
main::divide(100,0) called at line 33 in file ./test.pl (package main):
0030:
0031: sub printDivision {
0032: my ($x,$y) = @_;
* 0033: print "$x durch $y = ".divide($x,$y)."\n";
0034: }
0035:
0036: sub divideList {
-------------------------------------
Verbose output of parameters for call of main::printDivision
100
0
-------------------------------------
main::printDivision(100,0) called at line 41 in file ./test.pl (package main):
0038: my $listReference = shift;
0039: my @list = @{$listReference};
0040: foreach my $i(@list){
* 0041: printDivision($x,$i);
0042: }
0043: }
0044:
-------------------------------------
Verbose output of parameters for call of main::divideList
100
0..6 2 5 33 50 0 7 65
-------------------------------------
main::divideList(100,ARRAY(0x9ff2528)) called at line 52 in file ./test.pl (package main):
0049:
0050: # do something else ...
0051:
* 0052: divideList(100,\@divList2);So kann man den Fehler über alle Funktionsaufrufe zurückverfolgen ohne in den Editor wechseln zu müssen. Man hat ja auch den Kontext des Quellcodes vor Augen.
Zusätzlich wurde noch eine kleine Verschönerung für die Ausgabe von Listen hinzugefügt. Mit eingebundenem Modul kann man Listen folgendermaßen ausgeben:
my @list = ("Hallo Welt", "a", "b",1,2,3); print "[@list]\n";
Statt der normalen Ausgabe:
[Hallo Welt a b 1 2 3]
bei der man nicht erkennen kann, wie viele Elemente die Liste enthält, bekommt man mit eingebundenem DebugMessages Modul folgende Ausgabe:
[Hallo Welt] [a] [b] [1] [2] [3]
Das Modul DebugMessages habe ich bei GitHub hoch geladen. Die Adresse ist: http://github.com/zimon/DebugMessages
Folgende Beiträge könnten Dich auch interessieren:
Schlagworte: Debugging, perl, Stacktrace







