Argomenti a favore o contro l'utilizzo di Try / Catch come operatori logici [chiuso]


36

Ho appena scoperto del delizioso codice nell'app della nostra azienda che utilizza i blocchi Try-Catch come operatori logici.
Significa "esegui un codice, se questo genera questo errore, esegui questo codice, ma se ciò genera questo errore, esegui invece questa terza cosa".
Usa "Finalmente" come appare la frase "else".
So che questo è intrinsecamente sbagliato, ma prima di andare a combattere ho sperato in alcuni argomenti ben ponderati.
Ehi, se hai argomenti PER l'uso di Try-Catch in questo modo, ti preghiamo di dirlo.

Per chiunque si stia chiedendo, la lingua è C # e il codice in questione è di circa 30 righe e cerca eccezioni specifiche, non gestisce TUTTE le eccezioni.


13
Ho solo argomenti contro questa pratica, e dato che sembri già convinto che sia una cattiva idea, non mi preoccuperò di pubblicarli.
FrustratedWithFormsDesigner,

15
@FrustratedWIthFormsDesigner: Questo è un cattivo ragionamento. E le persone che non sono convinte? Che dire della mia vera domanda in cui chiedo specificatamente i motivi poiché non posso davvero dire "perché" solo che so che è sbagliato.
James P. Wright,

