科学図書館ではどのようにエラーを報告すべきですか?


11

さまざまなソフトウェアエンジニアリングの分野には、ライブラリがエラーやその他の例外的な条件にどのように対処すべきかについて多くの哲学があります。私が見たもののいくつか:

  1. ポインター引数によって返された結果を含むエラーコードを返します。これがPETScの機能です。
  2. センチネル値でエラーを返します。たとえば、メモリを割り当てることができなかった場合、mallocはNULLを返しsqrt、負の数を渡すとNaNを返します。このアプローチは多くのlibc関数で使用されます。
  3. 例外をスローします。deal.II、Trilinosなどで使用されます。
  4. バリアント型を返します。たとえば、Result正しく実行された場合に型のオブジェクトを返し、Error失敗した方法を記述するために型を使用するC ++関数std::variant<Error, Result>
  5. assertとcrashを使用します。p4estおよびigraphの一部で使用されます。

各アプローチの問題:

  1. すべてのエラーをチェックすると、余分なコードが大量に発生します。結果が格納される値は常に最初に宣言する必要があり、一度しか使用されない可能性のある多くの一時変数が導入されます。このアプローチは、どのエラーが発生したかを説明しますが、原因を特定するのが難しい場合があります。
  2. エラーケースは無視するのは簡単です。さらに、出力タイプの範囲全体が妥当な結果である場合、多くの関数は意味のあるセンチネル値さえも持つことができません。#1と同じ問題の多く。
  3. C ++、Pythonなどでのみ可能です。CまたはFortranではできません。setjmp / longjmp sorceryまたはlibunwindを使用してCで模倣できます。
  4. C ++、Rust、OCamlなどでのみ可能です。CまたはFortranではできません。マクロソーサリーを使用してCで模倣できます。
  5. おそらく最も有益です。しかし、たとえばPythonラッパーを作成するCライブラリにこのアプローチを採用すると、範囲外のインデックスを配列に渡すような愚かな間違いにより、Pythonインタープリターがクラッシュします。

エラー処理に関するインターネット上のアドバイスの多くは、オペレーティングシステム、組み込み開発、またはWebアプリケーションの観点から書かれています。クラッシュは受け入れられないため、セキュリティについて心配する必要があります。科学アプリケーションには、これらの問題はほとんどありませんが、ほとんどありません。

別の考慮事項は、どの種類のエラーが回復可能かどうかです。mallocの失敗は回復不可能であり、いずれにしても、OSのメモリ不足のキラーが実行する前にそれに到達します。配列サイズの範囲外のインデックスも回復できません。ユーザーとしての私にとって、ライブラリでできることは、有益なエラーメッセージでクラッシュすることです。一方、たとえば、反復線形ソルバーの収束の失敗は、直接因子分解ソルバーを使用することで回復できます。

科学図書館はどのようにエラーを報告し、それらが処理されると期待すべきですか? もちろん、それはライブラリが実装されている言語に依存することを理解しています。しかし、私が知る限り、十分に有用なライブラリについては、実装されている言語以外の言語から呼び出したいと思うでしょう。

余談ですが、パブリックAPIの一部としてグローバルアサーションハンドラー関数ポインターを定義する場合、Cライブラリのアプローチ#5は大幅に改善できると思います。アサーションハンドラは、デフォルトでファイル/行番号を報告し、クラッシュします。このライブラリのC ++バインディングは、代わりにC ++例外をスローする新しいアサーションハンドラを定義します。同様に、Pythonバインディングは、CPython APIを使用してPython例外をスローするアサーションハンドラーを定義します。しかし、このアプローチを取る例は知りません。


もう1つの考慮事項は、パフォーマンスの影響です。これらのさまざまな方法は、ソフトウェアの速度にどのように影響しますか?コードの「制御」部分(入力ファイルの処理など)と計算コストの高い「エンジン」では、異なるエラー処理を使用する必要がありますか?
LedHead

ベストアンサーは言語によって異なります。
クリリス

回答:


10

あなたが参照するdeal.IIプロジェクトにエンコードされた私の視点を提供します。

まず、2種類のエラー状態があります。回復可能なエラーと回復できないエラーです。

  • 前者は、たとえば、入力ファイルを読み取ることができない場合、たとえば、ファイルから情報を読み取る$HOME/.dealii場合(存在する場合と存在しない場合)です。読み取り関数は、呼び出し側の関数に戻り、何をすべきかを判断する必要があります。また、リソースは現時点では使用できないが、1分後に再び使用できる場合もあります(リモートマウントされたファイルシステム)。

  • 後者は、たとえば、サイズ10のベクトルをサイズ20のベクトルに追加しようとしている場合です:試してみてください、これに関してできることは何もありません-コードにバグがあり、追加を試みたポイント。

