Cの小さなエラーごとにチェックする必要がありますか?


60

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

たとえば、ファイルを開こうとするときなど、エラーチェックが必要であることは明らかです。しかし、私はしばしば、printfまたはmalloc必要だと感じないからといって、関数のエラーチェックを無視します。

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

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


13
作業中のプロジェクトに必要な堅牢性レベルに依存します。信頼できない関係者(例:公開サーバー)から入力を受け取る可能性のあるシステム、または完全に信頼できない環境で動作するシステムは、コードが時限爆弾(または最も弱いリンクがハッキングされる)にならないように、非常に慎重にコーディングする必要があります)。明らかに、趣味や学習プロジェクトにはそのような堅牢性は必要ありません。
rwong

1
一部の言語には例外があります。例外をキャッチしない場合、スレッドは終了します。これは、悪い状態を継続させるよりも優れています。例外をキャッチすることを選択した場合、呼び出された関数とメソッドを含む多数のコード行の多くのエラーを1つのtryステートメントでカバーできるため、すべての呼び出しまたは操作をチェックする必要がありません。(また、一部の言語は、null間接参照や範囲外の配列インデックスなどの単純なエラーの検出に優れていることに注意してください。)
Erik Eidt

1
問題は主に方法論的なものです:実装することになっているものを理解しているときに通常エラーチェックを記述せず、「ハッピーパス」を取得するためにエラーチェックを今すぐ追加したくない場合最初に。しかし、あなたはまだmallocとco をチェックすることになっています。エラーチェックのステップをスキップする代わりに、コーディングアクティビティに優先順位を付けて、コードに満足したらいつでも適用されるTODOリストのエラーチェックを暗黙的な永続的なリファクタリングステップにする必要があります。greppableな「/ * CHECK * /」コメントを追加すると役立つ場合があります。
コアダンプ

7
誰も言及していないなんて信じられないerrno!それは事実だしながら、ケースであなたは、慣れていない「ほとんどCライブラリのすべての関数が返す0または-1またはNULLエラーがありますとき、」彼らはまた、グローバル設定errno変数あなたが使用してアクセスすることができ、#include <errno.h>その後、単に読みをの値errno。したがって、たとえば、open(2)が返さ-1れた場合errno == EACCES、アクセス許可エラーENOENTを示すか、要求されたファイルが存在しないことを示すかを確認できます。
wchargin

1
@ErikEidt Cはtry/をサポートしていませんがcatch、ジャンプでシミュレートできます。
マスト

回答:


29

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

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

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

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

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

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

  1. エラー処理には、gotoに似たジャンプを使用できます。ソフトウェアの専門家の間では議論の多い問題ですが、特に組み込みのパフォーマンスクリティカルなコード(Linuxカーネルなど)で有効に使用されています。
  1. カスケードifs:

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

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


21
申し訳ありませんが、ここで選択する必要はありません-「標準出力への書き込みは失敗しません。失敗した場合、ユーザーにどのように伝えますか?」-標準エラーに書き込みますか?一方への書き込みの失敗が、他方への書き込みが不可能であることを意味するという保証はありません。
-Damien_The_Unbeliever

4
@Damien_The_Unbeliever-特にstdoutファイルにリダイレクトできるため、またはシステムによっては存在しない場合もあるため。stdout「コンソールへの書き込み」ストリームではありませんが、通常はそうですが、そうである必要はありません。
デイヴァーオドラロ

3
@JABメッセージを送信するstdout...明らか:
TripeHound

7
@JAB:適切であれば、EX_IOERRで終了する場合があります。
ジョンマーシャル

6
何をするにしても、何も起こらなかったように走り続けるだけではありません!コマンドdump_database > backups/db_dump.txtが特定の時点で標準出力への書き込みに失敗した場合、コマンドを 続行して正常に終了させたくありません。(データベースがそのようにバックアップされるわけではありませんが、ポイントはまだ残っています)
ベンS

15

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

はい、しかし、どの関数を呼び出したか知っいますか?

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

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


5
この答えは本当だったので、私を笑わせましたが、質問には答えません。
ラバーダック

質問に答えないのはどうしてですか?
ロバートハーヴェイ

2
C(および他の言語)がブール値1と0を混在させたと思う理由の1つは、エラーコードを返す適切な関数がエラーなしで「0」を返す理由と同じです。しかし、あなたは言わなければならないif (!myfunc()) {/*proceed*/}。0はエラーではなく、ゼロ以外は何らかのエラーであり、各エラーには独自のゼロ以外のコードがありました。私の友人が言ったように、「真実はたった一つしかありませんが、多くの偽りがあります」。if()テストでは「0」を「true」にし、ゼロ以外の値を「false」にする必要があります。
ロバートブリストージョンソン

1
いいえ、あなたのやり方で、負のエラーコードは1つしかありません。そして、多くの可能性のあるno_errorコード。
ロバートブリストージョンソン