3
Le mie opinioni dipendono fortemente da ciò che effettivamente fa il codice in questione. Ci sono cose che non possono essere verificate in anticipo (o consentire le condizioni di gara quando si tenta di farlo, come molte operazioni sui file - nel ritardo tra il controllo e l'operazione, tutto può succedere al file) e devono essere try'd. Non tutti i casi eccezionali che giustificano un'eccezione in generale devono essere fatali in questo caso specifico. Quindi, potresti farlo in un modo più semplice, uguale o più robusto senza usare eccezioni?

11
Spero che non usino davvero il blocco "finally" come "else" perché il blocco "finally" viene sempre eseguito dopo il codice precedente, indipendentemente da eventuali eccezioni.
jimreed

2
Che lingua stanno usando? Va benissimo, per esempio, in OCaml, dove la libreria standard genera regolarmente eccezioni. La gestione delle eccezioni è molto economica lì. Ma non sarà così efficiente in CLI o JVM.
SK-logic,

Risposte:


50

La gestione delle eccezioni tende ad essere un modo costoso per gestire il controllo del flusso (certamente per C # e Java).

Il runtime fa un bel po 'di lavoro quando viene costruito un oggetto eccezione: mettere insieme la traccia dello stack, capire dove viene gestita l'eccezione e altro ancora.

Tutti questi costi in risorse di memoria e CPU che non necessitano di essere espansi se vengono utilizzate istruzioni di controllo del flusso per il controllo del flusso.

Inoltre, c'è un problema semantico. Le eccezioni sono per situazioni eccezionali, non per il normale controllo del flusso. Si dovrebbe usare la gestione delle eccezioni per gestire situazioni impreviste / eccezionali, non come un normale flusso di programma, perché altrimenti un'eccezione non rilevata ti dirà molto meno.

A parte questi due, c'è la questione che altri leggano il codice. L'uso delle eccezioni in questo modo non è qualcosa che la maggior parte dei programmatori si aspetta, quindi la leggibilità e la comprensibilità del tuo codice ne risentono. Quando si vede "Eccezione", si pensa: è successo qualcosa di brutto, qualcosa che non dovrebbe accadere normalmente. Quindi, usare le eccezioni in questo modo è semplicemente confuso.


1
Un buon punto sulle prestazioni, anche se questo dipenderà anche dalle specifiche della piattaforma.
FrustratedWithFormsDesigner,

4
Se questo è l'unico aspetto negativo, beh grazie - Rinuncerò alle prestazioni ogni giorno se mi compra un codice migliore (tranne nei percorsi critici per le prestazioni, cioè; ma per fortuna questo è solo il 20% di tutto il codice anche in applicazioni che sono generalmente prestazioni Critica).

5
@delnan - No, non l'unico lato negativo.
Oded,

2
A parte la parola non prevista, sono d'accordo con il tuo post. Ci sono momenti nel tuo codice in cui si verificano eccezioni. La maggior parte di essi dovrebbe essere prevedibile come errori di connessione con il DB o una chiamata di servizio o ogni volta che si ha a che fare con codice non gestito. Prendi problemi al di fuori del tuo controllo e li affronti. Ma sono d'accordo che non dovresti mai avere il controllo del flusso basato su eccezioni che puoi evitare nel tuo codice.
SoylentGray,

4
"La gestione delle eccezioni tende ad essere un modo costoso per gestire il controllo del flusso". Falso per Python.
S.Lott

17

Ho appena scoperto del delizioso codice nell'app della nostra azienda che utilizza i blocchi Try-Catch come operatori logici. Significa "esegui un codice, se questo genera questo errore, esegui questo codice, ma se ciò genera questo errore, esegui invece questa terza cosa". Usa "Finalmente" come appare la frase "else". So che questo è intrinsecamente sbagliato ...

Come fai a saperlo? Ho rinunciato a tutto quel tipo di "conoscenza" e ora credo solo che il codice più semplice sia il migliore. Supponiamo di voler convertire una stringa in un facoltativo che è vuoto se l'analisi non riesce. Non c'è niente di sbagliato in:

try { 
    return Optional.of(Long.valueOf(s)); 
} catch (NumberFormatException) { 
    return Optional.empty(); 
}

Sono completamente in disaccordo con la consueta interpretazione di "Le eccezioni sono per condizioni eccezionali". Quando una funzione non può restituire un valore utilizzabile o un metodo non può soddisfare le sue post-condizioni, genera un'eccezione. Non importa con quale frequenza vengono generate queste eccezioni fino a quando non si verifica un problema di prestazioni dimostrato.

Le eccezioni semplificano il codice, consentendo la separazione della gestione degli errori dal flusso normale. Scrivi semplicemente il codice più semplice possibile e, se è più facile usare try-catch o generare un'eccezione, fallo.

Le eccezioni semplificano i test riducendo il numero di percorsi attraverso il codice. Una funzione senza rami completerà o genererà un'eccezione. Una funzione con più ifistruzioni per verificare la presenza di codici di errore ha molti percorsi possibili. È molto facile sbagliare una delle condizioni o dimenticarsene completamente, in modo da ignorare alcune condizioni di errore.


4
Penso che questo sia in realtà un argomento piuttosto valido. Il tuo codice è pulito e conciso e ha senso in quello che sta facendo. Questo è in realtà simile a ciò che sta accadendo nel codice che vedo, solo che è molto più complesso, è nidificato e si estende su oltre 30 righe di codice.
James P. Wright,

@James: sembra che il codice andrebbe bene se hai estratto alcuni metodi più piccoli.
Kevin Cline,

1
Si potrebbe sostenere che Java potrebbe davvero usare qualcosa di più simile a TryParse di C #. In C # quel codice sarebbe int i; if(long.TryParse(s, out i)) return i; else return null;. TryParse restituisce false se non è stato possibile analizzare la stringa. Se può essere analizzato, imposta l'argomento output sui risultati dell'analisi e restituisce true. Non si verificano eccezioni (anche internamente in TryParse) e soprattutto nessuna eccezione di un tipo che significa errore del programmatore, come FormatExceptionindica sempre .NET .
Kevin Cathcart,

1
@Kevin Cathcart, ma perché è meglio? il codice che cattura NumberFormatException è molto più ovvio per me quindi controllare il risultato di TryParse per un valore nullo.
Winston Ewert,

1
@ChrisMiskowiec, sospetto che quello che trovi più chiaro sia praticamente una questione a cui sei abituato. Trovo che catturare l'eccezione sia molto più facile da seguire, quindi controllare i valori magici. Ma è tutto soggettivo. Oggettivamente, penso che dovremmo preferire le eccezioni perché hanno un default sano (crash del programma), piuttosto che un default che nasconde bug (continua come se non fosse successo nulla). È vero che .NET funziona male con le eccezioni. Ma questo è il risultato del design di .NET, non della natura delle eccezioni. Se su .NET, sì, devi farlo. Ma in linea di principio, le eccezioni sono migliori.
Winston Ewert,

12

Il lavoro di debug e manutenzione è molto difficile quando il flusso di controllo viene eseguito utilizzando eccezioni.

Inerentemente, le eccezioni sono progettate per essere un meccanismo per alterare il normale flusso di controllo del tuo programma - per svolgere attività insolite, causando effetti collaterali insoliti, come un modo per uscire da un legame particolarmente stretto che non può essere gestito con meno complicato si intende. Le eccezioni sono eccezionali. Ciò significa che, a seconda dell'ambiente in cui si sta lavorando, l'uso dell'eccezione per un flusso di controllo regolare può causare:

  • Inefficienza I cerchi aggiuntivi che l'ambiente deve superare per eseguire in sicurezza i cambiamenti di contesto relativamente difficili richiesti per le eccezioni richiedono strumenti e risorse.

  • Difficoltà di debug Talvolta informazioni utili (quando si tenta di eseguire il debug di un programma) vengono espulse dalla finestra quando si verificano eccezioni. È possibile perdere traccia dello stato o della cronologia del programma rilevanti per la comprensione del comportamento di runtime

  • Problemi di manutenzione È difficile seguire il flusso di esecuzione attraverso salti di eccezione. Oltre a ciò, un'eccezione può essere generata dall'interno del codice di tipo black box, che potrebbe non comportarsi in modo facile da capire quando si genera un'eccezione.

  • Cattive decisioni di progettazione I programmi costruiti in questo modo incoraggiano uno stato d'animo che, nella maggior parte dei casi, non si adatta facilmente alla risoluzione dei problemi in modo elegante. La complessità del programma finale scoraggia il programmatore dalla piena comprensione della sua esecuzione e incoraggia a prendere decisioni che portano a miglioramenti a breve termine con elevati costi a lungo termine.


10

Ho visto questo schema usato più volte.

Ci sono 2 problemi principali:

  • È estremamente costoso (istanza dell'oggetto eccezione, raccolta dello stack di chiamate ecc.). Alcuni compilatori potrebbero effettivamente essere in grado di ottimizzarlo via, ma in questo caso non vorrei contare su questo, perché le eccezioni non sono destinate a tale uso, quindi non puoi aspettarti che le persone ottimizzino per questo.
  • L'uso delle eccezioni per il flusso di controllo è in realtà un salto, molto simile a a goto. I salti sono considerati dannosi, per una serie di motivi. Dovrebbero essere utilizzati, se tutte le alternative presentano notevoli svantaggi. In effetti, in tutto il mio codice ricordo solo 2 casi, in cui i salti erano chiaramente la soluzione migliore.

2
Basta metterlo là fuori, se / else è anche considerato un salto. La differenza è che è un salto che può verificarsi solo in quel punto specifico (e si legge molto più facilmente) e non devi preoccuparti dei nomi delle etichette (che non devi nemmeno provare / catturare , ma rispetto a goto). Ma solo dicendo "è un salto, il che è male" dice anche che se / else è cattivo, quando non lo è.
jsternberg,

1
@jsternbarg: Sì, se / else è effettivamente compilato per i salti a livello di macchina, ma a livello di lingua, l'obiettivo di salto è la frase successiva, mentre nel caso dei loop è la dichiarazione corrente. Direi che è più un passo che un salto;)
back2dos,

9

A volte le eccezioni sono più veloci. Ho visto casi in cui le eccezioni agli oggetti null erano più veloci anche in Java rispetto all'uso delle strutture di controllo (al momento non posso citare lo studio, quindi dovrai fidarti di me). Il problema si presenta quando Java deve effettivamente impiegare il tempo e popolare la traccia dello stack di una classe di eccezioni personalizzata anziché utilizzare quelle native (che sembrano essere almeno parzialmente memorizzate nella cache). Prima di dire che qualcosa è unilateralmente più veloce o più lento, sarebbe bene fare un benchmark.

In Python non è solo più veloce, ma è molto più corretto fare qualcosa che potrebbe causare un'eccezione e quindi gestire l'errore. Sì, puoi applicare un sistema di tipi, ma questo va contro la filosofia della lingua - invece dovresti semplicemente provare a chiamare il metodo e catturare il risultato! (Verificare se un file è scrivibile è simile: prova a scrivere su di esso e rileva l'errore).

Ho visto volte in cui è più veloce fare qualcosa di stupido come le tabelle di query che non erano lì che capire se esiste una tabella in PHP + MySQL (la domanda in cui faccio il benchmark è in realtà la mia unica risposta accettata con voti negativi) .

Detto questo, l'uso delle eccezioni dovrebbe essere limitato per diversi motivi:

  1. Deglutizione accidentale di eccezioni nidificate. Questo è importante. Se prendi un'eccezione profondamente annidata che qualcun altro sta cercando di gestire, hai appena sparato al tuo collega programmatore (forse anche a te!) Nel piede.
  2. I test diventano non ovvi. Un blocco di codice che ha un'eccezione potrebbe avere una delle molte cose sbagliate. Un booleano, d'altra parte, mentre teoricamente potrebbe essere fastidioso eseguire il debug, in genere non lo è. Ciò è particolarmente vero in quanto gli try...catchaderenti al flusso di controllo generalmente (secondo la mia esperienza) non seguono la filosofia "minimizza il codice nel blocco try".
  3. Non consente un else ifblocco. Ho detto abbastanza (e se qualcuno risponde con un "Ma potrebbero usare classi di eccezioni diverse", la mia risposta è "Vai nella tua stanza e non uscire finché non hai pensato a quello che hai detto.")
  4. È grammaticalmente fuorviante. Exception, per il resto del mondo (come se non aderisse alla try...catchfilosofia del flusso di controllo), significa che qualcosa è entrato in uno stato instabile (sebbene forse recuperabile). Gli stati instabili sono CATTIVI e dovrebbe tenerci svegli tutti di notte se in realtà abbiamo eccezioni evitabili (in realtà mi fa impazzire, niente bugie).
  5. Non è conforme allo stile di codifica comune. Il nostro lavoro, sia con il nostro codice che con la nostra interfaccia utente, è rendere il mondo il più ovvio possibile. try...catchcontrol-flow va contro quelle che sono comunemente considerate le migliori pratiche. Ciò significa che ci vuole più tempo perché qualcuno di nuovo in un progetto apprenda il progetto, il che significa un aumento del numero di ore-uomo senza alcun guadagno.
  6. Ciò porta spesso alla duplicazione del codice. Infine, i blocchi, sebbene ciò non sia strettamente necessario, devono risolvere tutti i puntatori pendenti che sono stati lasciati aperti dal blocco try interrotto. Quindi prova i blocchi. Questo significa che puoi avere un try{ obj.openSomething(); /*something which causes exception*/ obj.doStuff(); obj.closeSomething();}catch(Exception e){obj.closeSomething();}. In uno if...elsescenario più tradizionale, closeSomething()è meno probabile (di nuovo, esperienza personale) essere un lavoro di copia e incolla. (Certo, questo particolare argomento ha più a che fare con le persone che ho incontrato che con la stessa filosofia).

AFAIK, # 6 è un problema solo in Java, che, a differenza di qualsiasi altro linguaggio moderno, non ha né chiusure né gestione delle risorse basata su stack. Non è certamente un problema inerente all'uso delle eccezioni.
Kevin Cline,

4 e 5 sembrano essere lo stesso punto per me. 3-6 non applicare se la tua lingua è Python. 1 e 2 sono potenziali problemi, ma penso che vengano gestiti minimizzando il codice nel blocco try.
Winston Ewert,

1
@ Winston Non sono d'accordo. 1 e 2 sono problemi importanti che, seppur diminuiti da standard di codifica migliori, si chiedono ancora se potrebbe non essere meglio semplicemente evitare il potenziale errore. 3 si applica se la tua lingua è Python. 4-5 possono anche richiedere Python ma non è necessario. 4 e 5 sono punti molto diversi: il fuorviante è diverso dalle differenze stilistiche. Il codice fuorviante potrebbe fare qualcosa come nominare il grilletto per avviare un veicolo il "pulsante di arresto". Stilisticamente, d'altra parte, potrebbe anche essere analogo evitare tutti i rientri di blocco in C.
cwallenpoole,

3) Python ha un blocco else per le eccezioni. È di grande aiuto per evitare i problemi che normalmente si presentano per 1 e 2.
Winston Ewert,

1
Giusto per essere chiari, non sto sostenendo che l'uso delle eccezioni sia il tuo meccanismo generale di controllo del flusso. Sostengo il lancio di un'eccezione per qualsiasi tipo di modalità "fallimento", non importa quanto minore. Penso che la versione di gestione delle eccezioni sia più pulita e meno probabilità di nascondere i bug rispetto alla versione di controllo del valore restituito. E idealmente, vorrei che le lingue rendessero più difficile rilevare errori nidificati.
Winston Ewert,

4

Il mio argomento principale è che l'uso di try / catch per la logica interrompe il flusso logico. Seguire la "logica" attraverso costrutti non logici è (per me) contro-intuitivo e confuso. Sono abituato a leggere la mia logica come "if Condition then A else B". Leggere la stessa frase di "Prova A cattura quindi esegui B" sembra strano. Sarebbe ancora più strano se l'istruzione Afosse un semplice compito, richiedendo un codice aggiuntivo per forzare un'eccezione se conditionè falso (e farlo probabilmente richiederebbe ifcomunque una dichiarazione).


2

Bene, solo una questione di etichetta, prima di "iniziare una discussione con loro", come dici tu, chiederei gentilmente "Perché usano la gestione delle eccezioni in tutti questi diversi luoghi?"

Voglio dire, ci sono diverse possibilità:

  1. sono incompetenti
  2. c'è una ragione perfettamente valida per quello che hanno fatto, che potrebbe non apparire a prima vista.
  3. a volte è una questione di gusti e può semplificare un flusso di programma complesso.
  4. È una reliquia del passato che nessuno è ancora cambiato e sarebbero felici se qualcuno lo facesse

... Penso che tutti questi siano ugualmente probabili. Quindi basta chiedere loro bene.


1

Try Catch deve essere utilizzato solo per la gestione delle eccezioni. Inoltre, gestione delle eccezioni specifica. Il tuo tentativo di cattura dovrebbe catturare solo le eccezioni previste, altrimenti non è ben formato. Se hai bisogno di usare una cattura, prova a catturare, probabilmente stai facendo qualcosa di sbagliato.

MODIFICARE:

Motivo: si può sostenere che se si utilizza try catch come operatore condizionale, in che modo verranno considerate le eccezioni REALI?


2
L'OP chiede ragioni / argomenti. Non ne hai fornito nessuno.
Oded,

"Puoi sostenere che se usi try catch come operatore condizionale, come spiegherai le eccezioni REALI?" - Catturando quelle eccezioni reali in una catchclausola diversa da quella che rileva eccezioni "non reali"?

@delnan - Grazie! hai dimostrato un punto che stavo per sollevare. Se una persona rispondesse a ciò che hai appena detto sia alla mia ragione che a quella di Oded, smetterei semplicemente di parlare perché non sta cercando ragioni per cui è solo testardo e non perdo tempo con testardaggine.
AJC,

Mi stai chiamando studdborn perché metto in evidenza alcuni difetti in alcuni degli argomenti elencati qui? Nota come non ho nulla da dire contro altri punti indicati qui. Non sono un fan dell'uso delle eccezioni per il controllo del flusso (anche se non condannerò nulla senza dettagli, quindi la mia richiesta di elaborazione da parte dell'OP), sto semplicemente recitando la parte del difensore del diavolo.

1
L'uso di try-catch come operatore condizionale non impedisce in alcun modo che funzioni anche per rilevare eccezioni "reali".
Winston Ewert,

1

Le eccezioni sono per quando succedono cose eccezionali. Il programma funziona secondo un flusso di lavoro regolare eccezionale?


1
Ma perché? Il punto della domanda è perché (o perché no) le eccezioni vanno bene solo per quello.
Winston Ewert,

Non sempre "eccezionale". Non trovare una chiave in una tabella hash non è così eccezionale, per esempio, eppure la libreria OCaml tende a sollevare un'eccezione in questi casi. Ed è ok - la gestione delle eccezioni è molto economica lì.
SK-logic,

1
-1: dichiarazione tautologica
Biscotti con farina di riso,

1

Motivo per utilizzare le eccezioni:

  1. Se dimentichi di cogliere una circostanza eccezionale, il tuo programma morirà e la traccia dello stack ti dirà esattamente perché. Se si dimentica di gestire il valore restituito per una circostanza di eccezione, non è possibile stabilire fino a che punto il programma presenterà un comportamento errato.
  2. L'uso dei valori di ritorno funziona solo se è possibile restituire un valore sentinella. Se tutti i possibili valori di ritorno sono già validi, cosa hai intenzione di fare?
  3. Un'eccezione porterà ulteriori informazioni su ciò che è accaduto che potrebbero essere utili.

Motivi per non utilizzare le eccezioni:

  1. Molte lingue non sono progettate per rendere veloci le eccezioni.
  2. Le eccezioni che viaggiano su più livelli nella pila possono lasciare uno stato incoerente sulla loro scia

Alla fine:

L'obiettivo è scrivere codice che comunichi ciò che sta succedendo. Le eccezioni possono aiutare / ostacolare ciò a seconda di cosa sta facendo il codice. Un tentativo / cattura in Python per un KeyError su un riferimento del dizionario è perfettamente (fintanto che conosci la lingua) provare / catturare per lo stesso KeyError a cinque livelli di funzione è pericoloso.


0

Uso try-> catch come controllo di flusso in determinate situazioni. Se la tua logica primaria dipende da qualcosa che fallisce, ma non vuoi semplicemente lanciare un'eccezione ed uscire ... Ecco a cosa serve un blocco try-> catch. Scrivo molti script unix lato server non monitorati, ed è molto più importante per loro non fallire, di quanto non lo sia per loro graziosamente.

Quindi prova il piano A, e se il piano A muore, prendi e corri con il piano B ... E se il piano B fallisce, usa infine per dare il via al piano C, che risolverà uno dei servizi falliti in A o B, oppure pagina me.


0

Dipende dalla lingua e forse dall'implementazione utilizzata. Lo standard in C ++ è che le eccezioni dovrebbero essere salvate per eventi davvero eccezionali. D'altra parte, in Python una delle linee guida è " è più facile chiedere perdono che autorizzazione ", quindi è incoraggiata l'integrazione di try / tranne blocchi nella logica del programma.


"Lo standard in C ++ è che le eccezioni dovrebbero essere salvate per eventi davvero eccezionali." Questo non ha senso. Anche l'inventore della lingua non è d'accordo con te .
arayq2,

-1

È una cattiva abitudine, ma lo faccio di tanto in tanto in Python. Come invece di verificare se esiste un file, provo solo a eliminarlo. Se genera un'eccezione, prendo e vado avanti sapendo che non c'era. <== (non necessariamente vero se un altro utente possiede il file, ma lo faccio nella home directory di un utente in modo che NON DOVREBBE accadere).

Tuttavia, abusato è una cattiva pratica.


3
In questo esempio, usare le eccezioni invece di "guardare prima di saltare" è in realtà una buona idea: evita le condizioni di gara. Un controllo per la disponibilità dei file (esistenza, autorizzazioni, non essere bloccati) non significa che un successivo - anche se sarà solo dopo alcuni codici operativi - l'accesso (apertura, eliminazione, spostamento, blocco, qualunque cosa) avrà successo, poiché il file potrebbe essere modificato (cancellato, spostato, cambiate le sue autorizzazioni) in mezzo.

Se python lancia un'eccezione solo perché non esiste un file, sembra incoraggiare cattive abitudini, come descrive Delnan.
Per Johansson,

@delnan: Anche se vero, IMHO se è probabile che si verifichino tali condizioni di gara, dovresti ripensare il tuo progetto. In tal caso, c'è chiaramente una mancanza di separazione dei problemi nei sistemi. A meno che non abbiate ottime ragioni per fare le cose in questo modo, dovreste o unificare l'accesso da parte vostra o utilizzare un database adeguato per gestire più client che provano a eseguire transazioni sugli stessi dati persistenti.
back2dos,

1
Questa non è una cattiva abitudine. È lo stile consigliato in Python.
Winston Ewert,

@ back2dos, puoi impedirlo quando hai a che fare con risorse esterne come altri utenti del database / file system?
Winston Ewert,

-1

Mi piace il modo in cui Apple lo definisce : le eccezioni sono solo per errori del programmatore e errori fatali di runtime. Altrimenti usa i codici di errore. Mi aiuta a decidere quando usarlo, pensando a me stesso "È stato un errore del programmatore?"


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.