プロが教えるわが家の防犯対策術!

引数に対する制限値チェック方法

 プログラミング(組み込み系C言語)関数作成時にいつも私が迷ところなのですが、
 関数の引数に対する制限値(範囲外)チェックどうするか、次の(1)、(2)で悩んでます。。
  【1】関数内で制限値チェックで行い、制限値外であれば戻り値でエラーコードを返す。
  【2】関数呼び出し時に、引数となる値(変数)をチェックし、制限値内であることを確認してから、関数を呼び出す。

 上記の【1】、【2】の方法どちらがよいでしょうか?
 状況にもよるとは思うのですが、その場合はどういった状況時に(1)||(2)がよいのか教えてください。
 (【3】もあればお願いします。)

 --【1】がお勧めの場合の質問
 (1)本関数での"結果"を返したいときどうすればよいかアドバイスください。
    戻り値("結果")と、エラーコードを兼用するのはなんかイヤです。。
    エラーコード付き関数は、全て同じ戻り値(1:OK時、-1:NG時 みたいに)
    としてまとめたいからです。
 (2)極端にほとんどの関数の戻り値を、OK/NG とす。これってどうですか?、


///////////////////////
 【1】、【2】の利点、欠点を僕なりに考えてみました。

 ##【1】の利点/欠点
 利点:
  ・本関数呼び出し時に、毎回制限チェックをしなくてよくなる。
   (汎用的に様々な場面で、使用するのであればこれは良い利点だと思います。)
 欠点:
  ・戻り値のとして、エラーコードを返さなくてはならないため、本関数での結果を返したい場合、
   以下方法をとらないといけない。
     1、引数をポインタとして、その引数で値を返す。
     2、戻り値として、エラーコードと兼用して返す。
       (例:エラー時の戻り値 = 0、正常にの戻り値 = 1~ 255)

 ##【2】の利点/欠点
 利点:
  ・エラーコードを返す必要がなくなるため、戻り値が有効利用できる。
 欠点:
  ・関す呼び出し毎に、制限チェックが発生し、制限チェック忘れが発生する。
   (汎用的に使うにのであればなお・・・)

A 回答 (5件)

私も何度も考えたことがある疑問ですね.



C++ とか C# でも,こういう場合 "例外処理" という方法を使ってしっかり対処するくらいなので,
間違いなく【1】が有利だと思います.

例外処理を使えないとなると,"結果をもらうのにポインタ引数を使う" ということがしばしば出て
きてしまいますよね.

分かっていても,やはり見苦しいものがあります.

エラーか否かを 0 か 1 (C++ とかで言う bool) で返しつつポインタにした引数に値を入れようと
すると,0 (エラー) を返すときに引数に何を入れて返してよいか分からなくなるからです.

確かに,"どうせエラーだから何でもイイ" のですが,"何でもイイ" というコードを書くこと自体
ナンセンスで,スマートではないですよね.

それに,基本的には "結果" を "メソッドに対する戻り値"として期待しているのに,わざわざ
引数として与えなければならないところに違和感を感じます.
(当然,メソッドに "結果" をふたつ同時に求めない場合ですが)

それ以外にも,プログラムのリーダビリティを損なうというか,読んでいて気分が悪くなりますよね.

例えば,以下の Type A と Type B のどっちが読みやすいかということについてですが,きっと
Type A の方を好む方が多数だと思います.


//------------------------------------------------------------------------------

※ input には何が入っているか分からない
※ Type B の FunctionFoo は,エラーのときは 0,そうでないときは 1 を返す


//**************************************
// Type A
//**************************************

if( この条件式を満たさないための値 != input )
{
  int output = FunctionFoo(input);
  /* output を使用するその後の処理 */
}


//**************************************
// Type B
//**************************************

int output;

if( FunctionFoo(&output, input) )
{
  /* output を使用するその後の処理 */
}

//------------------------------------------------------------------------------


結論から言うと【1】になるのですが,やはり理想としては Type A のようなリーダビリティも
求めたくなるのが現状です.
(つまり,例外処理を使えないことに苦言を呈したい)
    • good
    • 0
この回答へのお礼

やっぱり引数で結果をうけとるってなんかスッキリしないですよね~
Type Aでも関数呼び出すたんびに制限チェックしていたら、
なんかリーダビリティも低くんっているような気がします。

これだって方法ないんでしょうか??

お礼日時:2008/02/08 00:20

ANo.4補足


> >【3】関数内で制限値チェックで行い、制限値外であれば呼び出し元には戻らない。
> これは制限値外ならばプログラムを停止するということなのでしょうか?

そう。プログラムに間違いがあるとどんな被害が発生するか分からないので可及的速やかに停止させるべきという考えです。
# プログラムに間違いがなければ制限値外にはならないことが前提です

