Philipp Hasenfratz: OOP Error-/ExceptionHandling

Beitrag lesen

Halihallo alle

Bei der Modellierung eines Error-/ExceptionHandlings habe ich einige
Vorschläge bezüglich der Umsetzung, die mir allesammt nicht so ganz
gefallen. Ich hoffe, ihr habe hier einige Vorschläge zur Verbesserung
oder ein anderes Verfahren, das ihr einsetzt:

Die von Perl mitgelieferten Funktionen croak/die sind zwar schön für
die Konsole; taugen aber in einer grossen Applikation nicht sehr
viel, da die Fehler abgefangen und verarbeitet werden sollen.

Java bietet hierfür try-catch-finally-Konstrukte an, die vom
Grundgedanken her ganz OK sind. Wie setzt man etwas derartiges am
besten in Perl um?

Nun, das von Perl mitgelieferte Pendant wäre eine eval-die
Konstruktion im Stile:

sub t { die('Error processing foo.'); }

eval { t() };
if ($@) {
   # handle Exception 'Error processing foo.'
}

Dies ist zwar schon schön und gut, ist jedoch für grosse
Applikationen etwas starr, "funktional" und zudem langsam.

Zudem wäre ich auch froh, wenn die Fehlermeldungen irgendwo
gespeichert werden, da diese z.B. in einer Fehlerseite dann
ausgegeben werden sollen.

1. Vorschlag:
Ein etwas "objektorientierterer" Ansatz wäre ein Instanzenattribut
errstr und errcode, um die Beschreibung bzw. den Fehlercode in der
Instanz zu speichern. Dazu einige vererbte Methoden getErrorDesc(),
isError(), getErrorCode().
Nachteile bei diesem Vorgehen sind folgende:
 - Fehler innerhalb des Constructors new sind nicht abzufangen, da
   noch gar keine Instanz angelegt ist.
 - Fehler müssen umbedingt abgefangen werden, ansonsten gehen sie
   irgendwann verloren.

2. Vorschlag:
Um die Nachteile vom 1. Vorschlag auszumerzen kämen einige statische
Klassenvariablen zum Einsatz. Die Methoden getErrorDesc(), isError(),
getErrorCode(), getNextError() etc. wären Klassenmethoden und würden
auf die Klassenvariablen zugreifen. Somit wäre auch ein Fehler im
Construktor abzufangen und die Fehler würden während des ganzen
Programmes erhalten bleiben. Problem hier: Man kann zwischen
Fehlern einzelner Objektinstanzen nicht mehr unterscheiden.

3. Vorschlag:
Jede Klasse erbt von der Klasse ErrorSuccessful, welche eine Methode
isError() definiert, welche stets den Wert undef (falsch)
zurückliefert. Falls jedoch in einer Methode ein Fehler auftritt,
wird statt des normalen Rückgabewertes eine neue Instanz der Klasse
Error zurückgegeben, welche nun die Methoden und Variablen getError
(), getErrorCode definiert.

Somit wäre folgendes denkbar:

package Person;
use base qw(ErrorSuccessful);
sub new {
  my ($class,$name) = @_;
  if (!$name =~ /[1]+$/) {
    return new Error('ERR_NO_VALID_NAME');
  } else {
    return $class->SUPER::new(name=>$name);
  }
}

package main;

$person1 = new Person('Hasenfratz');
$person2 = new Person('98745jdfs.,ö.');

if ($person1->isError()) {
   # wird nicht aufgerufen, da "Hasenfratz" syntaktisch korrekt.
}
if ($person2->isError()) {
   die($person2->getErrorDesc());
   # wird ausgegeben, da invalide characters im Namen.
   # getErrorDesc definiert, da die Klasse nicht Person ist, sondern
   # Error
}

---------------------

Probleme bei diesem 3. Vorschlag:
 - Jede Klasse erbt von ErrorSuccessful was schlicht unglücklich
   ist.
 - Wenn man den Fehler nicht abfragt (eg. wenn er egal ist), läuft
   man gefahr eine Methode aus Person zu verwenden, welche in der
   Klasse Error natürlich nicht definiert ist.
 - Der Rückgabewert müsste der Einfachheitshalber stehts ein Objekt
   (also eine blessed reference) sein.

4. Vorschlag:
"Back to the roots": eval-die-Konstrukt:

sub test() {
  die( Error->new(
           'ERR_WRONG_CHARACTER', 'Falsches Zeichen in Name.'
                 )->toString() ); }

eval { test() };
if ($@) {
    my ($err_code, $err_msg, $err_line, ...) = Error->parseError($@);
    # do something with $err_code, $err_msg, ...
}

Erklärung:
Man könnte eine Klasse Error implementieren, welche aufgrund der
Argumente des Constructors per toString() eine 'die'-Fehlermeldung
produziert. Diese kann über eval {} abgefangen werden und per
parseError() Methode wieder in die einzelnen Komponenten aufgelöst
werden. Dies käme einem try-catch-finally-Konstrukt schon sehr nahe.

Nachteil: Die Fehlermeldungen werden nirgens gespeichert und müssten
manuell weitergegeben werden. Zudem sind die eval {} Konstrukte
langsam, da Perl immer den Parser anwerfen muss.
Ein weiterer Nachteil ist die (beinahe) Unmöglichkeit mehrere
Fehlermeldungen zurückzugeben. Nehmen wir z.B. ein HTML-Formular,
welches Daten eines Benutzers enthält. Es könnte der Name falsch
sein, die Adresse, der Wohnort, die E-Mail, ... Mir wäre eben auch
die Lösung lieb, dass mehrere Fehlermeldungen zusammen verwaltet
werden und diese in einer Fehlerseite dann allesammt aufgelistet
werden. Bei den anderen Vorschlägen könnte man hierfür eine Art
ErrorList implementieren, worin man über getNextError() an die
nächste Error-Instanz käme.

-----------------

So, was meint ihr dazu? - Habt ihr noch eine andere gute Idee? - Wie
löst ihr dies in grösseren Applikationen, die "empfindlich auf Fehler
reagieren und nicht einfach gleich mit "Fehler: ..." antworten"?

Ich hoffe, ich konnte mein Anliegen halbwegs beschreiben, sodass man
es versteht...

Viele Grüsse

Philipp


  1. A-Za-z ↩︎