# Wann try.catch nutzen?



## GambaJo (31. Aug 2008)

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?


----------



## musiKk (31. Aug 2008)

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.


----------



## Landei (31. Aug 2008)

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).


----------



## Marco13 (31. Aug 2008)

Kurze Anwort: In Ausnahmefällen :wink:

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

```
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

```
void writeUserSelectedNumberToUserSelectedFile() throws InvalidFileNameException, NumberFormatException, IOException
{
    String fileName = benutzerGibtDateinamenEin();
    FileHandle fileHandle = openFile(fileName);
    int number = benutzerGibtZahlEin();
    fileHandle.write(number);
}
```
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:

```
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

```
void writeUserSelectedNumberToUserSelectedFile() /* throws GARNICHTS */
{
    String fileName;
    FileHandle fileHandle;
    int number;

    try
    {
        fileName = benutzerGibtDateinamenEin();
    } 
    catch (InvalidFileNameException e)
    {
        print("Ungültiger Dateiname "+e.getMessage);
        return;
    }

    try
    {
        fileHandle = openFile(fileName);
    } 
    catch (IOException e)
    {
        print("Datei nicht gefunden "+e.getMessage);
        return;
    }

    try
    {
        number = benutzerGibtZahlEin();
    } 
    catch (NumberFormatException e)
    {
        print("Ungültige Zahleneingabe "+e.getMessage);
        return;
    }

    try
    {
        fileHandle.write(number);
    } 
    catch (IOException e)
    {
        print("Datei schreibgeschützt "+e.getMessage);
        return;
    }

}
```
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.


----------



## GambaJo (31. Aug 2008)

Auf die Idee Usereingaben per exceptions zu prüfen, wäre ich nicht gekommen. Muss mir da noch ein paar Gedanken zu machen.

Das mit den Dateien habe ich auch schon gelesen.

Verstehe ich das richtig, dass man try/catch nur da nutzt, wo es zu *erwarteten* Komplikationen kommen kann, man aber im Code keinen Einfluss drauf hat?


----------



## musiKk (1. Sep 2008)

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).


----------



## GambaJo (3. Sep 2008)

Ich hab da noch eine Frage zu "throws".

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:


```
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?


----------



## Murray (3. Sep 2008)

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.


----------



## GambaJo (3. Sep 2008)

Angenommen die Methode würde wie folgt aussehen:


```
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

All Implemented Interfaces:
    java.io.Serializable


----------



## musiKk (3. Sep 2008)

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.


----------



## GambaJo (3. Sep 2008)

Ich hab das jetzt mal so umgeschrieben:

Methode aus dem DAL:


```
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:


```
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.


----------



## Marco13 (4. Sep 2008)

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

```
void connectToDataBase throws EineExceptionDieDerAufruferKennenDarf
{
    try
    {
         ....
    } 
    catch (Db4oException e)
    {
        throw new EineExceptionDieDerAufruferKennenDarf(e.getMessage());
    }
}
```


----------

