L'uso di blocchi try-catch nidificati è un anti-pattern?


95

È un antipasto? È una pratica accettabile?

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

Puoi darci il caso per questo? Perché non riesci a gestire tutti i tipi di errore nella cattura di livello superiore?
Morons,

2
Di recente ho visto questo tipo di codice, eseguito da programmatori inesperti che non sanno davvero cosa stanno chiamando all'interno dei blocchi try e non vogliono preoccuparsi di testare il codice. Nell'esempio di codice che ho visto, è stata la stessa operazione ma eseguita ogni volta con i parametri di fallback.
Mister Smith,

@LokiAstari -Il tuo esempio è un tentativo nella sezione Finalmente .. Dove non c'è cattura. Questo è nidificato nella sezione Prova .. È diverso.
Morons,

4
Perché dovrebbe essere un anti-pattern?

2
+1 per "ulteriori tentativi di cattura?"
JoelFan,

Risposte:


85

Questo è talvolta inevitabile, soprattutto se il codice di recupero potrebbe generare un'eccezione.

Non carino, ma a volte non ci sono alternative.


17
@MisterSmith - Non sempre.
Oded,

4
Sì, questo è un po 'quello che stavo cercando di ottenere. Ovviamente arriva un punto nelle dichiarazioni nidificate try / catch in cui devi solo dire abbastanza è abbastanza. Stavo facendo un caso per l'annidamento anziché il tentativo / cattura sequenziale, dicendo che ci sono situazioni in cui si desidera eseguire il codice solo nel secondo tentativo da eseguire se il primo tentativo cade.
AndrewC,

5
@MisterSmith: Preferirei i catch-catch nidificati rispetto ai try-catch sequenziali che sono parzialmente controllati con variabili flag (se funzionalmente fossero gli stessi).
FrustratedWithFormsDesigner

31
provare {transazione.commit (); } catch {try {action.rollback (); } catch {seriouslogging ()} notsoseriouslogging (); } è un esempio di un tentativo di cattura nidificato necessario
Thanos Papathanasiou,

3
Almeno estraete il blocco catch in un metodo, ragazzi! Rendiamolo almeno leggibile.
Sig. Cochese,

43

Non penso che sia un antipasto, solo ampiamente abusato.

La maggior parte dei tentativi di cattura nidificati sono davvero evitabili e noiosi infernali, di solito il prodotto di sviluppatori junior.

Ma ci sono volte in cui non puoi farne a meno.

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

Inoltre, avrai bisogno di un bool extra da qualche parte per indicare il fallito rollback ...


19

La logica va bene: in alcune situazioni può avere perfettamente senso provare un approccio di fallback, che potrebbe a sua volta sperimentare eventi eccezionali .... quindi questo schema è praticamente inevitabile.

Tuttavia, suggerirei quanto segue per migliorare il codice:

  • Refactoring la prova interna ... catch blocchi fuori in funzioni separate, ad esempio, attemptFallbackMethode attemptMinimalRecovery.
  • Sii più specifico sui particolari tipi di eccezione che vengono catturati. Ti aspetti davvero una sottoclasse delle eccezioni e, in tal caso, vuoi davvero gestirle tutte allo stesso modo?
  • Considera se un finallyblocco potrebbe avere più senso - questo di solito è il caso di tutto ciò che sembra "codice di pulizia delle risorse"

14

Va bene. Un refactoring da considerare sta spingendo il codice nel suo metodo e usando le prime uscite per il successo, permettendoti di scrivere i diversi tentativi di fare qualcosa allo stesso livello:

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?

Una volta che lo hai fatto in quel modo, potresti pensare di racchiuderlo in un modello di strategia.

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

Quindi usa il TrySeveralThingsStrategy, che è una specie di strategia composita (due modelli al prezzo di uno!).

Un avvertimento enorme: non farlo a meno che le tue strategie non siano sufficientemente complesse o che tu non voglia usarle in modo flessibile. Altrimenti, stai lanciando alcune righe di codice semplice con un enorme mucchio di inutili orientamenti degli oggetti.


7

Non penso che sia automaticamente un anti-pattern, ma lo eviterei se trovassi un modo più semplice e pulito di fare la stessa cosa. Se il linguaggio di programmazione in cui stai lavorando ha un finallycostrutto, ciò potrebbe aiutare a ripulirlo, in alcuni casi.


6

Non un anti-pattern di per sé, ma un pattern di codice che ti dice che devi refactoring.

Ed è abbastanza semplice, devi solo conoscere una regola empirica che sta scrivendo non più di un blocco try nello stesso metodo. Se sai bene scrivere insieme il codice correlato, di solito è solo copiare e incollare ogni blocco try con i suoi blocchi catch e incollarlo all'interno di un nuovo metodo, quindi sostituire il blocco originale con una chiamata a questo metodo.

Questa regola empirica si basa sul suggerimento di Robert C. Martin del suo libro "Clean Code":

