it-swarm.dev

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

Ist das ein Antimuster? 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?
            }
        }
    }
105
Mister Smith

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

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

94
Oded

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

Die meisten verschachtelten Versuchsfänge sind in der Tat vermeidbar und hässlich, normalerweise das Produkt von Junior-Entwicklern.

Aber manchmal kann man nichts dagegen tun.

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

46

Die Logik ist in Ordnung - es kann in manchen Situationen durchaus sinnvoll sein, einen Fallback-Ansatz zu versuchen, bei dem selbst außergewöhnliche Ereignisse auftreten können. Daher ist dieses Muster so gut wie unvermeidlich.

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

  • Refaktorieren Sie den inneren Versuch ... fangen Sie Blöcke in separate Funktionen aus, z. attemptFallbackMethod und attemptMinimalRecovery.
  • Seien Sie genauer über die bestimmten Ausnahmetypen, die abgefangen werden. Erwarten Sie wirklich any Exception-Unterklasse und wenn ja, möchten Sie sie wirklich alle gleich behandeln?
  • Überlegen Sie, ob ein finally -Block sinnvoller sein könnte - dies ist normalerweise bei allem der Fall, was sich wie "Ressourcenbereinigungscode" anfühlt.
20
mikera

Es ist in Ordnung. Ein zu berücksichtigendes Refactoring besteht darin, den Code in seine 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önnten Sie darüber nachdenken, es in ein Strategiemuster zu packen.

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");
    }
}

Verwenden Sie dann einfach das TrySeveralThingsStrategy, eine Art zusammengesetzte Strategie (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 speisen Sie ein paar Zeilen einfachen Codes mit einem riesigen Haufen unnötiger Objektorientierung.

17
Tom Anderson

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

Kein Anti-Pattern an sich, sondern ein Codemuster, das besagt, dass Sie umgestalten müssen.

Und es ist ziemlich einfach, Sie müssen nur eine Faustregel kennen, die nicht mehr als einen Try-Block in derselben Methode schreibt. Wenn Sie wissen, wie man verwandten Code zusammen schreibt, kopieren und fügen Sie normalerweise nur jeden try-Block mit seinen catch-Blöcken ein und fügen ihn in eine neue Methode ein. 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 vorhanden ist, sollte es das allererste Wort in der Funktion sein und nach den blocken catch/finally nichts mehr vorhanden sein.

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-Block umgestalten 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 :)

6
Adrián Pérez

Dies verringert sicherlich die Lesbarkeit des Codes. Ich würde sagen, wenn Sie die Chance haben, dann vermeiden Sie es, Try-Catches zu verschachteln.

Wenn Sie Try-Catches verschachteln müssen, halten Sie immer eine Minute inne und denken Sie:

  • habe ich die Chance, sie zu kombinieren?

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

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

Es ist offensichtlich, dass Sie drei oder mehr Ebenen von Try-Catches in einer einzigen Methode verschachteln müssen. Dies ist ein sicheres Zeichen für die Zeit für den Refactor.

4
CsBalazsHungary

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;

Im Grunde ist es eine Heuristik. Ein fehlgeschlagener Verbindungsversuch könnte nur ein Netzwerkfehler sein. Wenn er jedoch zweimal auftritt, bedeutet dies wahrscheinlich, dass der Computer, zu 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.

3
Mason Wheeler

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();
2
Adrian

Ich musste dies zufällig in einer Testklasse (JUnit) tun, in der die setUp () -Methode 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 fehlschlagen lassen müsste, würde ich 3 verschachtelte Try-Catch-Blöcke benötigen. Ich habe stattdessen eine neue Methode erstellt, bei der die Ausnahmen abgefangen wurden, und der Rückgabewert war eine neue Instanz der Klasse, die ich getestet habe, als sie erfolgreich war.

Natürlich brauchte ich nur eine Methode, weil ich das gleiche dreimal gemacht habe. Es ist vielleicht keine so gute Lösung für verschachtelte Blöcke, die völlig andere Dinge tun, aber zumindest würde Ihr Code in den meisten Fällen besser lesbar werden.

2
MarioDS

Ich denke tatsächlich, dass es ein Antimuster ist.

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.

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.

Wenn Sie also wissen, nach welcher Art von Fehler Sie suchen, seien Sie gena. Keine Notwendigkeit für verschachtelte oder mehrere Try-Catches innerhalb derselben Methode.

0
George Silva

In einigen Fällen ist ein verschachtelter Try-Catch unvermeidbar. Zum Beispiel, wenn der Fehlerbehebungscode selbst eine Ausnahme auslösen kann. Um die Lesbarkeit des Codes zu verbessern, können Sie den verschachtelten Block jederzeit in eine eigene Methode extrahieren. Weitere Beispiele zu verschachtelten Try-Catch-finally-Blöcken finden Sie in this Blogpost.

0
codelion

In Java irgendwo) wird nichts als Anti-Pattern erwähnt. Ja, wir nennen einige Dinge gute und schlechte Praktiken.

Wenn ein Try/Catch-Block innerhalb eines Catch-Blocks erforderlich ist, können Sie ihm nicht helfen. Und es gibt keine Alternative. Da ein Catch-Block nicht als Try-Teil funktionieren kann, 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 im 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 des try catch verwenden.

etwas, was empfohlen wird, nicht zu tun, 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)
{
}
0
gauravprasad