Exceptions Als Architektur Bestandteil
StartSeite | Neues | TestSeite | ForumSeite | Teilnehmer | Kategorien | Index | Hilfe | Einstellungen | Ändern
Hallo zusammen,
die Diskussionen zum Thema Exceptions auf diesem Wiki finde ich super-interessant.
Ich möchte mal einen Beitrag leisten, der die Sache aus Sicht meines Spezialgebiets,
der Software-Architektur, beleuchtet.
-- MatthiasBohlen
Dafür muss ich zunächst ein paar Sachen postulieren, die
außerhalb des Scopes dieses Problems liegen (sonst läuft die Sache aus dem Ruder
und wird nicht klar):
- Das Problem wird durch Java bloßgelegt, da dort Exceptions (meist) deklariert werden müssen und vom Compiler geprüft werden. Deshalb zeige ich das Problem in Java.
- Exceptions sind gut, wenn man sie mit Disziplin verwendet.
- Das Problem ist, den Überblick zu behalten.
- Verlorener Überblick führt automatisch zu Disziplinlosigkeit.
OK, also worum geht es mir wirklich?
Exceptions bei faulen Entwicklern | |
In Java hat man die Möglichkeit, Exceptions entweder sofort zu behandeln
(try...catch einfach sofort hinschreiben) oder aber durchzureichen an den Aufrufer
der aktuellen Methode. Dieser Aufrufer könnte (faulerweise) die Exception wieder
durchreichen und so weiter, bis es das arme Schwein am Ende trifft (meistens den
GUI-Entwickler!), der dann alle Exceptions fangen und wüste Meldungen ausgeben
muss, mit denen der User nichts anzufangen weiß.
Richtig? Falsch! Das Problem tritt viel früher auf!
Es passiert nämlich unterwegs etwas, was das ganze Entwicklungsteam aufregen kann.
Nehmen wir mal ein Beispiel: An sich hätten wir gern folgendes:
| Person lesePersonAusDatenbank (String name, String vorname)
throws PersonNichtGefundenException
{
// Person in Datenbank suchen
// gefundene Daten in Person-Objekt verpacken
// und mit "return" zurückgeben
// oder aber:
throw new PersonNichtGefundenException
("Person '" + vorname + " " + name + "' nicht gefunden.");
} |
|
|
Bei Datenbankoperationen kann nun dummerweise noch etwas anderes
passieren als ein simples "nicht gefunden": die Connection könnte
z. B. abreißen, weil jemand den DB-Server runterfährt oder das Ethernet
mal grade einen kleinen Aussetzer hat.
Für diese Fälle wirft die JDBC-API eine java.sql.SQLException
mit einer entsprechenden Meldung darin.
Was also macht ein fauler Entwickler mit dieser Exception? Ganz
einfach, er reicht sie durch:
| Person lesePersonAusDatenbank (String name, String vorname)
throws PersonNichtGefundenException, java.sql.SQLException
{
Connection con = ConnectionPool.getConnection("...");
Statement stmt = /* irgendwie statement bauen */ ;
ResultSet rs = /* irgendwie das stmt ausführen */ ;
while (rs.next())
{
Date gebDatum = rs.getDate("GEBDATUM");
String adresse = rs.getString("ADRESSE");
return new Person (name, vorname, gebDatum, adresse);
}
throw new PersonNichtGefundenException
("Person '" + vorname + " " + name + "' nicht gefunden.");
} |
|
|
Wir sehen in dem Beispiel schon das Problem: Um "faul" zu sein und eine Exception
durchzureichen, muss ich die throws-Klausel meiner Methode um ein Element
erweitern, also in diesem Falle um java.sql.SQLException. Damit zwinge ich nun
alle im Team, die lesePersonAusDatenbank() aufrufen wollen, sich mit dieser
dummen Exception auseinanderzusetzen oder sie ebenfalls in ihre throws-Klauseln
aufzunehmen. Solche Probleme greifen also bei faulen Entwicklungsteams rasch
um sich.
Denkt man daran, dass es noch viel mehr Gründe gibt, Exceptions zu bekommen,
so können solche throws-Klauseln sehr schnell sehr lang werden.
Abhilfe durch Architekturkonvention | |
Wir greifen am besten architektonisch ein und vereinbaren im Team eine
Konvention, wie mit Exceptions umgegangen wird. Eine solche Konvention
könnte so lauten:
- Pro Komponente gibt es eine Exception-Basisklasse. Wir nennen sie so wie die Komponente heißt und setzen "Exception" dahinter. Beispiel: Die Komponente "Kunden" bekommt eine "KundenException".
- Alle Exceptions, die in dieser Komponente geworfen werden, haben von der <Komponente>Exception abzuleiten.
- Die throws-Klauseln aller Methoden aller Klassen, die in der Schnittstelle einer Komponente vorkommen, lauten einfach "throws <Komponente>Exception".
- Das Werfen anderer Exceptions über die Komponentenschnittstelle hinweg ist verboten.
Jetzt werden einige schon zum Schrei ansetzen, aber wartet noch eine
Sekunde! :-)
Es braucht nun noch eine Festlegung, was mit Exceptions ist, die ich
kassiere und per definitionem nicht über die Schnittstelle weiterreichen
darf, also zum Beispiel die oben genannte SQLException. Die Antwort ist:
ich muss überlegen, was diese Exception im Sinne meiner Komponente, die
ich gerade schreibe, bedeutet. Als nächstes muss ich dieser Bedeutung
dann durch eine Exception, die aus meiner Komponente kommt,
Nachdruck verleihen.
Obiges Beispiel würde dann wie folgt gelöst:
Bei einem technischen Fehler in einer solchen Operation hat der User
eigentlich nur noch eine Chance: Den Admin benachrichtigen, der schaut
ins Log und startet evtl. den DB-Server neu oder steckt den Ethernet-Stecker
wieder rein. Etwas anderes geht schlecht. Also:
Erst Voraussetzungen schaffen: Die <Komponente>Exception definieren...
| class PersonenDatenbankException extends Exception
{
...
} |
|
|
und:
| class PersonNichtGefundenException
extends PersonenDatenbankException
{
...
} |
|
|
... dann zuschlagen:
| Person lesePersonAusDatenbank (String name, String vorname)
throws PersonenDatenbankException
{
try
{
Connection con = ConnectionPool.getConnection("...");
Statement stmt = /* irgendwie statement bauen */ ;
ResultSet rs = /* irgendwie das stmt ausführen */ ;
while (rs.next())
{
Date gebDatum = rs.getDate("GEBDATUM");
String adresse = rs.getString("ADRESSE");
return new Person (name, vorname, gebDatum, adresse);
}
throw new PersonNichtGefundenException
("Person '" + vorname + " " + name + "' nicht gefunden.");
}
catch (SQLException se)
{
FehlerLog.loggeTechnischenFehler(2137, "lesePersonAusDatenbank", se);
throw new PersonenDatenbankException
("Technischer Fehler Nr. 2137 - bitte Administrator benachrichtigen", se);
}
} |
|
|
Dann: Eintrag ins Betriebshandbuch dieser Software machen. Kapitel
"Administration der Anwendung", Abschnitt "Fehlerprotokoll",
Unterabschnitt "Bedeutung der Fehlernummern" ... na, Ihr wißt
schon, was ich meine.
Was hat's gebracht? | |
- Der Aufrufer von lesePersonAusDatenbank kann nun entscheiden, ob er einfach nur ganz allgemein Exceptions des Basistyps PersonenDatenbankException fangen will oder auf die spezielle Situation PersonNichtGefundenException näher eingehen will, indem er die Person zum Beispiel anlegt, wenn sie nicht da ist.
- Wenn eine neue Exception dazukommt, bleibt die Signatur der Methode stabil. Der Code des Aufrufers braucht nicht geändert werden.
Das Konzept ist erweiterbar:
- Eine andere Komponente (z. B. Vertrag) könnte auch lesePersonAusDatenbank aufrufen und sich dabei überlegen, was eine nicht gefundene Person für das Anlegen eines Vertrages bedeutet.
- Also könnte eine Workflow-Methode, die dabei ist einen Vertrag anzulegen, auf die PersonNichtGefundenException reagieren und daraus eine VertragsDatenNichtVollstaendigException machen, die eine Subklasse von VertragException ist.
Diskussion | |
Bitte beim Diskutieren nicht das Beispiel angreifen, sondern das Prinzip!
Was sagt Ihr dazu?
-- MatthiasBohlen
Jetzt werden einige schon zum Schrei ansetzen, aber wartet noch eine Sekunde! :-):
Da ich EinfachNurEinFaulerProgrammierer? bin, gefällt mir das Prinzip des Exception durchreichens. Daher würde ich versuchen das arme Schwein, das es am Ende trifft (meistens den GUI-Entwickler!) besser zu unterstützen. Zum Beispiel durch eine getrennte Einheit zur Fehlerbehandlung, die dann die entsprechenden Log Einträge vornimmt, anstatt das über alle Komponenten zu verteilen.
- Meine Aussage oben war vielleicht missverständlich. Ich wollte eigentlich damit sagen, dass das Problem des "armen Schweins am Ende" nichts ist im Vergleich dazu, dass die throws-Klauseln im gesamten System dazu tendieren, alle möglichen und unmöglchen Exceptions zu enthalten. Mein Beitrag hat zum Ziel, letzteres in den Griff zu bekommen, dann hat es der GUI-Entwickler auch leichter. -- mb
- Aja! Also meinst Du, Exceptions mehrerer Komponenten zu verwalten ist einfacher als die Exceptions nach Ursachen gruppiert? Ich frage, da ich mit so großen Projekten keine Erfahrung habe.
Wenn eine neue Exception dazukommt, bleibt die Signatur der Methode stabil. => Der Code des Aufrufers braucht nicht geändert werden.
Wenn eine neue Fehlersituation auftritt, muß der Aufrufer vielleicht geändert werden um auf diese Situation zu reagieren, das ändert sich nicht, wenn die Exception jetzt so oder so ist. Wird die Signatur verändert, beschwrt sich wenigstens der Kompiler. Vielleicht wäre Der Code des Aufrufers muß nicht unbedingt geändert werden. klarer?
Abschließend, das typische KO-Argument bei solchen Diskussionen: Ein wirklich fauler Programmierer würde sowieso nur 'Personen|Datenbank|Exception|s' werfen und 'Exceptions' fangen und damit die gesamte Architektur untergraben.
- Na klar! -- mb
-- DavidSchmitt
Siehe auch ExceptionsInSpracheLava
KategorieException
StartSeite | Neues | TestSeite | ForumSeite | Teilnehmer | Kategorien | Index | Hilfe | Einstellungen | Ändern
Text dieser Seite ändern (zuletzt geändert: 21. Januar 2004 19:59 (diff))