Dovremmo prendere solo in circostanze eccezionali?

0

Se la gestione degli errori lanciando eccezioni è buona o cattiva è controverso.

Sono eccezioni come flusso di controllo considerato un serio antipattern? In tal caso, perché?

La linea comune è che le eccezioni sono per "circostanze eccezionali". Ma cosa succede se un autore di una biblioteca ha deciso di lanciare un'eccezione? L'autore della biblioteca non può prevedere quale potrebbe essere una "circostanza eccezionale" per noi.

Ovviamente, se non possiamo escludere un'eccezione generata da una funzione che dobbiamo usare così com'è e che consente l'eccezione di ribollire fino alla cima è inaccettabile, allora dobbiamo catturare l'eccezione .

Spesso, tuttavia, possiamo adottare misure preventive. Con uno sforzo sufficiente potremmo persino essere in grado di garantire che le condizioni in cui la funzione della libreria genererebbe un'eccezione non si verificherà mai. Il lancio sarà ancora lì (non è una nostra scelta), ma possiamo eliminare la necessità di provare e catturare.

Un esempio è std::map.at() . (Fai finta che non ci sia std::map.find() .)

Qual è la saggezza generale? È di buon gusto scrivere codice con l'aspettativa che una funzione di libreria occasionalmente venga lanciata? Il codice cliente è obbligato a ridurre al minimo l'uso di eccezioni?

Modifica Per cercare di distinguerlo di più dalle domande passate e di essere più concreti, supponiamo di avere qualche funzione di libreria che potrebbe generare un errore non trovato nel file. Alcuni codici client vogliono utilizzare la libreria per accedere a un file. Il codice client viene programmato con l'aspettativa che il file a volte non esista, anche in "circostanze non eccezionali". Il codice client può essere in grado di verificare se la funzione della libreria genererà un'eccezione prima di essere chiamata, ma non è banale farlo. Il codice client deve ammucchiare ulteriori errori durante il controllo degli errori all'interno della libreria o il codice client deve utilizzare l'eccezione generata dalla libreria come flusso di controllo?

In tal caso aggiungere codice di gestione degli errori in cima alla libreria sarà quasi certamente meno efficace di un errore, richiederà meno codice e si potrebbe sostenere che deve provare / Prendi comunque. D'altra parte è un meccanismo di controllo del flusso decisamente non eccezionale.

    
posta Praxeolitic 14.08.2014 - 23:20
fonte

6 risposte

6

The common line is that exceptions are for "exceptional circumstances".

Bene, è sbagliato.

Catching è sicuramente qualcosa che potresti voler fare raramente, ma il lancio di eccezioni non è affatto per circostanze eccezionali. Devi tirare ogni volta che una condizione di run-time significa che non puoi adempiere al tuo contratto.

C'è una grande via di mezzo tra "Usare le eccezioni per sostituire if " e "Le eccezioni sono solo per circostanze eccezionali".

Oh, e contratti che significano "Ogni chiamante deve controllare ogni volta l'errore", come i codici di ritorno, sono ampiamente peggio di "Usare le eccezioni per sostituire if ".

    
risposta data 14.08.2014 - 23:54
fonte
4

Gestisci le eccezioni che puoi gestire e lascia che le altre facciano bolle in alto.

Quando una funzione di libreria genera un'eccezione FileNotFound e si dispone di un modo per gestire delicatamente tale eccezione. Fallo, ma la stessa funzione potrebbe anche generare un'eccezione IAmGoingToCorruptYourMemoryInEvilWays che non puoi gestire, quindi lasciarla in alto.

The common line is that exceptions are for "exceptional circumstances". But what if a library author has decided to throw an exception? The library author cannot predict what might be an "exceptional circumstance" for us.

Secondo me dovresti leggerlo come circostanze eccezionali all'interno di un sistema. E per la funzione di libreria il sistema è piccolo e l'atto di provare a leggere un file che non c'è è eccezionale. Nel sistema più grande in cui viene utilizzata la funzione di libreria, il file che non è presente potrebbe essere qualcosa che ci si può aspettare, quindi si gestisce l'eccezione FileNotFound in quel sistema.

    
risposta data 15.08.2014 - 00:30
fonte
2

What is the general wisdom? Is it in good taste to write code with the expectation that a library function will occasionally throw?

Penso che tu non capisca come vengono utilizzate le eccezioni.

