web-dev-qa-db-ja.com

Cのすべての小さなエラーをチェックする必要がありますか?

優れたプログラマーとして、自分のプログラムのすべての結果を処理する堅牢なコードを記述する必要があります。ただし、Cライブラリのほとんどすべての関数は、エラーが発生すると0または-1またはNULLを返します。

たとえばファイルを開こうとするときなど、エラーチェックが必要であることは明らかです。しかし、printfmallocなどの関数のエラーチェックは、私は必要がないと思うので無視することがよくあります。

if(fprintf(stderr, "%s", errMsg) < 0){
    perror("An error occurred while displaying the previous error.");
    exit(1);
}

特定のエラーを無視するのは良い習慣ですか、それともすべてのエラーを処理するより良い方法がありますか?

60

一般に、コードは適切な場合はいつでも例外条件を処理する必要があります。はい、これは曖昧な声明です。

これは、ソフトウェア例外処理を備えた高水準言語では、「実際に何かを実行できるメソッドで例外をキャッチする」としばしば言われます。ファイルエラーが発生した場合、スタックをUIコードにバブルアップさせて、実際にユーザーに「ファイルをディスクに保存できませんでした」と伝えることができます。例外メカニズムは、「ほとんどすべてのエラー」を効果的に飲み込み、適切な場所で暗黙的に処理します。

Cでは、そのような贅沢はありません。エラーを処理する方法はいくつかあります。その一部は言語/ライブラリ機能であり、一部はコーディングプラクティスです。

特定のエラーを無視することは良い習慣ですか、それともすべてのエラーを処理するより良い方法がありますか?

特定のエラーを無視しますか?多分。たとえば、標準出力への書き込みは失敗しないと想定するのが妥当です。それが失敗した場合、とにかくユーザーにどのように伝えますか?はい、特定のエラーを無視するか、防御的にコーディングしてエラーを防止することをお勧めします。たとえば、除算する前にゼロを確認します。

すべて、または少なくともほとんどのエラーを処理する方法があります。

  1. gotosと同様に、エラー処理にジャンプを使用できます 。ソフトウェアの専門家の間で論争の的となっている問題ですが、特に組み込みでパフォーマンスが重要なコード(Linuxカーネルなど)での有効な使用法があります。
  1. カスケードifs:

    if (!<something>) {
      printf("oh no 1!");
      return;
    }
    if (!<something else>) {
      printf("oh no 2!");
      return;
    }
    
  2. 最初の条件をテストします。ファイルを開くか作成し、その後の操作が成功すると想定します。

堅牢なコードは適切であり、エラーをチェックして処理する必要があります。どの方法がコードに最適であるかは、コードの機能、障害の重大度などに依存し、実際に答えることができるのはあなただけです。ただし、これらのメソッドは、実際にコードがエラーをチェックする方法を確認できるさまざまなオープンソースプロジェクトで、テスト済みで使用されています。

29
user22815

ただし、Cライブラリのほとんどすべての関数は、エラーが発生すると0または-1またはNULLを返します。

はい、しかしあなたは知っていますあなたが呼んだ関数ね?

実際には、エラーメッセージに含めることができる多くの情報があります。呼び出された関数、それを呼び出した関数の名前、渡されたパラメーター、および関数の戻り値がわかります。これは、非常に有益なエラーメッセージに関する多くの情報です。

すべての関数呼び出しでこれを行う必要はありません。しかし、「以前のエラーの表示中にエラーが発生しました」というエラーメッセージが初めて表示されたとき、本当に必要な情報が有益な情報だった場合、すぐにそのエラーメッセージが表示されます。問題のトラブルシューティングに役立つ情報を提供するエラーメッセージ。

15
Robert Harvey

TLDR;エラーを無視することはほとんどありません。

C言語には、各ライブラリ開発者が独自のソリューションを実装するための適切なエラー処理機能がありません。より最近の言語には例外が組み込まれており、この特定の問題を非常に扱いやすくしています。

しかし、Cに悩まされているときは、そのような特典はありません。残念ながら、失敗する可能性がほとんどない関数を呼び出すたびに、代金を支払う必要があります。それ以外の場合は、メモリ内のデータを意図せずに上書きするなど、はるかに悪い結果を被ることになります。したがって、原則として、常にエラーをチェックする必要があります。

fprintfの戻りを確認しないと、バグが残っている可能性が高く、最良の場合にはユーザーの期待どおりに動作せず、最悪の場合は飛行中にすべてが爆発します。そのように自分自身を傷つける言い訳はありません。

ただし、C開発者として、コードの保守を容易にすることもあなたの仕事です。したがって、明確にするために、エラーを無視しても安全な場合がありますエラーがアプリケーションの全体的な動作に脅威を与えない場合。

