if文とガード節の規約について
if文の規約とガード節について、有効であるため記録する。
要約
・if文を記述する際は、たとえ処理が無くともelse句を記述する規約は意味がない。
・else ifが存在する場合は、else句を必須とする規約に意味がある。
・if文の前などにガード節を記述するのは可読性からも、処理速度からも有効(言語による)。
・if文中にreturnを書くことを禁止する規約があった場合は、ガード節は可読性が落ちて有効でない。
カード節と分岐を一緒にするな - リーダブル・コード(19)
if 文(else if 文)には必ず else を付けるというコーディング・ルールが
あります。 このルールは、MECE(ミッシー、Mutually Exclusive and
Collectively Exhaustive)の考えに似ています。 MECE とは、それぞれが
重複することなく、全体としてモレがないという意味で、主に経済学で
言われるロジカル・シンキングの考えです。プログラムは、どんなデータが入力されても、どのような状態であっても
想定して動かなければなりません。 しかも、どんな異常なデータが入力されても、
異常であることを伝えるという動きを正しく動かさなければなりません。
いわゆる「異常系」の処理です。 しかし、その対策法が、else 節を必ず付ける
ことではないのです。人間の思考における分岐は、よくあることについて正常にどのように動かすか
という分岐(ケース)から列挙され、後の方に例外的なケースの分岐が列挙されます。
獲物に狙われて生死にかかわる状況では、よくある対処法を早く考えなければ
生き残れなかったでしょうから、人間の思考としてありうるでしょう。 つまり、
暗黙的ではありますが、よくあることからケースが挙げられていくというのは、
MECE の前提条件としてよいでしょう。しかし、プログラムにおいては逆で、先に例外的なケースが列挙されます。
なぜなら、異常な入力データであることを先に検出しなければ、
アプリケーションが異常終了してしまうからです。
この時点ですでに MECE と前提条件が異なっています。それなのに、
MECE の考えに近い else を必須にするルールを採用してしまうのは、
問題がありそうです。 具体的に見ていきましょう。よくあるのは、NULL 判定や 0 除算判定を先にすることです。
a = p->attr;
c = 3 / a;という2行のコードが正しく動くためには、たとえば、次のように例外的な
NULL 判定や 0 除算判定の処理を先に書く必要があります。if ( p == NULL ) { return ERROR_CODE; }
if ( a == 0 ) { return ERROR_CODE; }a = p->attr;
c = 3 / a;前半にあるそれぞれの if の節を、ガード節と呼びます。
めったに起きないが、起きた時には、何もしないで出ていく節のこと、
という定義があるようです。ガード節には else を付けることはできません。 付けるとしたら次のように
なりますが、わざわざ else を付ける意味はありませんし、メインの処理の
ネストが深くなります。if ( p == NULL ) { return ERROR_CODE; }
else if ( a == 0 ) { return ERROR_CODE; }
else {
a = p->attr;
c = 3 / a;
}そのため、if 文は else を必須とせず、else if 文だけ else を必須とする
コーディング・ルールも存在します。 これは、よく考えられたルールだと
思いますが、MECE の考えを else 節によって漏れなくするということが
現実的ではないことを1つ認めたということでもあります。では、もし、人間の思考に近づけるために、else 節に例外的なケースを
処理させるとしたらどうなるでしょう。 次のようなコードになります。if ( p != NULL ) {
if ( a != 0 ) {
a = p->attr;
c = 3 / a;
} else {
return ERROR_CODE;
}
} else {
return ERROR_CODE;
}このコードの読みにくさの原因は、else 節のケースを理解するときに、
if の条件式を参照しなけばならないからです。 それなら、ガード節ですぐに
return した方が、ガード節だけで完結するためシンプルです。
ちなみに、もし、途中で return することを禁止するコーディング・ルール
があると、このような書き方にしかできず、読みにくいコードを強制する
ことになってしまいます。また、この書き方も、メインの処理がネストの深い位置になってしまっています。
2つの if 文を && で1つの if 文にするような工夫をすれば、1レベル分の
ネストに抑えることは可能ですが、もし、判定式が連続していなければ、
ネストは深くならざるを得なくなります。 たとえば、a = p->attr;
b = Func( a );
if ( 3 / b == 2 ) { ... }のコードにガード節を使わないと、次のようになります。
if ( p != NULL ) {
a = p->attr;
b = Func( a );
if ( b != 0 ) {
if ( 3 / b == 2 ) { ... }
} else {
return ERROR_CODE;
}
} else {
return ERROR_CODE;
}このコードは、if 文を1つにまとめることができません。 では、ガード節を
使うどうなるでしょう。if ( p == NULL ) { return ERROR_CODE; }
a = p->attr;
b = Func( a );
if ( b == 0 ) { return ERROR_CODE; }
if ( 3 / b == 2 ) { ... }ネストしなくなりましたね。
ここで、ガード節がメインの処理の if 文に混ざって読みにくくなって
しまっていることに気づいたでしょう。 では、ガード節の if を IF に変えて
みましょう。IF ( p == NULL ) { return ERROR_CODE; }
a = p->attr;
b = Func( a );
IF ( b == 0 ) { return ERROR_CODE; }
if ( 3 / b == 2 ) { ... }IF はガード節なので、メインの処理を理解するときは読み飛ばすことができます。
その読み方をすれば、メインの処理だけ読めるようになったことが分かるでしょう。Module Mixer に付属の clib には、条件を満たしたときにブレークする
IF マクロを用意しています。 これは、エラーが発生した瞬間にブレークさせる
ことができる機能で、エラーの原因がすぐに分かる効果があります。 この機能は、
エラーを判定することが多いガード節と相性が良いだけでなく、読みやすさにも
良い影響を与えているのです。else を必須にすることは否定しますが、MECE の考えは否定しません。
テスト対象のコードではなく、テスト・プログラム(データ)を開発するときに
MECE の考えを導入するのです。参考:
リーダブル・コード 7.7 ネストを浅くする