Ist die Verwendung verschachtelter Try-Catch-Blöcke ein Anti-Pattern?

95

Ist das ein Gegenmuster? Es ist eine akzeptable Praxis?

    try {
        //do something
    } catch (Exception e) { 
        try {
            //do something in the same line, but being less ambitious
        } catch (Exception ex) {
            try {
                //Do the minimum acceptable
            } catch (Exception e1) {
                //More try catches?
            }
        }
    }
Herr Smith
quelle
Können Sie uns dafür ein Argument nennen? Warum können Sie nicht jeden Fehlertyp im Top-Level-Catch behandeln?
Morons
2
Ich habe diese Art von Code kürzlich gesehen, der von unerfahrenen Programmierern ausgeführt wurde, die nicht wirklich wissen, was sie in den try-Blöcken aufrufen, und die sich nicht die Mühe machen möchten, den Code zu testen. In dem Codebeispiel, das ich gesehen habe, war es derselbe Vorgang, der jedoch jedes Mal mit Fallback-Parametern ausgeführt wurde.
Mister Smith
@LokiAstari -Ihr Beispiel ist ein Versuch im Abschnitt "Schließlich". Wo es keinen Fang gibt. Dies ist eine in den Try-Bereich verschachtelte. Es ist anders.
Morons
4
Warum sollte es ein Anti-Pattern sein?
2
+1 für "Weitere Versuchsfänge?"
JoelFan

Antworten:

85

Dies ist manchmal unvermeidlich, insbesondere wenn Ihr Wiederherstellungscode möglicherweise eine Ausnahme auslöst.

Nicht schön, aber manchmal gibt es keine Alternativen.

Oded
quelle
17
@ MisterSmith - Nicht immer.
Oded
4
Ja, das ist eine Art von dem, woran ich versucht habe. Natürlich gibt es einen Punkt in Ihren geschachtelten try / catch-Anweisungen, an dem Sie nur sagen müssen, dass genug genug ist. Ich habe mich für das Verschachteln und nicht für sequenzielle Versuche / Fänge ausgesprochen und gesagt, dass es Situationen gibt, in denen der Code nur ausgeführt werden soll, wenn der erste Versuch fehlschlägt.
AndrewC
5
@MisterSmith: Ich würde geschachtelte Try-Catches gegenüber sequenziellen Try-Catches bevorzugen, die teilweise mit Flag-Variablen gesteuert werden (sofern sie funktional identisch sind).
FrustratedWithFormsDesigner
31
try {transaction.commit (); } catch {try {transaction.rollback (); } catch {seriouslogging ()} notsoseriouslogging (); } ist ein Beispiel für einen notwendigen verschachtelten Versuchsfang
Thanos Papathanasiou,
3
Extrahieren Sie zumindest den catch-Block in eine Methode, Jungs! Machen wir das wenigstens lesbar.
Herr Cochese
43

Ich denke nicht, dass es ein Antimuster ist, nur weit verbreitet missbraucht.

Die meisten geschachtelten Versuchsfänge sind in der Tat vermeidbar und uns Hölle hässlich, normalerweise das Produkt von Junior-Entwicklern.

Aber manchmal kann man nicht anders.

try{
     transaction.commit();
   }catch{
     logerror();
     try{
         transaction.rollback(); 
        }catch{
         seriousLogging();
        }
   }

Außerdem benötigen Sie irgendwo einen zusätzlichen Bool, um den fehlgeschlagenen Rollback anzuzeigen ...

Vielen Dank an Papathanasiou
quelle
19

Die Logik ist in Ordnung - es kann in manchen Situationen durchaus sinnvoll sein, einen Fallback-Ansatz zu versuchen, bei dem es zu außergewöhnlichen Ereignissen kommen kann. Daher ist dieses Muster so gut wie unvermeidlich.

Ich würde jedoch Folgendes vorschlagen, um den Code zu verbessern:

  • Refaktoriere den inneren Versuch ... fange Blöcke heraus in separate Funktionen, zB attemptFallbackMethodund attemptMinimalRecovery.
  • Machen Sie genauere Angaben zu den einzelnen Ausnahmetypen, die abgefangen werden. Erwarten Sie wirklich eine Exception-Unterklasse und wenn ja, möchten Sie sie wirklich alle gleich behandeln?
  • Überlegen Sie, ob ein finallyBlock sinnvoller sein könnte - dies gilt normalerweise für alles, was sich wie "Ressourcenbereinigungscode" anfühlt.
