さまざまなソフトウェアエンジニアリングの分野には、ライブラリがエラーやその他の例外的な条件にどのように対処すべきかについて多くの哲学があります。私が見たもののいくつか:
- ポインター引数によって返された結果を含むエラーコードを返します。これがPETScの機能です。
- センチネル値でエラーを返します。たとえば、メモリを割り当てることができなかった場合、mallocはNULLを返し
sqrt
、負の数を渡すとNaNを返します。このアプローチは多くのlibc関数で使用されます。 - 例外をスローします。deal.II、Trilinosなどで使用されます。
- バリアント型を返します。たとえば、
Result
正しく実行された場合に型のオブジェクトを返し、Error
失敗した方法を記述するために型を使用するC ++関数std::variant<Error, Result>
。 - assertとcrashを使用します。p4estおよびigraphの一部で使用されます。
各アプローチの問題:
- すべてのエラーをチェックすると、余分なコードが大量に発生します。結果が格納される値は常に最初に宣言する必要があり、一度しか使用されない可能性のある多くの一時変数が導入されます。このアプローチは、どのエラーが発生したかを説明しますが、原因を特定するのが難しい場合があります。
- エラーケースは無視するのは簡単です。さらに、出力タイプの範囲全体が妥当な結果である場合、多くの関数は意味のあるセンチネル値さえも持つことができません。#1と同じ問題の多く。
- C ++、Pythonなどでのみ可能です。CまたはFortranではできません。setjmp / longjmp sorceryまたはlibunwindを使用してCで模倣できます。
- C ++、Rust、OCamlなどでのみ可能です。CまたはFortranではできません。マクロソーサリーを使用してCで模倣できます。
- おそらく最も有益です。しかし、たとえばPythonラッパーを作成するCライブラリにこのアプローチを採用すると、範囲外のインデックスを配列に渡すような愚かな間違いにより、Pythonインタープリターがクラッシュします。
エラー処理に関するインターネット上のアドバイスの多くは、オペレーティングシステム、組み込み開発、またはWebアプリケーションの観点から書かれています。クラッシュは受け入れられないため、セキュリティについて心配する必要があります。科学アプリケーションには、これらの問題はほとんどありませんが、ほとんどありません。
別の考慮事項は、どの種類のエラーが回復可能かどうかです。mallocの失敗は回復不可能であり、いずれにしても、OSのメモリ不足のキラーが実行する前にそれに到達します。配列サイズの範囲外のインデックスも回復できません。ユーザーとしての私にとって、ライブラリでできることは、有益なエラーメッセージでクラッシュすることです。一方、たとえば、反復線形ソルバーの収束の失敗は、直接因子分解ソルバーを使用することで回復できます。
科学図書館はどのようにエラーを報告し、それらが処理されると期待すべきですか? もちろん、それはライブラリが実装されている言語に依存することを理解しています。しかし、私が知る限り、十分に有用なライブラリについては、実装されている言語以外の言語から呼び出したいと思うでしょう。
余談ですが、パブリックAPIの一部としてグローバルアサーションハンドラー関数ポインターを定義する場合、Cライブラリのアプローチ#5は大幅に改善できると思います。アサーションハンドラは、デフォルトでファイル/行番号を報告し、クラッシュします。このライブラリのC ++バインディングは、代わりにC ++例外をスローする新しいアサーションハンドラを定義します。同様に、Pythonバインディングは、CPython APIを使用してPython例外をスローするアサーションハンドラーを定義します。しかし、このアプローチを取る例は知りません。