ProfilWeblogVokabelnSpielchenQuizBücherwurm gRayman.de

Kategorien

Style

Anzeigen

Alle Einnahmen von den folgenden Anzeigen werden an die Deutsche Krebshilfe gespendet.

RSS 0.91

15. Februar 2005

/sw
Exceptions: Checked oder Unchecked  

Seiten: << 1 2 3

Diese Lösung der „Exception Translation“ scheint elegant zu sein, und wird von vielen Kollegen bevorzugt. Diese zeigt auch die ->Diskussion bei heise.de.

Ich glaube nicht daran, dass es „die eine richtige“ Vorgehensweise bei der Softwareentwicklung gibt. Bestimmte Techniken werden allgemein befolgt, oder zumindest allgemein anerkannt und akzeptiert. Bestimmte sind umstritten - und das sicherlich nicht ohne Grund. Für bestimmte Projekte, bestimmte Entwickler oder Teams ist „Exception Translation“ das Richtige.

In folgenden Abschnitten beschreibe ich, warum ich damit meine Probleme habe und viel lieber die RuntimeExceptions verwende. (Nicht zu verwechseln mit Errors, ich verwende RuntimeExceptions als normale „erwartete“ Exceptions, nicht als Indikator dafür, dass sich das Programm in einem undefinierten oder unreparierbaren Zustand befindet oder dass ein Programmierfehler vorliegt - ich verwende sie mit der gleichen Semantik wie man Exceptions in z.B. C# verwenden würde).

Aus welcher Domäne kommt die Exception?

In unserem Beispiel haben wir die Exception CustomerException in der Domäne Kundenverwaltung vorgestellt. Diese domänenspezifische Exception kann grundsätzlich zwei Ursachen haben.

Einerseits kann die Ursache tatsächlich in der Domäne Kundenverwaltung liegen. Ein Beispiel für eine solche Ursache wäre, wenn man z.B. einen Kunden anlegen würde, der noch nicht 18 ist, und unsere Geschäftsbedingungen würden sowas verhindern. Die Methode createCustomer würde in diesem Falle eine CustomerException werfen.

Obwohl aber die Ursache in der Domäne Kundenverwaltung liegt, kann es passieren, dass der Fehler in einer anderen (technischer) Domäne festgestellt wird. Zum Beispiel kann eine Verletzung eines Primärschlüssel bedeuten, dass eine Kundennummer bereits vergeben wurde. In diesem Falle würde die Methode createCustomer die SQLException abfangen und sie in eine CustomerException „übersetzen“.

Anderseits aber kann das Problem tatsächlich in einer anderen Domäne liegen. Es kann sein, dass die Datenbank keinen Festplattenplatz mehr hat, oder dass sie einfach überlastet ist, oder dass der Datenbankserver gerade lichterloh brennt...

Wir haben zwar die Schicht, in der die Datenhalung geschieht, gekapselt und abstrahiert, aber Abstraktionen tendieren dazu, Lecks zu haben.

Zu diesem Thema hat vor einiger Zeit Joel Spolsky ->einen interessanten Artikel veröffentlicht.

Wenn der Datenbankserver brennt, kann man es kaum in als eine sinnvolle kundenverwaltungsspezische Exception ausdrücken. Wir können zwar die SQLException abfangen und eine nichtssagende CustomerException werfen (wie in unserem Beispiel). Damit hätten wir aber den Leck in der Abstraktion nicht behoben, wir hätten ihn nur verschleiert - um letztendlich dem Benutzer eine Fehlermeldung der Art „Ein unerwarteter Fehler ist aufgetreten. [OK] [Cancel] [Dankeschön]“ zu präsentieren.

Damit beraubten wir den Benutzer der Chance den tatsächlichen Fehler schnell zu identifizieren und ihn eventuell zu beheben und mit dem Feuerlöscher in den Serverraum zu rennen :-)

Wenn ein Fehler auftritt, den wir nicht einer Exception die tatsächlich in unserer Domäne liegt, zuordnen können, sollten wir diese Tatsache nicht verschleiern. Wenn dieser Fehler eine checked Exception ist, können bzw. müssen wir sie zwar in eine andere übersetzen, wir sollten die ursprüngliche Exception nicht verschleiern, sondern sie in unsere neue Exception einbetten.

Dies scheint auch Sun erkannt zu haben, und seit der Version 1.4 des JDK enthalten die Exceptions die Eigenschaft cause, die im Konstruktor gesetzt werden kann und genau diesem Zweck dient.

Unser angepasster Quelltext würde also so aussehen:

public class JdbcCustomerProvider implements CustomerProvider {
  public Collection<Customer> getCustomers() throws CustomerException {
    try {
      ResultSet rs = connection.executeQuery(...);
      ... usw. ...
    } catch (SQLException sqle) {
      throw new CustomerException("Datenbankproblem!", sqle);
    } finally {
      // JDBC-Objekte schließen 
      ... usw. ...
    }
  }
}