mikera
quelle
14

Es ist in Ordnung. Ein zu berücksichtigendes Refactoring besteht darin, den Code in eine eigene Methode zu verschieben und frühe Exits für den Erfolg zu verwenden, damit Sie die verschiedenen Versuche schreiben können, etwas auf derselben Ebene zu tun:

try {
    // do something
    return;
} catch (Exception e) {
    // fall through; you probably want to log this
}
try {
    // do something in the same line, but being less ambitious
    return;
} catch (Exception e) {
    // fall through again; you probably want to log this too
}
try {
    // Do the minimum acceptable
    return;
} catch (Exception e) {
    // if you don't have any more fallbacks, then throw an exception here
}
//More try catches?

Sobald Sie es so ausgebrochen haben, können Sie darüber nachdenken, es in ein Strategiemuster einzuwickeln.

interface DoSomethingStrategy {
    public void doSomething() throws Exception;
}

class NormalStrategy implements DoSomethingStrategy {
    public void doSomething() throws Exception {
        // do something
    }
}

class FirstFallbackStrategy implements DoSomethingStrategy {
    public void doSomething() throws Exception {
        // do something in the same line, but being less ambitious
    }
}

class TrySeveralThingsStrategy implements DoSomethingStrategy {
    private DoSomethingStrategy[] strategies = {new NormalStrategy(), new FirstFallbackStrategy()};
    public void doSomething() throws Exception {
        for (DoSomethingStrategy strategy: strategies) {
            try {
                strategy.doSomething();
                return;
            }
            catch (Exception e) {
                // log and continue
            }
        }
        throw new Exception("all strategies failed");
    }
}

Dann verwenden Sie einfach die TrySeveralThingsStrategy, die eine Art zusammengesetzte Strategie ist (zwei Muster zum Preis von einem!).

Eine große Einschränkung: Tun Sie dies nur, wenn Ihre Strategien selbst ausreichend komplex sind oder Sie sie flexibel einsetzen möchten. Andernfalls wird ein paar Zeilen einfachen Codes mit einem großen Haufen unnötiger Objektorientierung überspült.

Tom Anderson
quelle
7

Ich denke nicht, dass es automatisch ein Anti-Pattern ist, aber ich würde es vermeiden, wenn ich einen einfacheren und saubereren Weg finden könnte, das Gleiche zu tun. Wenn die Programmiersprache, in der Sie arbeiten, über ein finallyKonstrukt verfügt, kann dies in einigen Fällen hilfreich sein.

FrustratedWithFormsDesigner
quelle
6

Kein Anti-Pattern an sich, sondern ein Code-Pattern, das Ihnen sagt, dass Sie umgestalten müssen.

Und es ist ziemlich einfach, Sie müssen nur eine Faustregel kennen, die in derselben Methode nicht mehr als einen try-Block schreibt. Wenn Sie wissen, wie man zusammengehörigen Code schreibt, müssen Sie in der Regel nur jeden try-Block mit seinen catch-Blöcken kopieren und einfügen und ihn in eine neue Methode einfügen. Ersetzen Sie dann den ursprünglichen Block durch einen Aufruf dieser Methode.

Diese Faustregel basiert auf dem Vorschlag von Robert C. Martin aus seinem Buch 'Clean Code':

Wenn das Schlüsselwort 'try' in einer Funktion existiert, sollte es das allererste Wort in der Funktion sein und es sollte nichts nach den catch / finally-Blöcken stehen.

Ein kurzes Beispiel zu "Pseudo-Java". Angenommen, wir haben so etwas:

try {
    FileInputStream is = new FileInputStream(PATH_ONE);
    String configData = InputStreamUtils.readString(is);
    return configData;
} catch (FileNotFoundException e) {
    try {
        FileInputStream is = new FileInputStream(PATH_TWO);
        String configData = InputStreamUtils.readString(is);
        return configData;
    } catch (FileNotFoundException e) {
        try {
            FileInputStream is = new FileInputStream(PATH_THREE);
            String configData = InputStreamUtils.readString(is);
            return configData;
        } catch (FileNotFoundException e) {
            return null;
        }
    }
}