se la parola chiave "provare" esiste in una funzione, dovrebbe essere la prima parola nella funzione e che non ci dovrebbe essere nulla dopo i blocchi catch / finally.

Un rapido esempio di "pseudo-java". Supponiamo di avere qualcosa del genere:

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

Quindi potremmo eseguire il refactoring di ogni tentativo di cattura e in questo caso ogni blocco try-catch prova la stessa cosa ma in posizioni diverse (quanto conveniente: D), dobbiamo solo copiare e incollare uno dei blocchi try-catch e crearne un metodo .

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

Ora lo usiamo con lo stesso scopo di prima.

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

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

Spero che aiuti :)


buon esempio. questo esempio è veramente il tipo di codice che dobbiamo refactoring. tuttavia, altre volte, è necessario il natch try-catch.
linehrr,

4

Sta sicuramente diminuendo la leggibilità del codice. Direi che, se ne hai la possibilità , evita di annidare i trucchi.

Se devi annidare i trucchi, fermati sempre per un minuto e pensa:

  • ho la possibilità di combinarli?

    try {  
      ... code  
    } catch (FirstKindOfException e) {  
      ... do something  
    } catch (SecondKindOfException e) {  
      ... do something else    
    }
    
  • dovrei semplicemente estrarre la parte nidificata in un nuovo metodo? Il codice sarà molto più pulito.

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

È ovvio se devi annidare tre o più livelli di prove di cattura, in un unico metodo, questo è un segno sicuro di tempo per il refactor.


3

Ho visto questo schema nel codice di rete e in realtà ha senso. Ecco l'idea di base, in pseudocodice:

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

Fondamentalmente è un euristico. Un tentativo fallito di connessione potrebbe essere solo un problema tecnico della rete, ma se accade due volte, ciò significa probabilmente che la macchina a cui stai tentando di collegarti è davvero irraggiungibile. Probabilmente ci sono altri modi per implementare questo concetto, ma molto probabilmente sarebbero anche più brutti dei tentativi nidificati.


2

Ho risolto questa situazione in questo modo (try-catch con 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

Ho "dovuto" farlo in una classe di test per coincidenza (JUnit), in cui il metodo setUp () doveva creare oggetti con parametri di costruzione non validi in un costruttore che generava un'eccezione.

Se dovessi far fallire la costruzione di 3 oggetti non validi, ad esempio, avrei bisogno di 3 blocchi try-catch, nidificati. Ho creato invece un nuovo metodo, in cui sono state rilevate le eccezioni e il valore restituito era una nuova istanza della classe che stavo testando quando ha avuto successo.

Certo, avevo solo bisogno di 1 metodo perché ho fatto lo stesso 3 volte. Potrebbe non essere una buona soluzione per i blocchi nidificati che fanno cose totalmente diverse, ma almeno il tuo codice diventerebbe più leggibile nella maggior parte dei casi.


0

In realtà penso che sia un antipasto.

In alcuni casi potresti desiderare più tentativi di cattura, ma solo se NON CONOSCI il tipo di errore che stai riscontrando, ad esempio:

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)
        {
        }
    }
}

Se non sai cosa stai cercando, DEVI utilizzare il primo modo, che è IMHO, brutto e non funzionale. Immagino che quest'ultimo sia molto meglio.

Quindi, se sai quale tipo di errore stai cercando, sii specifico . Non sono necessari tentativi di cattura nidificati o multipli all'interno dello stesso metodo.


2
Il codice come quello che hai mostrato non ha davvero senso nella maggior parte se non in tutti i casi. Tuttavia, il PO si riferiva a prove di cattura nidificate , che è una domanda alquanto diversa da quella per più dichiarazioni consecutive.
JimmyB,

0

In alcuni casi è inevitabile un Try-Catch nidificato. Ad esempio, quando il codice di recupero dell'errore stesso può generare ed eccezione. Ma per migliorare la leggibilità del codice è sempre possibile estrarre il blocco nidificato in un metodo a sé stante. Dai un'occhiata a questo post sul blog per ulteriori esempi sui blocchi nidificati Try-Catch-finally.


0

Non c'è nulla di menzionato come Anti Pattern in Java ovunque. Sì, chiamiamo poche cose buone pratiche e cattive pratiche.

Se è necessario un blocco try / catch all'interno di un blocco catch, non è possibile aiutarlo. E non c'è alternativa. Poiché un blocco catch non può funzionare come parte di prova se viene generata un'eccezione.

Per esempio :

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

Qui nell'esempio sopra riportato viene generata un'eccezione ma doMethod (utilizzato per gestire l'eccezione del metodo) genera anche un'eccezione. In questo caso, dobbiamo usare il try catch all'interno di try catch.

una cosa che si suggerisce di non fare è ..

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)
{
}

questo non sembra offrire nulla di sostanziale rispetto alle precedenti 12 risposte
moscerino del
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.