Exceptions vereinfachen die Prüfung über Kontrollstrukturen

Ein paar Beispiele dafür wie eval in Perl sinnvoll verwendet werden kann

Mit eval{} lassen sich Wege über lange Kontrollstrukturen abkürzen. Betrachte untenstehenden Code:

my $x = 'FF';
my $d = '255';

print eval{
    $x =~ /^[0-9a-fA-F]+$/ or
        die "Wrong format for hex!\n";
    $d =~ /^\d+$/ or
        die "Wrong format for decimal!\n";
    $x = hex($x);
    $x == $d ? "Equal" : "Not equal";
} || $@;

Fällt in einem eval{}Block eine Exception, ist die Rückgabe nicht näher definiert und der zur Exception gehörige Fehlertext ist in $@ zu finden. Damit lässt sich der Code vereinfachen dadurch daß einfach eine Exception geworfen wird anstatt alle Prüfungen in eine Kontrollstuktur zu setzen die sehr umfangreich und auch geschachtelt sein kann.

Obenstehender Code hat also Benutzereingaben zu prüfen, insbesondere ob die Zahlen im richtigen Format sind. Sollte das nicht der Fall sein, wird eine Exception geworfen bspw. die "Wrong format for hex!\n"; wobei der angehängte Zeilenvorschub bewirkt, daß kein Backtrace erzeugt wird.

Im Fehlerfall an $x geht also Wrong format for hex! in die print()-Ausgabe. Analog wird mit der anderen Zahl verfahren. Sofern beide Zahlen im richtigen Format vorliegen, gibt der eval{}Block das Ergebnis der in diesem Block notierten letzten Zeile zurück, bei Gleichheit also Equal ansonsten Not equal.

Wenn sichergestellt werden kann, daß sich Funktionen erwartungsgemäß verhalten kann die Prüfung auch der Funktion selbst überlassen werden:

my $x = 'x';
my $d = '255';

print eval{
    $d =~ /^\d+$/ or
        die "Wrong format for decimal!\n";
    $x = hex($x) or
        die "Wrong format for hex!\n";
    $x == $d ? "Equal" : "Not equal";
} || $@;

was den Code damit weiter vereinft. Mit die() geworfene Exceptions werden also aufgefangen, das Programm endet insgesamt fehlerfrei mit status 0. Jedoch wird sämtlicher, nach der Zeile mit dem die() notierter Code nicht mehr ausgeführt, es wird abgekürzt.

Wer seine Module aus der Hand gibt, sollte anstelle die() jedoch Carp::croak() oder ::confess() verwenden womit in jedem Fall ein Backtrace erzeugt wird der für den Anwender gedacht ist. Autoren ungezählter Module, die auf CPAN zu finden sind, setzen Konstrukte dieser Art erfolgreich ein.

In Perl-Modulen: use Carp;

Untenstehender Code demonstriert die Verwendung des Carp-Modules. Der Konstruktor einer Klasse soll sicherstellen, daß eine Zahl übergeben wird, ansonsten wird die Instanz nicht erstellt:

use strict;
use warnings;
use Carp qw(confess);

sub new{
    my $class = shift;
    return eval{
        my $number = shift
            or confess "A Number is required!";
        $number =~ /^\d+$/
            or confess "Number not numeric!";
        bless{ NR => $number }, $class;
    };
}

my $m = main->new() or die $@;

Der Anwender des Konstruktors bekommt damit einen umfangreichen Backtrace der letztendlich auch auf die Zeile zeigt, in welcher er den Konstruktor fehlerhaft aufgerufen hat.

A Number is required! at ... line 21.
    eval {...} called at ... line 20
    main::new('main') called at ... line 27

Der Vollständigkeit halber das Beispiel zum Vergleich zweier Zahlen in dezimal bzw. hexadezimal als eine Klasse umgesetzt:

use strict;
use warnings;
use Carp;

sub new{
    my $class = shift;
    # Numbers decimal, hexadecimal
    my %nr = (
        dec => 0,
        hex => 0,
    @_);

    return eval{
        croak "Wrong format for hexadecimal number!"
            unless $nr{hex} =~ /^[0-9a-fA-F]+$/;
        croak "Wrong format for decimal number!"
            unless $nr{dec} =~ /^\d+$/;
        my $self = bless{
            hex => hex($nr{hex}),
            dec => $nr{dec}
        }, $class;
        $self->compare() or carp "The numbers are not equal!";
        $self;
    };
}

sub compare{
    my $self = shift;
    $self->{hex} == $self->{dec};
}

my $m = main->new( hex => 'FF', dec => 255 ) or die $@;

Und so funktioniert es

Die Funktion, in diesem Fall eine Klassenmethode, also der Konstruktor gibt im Erfolgsfall eine Klasseninstanz zurück. Der Anwender prüft ganz einfach den Rückgabewert des Konstruktors, ist dieser Wert nicht vorhanden, wird $@ zum Finden der Ursache herangezogen. Im Gegensatz zu die() schneidet ein dem an confess() bzw. carp() oder croak()übergebenen Fehlertext anghängter Zeilenvorschub \n den Backtrace nicht ab, ein Solcher wird also mit dem Carp-Module grundsätzlich immer erzeugt.

Die Fehlermeldung steht in $@

Betrachte untenstehenden Code:

eval{ die "Ex 1\n" };
eval{ die "Ex 2\n" };
eval{ die "Ex 3\n" };
print $@; # Ex 3

Das macht deutlich, daß $@ als globale Variable möglicherweise nicht das enthält, was erwartet wird. Das heißt, daß kritischer Code unmittelbar nach Ausführung auf eine mögliche Exeption geprüft werden sollte. Ansonsten läuft das Script unbehelligt weiter weil Exceptions innerhalb eines eval-Block ja aufgefangen werden. Von daher sollte die letzte Anweisung in einem eval-Block stets einen wahren Wert liefern, was sicherstellt, daß der Block bis zur letzten Zeile durchlaufen wurde. Genau das ist die eigentliche Prüfung ob eine Exception gefallen ist, also nicht etwa die Prüfung ob in $@ etwas drinsteht!


Datenschutzerklärung: Diese Seite dient rein privaten Zwecken. Auf den für diese Domäne installierten Seiten werden grundsätzlich keine personenbezogenen Daten erhoben. Das Loggen der Zugriffe mit Ihrer Remote Adresse erfolgt beim Provider soweit das technisch erforderlich ist. s​os­@rolf­rost.de.