Ciò che è importante capire è: non puoi ignorare le eccezioni. In una normale esecuzione del programma, si verifica un'eccezione non .

Se non deve accadere, allora perché usare le eccezioni?

  1. per catturare un bug - qualcuno ha implementato qualcosa, che collide con altre parti del software. Forse impostare una costante in modo errato.
  2. per rilevare i parametri non validi: se l'utente immette un valore errato, che non è selezionato, che cosa fai? Forse qualcosa fuori portata, o stringa al posto di un numero.

In such a case adding error handling code on top of the library will almost certainly be less efficient than catching an error, will require less coding, and it could be argued that you must have a try/catch anyway.

Ancora una volta, dimostra che proviene da una persona che non sa quali sono le eccezioni.

Le eccezioni vengono lanciate nei luoghi in cui si verifica l'errore e catturate in punti in cui sai come gestire l'errore. Questo separa l'esecuzione del programma in due:

  • percorso di esecuzione normale
  • percorso di propagazione degli errori - quando si verifica un errore

In questo modo puoi concentrarti sulla normale esecuzione del programma, ignorando il percorso dell'errore. Quando si verifica un errore, genera un'eccezione e prosegui.

Poiché non è necessario controllare il codice di errore, il programma sarà più piccolo, più efficiente e più facile da mantenere e unit test. Gli ultimi due elementi sono particolarmente utili per gli sviluppatori.

Per quanto riguarda le prestazioni, il programma con l'uso di eccezioni sarà migliore o uguale al programma senza eccezioni, a causa di meccanismo a costo zero . L'unica cosa che viene pagata è un po 'aumento nell'aumento della dimensione del programma / della biblioteca.

Ovviamente, lanciare un'eccezione è costosa, ma non importa. Se il programma non può continuare, non importa quanto tempo ci vuole per risolvere l'errore. D'altra parte, con i codici di errore, paghi per ogni controllo.

    
risposta data 15.08.2014 - 13:55
fonte
1

Per me è tutta questione di coerenza dello strato di astrazione: se lanci qualcosa ti sorprendi nello stesso contesto probabilmente stai abusando. Se stai lanciando a causa di qualcosa che, al tuo livello attuale, non sai come gestire, il lancio è la strada giusta da percorrere.

Chi ti ha chiamato probabilmente sapeva PERCHÉ ha chiamato, e quindi sa anche cosa fare se qualcosa va storto.

Ora, a proposito del problema: il punto corretto da prendere è quello con il motivo per cui l'errore può essere compreso e il problema può essere risolto delicatamente.

Supponiamo di scrivere una funzione per manipolare un file: se il file non è presente o se non lo trovi nel formato previsto, poiché non sei stato tu a fornire il file, non puoi sapere perché è successo. Quindi, invece di tentare da solo una soluzione (che può non essere ciò che il tuo interlocutore desiderava) basta segnalare il problema.

Supponiamo che tu sia uno che chiama una funzione che deve manipolare un file: se sei stato tu a ottenere il nome del file da parte dell'utente, e il file viene rilevato come non presente o non come previsto, tu dovrebbe prendere e chiedere di nuovo ed eventualmente ammettere una risposta del tipo "non fare niente e lasciarmi andare". Se invece il nome del file ti è già stato dato da qualche parte, dovresti lasciare che l'eccezione salga.

Un altro caso tipico è una funzione che richiede la crescita di un contenitore. Se l'allocazione della memoria fallisce, l'allocatore lancia. Il contenitore - a quel punto - deve reagire per mettersi in uno stato consistente (senza puntatori penzolanti o strutture interne semicostruite), ma deve rilanciare poiché non può risolvere il problema della memoria mancante.

Probabilmente il problema si verifica in un punto in cui è possibile che venga respinta un'altra memoria (un'operazione ripetuta) o che l'utente venga informato del problema per dargli la possibilità di ottenere più memoria per l'attività effettiva (per esempio chiudendo qualche altra attività o applicazione) e ritenta il comando non riuscito.

    
risposta data 15.08.2014 - 11:01
fonte
0

Java è andato molto oltre e ha reso Eccezioni parte del metodo contratto e, IMHO, va bene. focalizza il tuo pensiero sulle eccezioni. Quando chiami un metodo, sai esattamente quali eccezioni potrebbero generare, perché il metodo ti dice quali sono e il compilatore Java insiste sul fatto che ti occupi di loro - prendili e gestiscili, o modifica il tuo contratto di metodo, dichiarando che "tu" potresti lanciare queste eccezioni ai tuoi chiamanti.

