Du verwendest einen veralteten Browser. Es ist möglich, dass diese oder andere Websites nicht korrekt angezeigt werden. Du solltest ein Upgrade durchführen oder ein alternativer Browser verwenden.
Ich arbeite schon sehr lange mit prozeduralen Programmiersprachen und frische nun mit Java meine OOP-Kenntnisse wieder etwas auf.
Dabei ist mir eine Frage gekommen: In welchen Fällen nutzt man try/catch-Blöcke?
Habe mir manche Code-Schnipsel angeschaut, an manchen Stellen wird try/catch genutzt, und manchmal nicht. Rein theoretisch kann zu ziemlich überall ein Fehler auftreten. So müsste im Prinzip alles per try/catch abgefangen werden. Wird es aber nicht.
Gibt es ein Muster oder eine Regel für die Nutzung von try/catch? Wenn nicht, wann ist es sinnvoll es zu nutzen, und wann nicht?
Man benutzt es immer dann, wenn man eine Funktion aufruft, die eine Exception werfen koennte, auf die die aufrufende Funktion reagieren kann. Die Alternative ist weiterwerfen per throws. Das gilt halt vor allem fuer checked Exceptions. Da muss eine der beiden Moeglichkeiten verwendet werden, sonst kompilierts nicht.
Im Prinzip solltest du try-catch es immer dann nutzen, wenn du etwas sinnvolles tun kannst. Z.B. wenn du eine IOException bekommen kannst, könntest du sie dort abfangen, wo du den Nutzer darüber informieren kannst. Anderes Beispiel: Bibliotheken reichen Fehler meist weiter, weil sie selber gar nicht wissen, was eine "sinnvolle" Reaktion wäre.
Oft wird auch eine Exception abgefangen und im catch-Block dafür eine andere ausgelöst, also die Original-Exception gewissermaßen in eine andere "übersetzt", die im Kontext des aktuellen Programms "sinnvoller" ist (z.B. könnte man eine Exception vom SAX-Parser in eine IOException umwandeln, um sie im Programm einheitlich mit anderen Lese-Problemen zu behandeln).
Mittlere Antwort: Wenn man die unerwüschten Folgen einer Ausnahmesituation (Exception) behandeln möchte. Vielleicht ein Beispiel - das etwas "konstruiert" und pseudocodelastig ist, aber vielleicht die Grundidee verdeutlicht:
- Der Benutzer kann einen Dateinamen angeben
- Die Datei wird geöffnet
- Der Benutzer gibt eine Zahl ein
- Die Zahl wird in die Datei geschrieben
Im Pseudocode also sowas wie
Code:
void writeUserSelectedNumberToUserSelectedFile()
{
String fileName = benutzerGibtDateinamenEin();
FileHandle fileHandle = openFile(fileName);
int number = benutzerGibtZahlEin();
fileHandle.write(number);
}
In jeder dieser Zeilen kann irgendwas schiefgehen:
- Der Dateiname kann ungültig sein (sowas wie "***:??:**") - das könnte sowas wie eine "InvalidFileNameException" (oder so) werfen
- Die Datei existiert vielleicht nicht - das wäre eine "IOException"
- Die Eingabe kann keine gültige Zahl sein (sowas wie "12XXHallo456") - also eine "NumberFormatException"
- Die Datei kann schreibgeschützt sein - nochmal "IOException"
Dann muss man sich überlegen, was in den jeweiligen Ausnahmefälllen passieren soll. Das einfachste ist, die Exceptions einfach weiterzureichen
Damit bürdet man demjenigen, der diese Methode benutzen will, die Last auf, sich um jeden Fall einzeln zu kümmern. In diesem Fall wäre das vmtl. ziemlich blöd und unpraktisch. Derjenige, der diese Methode aufruft, will nicht wissen, WAS genau schiefgelaufen ist. Er kann eh nicht wirklich was dran ändern.
Eine Alternative wäre, eine neue Exception-Art zu definieren, wie z.B. eine "StupidUserException", die man "immer" wirft, wenn "irgendwas" schiefgeht:
Code:
void writeUserSelectedNumberToUserSelectedFile() throws StupidUserException
{
try
{
String fileName = benutzerGibtDateinamenEin();
FileHandle fileHandle = openFile(fileName);
int number = benutzerGibtZahlEin();
fileHandle.write(number);
}
catch (InvalidFileNameException e)
{
throw new StupidUserException("Ungültiger Dateiname "+e.getMessage);
}
catch (IOException e)
{
throw new StupidUserException("Datei nicht gefunden oder schreibgeschützt "+e.getMessage);
}
catch (NumberFormatException e)
{
throw new StupidUserException("Ungültige Zahleneingabe "+e.getMessage);
}
}
Derjenige, der jetzt die Methode aufruft, muss sich nurnoch um die "StupidUserException" kümmern. WENN so eine Exception kommt, kann er z.B. dem Benutzer in einer Dialogbox die Meldung zeigen: "Oh, bist du blöööhht" - und vielleicht noch den String, den man sich mit stupidUserExcetpion.getMessage() holen kann. Derjenige, der die Methode aufruft, "Behandelt" die Ausnahmesituation dann "so gut er kann": Er sagt dem Benutzer, dass er einen Fehler gemacht hat, und worin dieser Fehler bestand.
Die letzte Möglichkeit wäre, alle Exceptions selbst zu behandeln
Wobei (sinngemäß) auch jeder der try-catch-Blöcke in einer Schleife stehen könnte, die so lange wiederholt, bis der Fehler NICHT mehr auftritt, oder die Methode einen boolean zurückgeben könnte, der anzeigt, ob die Methode erfolgreich abgearbeitet wurde oder nichts, usw....
Am sinnvollsten wäre in diesem (konstruierten!!!) Beispiel vermutlich die zweite Lösung: Man wirft eine Exception, die mehrere andere Exceptions "zusammenfasst". In Einzelfall kommt es eben immer darauf an, WER die Kompetenz hat, einen aufgetretenen Fehler zu behandeln. Und wer auch immer diesen Fehler behandelt: Er tut es in einem try-catch-Block.
Dazu kommen noch einige Feinheiten - eine Websuche nach
"Checked Exceptions" "Unchecked Exceptions"
liefert da ggf. noch mehr infos.
Nun, ich wuerde Fehler nicht "erwarten", ich rechne mit ihnen. Oft gibst du ja das Ruder aus der Hand, z. B. wenn du Dateien schreibst oder ueber das Netz verschickst. Da kann es Millionen Moeglichkeiten geben, dass etwas schief geht: Platte voll, Plattencontroller stirbt, OS spinnt, Router stirbt, Putzfrau zieht Kabel vom Switch, ... Man hofft es nicht, aber man kann es auch nicht beeinflussen (in diesem Sinne also "Ja" auf deine Frage). Durch die entsprechende Exception wirst du darauf hingewiesen und kannst reagieren (und wenn es nur der Versuch ist, einen Log-Eintrag zu schreiben und dann das Programm zu beenden).
Ich habe für meine kleine Applikation einen DataAbstractionLayer geschrieben. Dieser ist für die eigentliche Kommunikation mit der Datenbank zuständig. Ziel war es eine Klasse zu erstellen, die ich auch für zukünftige Projekte nutzen könnte.
Die Applikation kennt nur meinen DataAbstractionLayer und seine Methoden. Nur der DataAbstractionLayer kennt die Methoden, die die eigentliche Datenbank-API zur Verfügung stellt.
Der DataAbstractionLayer enthält z.B. eine Methode, über die sich die Applikation mit der Datenbank verbinden kann. Die Methode der Datenbank kann eine eigene Exception werfen, wenn z.B. die Datenbank gesperrt ist.
Bisher habe ich das direkt in der Methode des DataAbstractionLayers abgefangen und einen Fehlercode zurück gegeben. Nun möchte ich das aber so machen, dass kein Fehlercode zurück gegeben wird, sondern die Exception an den Aufruffer weiter geben. Das bedeutet aber, dass der Aufrufer diese Art von Exception kennen müsste. Damit müsste ich die Datenbank-API der gesamten Applikation bekannt machen, was ich ja nicht will.
Zur Veranschaulichung die aktuelle Methode:
Code:
public ObjectContainer connectToDataBase(String dbname) {
setErrMsg("");//clear the error-message
if (m_databaseName.isEmpty()) setDbName(dbname);
//proof if databasename is empty
if (m_databaseName.isEmpty()) {
setErrMsg("No Database specified.");
System.out.println(getErrMsg());
return null;
}
ObjectContainer container = null;
try {
//open or create a databasefile
container = Db4o.openFile(m_databaseName);
//set the container
setObjectContainer(container);
//set the update-depth
if(m_updateDepth > 0) container.ext().configure().updateDepth(m_updateDepth);
} catch (Db4oException ex) {
printException(ex);
return null;
}
return container;
}
Db4oException ist eine solche Exception. Wie kann ich das elegant lösen?
Wenn Deine Exception keine "checked exception", sonderm eine "runtime exception" ist (wenn sie also nicht von java.lang.Exception, sondern von java.lang.RuntimeException erbt), dann muss der Verwender sie nicht unbedingt kennen, um die Methode zu benutzen.
public ObjectContainer connectToDataBase(String dbname) throws Db4oException {
...
}
Wie müsste konkret der Try-catch-Block des Aufrufers aussehen (wie gesagt, der Aufrufer kennt Db4oException nicht)?
Das habe ich aus der API:
com.db4o.ext
Class Db4oException
java.lang.Object
extended by java.lang.Throwable
extended by java.lang.Exception
extended by java.lang.RuntimeException
extended by com.db4o.foundation.ChainedRuntimeException
extended by com.db4o.ext.Db4oException
All Implemented Interfaces:
java.io.Serializable com.db4o.ext
Class Db4oException
java.lang.Object
extended by java.lang.Throwable
extended by java.lang.Exception
extended by java.lang.RuntimeException
extended by com.db4o.foundation.ChainedRuntimeException
extended by com.db4o.ext.Db4oException
Die Exception erbt von RuntimeException, muss also nicht gefangen werden. Es gibt verschiedene Ansichten, wie man mit Exceptions umzugehen hat und eine davon sagt, dass man RuntimeExceptions auch nicht fangen sollte (vertrete ich zu einem gewissen Teil auch, aber nicht komplett). Wenn du diese hier dennoch fangen willst, kann jeder Typ ins catch, der auch in der Hierarchie ueber der Db4oException steht.
Wenn du die Exceptions, die dein DAL wirft von selbigem trennen willst, sodass der Aufrufer die in diesem Fall Db4oException nicht zu Gesicht bekommt, dann bietet es sich an, eine eigene Exception zu entwerfen. Das DAL faengt die Db4oException und wirft die eigene stattdessen. Da koenntest du den Fehlercode reinschreiben. Die Frage ist nur, wie sinnvoll das ist. Klar kann man argumentieren, dass man ueber einen Stacktrace Informationen leaken kann. Andererseits erschwert es das Debugging, weil im Fehlerfall an einem bestimmten Punkt halt schluss ist. Irgendwo fliegt eine Exception aus dem nirgendwo. Laesst sich natuerlich auch eingrenzen, wenn es entsprechend programmiert wird.
public void connectToDataBase(String dbname) throws Db4oException{
setDbName(dbname);
ObjectContainer container = null;
//open or create a databasefile
container = Db4o.openFile(m_databaseName);
//set the container
setObjectContainer(container);
//set the update-depth
if(m_updateDepth > 0) container.ext().configure().updateDepth(m_updateDepth);
}
Und hier der Aufrufer:
Code:
private void formWindowOpened(java.awt.event.WindowEvent evt) {
//open database
m_dal = new Db4oDataAbstractionLayer();
if(m_dal == null) return;
try{
m_dal.connectToDataBase(DB_NAME);
} catch (RuntimeException ex) {
JOptionPane.showMessageDialog(this,ex.getMessage(),"Fehler",JOptionPane.OK_CANCEL_OPTION);
this.dispose();
return;
}
setFirstColumnInvisible(jXTableRecipe);
fillRecipeMainList();//fill list from database
changeSelectionTableRecipe(0);//select the frist row
}
Hab dann testweise einen leeren Datenbanknamen übergeben, und bekam tatsächlich eine entsprechende Fehlermeldung. Das reicht ja so eigentlich, denke ich mal.
catch (RuntimeException ex)
ist eigentlich zu allgemein. Da werden auch Sachen gefangen, die nicht gefangen werden sollten. Der Aufrufer sollte die Db4oException kennen, oder du solltest in der connectToDataBase sowas machen wie