Robuste Software muss mit Fehlern umgehen können, also auch unter den ungünstigsten Umständen immer noch zuverlässig arbeiten. Das ist letztlich ein schwer zu erreichender Idealzustand, denn der Rahmen innerhalb dessen ein Fehler auftreten kann, lässt sich immer eine Stufe weiter fassen. Wer am Ende jeden Softwarefehler abdeckt, bleibt dennoch abhängig von der Hardware. Wer die redundant auslegt, ist dennoch von ihrer Versorgung mit Strom und Kühlung abhängig und so weiter.
Ein großes Problem bei Ausnahmezuständen ist vorauszusehen, welche auftreten können. Programmierer neigen dazu, bestimmte Fehler vor Augen zu haben. Ihre Programm erleben aber manche Überraschung und letztlich einen Absturz, wenn andere Situationen auftreten, als gedacht. Computerlinguisten haben daher die strukturierte Ausnahmebehandlung erfunden und in vielen modernen Sprachen direkt in der Syntax verankert.
Deren Muster ähneln sich von Sprache zu Sprache: Der Entwickler klammert einen Block Code ein (oft »try« genannt) und legt fest, welche Schwierigkeiten lokal auftreten dürfen. An einer anderen Stelle schreibt er Code, um sich mit der Lösung dieser Umstände zu befassen (meist in einem als »catch« bezeichneten Block). Das jeweilige Sprachmittel sorgt automatisch dafür, dass das Problem zur Lösung kommt.
Wer das Modell mit objektorientierten Methoden kombiniert, erhält ein mächtiges Instrumentarium an die Hand, das hilft, Code zu entwirren und lesbarer zu gestalten. Er befasst sich nicht mehr lokal mit vielen Einzelproblemen, sondern löst sie an zentraler Stelle. Das zahlt sich besonders in tief verschachteltem Code aus, der sonst durch die Fehlerbehandlung nur noch undurchsichtiger würde.
Zu viel Magie
Aber vielleicht liegt hier schon ein neues, viel größeres Problem: Wenn Code so unübersichtlich gerät, dass nur noch Exceptions ihn im Zaum halten, ist ohnehin nicht mehr viel zu retten. Experten wie Programmierpapst Joel Spolsky weisen nämlich auf die unheilvolle Nähe zum verpöhnten Goto hin. Denn Exceptions durchbrechen die vorhersehbaren Pfade der klassischen Kontrollstrukturen und springen direkt zu ihrem Exceptionhandler.
Ein Beispiel in Perl macht das deutlich: In Listing 1 antizipiert der Programmierer ein mögliches Problem in Zeile 4 in der Funktion »problem_code()«. Löst der Code eine Exception mittels »die()« aus, springt der Kontrollfluss unter Umgehung der Schleife und der Bedingung direkt in die Zeile 8 und trägt hoffentlich nützliche Informationen in der Variable »$@«. Der defensivere Ansatz aus Listing 2 erscheint komplizierter, da Zeile 3 explizit einen Rückgabewert abfragt und dann aber direkt mit einer Lösung reagiert.
01 eval {
02 foreach $i (1 .. 99) {
03 if (bedingung1($i)) {
04 problem_code();
05 }
06 }
07 1; # Alles in Ordnung!
08 } or do {
09 warn "Exception: $@";
10 };
|
01 foreach $i (1 .. 99) {
02 if (bedingung1($i)) {
03 if (problem_code() < 0) {
04 behandle_problem();
05 }
06 }
07 }
|
Exceptions sind ein nützliches Sprachmittel, solange sie ein Programmierer nicht zu sorglos verwendet. Anstelle einer gut trainierten Feuerwehr schützen langfristig brandhemmende Maßnahmen viel nachhaltiger den Code.