Perché non lo fa esplicitamente, .Net ha incoraggiato un sacco di pigramente, lanoso pensare alle eccezioni, portando a tutta la confusione già menzionata da altri.

Un metodo dovrebbe generare un'eccezione quando viene chiesto di fare qualcosa che non può.
La programmazione difensiva può solo darti tanta protezione - puoi "pre-testare" le condizioni quanto vuoi, ma alla fine devi solo chiamare "quel metodo" e puoi ancora ottenere "cose strane" "accadendo (eccezioni di sistema, condizioni di gara, ecc. ecc.) tra l'ultimo controllo e effettivamente cercando di fare il lavoro.

Dovresti solo rilevare un'eccezione quando il tuo codice può fare qualcosa di utile su di esso. Troppi sviluppatori "finiscono" i loro programmi e poi chiedono dei gestori di eccezioni "globali" - catturando e registrando l'eccezione [intera!] Su un file appena prima l'applicazione si arriccia e muore. OK, è un backstop, ma è solo "utile" fino a un certo punto. Cattura quella FileNotFoundException e prova a leggere un file diverso, tutto senza che l'utente sappia che è successo, forse è un po ' più "utile".

    
risposta data 15.08.2014 - 13:46
fonte
0

Le eccezioni dovrebbero essere per i percorsi di controllo eccezionali, nel senso che, a meno che non ci sia un errore esterno oltre il controllo del programma (utenti che interrompono un pulsante di interruzione, file corrotti che non devono essere corrotti, ecc.), dovrebbe operare attraverso il non- percorsi eccezionali. L'EH a costo zero cementa ulteriormente questa idea in pietra rendendo estremamente costosi i percorsi eccezionali in cambio di percorsi non eccezionali praticamente gratuiti.

Tuttavia, un implementatore di librerie in realtà dà forma all'idea del cliente di cosa non dovrebbe accadere in un regolare flusso di controllo. Se una funzione di libreria per aprire un file può generare un errore di FileNotFound , significa che il client non deve tentare, in nessun caso che possano controllare, di aprire file che non sono stati trovati. Non dovrebbero provare a aprire i file a sinistra ea destra, volenti o nolenti, e fare affidamento sul sistema di gestione delle eccezioni per scoprire cosa esistono e cosa no.

Detto questo, l'esecutore della biblioteca ha anche una responsabilità in questo caso. Poiché hanno deciso che l'apertura di un file che non esiste è un errore di input esterno che throws è un'eccezione enormemente costosa, e quindi non qualcosa che dovrebbe mai accadere in circostanze ordinarie, direi che è anche loro responsabilità assicurarsi che il il cliente può facilmente evitare di fare un simile tentativo attraverso i normali percorsi di controllo non eccezionali.

Quindi la libreria potrebbe fornire, ad esempio, un metodo can_open che è destinato a essere chiamato prima di tentare di utilizzare il metodo open (a meno che il client stesso non richieda il file per funzionare normalmente, a quel punto possono chiamare direttamente open direttamente, e il fatto che il metodo open possa throw sarebbe riservato per quelle circostanze straordinarie in cui il file potrebbe essere cancellato tra il controllo per vedere se can_open e poi provando a open esso. Queste dovrebbero essere circostanze eccezionali.

Design della biblioteca

Dal punto di vista del progettista di librerie, se una funzione open dovrebbe lanciare o restituire uno stato di successo / fallimento si baserà su modelli di utilizzo previsti.

Scenario A: Se prevedi che la tua libreria sarà utilizzata nei casi in cui è un evento molto comune per un file che viene richiesto di essere aperto per non esistere, restituire un codice di stato ha molto più senso rispetto al lancio da un file non esistente sarebbe un evento comune.

Scenario B: se prevedi che la tua libreria sarà utilizzata da persone che spesso richiedono un file specifico per funzionare normalmente, come un videogioco che richiede un file "game.dat" per essere eseguito correttamente, quindi ha più senso progettare una funzione open che gira quando il file non è lì e fornire qualcosa come un metodo can_open per le persone nello Scenario Un gruppo sopra il quale sarebbe il caso raro.

Qui non è un grosso problema in ogni caso e una specie di sballottamento, ma prova a progettare il caso d'uso comune se puoi anticiparlo.

    
risposta data 10.12.2017 - 20:07
fonte

Leggi altre domande sui tag