ぶっちゃけ、関数の返す結果値のチェックなんてしないモンですか?
オレはしている、私はしない、こーゆーケースではするけどこーゆー時はしない。いろいろな考えがあると思います。それぞれ理由をつけて答えてください。
非常にボリュームのある内容ありがとうございます!
ほかのエントリも読んでなんとなく思うことですが、全てはどこまでうまくいかなかった原因を追い詰めるか、そのポリシー次第で、結果値判断(エラートラップ)をどの程度の粒度でやるかが決まるな、と感じます。
たとえば、私の感じだとid:tezuboaさんの
C標準関数に対して考えるならば、fopenのように外的要因によりエラーとなる可能性のあるものと、memcpyのように外的要因の無いものとに分けられると思います。
前者でのエラーはエラー処理が必要。
後者でのエラーはバグでしか起こりえないためエラー処理は不要です。
というのは大いに違和感を感じるところです。どんな関数でも、必要だから呼び出しているわけで、呼び出した結果が期待通りでなかったら、後続処理は続けられない。連鎖的にエラーが生じるか、一見うまく言っているようで実は本来の期待結果と異なるものが最終的な出力として得られる「誤動作」ということになる。
でも
影響があると考えた場合は、きちんとしたエラー処理が必要です。
そのエラーをリカバリーするコードを記述するわけですが、リカバリーできない場合は、プログラムを停止するなどのコードを記述する場合もあります。
ではバグ対策はどうするか?
単体テストである程度潰したとしてもバグは必ず残ります。
よって、何処まで正常に動作したか、どこからおかしくなったかを事後に確認できるよう、ポイント、ポイントで動作情報のログを出力するように作るのがいいと考えています。
こーゆー、意味のある結果が得られる単位、リカバリーポイントを考慮したポイントで一括してエラーの有無をチェックすればいい、という考えですね。これは私も賛成です。
エラー処理とは、ちゃんとテストするのが前提にあってこそのものであり、単体テスト不足を補うためのものでは無いと考えています。
そこは多分memcpy関数に代表される信頼性の高い枯れた関数がエラー値を出すのは単体テストにおいてだけでそこでバグを摘出しきれる!しきるべき!という考えですかね。
でもここはちょっと違うと思うんですよ。テストは不具合=要件と違う動作をするかどうかを検査するもので、エラー処理は要件そのものです。要件なんだから、エラー処理だって単体テストするんですよ。
memcpy()のmanページを見ていただければ分かりますが、戻り値は有りますが、エラーかどうかは分かりませんのでエラーチェックは必要有りません(というかチェックしようがないです)。
このような場合の自分の対処方法ですが、
memcpy()に限らず標準関数で戻り値以外のチェックを行いたい場合は、関数をフックして自作の関数を呼び、この自作した関数ないで引数のチェック等を行うようにしています。
これでmemcpy()で書き込み不可能なメモリを指定されるなどしても問題はありません。
あと関数の戻り値以外でも以外でもチェックするべき箇所としては、0除算とか色々有ります。
元々の質問の関数チェックが9割方無いと言う状況では他にも問題は多数有りそうなのが心配ですね。
コメントありがとうございます。
長文なので読んでもらえないのではと心配してました。
まず、お詫びとして、memcpyを例にしたのはメモリー破壊の代表であり、皆がきらう関数No1だろうという理由からでした。
id:longicornさんの仰る通りmemcpyはvoid型であり、例としては不適切だったかもしれません。
『memcpyのエラー』=『期待通りのコピーが行われない』と読み替えて頂ければと思います。
さて、私とid:Rintaさんとでお互いに単体テストの定義(フェーズ)が食い違っているのかもしれませんね。
私の考える単体テストの主なポイントは以下です。
・1ライン毎に、意図した動作をするか確認する。
・ロジック上取りうる入力値全てでの動作を確認する。
(それ以降のテストは結合/総合テストフェーズとなります)
それを前提で考えると、単体テストを行ったのにも関わらずmemcpyでエラー(=期待通りのコピーが行われない)となるようなコードは作成できないと考えています。
エラーとするには、memcpyに渡す値がおかしいことしか考えられなく、これは、memcpyを呼び出す前のコードに問題がある、つまりはテスト漏れと考えられます。
>エラー処理は要件そのものです。
呼び出す前のチェック処理もエラー処理の1つだとのお考えならid:Rintaさんの意見に大賛成です。
ただ、自前の関数の場合は、呼び出す前にチェックしても期待通りの動作をするとは限らないので呼出し後のチェックも必要と考えます。
(その時点では良くても1年後に関数が改造されて意図しない動作となる可能性が大いにあるという意味です)
また、id:longicornさんの言うように1枚被せるのも有効だとは思います。が、将来改造される危険性が増える点は注意が必要だと思います。
呼び出し後のエラー処理については、過度のエラー処理、意味の無いエラー処理かどうかの見極めが大変なので、C標準関数は信用し、自前の関数は信用しないと考えるとポリシー的にもすっきりするような気がします。
どうでしょうか?(また長文ですみません)
C標準関数に対して考えるならば、fopenのように外的要因によりエラーとなる可能性のあるものと、memcpyのように外的要因の無いものとに分けられると思います。
前者でのエラーはエラー処理が必要。
後者でのエラーはバグでしか起こりえないためエラー処理は不要です。
しかし、前者でのエラーは正常動作中に十分起こり得るので、そのエラー処理自体が正常処理の一種でありこれが無いのは欠陥と考えられます。
外的要因を検討し、それに対する処理さえ行えばエラー処理は全く必要無いと考えられます。
標準関数ではなく、自作の関数に対して考える場合は、話は全く逆になります。
考えるべきは、もし戻り値がエラーだった場合に、他への影響が有るか無いかです。
例えば、ログ出力用の自作関数がエラーとなった場合、ログが出力されないだけでプログラム本来の動作には全く影響しないためエラー処理はいらないです。
影響があると考えた場合は、きちんとしたエラー処理が必要です。
そのエラーをリカバリーするコードを記述するわけですが、リカバリーできない場合は、プログラムを停止するなどのコードを記述する場合もあります。
ではバグ対策はどうするか?
単体テストである程度潰したとしてもバグは必ず残ります。
よって、何処まで正常に動作したか、どこからおかしくなったかを事後に確認できるよう、ポイント、ポイントで動作情報のログを出力するように作るのがいいと考えています。
余談ですが、上記の単体テストの『ある程度』とは、試験員が『完璧です』と報告するレベルを言っています。
よく『テスト結果が完璧なんて有り得ません』(正論)という人がいますが、その場合、どの辺りが不安かを尋ねると完璧じゃない理由がぼろぼろ出てきます。
つまり、ちゃんとテストしてないってことです。
単体テストとしてやるべきことを全て行い不安が無くなれば『完璧』と言えるはずであり、これが品質を決めると私は考えています。
エラー処理とは、ちゃんとテストするのが前提にあってこそのものであり、単体テスト不足を補うためのものでは無いと考えています。