Dann könnten wir jeden try catch refaktorieren, und in diesem Fall versucht jeder try catch-Block dasselbe, aber an verschiedenen Stellen (wie praktisch: D) müssen wir nur einen der try catch-Blöcke kopieren, einfügen und eine Methode daraus erstellen .

public String loadConfigFile(String path) {
    try {
        FileInputStream is = new FileInputStream(path);
        String configData = InputStreamUtils.readString(is);
        return configData;
    } catch (FileNotFoundException e) {
        return null;
    }
}

Jetzt verwenden wir dies mit dem gleichen Zweck wie zuvor.

String[] paths = new String[] {PATH_ONE, PATH_TWO, PATH_THREE};

String configData;
for(String path : paths) {
    configData = loadConfigFile(path);
    if (configData != null) {
        break;
    }
}

Ich hoffe das hilft :)

Adrián Pérez
quelle
gutes Beispiel. Dieses Beispiel ist wirklich die Art von Code, die wir überarbeiten müssen. In anderen Fällen ist jedoch ein verschachtelter Try-Catch erforderlich.
Linehrr
4

Dies verringert mit Sicherheit die Lesbarkeit des Codes. Ich würde sagen, wenn Sie die Chance haben , dann vermeiden Sie es , Versuchsfänge zu verschachteln.

