Domanda

Ho appena avuto un'esperienza di risoluzione dei problemi piuttosto dolorosa durante la risoluzione dei problemi di un codice simile al seguente:

try {
   doSomeStuff()
   doMore()
} finally {
   doSomeOtherStuff()
}

Il problema era difficile da risolvere perché doSomeStuff () ha generato un'eccezione, che a sua volta ha causato anche un'eccezione a doSomeOtherStuff (). La seconda eccezione (generata dal blocco finally) è stata generata dal mio codice, ma non ha un handle sulla prima eccezione (generata da doSomeStuff ()), che è stata la vera causa principale del problema.

Se invece il codice avesse detto questo, il problema sarebbe stato immediatamente evidente:

try {
    doSomeStuff()
    doMore()
} catch (Exception e) {
    log.error(e);
} finally {
   doSomeOtherStuff()
}

Quindi, la mia domanda è questa:

Un blocco finally utilizzato senza alcun blocco catch è un noto anti-pattern java? (Sembra certamente essere una sottoclasse non apparentemente evidente del ben noto anti-pattern " Non ingannare eccezioni! & Quot;)

È stato utile?

Soluzione

In generale, no, questo non è un anti-pattern. Il punto di blocco finale è assicurarsi che le cose vengano ripulite indipendentemente dal fatto che venga generata un'eccezione. Il punto centrale della gestione delle eccezioni è che, se non riesci a gestirlo, lo lasci passare a qualcuno che può, attraverso la gestione delle eccezioni di segnalazione fuori banda relativamente pulita. Se devi assicurarti che le cose vengano ripulite se viene generata un'eccezione, ma non riesci a gestire correttamente l'eccezione nell'ambito corrente, allora questa è esattamente la cosa giusta da fare. Potresti solo voler essere un po 'più attento a assicurarti che il tuo blocco finalmente non venga lanciato.

Altri suggerimenti

Penso che il vero "anti-pattern" qui sta facendo qualcosa in un blocco finalmente che può lanciare, senza avere una presa.

Niente affatto.

Cosa c'è che non va è il codice dentro finalmente.

Ricorda che finalmente verrà sempre eseguito, ed è solo rischioso (come hai appena visto) mettere qualcosa che potrebbe generare un'eccezione lì.

Non c'è assolutamente nulla di sbagliato nel provare con un catch finalmente e niente. Considera quanto segue:

InputStream in = null;
try {
    in = new FileInputStream("file.txt");
    // Do something that causes an IOException to be thrown
} finally {
    if (in != null) {
         try {
             in.close();
         } catch (IOException e) {
             // Nothing we can do.
         }
    }
}

Se viene generata un'eccezione e questo codice non sa come gestirla, allora l'eccezione dovrebbe far passare lo stack delle chiamate al chiamante. In questo caso vogliamo ancora ripulire lo stream, quindi penso che abbia perfettamente senso avere un blocco try senza una cattura.

Penso che sia ben lungi dall'essere un anti-pattern ed è qualcosa che faccio molto frequentemente quando è fondamentale deallocare le risorse ottenute durante l'esecuzione del metodo.

Una cosa che faccio quando ho a che fare con handle di file (per la scrittura) è svuotare lo stream prima di chiuderlo usando il metodo IOUtils.closeQuietly, che non genera eccezioni:


OutputStream os = null;
OutputStreamWriter wos = null;
try { 
   os = new FileOutputStream(...);
   wos = new OutputStreamWriter(os);
   // Lots of code

   wos.flush();
   os.flush();
finally {
   IOUtils.closeQuietly(wos);
   IOUtils.closeQuietly(os);
}

Mi piace farlo in questo modo per i seguenti motivi:

  • Non è del tutto sicuro ignorare un'eccezione quando si chiude un file - se ci sono byte che non sono stati ancora scritti nel file, il file potrebbe non essere nello stato previsto dal chiamante;
  • Quindi, se viene sollevata un'eccezione durante il metodo flush (), verrà propagata al chiamante ma mi assicurerò comunque che tutti i file siano chiusi. Il metodo IOUtils.closeQuietly (...) è meno dettagliato del corrispondente tentativo ... catch ... ignora me block;
  • Se si utilizzano più flussi di output, l'ordine per il metodo flush () è importante. I flussi che sono stati creati passando altri flussi come costruttori dovrebbero essere scaricati per primi. La stessa cosa vale per il metodo close (), ma il flush () è più chiaro secondo me.

Direi che un blocco try senza un blocco catch è un anti-pattern. Dicendo " Non avere un senza finalmente una cattura " è un sottoinsieme di " Non provare senza una cattura " ;.

Uso try / finally nel seguente formato:

try{
   Connection connection = ConnectionManager.openConnection();
   try{
       //work with the connection;
   }finally{
       if(connection != null){
          connection.close();           
       }
   }
}catch(ConnectionException connectionException){
   //handle connection exception;
}

Preferisco questo a try / catch / finally (+ try / catch annidato in finally). Penso che sia più conciso e non duplico la cattura (eccezione).

try {
    doSomeStuff()
    doMore()
} catch (Exception e) {
    log.error(e);
} finally {
   doSomeOtherStuff()
}

Non farlo neanche ... hai semplicemente nascosto più bug (non li hai nascosti esattamente ... ma hai reso più difficile gestirli. Quando catturi l'eccezione stai anche catturando qualsiasi tipo di RuntimeException (come NullPointer e ArrayIndexOutOfBounds).

In generale, prendi le eccezioni che devi catturare (eccezioni controllate) e gestisci le altre al momento del test. RuntimeExceptions è progettato per essere utilizzato per errori del programmatore e gli errori del programmatore sono cose che non dovrebbero accadere in un programma correttamente sottoposto a debug.

Secondo me, è più vero che finalmente con un catch indica un qualche tipo di problema. Il linguaggio delle risorse è molto semplice:

acquire
try {
    use
} finally {
    release
}

In Java puoi avere un'eccezione praticamente ovunque. Spesso l'acquisizione genera un'eccezione controllata, il modo ragionevole di gestirlo è quello di catturare il quanto. Non provare qualche orribile controllo null.

Se dovessi essere davvero anale, dovresti notare che ci sono priorità implicite tra le eccezioni. Ad esempio ThreadDeath dovrebbe bloccare tutto, sia che si tratti di acquisire / utilizzare / rilasciare. Gestire correttamente queste priorità è sgradevole.

Pertanto, allontana la gestione delle risorse con il linguaggio Execute Around.

Prova / Finalmente è un modo per liberare correttamente le risorse. Il codice nel blocco finally non dovrebbe MAI essere gettato poiché dovrebbe agire solo sulle risorse o dichiarare che è stato acquisito PRIMA di entrare nel blocco try.

A parte questo, penso che log4J sia quasi un anti-schema.

SE VUOI ISPEZIONARE UN PROGRAMMA IN ESECUZIONE UTILIZZARE UN STRUMENTO DI ISPEZIONE CORRETTO (ovvero un debugger, IDE o in un senso estremo un tessitore di codice byte ma NON METTERE DICHIARAZIONI DI REGISTRAZIONE IN OGNI POCHE LINEE!).

Nei due esempi presentati, il primo sembra corretto. Il secondo include il codice del logger e introduce un bug. Nel secondo esempio si sopprime un'eccezione se si viene generati dalle prime due istruzioni (ovvero la si prende e la si registra ma non la si ripropone. Questo è qualcosa che trovo molto comune nell'uso di log4j ed è un vero problema di progettazione dell'applicazione. con la tua modifica fai fallire il programma in un modo che sarebbe molto difficile da gestire per il sistema dato che praticamente vai avanti come se non avessi mai avuto un'eccezione (una specie come VB di base sull'errore riprende il prossimo costrutto).

try-finally può aiutarti a ridurre il codice copia-incolla nel caso in cui un metodo abbia più istruzioni return . Considera il seguente esempio (Android Java):

boolean doSomethingIfTableNotEmpty(SQLiteDatabase db) {
    Cursor cursor = db.rawQuery("SELECT * FROM table", null);
    if (cursor != null) { 
        try {
            if (cursor.getCount() == 0) { 
                return false;
            }
        } finally {
            // this will get executed even if return was executed above
            cursor.close();
        }
    }
    // database had rows, so do something...
    return true;
}

Se non ci fosse la clausola finalmente , potresti dover scrivere cursor.close () due volte: poco prima di return false e anche dopo la clausola if circostante.

Penso che provare senza catturare sia anti-pattern. L'uso di try / catch per gestire condizioni eccezionali (errori di I / O del file, timeout del socket, ecc.) Non è un modello.

Se stai usando try / finally per la pulizia, considera invece un blocco using.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top