Qual è un "buon numero" di eccezioni da implementare per la mia biblioteca?


20

Mi sono sempre chiesto quante diverse classi di eccezioni dovrei implementare e lanciare per vari pezzi del mio software. Il mio sviluppo particolare è generalmente correlato a C ++ / C # / Java, ma credo che questa sia una domanda per tutte le lingue.

Voglio capire qual è un buon numero di diverse eccezioni da lanciare e cosa la comunità degli sviluppatori si aspetta da una buona biblioteca.

I compromessi che vedo includono:

  • Altre classi di eccezioni possono consentire livelli molto precisi di gestione degli errori per gli utenti API (inclini alla configurazione dell'utente o errori di dati o file non trovati)
  • Più classi di eccezioni consentono di incorporare nell'eccezione informazioni specifiche sull'errore, anziché solo un messaggio di stringa o un codice di errore
  • Più classi di eccezioni possono comportare una maggiore manutenzione del codice
  • Più classi di eccezioni possono significare che l'API è meno accessibile agli utenti

Gli scenari in cui desidero comprendere l'utilizzo delle eccezioni includono:

  • Durante la fase di "configurazione", che potrebbe includere il caricamento di file o l'impostazione di parametri
  • Durante una fase di tipo "operazione" in cui la libreria potrebbe eseguire attività e svolgere attività, magari in un altro thread

Altri schemi di segnalazione errori senza l'utilizzo di eccezioni o meno eccezioni (come confronto) potrebbero includere:

  • Meno eccezioni, ma incorporando un codice di errore che può essere utilizzato come ricerca
  • Restituzione di codici di errore e flag direttamente dalle funzioni (a volte non è possibile dai thread)
  • Implementato un evento o un sistema di callback in caso di errore (evita il riavvolgimento dello stack)

Come sviluppatori, cosa preferisci vedere?

Se ci sono MOLTE eccezioni, ti preoccupi comunque di gestirle separatamente?

Preferisci i tipi di gestione degli errori a seconda della fase operativa?



5
"l'API è meno accessibile agli utenti" - può essere gestita avendo diverse classi di eccezione che ereditano tutte da una classe base comune per l'API. Quindi, appena inizi, puoi vedere da quale API proviene l'eccezione senza doversi preoccupare di tutti i dettagli. Quando stai completando il tuo primo programma utilizzando l'API, quindi se hai bisogno di ulteriori informazioni sull'errore, inizi a guardare cosa significano effettivamente le diverse eccezioni. Ovviamente devi giocare contemporaneamente con la gerarchia delle eccezioni standard della lingua che stai usando.
Steve Jessop,

pertinente punto Steve
Fuzz,

Risposte:


18

Lo mantengo semplice.

Una libreria ha un tipo di eccezione di base esteso da std ::: runtime_error (che viene dal C ++ si applica come appropriato ad altre lingue). Questa eccezione accetta una stringa di messaggio in modo che possiamo accedere; ogni punto di lancio ha un messaggio unico (di solito con un ID univoco).

Questo è tutto.

Nota 1 : nelle situazioni in cui qualcuno che rileva l'eccezione può correggere le eccezioni e riavviare l'azione. Aggiungerò eccezioni derivate per cose che possono essere potenzialmente risolte in modo univoco in una posizione remota. Ma questo è molto raro (ricorda che è improbabile che il ricevitore sia vicino al punto di lancio, quindi risolvere il problema sarà difficile (ma tutto dipende dalla situazione)).

Nota 2 : a volte la libreria è così semplice che non vale la pena darla con una propria eccezione e std :: runtime_error lo farà. È importante avere un'eccezione solo se la capacità di distinguerla da std :: runtime_error può fornire all'utente informazioni sufficienti per farci qualcosa.

Nota 3 : All'interno di una classe di solito preferisco i codici di errore (ma questi non sfuggiranno mai all'API pubblica della mia classe).

Guardando i tuoi compromessi:

I compromessi che vedo includono:

Altre classi di eccezioni possono consentire livelli molto precisi di gestione degli errori per gli utenti API (inclini alla configurazione dell'utente o errori di dati o file non trovati)

Più eccezioni ti danno davvero un controllo più preciso del grano? La domanda diventa: il codice catching può davvero correggere l'errore in base all'eccezione. Sono sicuro che ci sono situazioni del genere e in questi casi dovresti avere un'altra eccezione. Ma tutte le eccezioni che hai elencato sopra l'unica correzione utile è quella di generare un grande avviso e interrompere l'applicazione.

Più classi di eccezioni consentono di incorporare nell'eccezione informazioni specifiche sull'errore, anziché solo un messaggio di stringa o un codice di errore

Questa è un'ottima ragione per usare le eccezioni. Ma le informazioni devono essere utili alla persona che le sta memorizzando nella cache. Possono utilizzare le informazioni per eseguire alcune azioni correttive? Se l'oggetto è interno alla tua libreria e non può essere utilizzato per influenzare nessuna delle API, le informazioni sono inutili. Devi essere molto preciso sul fatto che le informazioni generate hanno un valore utile per la persona che può catturarle. La persona che li cattura di solito si trova all'esterno dell'API pubblica, quindi personalizza le tue informazioni in modo che possano essere utilizzate con le cose nell'API pubblica.

Se tutto ciò che possono fare è registrare l'eccezione, allora è meglio semplicemente lanciare un messaggio di errore piuttosto che molti dati. Poiché il ricevitore genererà di solito un messaggio di errore con i dati. Se si genera il messaggio di errore, questo sarà coerente tra tutti i catcher, se si consente al catcher di creare il messaggio di errore, è possibile ottenere lo stesso errore riportato in modo diverso a seconda di chi sta chiamando e intercettando.

Meno eccezioni, ma incorporando un codice di errore che può essere utilizzato come ricerca

Devi determinare il tempo in cui il codice di errore può essere utilizzato in modo significativo. Se è possibile, dovresti avere la sua eccezione. Altrimenti i tuoi utenti ora devono implementare le istruzioni switch all'interno del catch (che sconfigge l'intero punto di avere il catch gestire automaticamente le cose).

