なみひらブログ

学んだことを日々記録する。~ since 2012/06/24 ~

不正パラメータチェックについて

「防御的プログラミング」とは、

引数が正しい値でないことを検査し、不正であればシステムを停止させて、デバッグさせること。

 

コード例:

unsigned int hash(char *str)

{

   // 引数チェック

    if (str == NULL) {

        // strがNULLのときは、assertでシステム停止、かつ理由メッセージを表示

        assert(0 && "illegal argument");

        // assertの後ろにリリース用の対応を記述

        return 0;

    }

 

    unsigned int h = 1;

 

    for (unsigned char *p = (unsigned char *) str; *p != '\0'; p++) {

        h = 31 * h + *p;

    }

    return h;

}

 

上記の関数は、文字列に対応したハッシュ値(数字)を返します。

文字列を想定しているので、NULLを渡されると困ります。

ということで、まず文字列がNULLでないことをチェックします。

NULLならシステムを止める。そのとき、理由も一緒に表示する。

以上の操作は、開発中やテスト中、デバッグ中に必要です。

 

しかし、いざこのhash()関数を外部に公開・リリースするときは、

普通はassert()の機能は使いません。

コンパイルやビルド時にそのassert()機能を消します。

例えばC言語では、コンパイルオプション「-DNDEBUG」をつけて、

gcc main.c -DNDEBUG

とコンパイルすれば、assert()は何も機能しなくなります。

 

そのかわりリリースのときは、この関数は、NULLが渡されると0を返す仕様になります。

(処理がassert()で止まらないので、処理が続くため)

そのことはちゃんとAPI仕様に書きましょう。

例:

/**

 * 与えられた文字列のハッシュ値を返します。

 * strがNULLであれば、デバッグ版ではassertで停止します。リリース版では、0を返します。

 *

 * @param 文字列

 * @return ハッシュ値

 */

unsigned int hash(char *str);

 

以上のような、不正パラメータのチェックを怠ると、

変な値を渡されて障害(メモリ破壊など)が発生し、相手に訴えられます(´・ω・`)

 

Effetive Java 第2版には以下のように書かれています。

【項目38 パラメータの正当性を検査する】より

「まとめると、メソッドあるいはコンストラクタを書く場合には、その都度、そのパラメータにどのような制約が存在するかを考えるべきです。それらの制約を文書化すべきですし、メソッド本体の初めで明示的に検査することで制約を強制すべきです。そうする習慣を身に付けることが重要です。その習慣により必要とされる地道な作業は、正当性検査に初めて引っ掛った時に報われます。」

 

--------------------------------------------------------------------------

※予備知識1

assert(0 && "illegal argument");という書き方が一般的です。

assert文は()の中が0(つまりfalse)になったときに、システムが強制停止します。

プログラミング内で「ココ通ったらバグだ!」っていうところには

assert(0 && "エラー内容");

と書きます。

システムがカッコ内の始め0を評価した時点で0と分かるので、確実にシステムが停止します。

上の例の場合に

assert((str != NULL) && "illegal argument");

という書き方はやめましょう。ややこしいし、リリース対応ができないので。