web-dev-qa-db-ja.com

C関数から常にエラーコードを返す必要がありますか?

大規模なアプリケーションの多くの場所に次のコードがあります。

if (datastruct.data_ok &&
    cur_time > datastruct.start_time && cur_time < datastruct.end_time)
{
  do_something();
}

ここで、繰り返しコードを簡略化する関数time_ok(...)を定義して、繰り返しコードを排除したいと思いました。

if (time_ok(&datastruct, cur_time))
{
  do_something();
}

私の意見では、これはよくできたリファクタリングなので、実行する必要があります。将来、条件の定義が変更された場合でも、変更する必要がある場所は1つだけなので、コードの保守が容易になります。

ただし、いくつかのチームメンバーは、関数にNULL引数を与えることで関数が悪用され、プログラムがクラッシュすることに気づきました。彼らはまた、戻り値はエラーコードではなくブール値であり、すべての関数はエラーコードを返す必要があると不満を述べました。

したがって、彼によると、リファクタリングは次のように行われます。

bool time_ok_condition;
int err;
err = time_ok(&datastruct, &time_ok_condition, cur_time);
if (err != 0)
{
  fprintf(stderr, "Error %d at file %s line %d", err, __FILE__, __LINE__);
  exit(1);
}
if (time_ok_condition)
{
  do_something();
}

ただし、私の意見では、これによりコードが非常に長くなり、実際にこれをすべて実行する必要がある場合は、元のリファクタリングされていないコードを使用します。

私はマクロを使用してこのルールを回避する方法を発明しました:

if (TIME_OK(datastruct, cur_time))
{
  do_something();
}

...マクロは構造体の名前を引数として構造体へのポインタではなく構造体名を取るため、NULL引数でプログラムをクラッシュさせることはありません。ただし、マクロを使用するこのアプローチは、マクロが引数を複数回評価するため、特に好きではありません。

CはUnix用に開発されており、Unixシステムコールは伝統的にエラーコードを返しますが、strstr、strchr、strspnなどの標準Cライブラリ関数はエラーコードを返さないため、プログラムをクラッシュしてNULL引数を処理します。

これで、プログラムがコアダンプでクラッシュするか、このプログラムでは決して発生しない理論上のエラーに対して意味のあるエラーメッセージを出力するかどうかは、まったく別の質問です(質問 「これが発生する可能性はありません」のスタイルのエラーCでは処理されますか? スタックオーバーフローで)。

ただし、この質問は、エラーコード以外の何かを返すことを許可する必要があるかどうかについてです。ブールリターンの範囲はエラーを表すことができないため、肯定的な答えは実際にはこの非常に特殊なケースで「コアダンプによるクラッシュ」戦略の使用を必要としますが、必ずしも「コアダンプによるクラッシュ」戦略がすべての場合に最適であることを意味するわけではありません。

誰が正しいのですか? C関数は常にUnixシステムコールのようなエラーコードを返す必要がありますか、それとも、コードの単純化などの正当な理由がある場合に他の何かを返す関数を書くことを許可する必要がありますか?

6
juhist

エラーコードの要件は、新しい関数が2ではなく3つの結果を持つことができるという事実から生じます。時間構造体は大丈夫、大丈夫ではない、または渡された引数がNULLです。関数について念頭に置いている契約は、「NULL値を渡さないでください。そうしないと、プログラムがクラッシュしたり、未定義の動作が表示されたりする」ということです。

この以前のPSEの投稿による と最上位の回答によると、同僚の視点は非常に知識が豊富なものであり、コンテキスト内では、NULLである可能性が極めて低い関数で関数が呼び出されます。したがって、エラーコードが返されてもNULLを処理しない方が安全である可能性があります(プログラムがクラッシュするか、エラーメッセージですぐに終了するため、重大なエラーが隠されない限り)。

もちろん、「time_ok」関数がライブラリの一部になり、多くの異なるコンテキストで再利用される可能性がある場合(およびexitの呼び出しが適切でないオプションの場合)、それは呼び出し側がその問題の処理方法を決定できるように、NULL引数のエラーコードを返す方が確かに優れています。

4
Doc Brown

すべての関数はエラーコードを返す必要がありますか?

次のような関数を変更する正当な理由がわかりません

int Add( int lhs, int rhs ) {...}

// Out parameter: result
error_t Add( int lhs, int rhs, int * result ) {...}

特に今のところ、結果がnullである可能性に対処する必要があります。


あなたの具体的な例では、ポインターなしの最初のフォームに似たものを使用し、コンパイラに inlining の間の決定を残して、コピーとコードサイズの増加を保存します(この小さなものに対して私は期待しています)常にインライン化を選択しますが、判断するにはより良い位置にあります)

bool time_ok( data_t datastruct, time_t cur_time ) {
    return datastruct.data_ok && 
           ( cur_time > datastruct.start_time ) && 
           ( cur_time < datastruct.end_time )
}

ここで、data_tおよびtime_tは、datastructおよびcur_timeのタイプです。

これは、time_okが Total Function (つまり、 Partial Function ではない)であるという事実に依存しています。これは、すべてのdata_tおよびすべてのtime_t time_okが返すことができる定義済みboolがあります。これは、このような場合に最も簡単に証明できます。この場合、発生するのは値の比較と結合のみです。

1
Caleth