В различных дисциплинах разработки программного обеспечения есть много философий о том, как библиотеки должны справляться с ошибками или другими исключительными условиями. Несколько из тех, что я видел:
- Вернуть код ошибки с результатом, возвращаемым аргументом указателя. Это то, что делает PETSc.
- Вернуть ошибки по часовой стрелке. Например, malloc возвращает NULL, если не удалось выделить память,
sqrt
возвращает NaN, если вы передаете отрицательное число и т. Д. Этот подход используется во многих функциях libc. - Брось исключения. Используется в сделке. II, Трилино и др.
- Вернуть тип варианта; например, функция C ++, которая возвращает объект типа,
Result
если он работает правильно, и использует типError
для описания возвратаstd::variant<Error, Result>
. - Используйте assert и crash. Используется в p4est и некоторых частях igraph.
Проблемы с каждым подходом:
- Проверка на каждую ошибку вводит много дополнительного кода. Значения, в которые будет сохраняться результат, всегда должны быть объявлены первыми, вводя множество временных переменных, которые могут использоваться только один раз. Этот подход объясняет, какая ошибка произошла, но может быть трудно определить, почему или для глубокого стека вызовов, где.
- Случай ошибки легко игнорировать. Вдобавок ко всему, многие функции не могут даже иметь значимое значение для часового типа, если весь диапазон типов вывода является правдоподобным результатом. Многие из тех же проблем, что и # 1.
- Возможно только в C ++, Python и т. Д., Но не в C или Fortran. Может быть имитирован в C с помощью волшебства setjmp / longjmp или libunwind .
- Возможно только в C ++, Rust, OCaml и т. Д., Но не в C или Fortran. Может быть имитирован в C с помощью макро-магии.
- Возможно, самый информативный. Но если вы примете этот подход, скажем, для библиотеки C, для которой вы затем напишите обертку Python, глупая ошибка, такая как передача индекса за пределы массива, приведет к сбою интерпретатора Python.
Большая часть рекомендаций в Интернете об обработке ошибок написана с точки зрения операционных систем, встроенных разработок или веб-приложений. Сбои недопустимы, и вам нужно беспокоиться о безопасности. Научные приложения не имеют этих проблем почти в той же степени, если вообще.
Другое соображение - какие ошибки можно исправить или нет. Ошибка malloc не подлежит восстановлению, и в любом случае убийца нехватки памяти ОС доберется до нее раньше, чем вы. Индекс за пределами размера массива также не подлежит восстановлению. Для меня как для пользователя самое приятное, что может сделать библиотека, - это аварийно завершить работу с информативным сообщением об ошибке. С другой стороны, неспособность, скажем, итеративного линейного решателя сходиться может быть восстановлена с помощью решателя прямой факторизации.
Как научные библиотеки должны сообщать об ошибках и ожидать, что они будут обработаны? Я, конечно, понимаю, что это зависит от того, на каком языке реализована библиотека. Но, насколько я могу судить, для любой достаточно полезной библиотеки люди захотят вызывать ее не с того языка, на котором она реализована.
Кроме того, я думаю, что подход № 5 может быть существенно улучшен для библиотеки C, если он определяет глобальный указатель на функцию-обработчик утверждений как часть общедоступного API. Обработчик утверждений по умолчанию будет сообщать номер файла / строки и сбои. Привязки C ++ для этой библиотеки будут определять новый обработчик утверждений, который вместо этого генерирует исключение C ++. Аналогично, привязки Python будут определять обработчик утверждений, который использует API CPython для генерирования исключения Python. Но я не знаю ни одного примера такого подхода.