L'uso della clausola finally per fare il lavoro dopo il reso è di cattivo gusto / pericoloso?

30

Come parte della scrittura di un Iterator, mi sono ritrovato a scrivere il seguente pezzo di codice (gestione degli errori di stripping)

public T next() {
  try {
    return next;
  } finally {
    next = fetcher.fetchNext(next);
  }
}

trovarlo leggermente più facile da leggere di

public T next() {
  T tmp = next;
  next = fetcher.fetchNext(next);
  return tmp;
}

So che è un semplice esempio, in cui la differenza nella leggibilità potrebbe non essere così travolgente, ma sono interessato all'opinione generale se sia male usare try-finally in casi come questo in cui non ci sono eccezioni coinvolto, o se è effettivamente preferito quando semplifica il codice.

Se è cattivo: perché? Stile, prestazioni, insidie, ...?

Conclusione Grazie a tutte le tue risposte! Immagino che la conclusione (almeno per me) sia che il primo esempio potrebbe essere più leggibile se fosse un modello comune, ma non lo è. Quindi la confusione introdotta usando un costrutto al di fuori del suo scopo, insieme al flusso di eccezioni eventualmente offuscato, supererà qualsiasi semplificazione.

    
posta Halle Knast 28.04.2012 - 03:14
fonte

5 risposte

18

Personalmente, vorrei dare un pensiero all'idea di separare le query dei comandi . Quando si arriva a destra, next () ha due scopi: lo scopo pubblicizzato di recuperare l'elemento successivo e l'effetto collaterale nascosto di mutare lo stato interno di next. Quindi, quello che stai facendo è eseguire lo scopo pubblicizzato nel corpo del metodo e poi virare su un effetto collaterale nascosto in una clausola finale, che sembra ... scomoda, anche se non "sbagliata", esattamente.

In realtà si riduce a quanto è comprensibile il codice, direi, e in questo caso la risposta è "meh". Stai "provando" una semplice dichiarazione di ritorno e poi esegui un effetto collaterale nascosto in un blocco di codice che dovrebbe essere per il recupero degli errori sicuro. Quello che stai facendo è "intelligente", e il codice "intelligente" spesso induce i manutentori a borbottare "che cosa ... oh, penso di averlo capito". Meglio per le persone che leggono il tuo codice per borbottare "sì, uh-huh, ha senso, sì ..."

Quindi, cosa succede se si separano le mutazioni di stato dalle chiamate di accesso? Immagino che il problema di leggibilità di cui sei preoccupato diventi discutibile, ma non so come ciò influenzi la tua astrazione più ampia. Qualcosa da considerare, comunque.

    
risposta data 28.04.2012 - 05:20
fonte
37

Puramente dal punto di vista dello stile, penso che queste tre linee:

T current = next;
next = fetcher.getNext();
return current;

... sono entrambi più ovvi e più brevi di un blocco try / finally. Dato che non ti aspetti alcuna eccezione da lanciare, usare un blocco try confonderà le persone.

    
risposta data 28.04.2012 - 05:32
fonte
6

Ciò dipenderebbe dallo scopo del codice all'interno del blocco finally. L'esempio canonico sta chiudendo un flusso dopo aver letto / scritto da esso, una sorta di "pulizia" che deve essere sempre fatta. Ripristino di un oggetto (in questo caso un iteratore) in uno stato valido IMHO conta anche come pulizia, quindi non vedo alcun problema qui. Se OTOH stavi usando return non appena il tuo valore di ritorno è stato trovato, e aggiungendo un sacco di codice non correlato nel blocco finally, allora oscurerebbe lo scopo e renderà tutto meno comprensibile.

Non vedo alcun problema nell'usarlo quando "non ci sono eccezioni coinvolte". È molto comune utilizzare try...finally senza catch quando il codice può solo generare RuntimeException e non hai intenzione di gestirli. A volte, alla fine è solo una salvaguardia, e sai per la logica del tuo programma che nessuna eccezione sarà mai lanciata (la classica condizione "questo non dovrebbe mai accadere").

Insidie: qualsiasi eccezione generata all'interno del blocco try renderà il finally bock eseguito. Questo può metterti in uno stato incoerente. Quindi, se la tua dichiarazione di ritorno fosse qualcosa del tipo:

return preProcess(next);

e questo codice ha generato un'eccezione, fetchNext verrebbe comunque eseguito. OTOH se lo hai codificato come:

T ret = preProcess(next);
next = fetcher.fetchNext(next);
return ret;

quindi non funzionerebbe. So che stai ipotizzando che il codice di prova possa mai aumentare qualsiasi eccezione, ma per casi più complessi di questo come puoi essere sicuro? Se il tuo iteratore fosse un oggetto di lunga durata, ciò continuerebbe a esistere anche se un errore irreversibile si verificasse nel thread corrente, piuttosto che sarebbe importante mantenerlo in uno stato valido in ogni momento. Altrimenti, non importa molto ...

Prestazioni: sarebbe interessante decompilare tale codice per vedere come funziona sotto il cofano, ma non conosco abbastanza la JVM per fare una buona ipotesi ... Le eccezioni sono solitamente "eccezionali", quindi il codice che li gestisce non ha bisogno di essere ottimizzato per la velocità (da qui il consiglio di non usare mai eccezioni nel normale flusso di controllo dei tuoi programmi), ma non so circa finally .

    
risposta data 28.04.2012 - 04:10
fonte
3

La frase del titolo: "... finally clause for doing work after return ..." è false. Il blocco finally avviene prima che la funzione ritorni. Questo è il vero punto di fatto, infine.

Quello che stai abusando qui è l'ordine di valutazione, in cui il valore di next viene memorizzato per essere restituito prima di mutarlo. Non è una pratica comune e, a mio parere, sbagliata in quanto ti rende il codice non sequenziale e quindi molto più difficile da seguire.

L'uso previsto dei blocchi finally è per la gestione delle eccezioni in cui devono verificarsi alcune conseguenze indipendentemente dalle eccezioni generate ad es. ripulire alcune risorse, chiudere la connessione DB, chiudere un file / socket ecc.

    
risposta data 10.01.2013 - 21:52
fonte
0

Sarei molto attento, perché (in generale, non nel tuo esempio) la parte in try può generare un'eccezione, e se viene buttata fuori, nulla verrà restituito e se hai intenzione di eseguire ciò che è finalmente dopo il ritorno , verrà eseguito quando non lo avresti voluto.

E altri tipi di worm sono, in generale, che alcune azioni utili possono finalmente generare eccezioni, quindi puoi terminare con un metodo davvero disordinato.

Per questo motivo, qualcuno che lo legge deve pensare a questi problemi, quindi è più difficile da capire.

    
risposta data 11.01.2013 - 14:44
fonte

Leggi altre domande sui tag