Buon uso di catch-block?


15

Mi trovo sempre a lottare con questo ... cercando di trovare il giusto equilibrio tra provare / catturare e il codice che non diventa questo osceno pasticcio di schede, parentesi ed eccezioni che vengono rigirate nello stack di chiamate come una patata bollente. Ad esempio, ho un'app che sto sviluppando in questo momento che utilizza SQLite. Ho un'interfaccia di database che estrae le chiamate SQLite e un modello che accetta le cose per entrare / uscire dal database ... Quindi se / quando si verifica un'eccezione SQLite, deve essere lanciato al modello (che l'ha chiamato ), che deve passarlo a chiunque abbia chiamato AddRecord / DeleteRecord / qualunque cosa ...  

Sono un fan delle eccezioni rispetto alla restituzione dei codici di errore perché i codici di errore possono essere ignorati, dimenticati, ecc., Mentre un'eccezione deve essenzialmente essere gestita (garantito, potrei catturare e passare immediatamente ...) Sono certo che deve esserci un modo migliore di quello che sto succedendo adesso.

Modifica:  avrei dovuto formularlo in modo leggermente diverso. Capisco di respingere come tipi diversi e simili, l'ho espresso male e questa è colpa mia. La mia domanda è ... come si fa a mantenere pulito il codice quando lo si fa? Comincia a sentirmi estremamente disordinato dopo un po '.


Quale linguaggio di programmazione?
Apalala,

2
C # al momento, ma sto cercando di pensare in generale.
trycatch

C # non forza la dichiarazione di eccezioni emessa, il che rende più semplice gestire le eccezioni laddove è ragionevole ed evita che i programmatori siano tentati di catturarle senza gestirle effettivamente. Anders Hejlsberg, designer di C #, fa valere le
Apalala

Risposte:


14

Pensalo in termini di digitazione forte, anche se non stai usando un linguaggio fortemente tipizzato - se il tuo metodo non è in grado di restituire il tipo che ti aspettavi, dovrebbe generare un'eccezione.

Inoltre, piuttosto che lanciare SQLException fino al modello (o peggio, UI), ogni livello dovrebbe catturare le eccezioni note e avvolgerle / mutarle / sostituirle con eccezioni adatte a quel livello:

Layer      Handles Exception
----------------------------
UI         DataNotFoundException
Model      DatabaseRetrievalException
DAO        SQLException

Questo dovrebbe aiutare a limitare il numero di eccezioni che stai cercando in ogni livello e ti aiuta a mantenere un sistema di eccezioni organizzato.


Ho apportato alcune modifiche, ho scritto male la Q originale ... la mia domanda è più su come mantenere pulito il codice mentre gestisco tutto il tentativo e la cattura e quant'altro. Inizia a
sembrare

1
@Ed non ti dà fastidio che la tua UI o Model stia rilevando "SQLException"? Per me non è molto rilevante. A ciascuno il suo, immagino.
Nicole,

1
@Ed, potrebbe essere un po 'OK nelle lingue che non hanno verificato le eccezioni, ma nelle lingue con le eccezioni controllate è davvero brutto avere throws SQLExceptionun metodo che non implica che sia coinvolto anche SQL. E cosa succede se decidi che alcune operazioni dovrebbero andare in un archivio di file? Ora devi dichiarare throws SQLException, IOException, ecc. Ti sfuggirà di mano.
Mike Daniels,

1
@Ed per l'utente finale SQLException non significa molto, soprattutto se il sistema può supportare più tipi di persistenza oltre ai database relazionali. Come programmatore di gui, preferirei avere a che fare con DataNotFoundException piuttosto che un intero insieme di eccezioni di basso livello. Molto spesso un'eccezione a una libreria di basso livello è solo una parte della normale vita di cui sopra, in questo caso sono molto più facili da gestire quando il loro livello di astrazione corrisponde a quello dell'applicazione.
Newtopian

1
@Newtopian - Non ho mai detto di presentare l'eccezione grezza all'utente finale. Per gli utenti finali, è sufficiente un semplice messaggio "Il programma ha smesso di funzionare" mentre si registra l'eccezione da qualche parte utile per il supporto. In modalità Debug è utile visualizzare l'eccezione. Non dovresti preoccuparti di ogni possibile eccezione che un'API potrebbe generare a meno che tu non abbia un gestore specifico per tali eccezioni; lascia che il tuo ricevitore di eccezioni non gestite gestisca tutto.
Ed James,