Se non è possibile, perché non utilizzare un messaggio di errore nell'eccezione (non è necessario dividere il codice e il messaggio rende difficile cercare).

Restituzione di codici di errore e flag direttamente dalle funzioni (a volte non è possibile dai thread)

Restituire i codici di errore è ottimo internamente. Ti consente di correggere i bug lì e poi e devi assicurarti di correggere tutti i codici di errore e tenerne conto. Ma farle passare attraverso l'API pubblica è una cattiva idea. Il problema è che i programmatori spesso dimenticano di verificare gli stati di errore (almeno con un'eccezione un errore non controllato costringerà l'applicazione a chiudere un errore non gestito in genere corromperà tutti i tuoi dati).

Implementato un evento o un sistema di callback in caso di errore (evita il riavvolgimento dello stack)

Questo metodo viene spesso utilizzato insieme ad altri meccanismi di gestione degli errori (non in alternativa). Pensa al tuo programma Windows. Un utente avvia un'azione selezionando una voce di menu. Questo genera un'azione sulla coda degli eventi. La coda degli eventi alla fine assegna un thread per gestire l'azione. Il thread dovrebbe gestire l'azione e infine tornare al pool di thread e attendere un'altra attività. Qui un'eccezione deve essere rilevata alla base dal thread assegnato al lavoro. Il risultato della cattura dell'eccezione di solito comporta la generazione di un evento per il ciclo principale che alla fine comporterà la visualizzazione di un messaggio di errore per l'utente.

Ma a meno che tu non possa continuare di fronte all'eccezione, lo stack si svolgerà (almeno per il thread).


+1; Comunque "Altrimenti i tuoi utenti ora devono implementare le istruzioni switch all'interno del catch (che sconfigge l'intero punto di avere il catch gestire automaticamente le cose)." - Svolgimento dello stack delle chiamate (se si è ancora in grado di usarlo) e la gestione forzata degli errori sono ancora grandi vantaggi. Ma certamente toglierà la capacità di svolgere lo stack di chiamate quando devi catturarlo nel mezzo e ricominciare.
Merlyn Morgan-Graham,

9

Di solito inizio con:

  1. Una classe di eccezione per i bug degli argomenti . Ad esempio "argomento non può essere nullo", "argomento deve essere positivo" e così via. Java e C # hanno classi predefinite per quelli; in C ++ di solito creo solo una classe, derivata da std :: exception.
  2. Una classe di eccezione per i bug precondizionati . Quelli sono per test più complessi, come "indice deve essere più piccolo della dimensione".
  3. Una classe di eccezione per i bug di asserzione . Quelli servono per controllare lo stato per metodi a metà coerenza. Ad esempio, quando si scorre su un elenco per contare elementi negativi, zero o positivi, alla fine quei tre dovrebbero sommarsi alla dimensione.
  4. Una classe di eccezione di base per la libreria stessa. All'inizio, lancia questa classe. Solo quando se ne presenta la necessità, iniziare ad aggiungere sottoclassi.
  5. Preferisco non concludere eccezioni, ma so che le opinioni differiscono su questo punto (ed è molto correlata al linguaggio usato). Se esegui il wrapping, avrai bisogno di ulteriori classi di wrapping .

Poiché le classi per i primi 3 casi sono ausili per il debug, non devono essere gestite dal codice. Invece, dovrebbero essere catturati solo da un gestore di livello superiore che visualizza le informazioni in modo tale che l'utente possa copiarle e incollarle allo sviluppatore (o ancora meglio: premere il pulsante "invia rapporto"). Quindi includi informazioni utili per lo sviluppatore: file, funzione, numero di riga e alcuni messaggi che identificano chiaramente quale controllo non è riuscito.

Poiché i primi 3 casi sono gli stessi per ogni progetto, in C ++ di solito li copio dal progetto precedente. Poiché molti stanno facendo esattamente lo stesso, i progettisti di C # e Java hanno aggiunto classi standard per quei casi alla libreria standard. [AGGIORNAMENTO:] Per i programmatori pigri: una classe potrebbe essere sufficiente e con un po 'di fortuna la tua libreria standard ha già una classe di eccezione adatta. Preferisco aggiungere informazioni come nomefile e lino numero, che le classi predefinite in C ++ non forniscono. [Fine aggiornamento]

A seconda della libreria, il quarto caso potrebbe avere una sola classe o diventare una manciata di classi. Preferisco che l'approccio agile inizi in modo semplice, aggiungendo sottoclassi quando se ne presenta la necessità.

Per un'argomentazione dettagliata sul mio quarto caso, vedi la risposta di Loki Astari . Sono totalmente d'accordo con la sua risposta dettagliata.


+1; C'è una netta differenza tra il modo in cui si dovrebbero scrivere le eccezioni di scrittura in un framework rispetto a come si dovrebbero scrivere le eccezioni in un'applicazione. La distinzione è un po 'confusa, ma te ne accenni (rimandando a Loki per il caso della biblioteca).
Merlyn Morgan-Graham,
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.