Che cos'è un "buon numero" di eccezioni da implementare per la mia libreria?

19

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

Voglio capire quale sia un buon numero di diverse eccezioni da lanciare e quali sono le aspettative della comunità di sviluppatori su una buona libreria.

I trade-off che vedo includono:

  • Altre classi di eccezioni possono consentire livelli di granulosità molto fine della gestione degli errori per gli utenti API (incline alla configurazione dell'utente o errori nei dati o file non trovati)
  • Altre classi di eccezioni consentono di incorporare le informazioni specifiche dell'errore nell'eccezione, anziché solo un messaggio di stringa o un codice di errore
  • Altre classi di eccezioni possono significare più manutenzione del codice
  • Altre 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 fare del lavoro, forse in un'altra discussione

Altri modelli di segnalazione degli 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 dei codici di errore e dei flag direttamente dalle funzioni (a volte non è possibile dai thread)
  • Implementato un evento o un sistema di callback su errore (evita lo stack unwinding)

Come sviluppatori, cosa preferisci vedere?

Se ci sono MOLTE eccezioni, ti infastidiscono gli errori gestendoli separatamente comunque?

Hai una preferenza per i tipi di gestione degli errori a seconda della fase operativa?

    
posta Fuzz 06.11.2011 - 14:47
fonte

2 risposte

18

Lo tengo semplice.

Una libreria ha un tipo di eccezione di base esteso da std ::: runtime_error (cioè da C ++ si applica come appropriato ad altre lingue). Questa eccezione richiede una stringa di messaggio in modo che possiamo accedere; ogni punto di lancio ha un messaggio univoco (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 riparate potenzialmente in modo univoco in una posizione remota. Ma questo è molto raro (ricorda che è improbabile che il catcher 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 dargli una propria eccezione e lo farà std :: runtime_error. È importante avere un'eccezione solo se la capacità di distinguerlo da std :: runtime_error può fornire all'utente informazioni sufficienti per fare qualcosa con esso.

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

Guarda i tuoi compromessi:

I trade-off che vedo includono:

More exception classes can allow very fine grain levels of error handling for API users (prone to user configuration or data errors, or files not being found)

Altre eccezioni ti danno davvero un controllo più preciso? 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 è generare un grosso avvertimento e fermare l'applicazione.

More exception classes allows error specific information to be embedded in the exception, rather than just a string message or error code

Questa è un'ottima ragione per usare le eccezioni. Ma l'informazione deve essere utile alla persona che sta memorizzando la cache. Possono usare le informazioni per eseguire alcune azioni correttive? Se l'oggetto è interno alla tua libreria e non può essere utilizzato per influenzare alcuna API, l'informazione è inutile. Devi essere molto specifico sul fatto che l'informazione generata ha un valore utile per la persona che può prenderlo. La persona che la cattura è solitamente al di fuori dell'API pubblica, quindi personalizza le tue informazioni in modo che possano essere utilizzate con le cose nella tua API pubblica.

Se tutto ciò che possono fare è registrare l'eccezione, allora è meglio lanciare un messaggio di errore piuttosto che molti dati. Poiché il catcher di solito genera un messaggio di errore con i dati. Se crei il messaggio di errore, sarà coerente su tutti i catcher, se permetti al catcher di generare il messaggio di errore potresti ottenere lo stesso errore riportato in modo diverso a seconda di chi sta chiamando e catturando.

Less exceptions, but embedding an error code that can be used as a lookup

Devi determinare il tempo in cui il codice di errore può essere usato in modo significativo. Se può, allora dovresti avere una sua eccezione. Altrimenti ora gli utenti devono implementare le istruzioni switch all'interno di catch (che sconfigge l'intero punto in cui il catch gestisce automaticamente le cose).

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

Returning error codes and flags directly from functions (sometimes not possible from threads)

Il ritorno dei codici di errore è ottimo internamente. Ti permette di correggere i bug lì e poi e devi assicurarti di correggere tutti i codici di errore e di tenerne conto. Ma farli trapelare attraverso la tua API pubblica è una cattiva idea. Il problema è che i programmatori dimenticano spesso di controllare gli stati di errore (almeno con un'eccezione un errore non controllato costringerà l'applicazione a uscire da un errore non gestito in genere corromperà tutti i dati).

Implemented an event or callback system upon error (avoids stack unwinding)

Questo metodo è spesso usato insieme ad altri meccanismi di gestione degli errori (non come alternativa). Pensa al tuo programma Windows. Un utente avvia un'azione selezionando una voce di menu. Questo genera un'azione sulla coda degli eventi. Alla fine la coda di eventi assegna un thread per gestire l'azione. Il thread dovrebbe gestire l'azione e alla fine tornare al pool di thread e attendere un'altra attività. Qui un'eccezione deve essere catturata alla base dal thread assegnato al lavoro. Il risultato dell'ottenere l'eccezione di solito genererà un evento generato per il ciclo principale che alla fine si tradurrà in un messaggio di errore visualizzato all'utente.

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

    
risposta data 06.11.2011 - 18:54
fonte
9

Di solito inizio con:

  1. Una classe di eccezioni per i bug degli argomenti . Per esempio. "l'argomento non può essere nullo", "l'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 eccezioni per i . Questi sono per test più complessi, come "l'indice deve essere più piccolo della dimensione".
  3. Una classe di eccezioni per i bug di asserzione . Quelli servono a controllare lo stato per i metodi a metà strada della costanza. Per esempio. quando si itera su un elenco per contare elementi negativi, zero o positivi, alla fine questi tre dovrebbero sommarsi alla dimensione.
  4. Una classe di eccezioni di base per la libreria stessa. All'inizio, butta questa classe. Solo quando è necessario, inizia ad aggiungere sottoclassi.
  5. Preferisco non avvolgere le eccezioni, ma so che le opinioni divergono su questo punto (ed è molto correlata al linguaggio usato). Se esegui il wrap, avrai bisogno di wrapping exception classes aggiuntive

Poiché le classi per i primi 3 casi sono di aiuto al debug, non sono pensate per essere gestite dal codice. Invece, dovrebbero essere catturati solo da un gestore di primo livello che visualizza le informazioni in modo che l'utente possa copiarlo e incollarlo allo sviluppatore (o anche meglio: premere il pulsante "invia report"). Quindi includi informazioni utili per lo sviluppatore: file, funzione, numero di riga e alcuni messaggi che identificano chiaramente il controllo non riuscito.

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

A seconda della libreria, il quarto caso potrebbe avere solo una classe, o potrebbe diventare una manciata di classi. Preferisco l'agile aproach per iniziare in modo semplice, aggiungendo sottoclassi quando è necessario.

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

    
risposta data 06.11.2011 - 16:53
fonte

Leggi altre domande sui tag