エラー処理¶
GROMACS が適切なライブラリとして動作するように、エラーを一貫性があり予測可能な方法で処理する必要があります。このセクションでは、「ユーザー」とは、GROMACS をコマンドラインツール、ワークフロー、または公開 API の呼び出しを通じて使用するエンドユーザーを指します。エラーにはさまざまな種類があり、その処理方法も異なります。このセクションはまだ開発段階であり、特に C++ コミュニティ全体がこれらの分野で合意に至るにはまだ時間がかかっています。
使用すべき方法に関する簡単な概要¶
より詳細なルールと根拠は、以下のとおりに記載されていますが、要約すると、コードが本来の機能を実行できない理由が存在する場合、次の手順に従ってください。
コンパイル時に理由を確認できる場合は、「static_assert」を使用してください。
もしその理由が通常であれば、使用する型でそれを表現し(例:`std::optional`を返す)、これは正常なケースであることを文書化してください。
もしその理由が、ホットなコードパスで内部の整合性またはプリコンディションが違反された場合(たとえば、予期しないnullポインタが渡された場合)である場合、`GMX_ASSERT`を使用してください。
それ以外の場合、理由が内部のインバリアントまたはプリコンディションが違反された場合、``GMX_RELEASE_ASSERT``を使用します。
それ以外の場合(通常はシステムコールまたはGPU SDKからのエラー、不正なユーザー入力など)、``GMX_THROW``を使用します。
指針¶
GROMACS は、他の場所で合意されたアプローチを採用すべきです。たとえば、C++ Core Guidelines のようなものです。特に、エラー処理に関するセクション を参考にしてください。
ライブラリは、API仕様の一部としてそう定義されている場合を除き、stdio/stderrに出力を出力してはなりません。また、たとえそう定義されていても、ユーザーがその出力を抑制またはリダイレクトする方法があるべきです。
通常、ライブラリはユーザーが制御できない場合にプログラムを終了すべきではありません。
関数、クラス、モジュール、およびライブラリのインターフェースを設計する際には、実行時に渡される値が有効であることを保証する。
const参照またはnot_nullポインタを使用し、生のポインタを使用しない。 可能な限り、オブジェクトを返す。 例えば、渡される値の型にはクラスの列挙型を使用する。 実行時の値ではなく、これらの列挙型をテンプレートパラメータとして扱うことを検討する。 新しい領域で作業を開始する際には、既存のインターフェースをリファクタリングして、これらの側面を改善する。APIの境界でユーザーの入力を検証し、できるだけ早く整合性を確立します。たとえば、ユーザーの選択を型システムに表現することで、これを行います。これらは、エラー処理が依存する前提条件となります。
アサーションを使用して、制約条件と事前条件を検証します。 違反を検出するための別の手法を使用することで、保守担当者にチェックの理由を明確に伝えることが有益です。
特定のルール¶
可能な限り、
static_assertを使用して、コンパイル時にエラーを検出してください。関数が割り当てられたタスクを実行できないことを示すために、
C++ Core Guidelines E.2に従って、例外をスローします。特に、コンストラクタは、有効なオブジェクトを構築できない場合に例外をスローする必要があります。これは、C++ Core Guidelines C.42に基づいています。ただし、いくつかのケースでは、根本的な原因は、別のコンポーネントが正しい事前条件を設定していないことが原因であるため、このような場合はアサーションを使用して処理する必要があります(後述)。API の境界において、割り当てられたコードの役割は入力の検証を行うものであり、検証に失敗した場合は例外をスローする必要があります。
多くのプログラミングエラーは、他の関数の前提条件に違反します。契約に関する言語サポートがない限り、最も効果的な方法は、これらのエラーを*アサーション*でチェックすることです。ただし、特定のユーザーからの入力を検証する責任は、単一のコンポーネントにのみ割り当てるべきであり、他のコンポーネントは、その検証結果を前提条件として使用する必要があります。
「検証を行う場合は、デフォルトで
GMX_RELEASE_ASSERTマクロを使用します。このマクロは、``Release``を含むすべてのビルド構成でチェックを実行します。」MD ステップなどの内部ループでコードが呼び出される場合に、
GMX_ASSERTを使用できます。このマクロは、NDEBUGが定義されていない場合にのみチェックを実行します。これには、デフォルトのビルドタイプであるRelWithAssertのビルド構成も含まれます(これは CI で使用されるデフォルトのビルドタイプです)。両方のインターフェース(チェックされたものとチェックされていないもの)を提供することが適切である場合がある。例えば、
std::vectorがat()とoperator[]をそれぞれ使用しているように。ただし、後者の場合でも、適切な設定でlibstdc++をコンパイルした場合、チェックが実行されます。低レベルAPI(CおよびC++の標準ライブラリ、GPU SDKなど)を呼び出す際には、常に成功/失敗を確認してください。 失敗した場合、適切な対応はエラーをスローすることです。 スローする際には、エラーコードから取得した説明的な文字列を別のAPI呼び出しと組み合わせて使用することが一般的です。
低レベルのコンポーネント(メモリやファイルシステム I/O)からの例外を捕捉してください。一般的なガイドラインとして、誤ったユーザー入力によって捕捉されない例外が発生し、プログラムが終了する状況は避けるべきです。代わりに、例外をより上位のスタックフレームで捕捉し、適切な診断メッセージを作成し、実行を終了させる必要があるかどうか(それがコードの範囲内である場合)を決定する必要があります。
api/legacy/include/gromacs/utility/exceptions.hには、例外のグローバルなリストが定義されており、ライブラリは失敗した場合に、このリストから適切な例外をスローする必要があります。スローする例外の種類は拡張可能であり、現在では以下のようなものが含まれます。メモリ不足 (例:
std::bad_alloc)ファイル入出力エラー (例: 見つからない)
無効なユーザー入力 (解釈できません)
ユーザーからの入力が矛盾している(構文解析は正常だが、内部で矛盾がある)
シミュレーションの不安定性
無効なAPI呼び出し/値/内部エラー (このような場合、アサーションも使用されることがあります)
モジュール内で、例外を安全に扱わないコードから呼び出される場合、エラー処理には例外を使用できますが、呼び出し元コードに例外を伝播させることは避けてください。
OpenMP でスレッドを開始または結合する際に、エラーを伝播させるために例外を使用することは避けてください。なぜなら、OpenMP は例外がキャッチされるか、プログラムがクラッシュするかについて保証できないからです。現在、OpenMP スレッドセクションを離れる前に、すべての例外をキャッチしています。例外をスローする場合は、同じスレッド/OpenMP セクション内で適切にキャッチおよび処理されるようにしてください。
非ブロッキングAPI呼び出し(例:MPIまたはGPU SDK)が実行された領域内で、エラーを伝播するために例外を使用することは避けてください。なぜなら、API呼び出しの相手がブロックされたままになる場合、上位で例外をキャッチして実行を継続する利点は失われるからです。
また、ライブラリのルーチンが警告や致命的なエラーを報告したいが、それでも処理を継続する必要がある場合もあります。 この場合、すべての問題を収集し、報告 (grompp が注釈、警告、エラーを報告する方法に類似) することをお勧めします。 ユーザーにとって、報告されたエラーを修正しても、プログラムを再実行するたびに新しいエラーメッセージが表示されることは、非常に不快です。
関数は、通常の動作の一部として失敗しないようにする必要があります。ただし、何もせずに待つことも、通常の動作と見なすことができます。データにアクセスする関数は、通常、データが利用できない場合でも呼び出せるように設計されている必要がありますが、それでも正常な方法でエラーを処理する必要があります。失敗が通常の動作でない場合、例外をスローすることが適切です。
gmx_fatal、gmx_warning、gmx_incons、``gmx_comm``などのエラー処理方法は非推奨であり、上記のガイドラインに従って、例外をスローまたはアサーションを使用するように一般的にリファクタリングする必要があります。現在、シミュレーション中に他のMPIスレッドの状態を確認し、協調的な復旧を行う試みは行われていません。しかし、設定コードは定期的にこのようなチェックを行うべきです。
当社は、エラーを報告する際に、非直列化文字列を使用して問題を記述できるようにするために、
GMX_RELEASE_ASSERTとGMX_ASSERTをassertよりも使用しています。これは、テストカバレッジが不足している場合に、ユーザーがこのようなエラーを発見する問題のトラブルシューティングに特に役立ちます。
このすべてを機能させるためのコーディングガイドラインについては、:ref:`例外処理の実装`を参照してください。