Soweit so gut. Wir werfen zwar eine CustomerException, es ist aber in Wirklichkeit keine. In Wirklichkeit ist es eine veschleierte SQLException, die wir aber nicht direkt durchlassen dürfen, weil es unser Kontrakt verbietet.

Wir haben also einen Weg gefunden, unseren Kontrakt zwar formal zu erfüllen, tatsächlich umgehen wir ihn aber. Nicht gerade ein Zeichen hoher Moral, aber was bleibt uns anderes übrig? Das System zwingt uns dazu zu mogeln. Wäre SQLException nicht checked, würden wir sie ganz offen durchlassen, wenn wir sie nicht in eine waschechte CustomerException umwandeln wüssten :-)

Den Kontrakt, der uns dazu verpflichtet keine SQLException zu werfen, gibt es aus zwei Gründen: Wir wollen unserem Aufrufer die Mühe, sich mit JDBC zu befassen, ersparen; und wir wollen die Quelltextabhängigkeiten des direkten Aufrufers zu JDBC vermeiden. (Schließlich kann es sein, dass er gar nicht mit JDCB zu tun haben wird, er kann nur den HttpCustomerProvider verwenden).

Die erste noble Absicht können wir aber, wie sich gezeigt hat, leider nicht erfüllen. Die Abstraktionen haben Lecks und wir werden gezwungen entweder die SQLException unbehandelt zu verschlingen und sie durch wenigsagende CustomerException zu ersetzen; oder wir beugen unseren Kontrakt und „schmuggeln“ die SQLException sowieso zu dem Aufrufer und wahrscheinlich noch weiter, um dem Benutzer den Stacktrace anzuzeigen.

Die zweite Absicht ist erfüllbar und sie ist auch sehr wichtig. In den Quelltexten der Kundenverwaltungsschicht sollten tatsächlich keine JDBC-Bezüge stehen, wenn sie nicht unvermeidbar sind. Diese Absicht liese sich aber mit viel weniger Tipparbeit erledigen, wenn wir die SQLException unchecked machen könnten.

OK, bei SQLException bleibt uns nichts anderes übrig, aber wenn wir eigene Exceptions definieren, spricht meiner Meinung nach, kaum etwas dafür, sie als checked Exceptions zu deklarieren.

Und somit kommen wir zu der fünften Möglichkeit, wie eine Methode mit einer checked Exception umgehen kann:

Die checked Exception in eine unchecked einbetten

Um bei unserem Beispiel zu bleiben: Wäre die SQLException unchecked, würde ich die Verletzung der Primärschlüssel in der Tabelle CUSTOMERS immer noch in einen CustomerException mit der Meldung „Kundennummer bereits vergeben.“ umwandeln, denn es würde sich tatsächlich um eine Fehlermeldung aus der Domäne Kundenverwaltung handeln. (Vorausgesetzt, diese Funktionalität wäre so spezifiziert. Man sollte ja nicht das Geld des Auftraggebers verschwenden, und unspezifizierte Funktionalität implementieren.) Andere SQLExceptions würde ich aber durchlaufen lassen.

Da die SQLExceptions aber nicht unchecked ist, bleibt mir in meinen Quelltexten nichts anderes übrig, als sie abzufangen und sie in eine RuntimeException einzubetten.

Dabei kann es sich je nach Bedarf der Anwendung um eine unspezifische SoftenedCheckedException oder um eine spezifische SoftendedSQLException handeln.

Fazit

Die checked Exceptions in Java ermöglichen das formelle Deklarieren eines Kontraktes zwischen dem Aufrufer und der Methode, in dem sich die Methode verpflichtet bestimmte Exceptions nicht zu werfen. Der Vorteil für den Aufrufer ist, dass er sich um solche checked Exceptions nicht kümmern muss.

Allerdings wird der Kontrakt in vielen Fällen nur formell eingehalten und die ursprünglichen checked Exceptions werden trotzdem geworfen, allerdings eingebettet in andere checked oder unchecked Exceptions. Die Verpflichtung des Kontraktes wird also häufig umgangen.

Der Aufrufer kann manchmal auf den Vorteil, den er aus einem solchen Kontrakt ziehen könnte, verzichten, weil er die Exception durchaus behandeln könnte, indem er einfach dem Benutzer eine Fehlermeldung anzeigt.

Für mich bedeutet es also, dass die checked Exceptions mir als Entwickler meistens mehr Arbeit als Nutzen bringen und deswegen vermeide ich deren Verwendung.

Da das Thema aber „umstritten“ ist, gehe ich davon aus, dass andere Entwickler andere Erfahrungen bezüglich des Kosten/Nutzen-Verhältnissen bezüglich checked Exceptions haben. Falls jemand diesen Artikel zu Ende gelesen hat, und andere Erfahrungen mit checked Exceptions gemacht hat, werde ich dankbar, wenn er sie mit mir teilt. :-)

3 Kommentar(e) permalink

      Impressum:  Gregor Raýman · Auf dem Kirchbüchel 3 · D-53127 Bonn Kontakt: webmaster@grayman.de         Valid HTML 4.01! Valid HTML 4.01! .