Esistono molte filosofie in diverse discipline di ingegneria del software su come le biblioteche dovrebbero affrontare errori o altre condizioni eccezionali. Alcuni di quelli che ho visto:
- Restituisce un codice di errore con il risultato restituito da un argomento pointer. Questo è ciò che fa PETSc.
- Restituisce errori per un valore sentinella. Ad esempio, malloc restituisce NULL se non è in grado di allocare memoria,
sqrt
restituirà NaN se si passa un numero negativo, ecc. Questo approccio viene utilizzato in molte funzioni libc. - Genera eccezioni. Utilizzato nell'affare.II, trilinos, ecc.
- Restituisce un tipo di variante; ad esempio una funzione C ++ che restituisce un oggetto di tipo
Result
se viene eseguito correttamente e utilizza un tipoError
per descrivere come restituirebbe l'errorestd::variant<Error, Result>
. - Usa assert e crash. Utilizzato in p4est e in alcune parti dell'igrafo.
Problemi con ciascun approccio:
- Il controllo di ogni errore introduce un sacco di codice aggiuntivo. I valori in cui verrà archiviato un risultato devono sempre essere dichiarati per primi, introducendo molte variabili temporanee che potrebbero essere utilizzate una sola volta. Questo approccio spiega quale errore si è verificato, ma può essere difficile determinare perché o, per uno stack di chiamate profonde, dove.
- Il caso di errore è facile da ignorare. Inoltre, molte funzioni non possono nemmeno avere un valore sentinella significativo se l'intero intervallo di tipi di output è un risultato plausibile. Molti degli stessi problemi del n. 1.
- Possibile solo in C ++, Python, ecc., Non in C o Fortran. Può essere imitato in C usando setjmp / longjmp stregoneria o libunwind .
- Possibile solo in C ++, Rust, OCaml, ecc., Non in C o Fortran. Può essere imitato in C usando la stregoneria macro.
- Probabilmente il più informativo. Ma se adotti questo approccio, diciamo, per una libreria C per la quale scrivi un wrapper Python, un errore sciocco come passare un indice fuori limite a un array causerà il crash dell'interprete Python.
Gran parte dei consigli su Internet sulla gestione degli errori sono scritti dal punto di vista dei sistemi operativi, dello sviluppo integrato o delle applicazioni web. Gli arresti anomali sono inaccettabili e devi preoccuparti della sicurezza. Le applicazioni scientifiche non hanno questi problemi quasi nella stessa misura, se non del tutto.
Un'altra considerazione è quali tipi di errori sono recuperabili o meno. Un errore di malloc non è recuperabile e, in ogni caso, il killer di memoria insufficiente del sistema operativo ci riuscirà prima di farlo. Neanche un indice fuori limite per una dimensione dell'array è recuperabile. Per me come utente, la cosa più bella che una libreria può fare è andare in crash con un messaggio di errore informativo. D'altra parte, il fallimento di, per esempio, un solutore lineare iterativo a convergere potrebbe essere recuperato usando un risolutore a fattorizzazione diretta.
In che modo le biblioteche scientifiche dovrebbero segnalare gli errori e aspettarsi che vengano gestiti? Mi rendo conto ovviamente che dipende da quale lingua è implementata la biblioteca. Ma per quanto posso dire, per qualsiasi biblioteca sufficientemente utile, la gente vorrà chiamarla da una lingua diversa da quella in cui è implementata.
A parte questo, penso che l'approccio n. 5 possa essere sostanzialmente migliorato per una libreria C se definisce un puntatore alla funzione di gestore di asserzioni globali come parte dell'API pubblica. Il gestore dell'asserzione imposterebbe per impostazione predefinita la segnalazione del numero di file / riga e l'arresto anomalo. I binding C ++ per questa libreria definiscono un nuovo gestore di asserzioni che genera invece un'eccezione C ++. Allo stesso modo, i collegamenti Python definiscono un gestore di asserzioni che utilizza l'API CPython per generare un'eccezione Python. Ma non conosco esempi che adottino questo approccio.