これはこれを行うのと同じ問題です:

_try
{
    run();
} catch (Exception) {
    // comments expected here!!!
}
_

空のcatchブロック内に適切なコメントがないことがわかった場合、これは確かに問題です。 malloc()の呼び出しが100%の時間で正常に実行されると考える理由はありません。

11
Alex

問題は実際には言語固有ではなく、ユーザー固有です。ユーザーの観点から質問について考えてください。ユーザーは、コマンドラインでプログラムの名前を入力してEnterキーを押すなどの操作を行います。ユーザーは何を期待していますか?何か問題が発生したかどうかをどのようにして確認できますか?エラーが発生した場合、仲裁する余裕がありますか?

多くのタイプのコードでは、そのようなチェックはやりすぎです。ただし、原子炉用のコードなど、信頼性の高い安全性が重要なコードでは、病理学的エラーチェックと計画的な復旧パスは、日常の業務の一部です。時間をかけて「Xが失敗した場合はどうなりますか?どうすれば安全な状態に戻すことができますか?」ビデオゲーム用のコードなど、信頼性の低いコードでは、はるかに少ないエラーチェックで回避できます。

別の同様のアプローチは、エラーをキャッチすることで、実際にどの程度状態を改善できるかです。私は例外を誇らしげにキャッチするC++プログラムの数を数えることはできません。実際にどうすればよいかわからなかったために再スローするためだけですが、例外処理を行うことになっていることはわかっていました。そのようなプログラムは余分な努力から何も得ませんでした。単にエラーコードをチェックしないよりも、状況を実際に処理できると思われるエラーチェックコードのみを追加してください。つまり、C++には、例外処理中に発生する例外をより意味のある方法でキャッチするための特定のルールがあります(つまり、terminate()を呼び出して、自分用に構築した葬儀の火葬が確実に点灯するようにしますその適切な栄光の中で)

あなたはどれほど病的ですか?私は「SafetyBool」を定義するプログラムに取り組みました。その真と偽の値は、1と0が均等に分布するように慎重に選択され、ハードウェア障害(シングルビットフリップ、データバス)のいずれかが選択されるように選択されました。トレースが壊れるなど)、ブール値が誤って解釈されることはありませんでした。言うまでもなく、これが古いプログラムで使用される汎用のプログラミング手法であるとは主張しません。

9
Cort Ammon
  • 安全要件が異なれば、要求される正確性のレベルも異なります。航空または自動車制御ソフトウェアでは、すべての戻り値がチェックされます。 MISRA.FUNC.UNUSEDRET 。おそらくあなたのマシンを決して離れないコンセプトの素早い証明で。
  • コーディングには時間がかかります。安全性が重要ではないソフトウェアでの無関係なチェックが多すぎると、他の場所で費やしたほうが効果的です。しかし、品質とコストのスイートスポットはどこにあるのでしょうか。それは、デバッグツールとソフトウェアの複雑さに依存します。
  • エラー処理により、制御フローが不明瞭になり、新しいエラーが発生する場合があります。私はリチャードの「ネットワーク」スティーブンスのラッパー関数がとても好きで、少なくともエラーを報告します。
  • エラー処理が、パフォーマンスの問題になることはまれです。しかし、ほとんどのCライブラリ呼び出しは非常に時間がかかるため、戻り値をチェックするコストは計り知れないほど小さくなります。

少し抽象的な質問をします。そして、それは必ずしもC言語のためのものではありません。

大規模なプログラムの場合は、抽象化レイヤーがあります。エンジンの一部、ライブラリ、またはフレームワーク内。そのレイヤーは、有効なデータを取得する天気を気にしないか、出力がいくつかのデフォルト値になります:0-1nullなど.

次に、抽象層へのインターフェースとなる層があります。これは、多くのエラー処理を実行し、依存関係の注入、イベントのリスニングなどの他の処理も実行します。

そして後で、実際にルールを設定して出力を処理する具体的な実装レイヤーができます。

したがって、私の考えでは、コードの一部からエラー処理を完全に除外した方がよい場合があります。その部分は単にその仕事を行わないからです。そして、出力を評価してエラーを指摘するプロセッサをいくつか用意します。

これは主に、コードの可読性とスケーラビリティの向上につながる責任を分離するために行われます。

3
Creative Magic

一般に、notでエラー条件をチェックする正当な理由がない限り、チェックする必要があります。エラー状態をチェックしないのは、失敗した場合に意味のあることを実行できない可能性がある場合です。ただし、実行可能なオプションは1つしか存在しないため、これは非常に難しいバーです。

0