1
@ robertbristow-johnson:それを「エラーはありませんでした」と読んでください。 if(function()) { // do something }「エラーなしで関数が実行されたら、何かをする」と読みます。または、さらに良いことに、「関数が正常に実行されたら、何かをします」。真剣に、コンベンションを理解している人はこれによって混同されませんfalseエラーが発生したときに(または0)を返すと、エラーコードを使用できません。 ゼロはCで偽を意味します。これが数学の仕組みだからです。参照してくださいprogrammers.stackexchange.com/q/198284
ロバート・ハーヴェイに

11

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

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

しかし、Cにこだわっているときは、そのような特典はありません。残念ながら、失敗する可能性のある関数を呼び出すたびに、単に価格を支払う必要があります。そうしないと、意図せずにメモリ内のデータを上書きするなど、はるかに悪い結果になります。したがって、一般的なルールとして、常にエラーをチェックする必要があります。

返品を確認しないとfprintf、バグを残してしまう可能性が高く、最良の場合、ユーザーが期待することは行われず、最悪の場合、フライト中に全体が爆発します。そのように自分自身を損なう言い訳はありません。

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

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

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

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


私は保守性が本当に重要な側面であり、その段落が本当に私の質問に答えたことに同意します。詳細な回答をありがとう。
デレク朕會功夫

デスクトッププログラムが数MBのメモリしか使用しない場合、malloc呼び出しを確認せずにクラッシュすることのない膨大な数のプログラムが怠zyである十分な理由だと思います。
whatsisname

5
@whatsisname:クラッシュしないからといって、静かにデータを破損していないわけではありません。戻り値malloc()必ずチェックしたい関数です!
TMN

1
@TMN:mallocが失敗すると、プログラムはすぐにセグメンテーションフォールトして終了し、処理を続行しません。それはリスクと適切なものがすべてであり、「ほとんど決して」ではありません。
-whatsisname

2
@Alex Cには、適切なエラー処理が欠けていません。例外が必要な場合は、それらを使用できます。例外を使用しない標準ライブラリは意図的な選択です(例外により、メモリ管理を自動化する必要があり、低レベルのコードにはそれを望まないことがよくあります)。
マーティンクネフ

9

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

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

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

どのくらい病理学的になりますか?私は、「SafetyBool」を定義するプログラムを作成しました。そのSafetyBoolの真と偽の値は、1と0の均等な分布を持つように慎重に選択されました。トレースが壊れるなど)、ブール値が誤って解釈されることはありませんでした。言うまでもなく、私はこれが古いプログラムで使用される汎用プログラミング手法であるとは主張しません。


6
  • さまざまな安全要件により、さまざまなレベルの正確さが求められます。航空または自動車制御ソフトウェアでは、すべての戻り値がチェックされます。cf。MISRA.FUNC.UNUSEDRET。マシンを決して離れることのない、概念の簡単な証明で、おそらくそうではありません。
  • コーディングには時間がかかります。安全性が重要ではないソフトウェアの無関係なチェックが多すぎると、他の場所で費やす労力が増えます。しかし、品質とコストのスイートスポットはどこですか?それはデバッグツールとソフトウェアの複雑さに依存します。
  • エラー処理により、制御フローがわかりにくくなり、新しいエラーが発生する場合があります。私は、少なくともエラーを報告するRichard "network" Stevensのラッパー関数がとても好きです。
  • エラー処理がパフォーマンスの問題になることはまれです。しかし、ほとんどのCライブラリの呼び出しには非常に時間がかかるため、戻り値をチェックするコストは計り知れないほど小さいです。

3

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

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

次に、抽象レイヤーへのインターフェイスとなるレイヤーがあり、多くのエラー処理や、依存関係の注入、イベントリスニングなどのその他の処理を行います。

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

したがって、これに対する私の見解は、コードの一部からエラー処理を完全に除外した方がよい場合があるということです。そして、出力を評価してエラーを指すプロセッサーを用意します。

これは主に、コードを読みやすくし、スケーラビリティを向上させる責任を分離するために行われます。


0

一般に、そのエラー状態をチェックしない正当な理由がない限り、そうすべきです。エラー状態をチェックしない理由として考えられる唯一の正当な理由は、失敗した場合に意味のある何かを実行できない可能性があることです。ただし、実行可能なオプションが常に1つあるため、これは満たすのが非常に難しいバーです。


エラーはあなたが説明しなかった状況であるため、私は一般的にあなたに同意しません。エラーが発生した条件がわからない場合、エラーがどのように現れるかを知るのは困難です。したがって、データを実際に処理する前のエラー処理。
クリエイティブマジック

たとえば、エラーを修正または続行する方法がわからなくても、何かがエラーを返すかスローした場合、いつでも正常に失敗し、エラーを記録し、期待どおりに動作しなかったことをユーザーに伝えることができます。ポイントは、エラーが気付かれない、ログに記録されない、「サイレントに失敗する」、またはアプリケーションをクラッシュさせるべきではないということです。それが「正常に失敗する」または「正常に終了する」ことを意味します。
巧妙な新語
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.