> また、結論としては、【1】、【2】どちらも必要と考えてよいんでしょうか?


基本的に【2】です。ただそれだけだと間違いの発見ができないのでデバッグ用にassertを入れましょうということです。
assertだとリリース時にはチェックしません(参考URL参照)。当然、プログラムはassertがチェックしなくても問題のないコードでなければいけません。関数内で制限値チェックをしているから呼び出し側ではチェックしないというようなコードは許されません。
# もちろんロジック的に制限値を外れる恐れはないからチェックしないというのはOKです

参考URL:http://www.linux.or.jp/JM/html/LDP_man-pages/man …
    • good
    • 0

一般的に安全性を考えて【1】を行う場合が多いようですが、プログラムを複雑化させてバグを増やす原因になります。


下記URLの「オブジェクト指向入門」に書かれている「契約による設計」の概念に沿えば、【2】の方がお勧めでしょうね。
# http://www.seshop.com/detail.asp?pid=7595

ただ上記書籍でも関数の入り口でのチェックは行っているので、正確には次の方法が良いと思います。
【3】関数内で制限値チェックで行い、制限値外であれば呼び出し元には戻らない。

具体的にはassertを使って制限値チェックを行うか、同様の自作関数(マクロ)を用意してデバッグに活用する。
# 自作関数ならデバッグ時にはこの関数内にブレークポイントを置けば確実に捕捉できスタックトレースなども取れる

この方法は、【1】のインタフェースが複雑化する欠点と【2】の制限チェックを忘れる欠点の両方をカバーすることができます。
なお、呼び出し側の制限チェックを省くことはできません。制限を満たさない呼び出しはあってはいけないからです。

この回答への補足

>【3】関数内で制限値チェックで行い、制限値外であれば呼び出し元には戻らない。
これは制限値外ならばプログラムを停止するということなのでしょうか?
また、結論としては、【1】、【2】どちらも必要と考えてよいんでしょうか?

補足日時:2008/02/08 00:03
    • good
    • 0

あらゆる場面において【1】にするべきです。


制限値チェック云々より、「責任範囲の明確化」です。

【1】のルールでその関数の責任が明確化されます。

使う人によって、毒になる関数があったら、
障害が起きたときに追跡が大変です。

障害の原因を探すときには、コードのどこからどこまでが、
シロか調査範囲を切り分けますが、【2】のポリシーで作成した
関数の使用箇所が、ありとあらゆる場所にちりばめられていたら、
シロと言える場所がなくなり、ソースコードを全探索する羽目になります。

「A君の作った関数、変な値入れられたらどうなるの?」
って突っ込まれたら、どうします?
「僕の作った関数は入力チェックをしていますから、エラーを返します」
と、言えたら良いですが、
「使う人がちゃんとチェックしていれば、問題ないですが、範囲外の値を入れられた
場合の動作はわかりません。」
なんて言ったら、
「それじゃ、使用箇所全体が信用できんじゃないか。」
となってしまいます。

さらに、チェック忘れ以外に、チェックミスも発生することも忘れないで下さい。
断然チェックミスのほうがたちが悪いです。

まあ、【2】でやってみて、痛い目を見るのも良い経験でしょうが、
周囲の信頼を得たいなら【1】のポリシーでいくのが固いと思いますよ。

>戻り値("結果")と、エラーコードを兼用するのはなんかイヤです。。
>極端にほとんどの関数の戻り値を、OK/NG とす。これってどうですか?、

頭の中まで、論理回路にしなくても良いですよ。
戻り値=成否、エラーコードを返すと固定的に考えるからイヤになるのでは?
エラーコードじゃなくて、結果だと思えば、
0:異常なし
1:故障1
2:故障2
3:故障3
と思えば、自然じゃない??
if( result == 0 ) {
 // 異常なし
} else {
 // 故障
}
    • good
    • 0
この回答へのお礼

【1】で責任の明確ですか。。そのとおりですね!
【1】がよい気がしてきました!
痛い目あうのはいやです・・

お礼日時:2008/02/08 00:17

下記のようにしてはだめですか?


質問者さんの「1」の簡素化で別々の関数にします。
あまり気に入らないかもしれませんが…

<サンプル>

#define FUNC(n) (n>=0)?func(n):err()
// 上行の「n>=0」の部分に範囲内の条件

int func(int N)
{
printf("正常範囲です\n");
// エラー発生時は、return エラー値>0
return 0;
}

int err(void)
{
printf("範囲外です\n");
return -1;
}

int main()
{
FUNC(1); // 範囲内
FUNC(-2); // 範囲外
return 0;
}
    • good
    • 0
この回答へのお礼

なるほど、こんな工夫もできるのですね!
参考になりました。ありがとうございます。

お礼日時:2008/02/08 00:14

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!