Wenn Sie Versuchsfänge nisten müssen, halten Sie immer eine Minute inne und überlegen Sie:

  • kann ich sie kombinieren?

    try {  
      ... code  
    } catch (FirstKindOfException e) {  
      ... do something  
    } catch (SecondKindOfException e) {  
      ... do something else    
    }
    
  • soll ich den verschachtelten Teil einfach in eine neue Methode extrahieren? Der Code wird viel sauberer sein.

    ...  
    try {  
      ... code  
    } catch (FirstKindOfException e) {  
       panicMethod();  
    }   
    ...
    
    private void panicMethod(){   
    try{  
    ... do the nested things  
    catch (SecondKindOfException e) {  
      ... do something else    
      }  
    }
    

Es liegt auf der Hand, dass Sie drei oder mehr Ebenen von Try-Catches in einer einzigen Methode verschachteln müssen, was ein sicheres Zeichen für die Zeit des Refactors ist.

CsBalazsHungary
quelle
3

Ich habe dieses Muster im Netzwerkcode gesehen, und es macht tatsächlich Sinn. Hier ist die Grundidee im Pseudocode:

try
   connect;
catch (ConnectionFailure)
   try
      sleep(500);
      connect;
   catch(ConnectionFailure)
      return CANT_CONNECT;
   end try;
end try;

Grundsätzlich ist es eine Heuristik. Ein fehlgeschlagener Verbindungsversuch kann nur eine Netzwerkstörung sein. Wenn dies jedoch zweimal passiert, bedeutet dies wahrscheinlich, dass der Computer, mit dem Sie eine Verbindung herstellen möchten, nicht erreichbar ist. Es gibt wahrscheinlich andere Möglichkeiten, dieses Konzept zu implementieren, aber sie wären höchstwahrscheinlich noch hässlicher als die verschachtelten Versuche.

Mason Wheeler
quelle
2

Ich habe diese Situation folgendermaßen gelöst (Try-Catch mit Fallback):

$variableForWhichINeedFallback = null;
$fallbackOptions = array('Option1', 'Option2', 'Option3');
while (!$variableForWhichINeedFallback && $fallbackOptions){
    $fallbackOption = array_pop($fallbackOptions);
    try{
        $variableForWhichINeedFallback = doSomethingExceptionalWith($fallbackOption);
    }
    catch{
        continue;
    }
}
if (!$variableForWhichINeedFallback)
    raise new ExceptionalException();
Adrian
quelle
2

Ich musste dies in einer Testklasse (JUnit) tun, in der die Methode setUp () Objekte mit ungültigen Konstruktorparametern in einem Konstruktor erstellen musste, der eine Ausnahme auslöste.

Wenn ich zum Beispiel die Konstruktion von 3 ungültigen Objekten zum Scheitern bringen müsste, bräuchte ich 3 geschachtelte Try-Catch-Blöcke. Stattdessen habe ich eine neue Methode erstellt, bei der die Ausnahmen abgefangen wurden und der Rückgabewert eine neue Instanz der Klasse war, die ich bei Erfolg getestet habe.

Natürlich brauchte ich nur eine Methode, weil ich das dreimal gemacht habe. Es ist vielleicht keine so gute Lösung für verschachtelte Blöcke, die ganz andere Aufgaben ausführen, aber in den meisten Fällen wird Ihr Code besser lesbar.

MarioDS
quelle
0

Ich denke eigentlich, es ist ein Gegenmuster.

In einigen Fällen möchten Sie möglicherweise mehrere Try-Catches, aber nur, wenn Sie nicht wissen, welche Art von Fehler Sie suchen, z. B .:

public class Test
{
    public static void Test()
    {            
        try
        {
           DoOp1();
        }
        catch(Exception ex)
        {
            // treat
        }

        try
        {
           DoOp2();
        }
        catch(Exception ex)
        {
            // treat
        }

        try
        {
           DoOp3();
        }
        catch(Exception ex)
        {
            // treat
        }
    }

    public static void Test()
    {
        try
        {
            DoOp1();
            DoOp2();
            DoOp3();
        }
        catch (DoOp1Exception ex1)
        {
        }
        catch (DoOp2Exception ex2)
        {
        }
        catch (DoOp3Exception ex3)
        {
        }
    }
}

Wenn Sie nicht wissen, wonach Sie suchen, MÜSSEN Sie die erste Methode verwenden, die meiner Meinung nach hässlich und nicht funktionsfähig ist. Ich denke, letzteres ist viel besser.

Also, wenn Sie wissen, nach welcher Art von Fehler Sie suchen, seien Sie genau . Keine Notwendigkeit für verschachtelte oder mehrfache Try-Catches innerhalb derselben Methode.

George Silva
quelle
2
Code wie der, den Sie gezeigt haben, ergibt in den meisten Fällen, wenn nicht sogar in allen Fällen, keinen Sinn. Das OP bezog sich jedoch auf verschachtelte Versuchsfänge, was eine ganz andere Frage ist als die für mehrere aufeinanderfolgende Aussagen.
JimmyB
0

In einigen Fällen ist ein verschachtelter Try-Catch unvermeidlich. Zum Beispiel, wenn der Fehlerbehebungscode selbst eine Ausnahme auslösen kann. Um die Lesbarkeit des Codes zu verbessern, können Sie den verschachtelten Block immer in eine eigene Methode extrahieren. In diesem Blogbeitrag finden Sie weitere Beispiele für geschachtelte Try-Catch-finally-Blöcke.

codelion
quelle
0

Es gibt nirgendwo in Java etwas, das als Anti-Pattern bezeichnet wird. Ja, wir nennen einige Dinge gute und schlechte Praxis.

Wenn ein try / catch-Block innerhalb eines catch-Blocks erforderlich ist, können Sie ihm nicht helfen. Und es gibt keine Alternative. Ein catch-Block kann nicht als try-Teil verwendet werden, wenn eine Ausnahme ausgelöst wird.

Beispielsweise :

String str=null;
try{
   str = method(a);
}
catch(Exception)
{
try{
   str = doMethod(a);
}
catch(Exception ex)
{
  throw ex;
}

Hier in dem obigen Beispiel löst die Methode eine Ausnahme aus, aber die doMethod (die zur Behandlung der Methodenausnahme verwendet wird) löst sogar eine Ausnahme aus. In diesem Fall müssen wir den try catch innerhalb von try catch verwenden.

etwas, was nicht empfohlen wird, ist ..

try 
{
  .....1
}
catch(Exception ex)
{
}
try 
{
  .....2
}
catch(Exception ex)
{
}
try 
{
  .....3
}
catch(Exception ex)
{
}
try 
{
  .....3
}
catch(Exception ex)
{
}
try 
{
  .....4
}
catch(Exception ex)
{
}
gauravprasad
quelle
dies scheint nicht alles wesentliche über vor 12 Antworten zu bieten
gnat