9

Le eccezioni consentono di scrivere un codice più pulito perché la maggior parte di esso si occupa del caso normale e i casi eccezionali possono essere gestiti in un secondo momento, anche in un contesto diverso.

La regola per gestire (intercettare) le eccezioni è che deve essere fatta dal contesto che può effettivamente fare qualcosa al riguardo. Ma questo ha un'eccezione:

Le eccezioni devono essere rilevate ai confini del modulo (specialmente i limiti dei livelli) e anche se solo per racchiuderli si genera un'eccezione di livello superiore che ha significato per il chiamante. Ogni modulo e livello deve nascondere i dettagli di implementazione anche per quanto riguarda le eccezioni (un modulo heap può generare HeapFull ma mai ArrayIndexOutOfBounds).

Nel tuo esempio, è improbabile che i livelli superiori possano fare qualcosa per un'eccezione SQLite (se lo fanno, allora è tutto così accoppiato a SQLite che non sarai in grado di passare il livello dati a qualcos'altro). Esistono una serie di motivi prevedibili per cui cose come Aggiungi / Elimina / Aggiorna non riescono e alcune di esse (modifiche incompatibili nelle transazioni simultanee) sono impossibili da recuperare anche nel livello dati / persistenza (violazione delle regole di integrità, fi). Il livello di persistenza dovrebbe tradurre le eccezioni in qualcosa di significativo nei termini del livello del modello in modo che i livelli superiori possano decidere se riprovare o fallire con grazia.


Sono d'accordo e ho modificato la mia domanda per riflettere quella in cui l'avevo formulata male. Intendevo che questo fosse più una questione di come evitare che i tentativi e la cattura ingombrassero le cose.
trycatch

Puoi applicare i principi che io ed @ James abbiamo citato all'interno di un livello o modulo. Invece di chiamare SQLite direttamente da ogni luogo, avere alcuni metodi / funzioni che parlano a SQLite e si riprendono dalle eccezioni o li traducono in più generici. Se una transazione comporta diverse query e aggiornamenti, non è necessario gestire tutte le possibili eccezioni in ciascuna di esse: una singola prova esterna può tradurre le eccezioni e una interna può occuparsi di aggiornamenti parziali con un rollback. È inoltre possibile spostare gli aggiornamenti nella propria funzione per semplificare la gestione delle eccezioni.
Apalala,

1
questa roba sarebbe più facile da capire con esempi di codice.
Fai clic su Aggiorna

Questa era probabilmente una domanda più adatta allo stackoverflow sin dall'inizio.
Apalala,

5

Come regola generale, dovresti rilevare eccezioni specifiche (ad esempio IOException) e solo se hai qualcosa di specifico da fare una volta rilevata l'eccezione.

Altrimenti, è spesso meglio lasciare che le Eccezioni emergano in superficie in modo che possano essere esposte e gestite. Alcune persone lo chiamano fail-fast.

Dovresti avere un qualche tipo di gestore alla radice della tua applicazione per catturare le eccezioni non gestite che sono confuse dal basso. Ciò ti dà la possibilità di presentare, segnalare o gestire l'eccezione in modo adeguato.

Il wrapping delle eccezioni è utile quando è necessario generare un'eccezione in un sistema distribuito e il client non ha la definizione dell'errore sul lato server.


Catturare e mettere a tacere le eccezioni è una cosa terribile da fare. Se il programma si interrompe, dovrebbe bloccarsi con un'eccezione e un traceback. Dovrebbe confondersi nel registrare silenziosamente le cose, ma creando confusione nei dati.
S.Lott

1
@ S.Lott Sono d'accordo sul fatto che non si dovrebbe mettere a tacere l'eccezione perché li trovano fastidiosi ma solo il crash di un'applicazione è un po 'estremo. Esistono molti casi in cui è possibile rilevare l'eccezione e ripristinare il sistema in uno stato noto, in questi casi il gestore globale di tutto è abbastanza utile. Se, tuttavia, il processo di ripristino fallisce a sua volta in un modo che non può essere affrontato in modo sicuro, allora sì, lasciare che si blocchi è molto meglio che conficcare la testa nella sabbia.
Newtopian

