Execption Handling – Anti Patterns

Mir begegnet in alarmierender Häufigkeit falsche Verwendung von Exceptions — übrigens unabhängig von der Programmiersprache.

Bevor wir uns ein besonders augenfälliges Gegenbeispiel ansehen, rekapitulieren wir noch einmal kurz, wofür Exceptions eigentlich gedacht sind. Der Name (zu Deutsch „Ausnahmen“) ist dabei ein guter Indikator.

Exceptions wurden erfunden, um im Code die Lesbarkeit der Fehlerbehandlung zu verbessern. Als Design Prinzip steckt dahinter die „Separation of Concerns (SoC)“ (dt. „Trennung von Belangen“).

Code hat immer eine normale Aufgabe, die er erfüllen soll — beispielsweise den Umrechnungskurs zwischen zwei Währungen an einem bestimmten Tag zu ermitteln. Daneben gilt es mit Fehlersituationen umzugehen. Um im Beispiel zu bleiben wäre das der Fall, wenn der Code die Währungskurse von einem Webservice abfragt und dieser einmal nicht erreichbar ist. Oder aber, wenn sich unerwartet die Schnittstelle des verwendeten Services geändert hat.

Exceptions erlauben es in Ausnahmesituationen die normale Programmverarbeitung zu unterbrechen und in die Fehlerbehandlung zu springen. Dabei „wirft“ man eine Exception, die die Ausnahmesituation beschreibt und der Fehlerbehandlungscode fängt sie.

try {
  final ExchangeRates exchangeRates = updateExchangeRatesFromService(service);
  exchangeRateCache.getInstance().update(exchangeRates);
}
catch(WebServiceInterfaceMismatchException exception) {
  // handle errors caused by interface mismatch
}
catch(WebServiceUnreachableException exception) {
  // handle unreachable webservice
}

Eine wichtige Regel dabei ist, dass man der Versuchung widersteht, Exceptions für die Steuerung des normalen Programmflusses zu verwenden. In unserem Beispiel oben wäre es bereits ein Streitfall, ob die Nichtverfügbarkeit eines Dienstes eine Exception auslösen sollte — schließlich ist das gar nicht so unwahrscheinlich. Eine Schnittstellenänderung kann man dagegen getrost als unerwartet betrachten.

Der Code oben zeigt, wo der Vorteil von Exceptions liegt. Das, was der Code im Normalfall tut ist nicht durch Fehlerbehandlung verunreinigt — die hat ihren eigenen Bereich in den catch-Blöcken.

Jetzt zum versprochenen Beispiel, wie man es auf keinen Fall machen sollte. Das Beispiel stammt aus der Klasse org.pac4j.sparkjava.Security im Packet org.pac4j/spark-pac4j in Version 2.2.0 (siehte GitHub).

try {
    securityLogic.perform( /* removed to make the example simpler*/ );
    logger.debug("Halt the request processing");
    // stop the processing if no success granted access exception has been raised
    halt();
} catch (final SecurityGrantedAccessException e) {
    // ignore this exception, it meants the access is granted: continue
    logger.debug("Received SecurityGrantedAccessException -> continue");
}

Man sieht deutlich, dass die SecurityGrantedAccessException wie ein Rückgabewert verwendet wird und nicht zur Ausnahmebehandlung — schließlich sollte erfolgreiche Autorisierung der Normalfall sein.

An der Stelle sollte man sich verdeutlichen, dass der Code in einem besonders sicherheitskritischen Teil liegt. Der Code funktioniert zwar wie er soll, sauber ist er meiner persönlichen Meinung nach aber nicht.

Allerdings möchte ich auch ganz klar loben, dass der Quellcode öffentlich zugänglich ist, so kann man wenigstens selbst ein Code-Review machen. Geschlossener Quellcode ist nicht besser, man hat nur keine Chance sich selbst über dessen Qualität zu informieren.

Im Effekt handelt es sich bei dem gezeigten Beispiel um eine verschleierte GOTO-Anweisung und spätestens seit Edsger Dijkstras Aufsatz „Goto considered harmful“ von 1968 sollten wir das nicht mehr verwenden.

Auch wenn es etwas umständlicher ist: wenn ich zwei oder mehr Rückgabewerte in Java brauche (meist den eigentlichen Wert und einen Status), dann ist die saubere Lösung, ein Objekt zurückzugeben, dass beide enthält. Der normale Programmfluss kann dann über den Status im Rückgabeobjekt gesteuert werden.

Exceptions sollten das sein, was ihr Name sagt: Ausnahmen.

Kommentar schreiben

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.