これらの2つの条件は、使用しているプログラミング言語に関係なく、異なる方法で処理する必要があります。

  • 2番目のケースでは、依存関係がないため、プログラムを終了します。例外をスローするか、何も実行できないことを呼び出し元に示すエラーコードを返すことでそれを行うことができますが、プログラマーが問題をデバッグするのが非常に簡単になるため、プログラムをすぐに中止することもできます。

  • 前者の場合、処理できる例外的な状況が発生しました。CとFortranにはこれを表現する手段がありませんでしたが、後に登場したすべての合理的な言語は、言語標準に「例外」を提供することでそのような「例外」リターンを処理する方法を取り入れました。これらを使用してください-それが彼らがそこにいるものです。また、それらを無視することを忘れないように設計されています(そうする場合、例外は1レベルだけ上に伝播します)。

言い換えれば、私がここで提唱しているのは(およびdeal.IIが行うこと)コンテキストに応じて、戦略3と5を組み合わせたものです。3はCやFortranのような言語では機能しないのは事実です。その場合、やりたいことを表現するのが難しくなる言語を使用しないのがよい理由だと主張するかもしれません。

エラーが回復できない場合でも、一部のシステムはクラッシュしないはずです。例は、関数のセットがいくつかのクエリに対して繰り返し呼び出される場合です。たとえば、統計的なサンプリングスキームで指定された入力の尤度関数を評価する場合です。その状況では問題が意味をなさないため、評価者は負の値を処理できない可能性があります(たとえば、厚さ金属板の剛性の評価x)、しかし、エバリュエーターは繰り返し呼び出される必要があるので、単にクラッシュするのではなく、例外をスローするだけです。このような場合、負の値を渡すことは回復不能ですが、プログラムを中止するのではなく、例外をスローする必要があります。私は数年前にこのスタンスに反対していましたが、xSDKコミュニティソフトウェアガイドラインがプログラムをクラッシュさせない(または、少なくともクラッシュから例外に切り替える方法が必要であるという要件を規定しているので)考えを変えました。 IIには、Assertを呼び出す代わりに例外をスローするオプションがありますabort()


私はちょうど反対をお勧めします:状況を処理できないときに例外をスローし、処理できるときにエラーコードを返します。問題は、スローされた例外の処理が難しいことです。アプリケーションプログラマは、例外をキャッチして処理するために、考えられるすべての例外のタイプを知っている必要があります。クラッシュは大丈夫であり、処理できない状況でも歓迎されます。クラッシュポイントはそのままでPythonで報告されるためです。たとえば、処理できる状況では、(ほとんど)歓迎されません。
cdalitz

@cdalitz:C ++の設計上の欠陥で、あらゆるタイプのオブジェクトを投げることができます。ただし、合理的なソフトウェア(Trilinosを除く)はstd::exception、から派生した例外のみをスローし、派生型を知らなくても参照によってキャッチできます。
ウォルフガングバンガース

1
しかし、元の質問で概説した理由でエラーコードを返すことに強く反対します。(i)エラーコードはあまりにも頻繁に無視されるため、エラーはまったく処理されません。(ii)多くの場合、関数の戻り値の型が固定されているため、合理的に返すことができる例外的な値はありません。(iii)関数にはさまざまな戻り値の型があり、それぞれの場合に、エラーを表す「例外的な」値を個別に定義する必要があります。
ウォルフガングバンガース

WBは(「申し訳ありませんが、何らかの理由で '@'トリックは機能せず、何らかの理由でStackExchageによってユーザー名が削除されます」):「エラーコードはあまりにも頻繁に無視されます」と書いています。これは、例外をキャッチするためにさらに保持されます。多くのソフトウェア開発者は、try / catchブロック内のすべての関数呼び出しをブラケットする手間をかけません。しかし、それはほとんど趣味の問題です。ドキュメントが関数がスローする例外と例外を明確に述べている限り、私はそれを処理することができます。しかし、もう一度言うことができます:ドキュメントを書く義務はあまりにも頻繁に無視されます
;

しかし、ポイントは、例外をキャッチするのを忘れた場合、ダウンストリームの問題はないということです。プログラムはただ中断します。問題が発生した場所を簡単に見つけることができます。エラーコードの確認を忘れると、未定義の内部状態が原因でプログラムが後の時点でクラッシュする可能性がありますが、元の問題がどこで発生したかは完全に不明のままです。この種のバグを見つけるのは非常に困難です。
ウォルフガングバンガース
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.