2
di nuovo tutto dipende da cosa stai costruendo, ma in genere non sono d'accordo con il fatto che li lasci solo gonfiare perché crea una perdita di astrazione. Quando uso un'API preferirei di gran lunga vedere che mostra eccezioni che sono in sintonia con il modello dell'API. Odio anche le sorprese, quindi odio quando un'API fa semplicemente sbirciare l'eccezione relativa alla sua implementazione senza preavviso. Se non ho idea di cosa mi verrà in mente, come posso eventualmente reagire! Mi ritrovo troppo spesso a utilizzare reti di cattura molto più ampie per evitare che le sorprese possano arrestare la mia app senza preavviso.
Newtopian

@Newtopian: le eccezioni "Wrapping" e "Rewriting" riducono la perdita di astrazioni. Si gonfiano ancora in modo appropriato. "Se non ho idea di cosa mi verrà in mente come posso eventualmente reagire! Mi ritrovo troppo spesso a utilizzare reti di cattura molto più ampie" È la cosa che stiamo suggerendo di smettere di fare. Non è necessario catturare tutto. L'80% delle volte, la cosa giusta è non prendere nulla. Il 20% delle volte c'è una risposta significativa.
S.Lott

1
@Newtopian, penso che dobbiamo distinguere tra le eccezioni che normalmente verranno generate da un oggetto e quindi dovrebbero essere racchiuse, e le eccezioni che sorgono a causa di bug nel codice dell'oggetto e non dovrebbero essere racchiuse.
Winston Ewert,

4

Immagina di scrivere una classe di stack. Non inserisci alcun codice di gestione delle eccezioni nella classe, di conseguenza potrebbe produrre le seguenti eccezioni.

  1. ArrayIndexError: generato quando l'utente tenta di eseguire il pop dallo stack vuoto
  2. NullPtrException - generato a causa di un bug nell'implementazione che causava un tentativo di riferimento a un riferimento null

Un approccio semplicistico al wrapping delle eccezioni potrebbe decidere di includere entrambe queste eccezioni in una classe di eccezioni StackError. Tuttavia, questo manca davvero il punto di concludere le eccezioni. Se un oggetto genera un'eccezione di basso livello, ciò dovrebbe significare che l'oggetto è rotto. Tuttavia, esiste un caso in cui ciò è accettabile: quando l'oggetto è effettivamente rotto.

Il punto di concludere le eccezioni è che l'oggetto dovrebbe fornire le giuste eccezioni per gli errori normali. Lo stack dovrebbe generare StackEmpty non ArrayIndexError quando si estrae da uno stack vuoto. L'intento non è quello di evitare di generare altre eccezioni se l'oggetto o il codice sono rotti.

Ciò che vogliamo davvero evitare è la cattura di eccezioni di basso livello che sono state passate attraverso oggetti di alto livello. Una classe di stack che genera un ArrayIndexError quando il pop-up da uno stack vuoto è un problema minore. Se si rileva effettivamente ArrayIndexError, abbiamo un problema serio. La propagazione di errori di basso livello è un peccato molto meno grave che colpirli.

Per riportare questo al tuo esempio di SQLException: perché ricevi SQLExceptions? Uno dei motivi è perché stai inviando query non valide. Tuttavia, se il livello di accesso ai dati genera query errate, si interrompe. Non dovrebbe tentare di riavvolgere la sua interruzione in un'eccezione DataAccessFailure.

Tuttavia, potrebbe sorgere anche una SQLException a causa di una perdita di connessione al database. La mia strategia su questo punto è quella di catturare l'eccezione all'ultima linea di difesa, riferire all'utente che la connettività al database è stata persa e l'arresto. Poiché l'applicazione ha accesso in perdita al database, non c'è davvero molto altro da fare.

Non so come sia il tuo codice. Ma sembra che potresti tradurre ciecamente tutte le eccezioni in eccezioni di livello superiore. Dovresti farlo solo in un numero relativamente piccolo di casi. La maggior parte delle eccezioni di livello inferiore indicano bug nel codice. Catturarli e riavvolgerli è controproducente.

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.