Existen muchas filosofías en diferentes disciplinas de ingeniería de software sobre cómo las bibliotecas deben hacer frente a errores u otras condiciones excepcionales. Algunos de los que he visto:
- Devuelve un código de error con el resultado devuelto por un argumento puntero. Esto es lo que hace PETSc.
- Devuelve errores por un valor centinela. Por ejemplo, malloc devuelve NULL si no puede asignar memoria,
sqrt
devolverá NaN si pasa un número negativo, etc. Este enfoque se utiliza en muchas funciones de libc. - Lanzar excepciones. Utilizado en deal.II, Trilinos, etc.
- Devuelve un tipo de variante; por ejemplo, una función de C ++ que devuelve un objeto de tipo
Result
si se ejecuta correctamente y usa un tipoError
para describir cómo volvería a fallarstd::variant<Error, Result>
. - Utilice afirmar y bloquearse. Usado en p4est y algunas partes de igraph.
Problemas con cada enfoque:
- La comprobación de cada error introduce muchos códigos adicionales. Los valores en los que se almacenará un resultado siempre deben declararse primero, introduciendo muchas variables temporales que solo pueden usarse una vez. Este enfoque explica qué error ocurrió, pero puede ser difícil determinar por qué o, para una pila de llamadas profundas, dónde.
- El caso de error es fácil de ignorar. Además de eso, muchas funciones ni siquiera pueden tener un valor centinela significativo si todo el rango de tipos de salida es un resultado plausible. Muchos de los mismos problemas que # 1.
- Solo es posible en C ++, Python, etc., no en C o Fortran. Se puede imitar en C usando la brujería setjmp / longjmp o libunwind .
- Solo es posible en C ++, Rust, OCaml, etc., no en C o Fortran. Se puede imitar en C usando macro brujería.
- Posiblemente el más informativo. Pero si adopta este enfoque para, por ejemplo, una biblioteca C para la que luego escribe un contenedor de Python, un error tonto como pasar un índice fuera de los límites a una matriz bloqueará el intérprete de Python.
Gran parte de los consejos en Internet sobre el manejo de errores se escriben desde el punto de vista de los sistemas operativos, el desarrollo integrado o las aplicaciones web. Los bloqueos son inaceptables y debe preocuparse por la seguridad. Las aplicaciones científicas no tienen estos problemas en la misma medida, si es que lo hacen.
Otra consideración es qué tipos de errores son recuperables o no. Un error de malloc no es recuperable y, en cualquier caso, el asesino de falta de memoria del sistema operativo lo resolverá antes que usted. Un índice fuera de los límites para un tamaño de matriz tampoco es recuperable. Para mí, como usuario, lo mejor que puede hacer una biblioteca es bloquearse con un mensaje de error informativo. Por otro lado, la falla de, por ejemplo, un solucionador lineal iterativo para converger podría recuperarse usando un solucionador de factorización directa.
¿Cómo deberían las bibliotecas científicas informar errores y esperar que se manejen? Por supuesto, me doy cuenta de que depende del idioma en que se implemente la biblioteca. Pero, por lo que puedo decir, para cualquier biblioteca lo suficientemente útil, la gente querrá llamarlo desde otro idioma que no sea el que está implementado.
Como comentario aparte, creo que el enfoque n. ° 5 se puede mejorar sustancialmente para una biblioteca C si define un puntero de función de controlador de aserción global como parte de la API pública. El controlador de aserción sería predeterminado para informar el número de archivo / línea y el bloqueo. Los enlaces de C ++ para esta biblioteca definirían un nuevo controlador de aserciones que en su lugar arroja una excepción de C ++. Del mismo modo, los enlaces de Python definirían un controlador de aserciones que usa la API de CPython para lanzar una excepción de Python. Pero no conozco ningún ejemplo que tome este enfoque.