Programming Tips 1024
Phinloda様がC Magazineの原稿として、このコーナーを取り上げてくださったので、その掲載記事の元原稿をPhinloda様のご好意により掲載させていただく次第です。Phinloda様、ありがとうございます。やねうらおは、生まれてきてよかったです。おかあちゃん、ぼくちんを産んでくれて、ありがとう!○(≧∇≦)o (← 何ゆうとんのや..)
とあるサイト(※)にこういうネタがあった。 ---- List 1 ---- if (x != 1 && x != 2 && x != 5) { // xが1,2,5以外で実行 } ---- List 1 end ----- List 1 に対して、List 2 のように書いてはいけないのか、という話である。 ---- List 2 ---- if (x == 1 || x == 2 || x == 5) goto Exit; Exit: ---- List 2 end ----- そのココロは、この場合に goto を使うことの是非を問題にしているのである。 それを承知で言いがかりに近いことを一つだけ書かせていただくと、あまりよく ないと思う。ただし、List 3 ならばよい。 ---- List 3 ---- if (x == 1 || x == 2 || x == 5) goto Exit; // xが1か2か5なら実行しない Exit: ---- List 3 end ----- 論理的には「1、2、5以外なら実行する」と「1か2か5なら実行しない」は同値で ある。しかし、思考的には別物だ。というのがポイント。これが後々とんでもな いバグの原因になったりする。もしこのプログラムを書いた人か、またはその仕 様を設計した人が、頭の中で「xが1、2、5以外で実行」と考えていたのであれば、 それに対する論理的に同値なバリエーションが多種あったとしても、ここはやは り「x != 1 && x != 2 && x != 5」と書くべきなのである。 とはいっても、「1、2、5以外ならば」という条件設定そのものが既にややこし い。「理解しやすい条件で判断する」というのは重要で、細かいひっかかりが積 もり積もれば全体の readability に響いてしまう。「1、2、5以外ならば」より も「1か2か5なら」の方が、条件としては圧倒的に明快だ。という意味で、ここ はList 1のコメントにある発想をまず捨てて、「1か2か5なら実行しない」とい う考え方をすべきだと思う。で、その前提で考え直すと、多分、私の好みとして はこう書きたいというのが List 4。 ---- List 4 ---- if (x == 1 || x == 2 || x == 5) { // 何もしない } else { // 1,2,5以外 } ---- List 4 end ----- ちなみに、そのページの著者ですが、このあたりの話は全て把握しているようで、 全体的にも納得力のある内容で面白いから、見てみるといいと思う。ということ で、そのページの著者曰く、List 2 のように書くメリットとして、(1) 処理の 行数が多い場合 List 2の書き方が見通しがいい、(2) 字下げの必要がない、(3) 条件部分が読みやすい、の3点をあげている。 (3) に関しては List 2 も List 4 も同じだからイーブンのはず。多分、最も大 きな差は(2) だろう、つまり、インデントだ。「またか」って感じでしょうか。 考えてみればCにしろC++にしろ、字下げの必要がない言語なのだ。単にインデン トが違うだけなら、コードの実行時の性能とか動作には多分影響しないはず。{} で囲ったら字下げするという、コンパイラには関係ないルールを勝手に人間が実 践しているだけの話なのだ。 では、なぜインデントのルールの話を始めると宗教じみた主義の激論になるのか。 それはインデントが「見やすさ」に大きく影響するからである。読みにくいプロ グラムというのは育てるのが難しくて、結果的に、品質にも影響するものである。 ところが、どうすれば人間が見た時に都合がよいか、という話に落とし穴がある。 大抵の人は「自分が見慣れた書き方」が一番見やすいと思っているのだ。自分な りのルールに慣れてしまうことで、その人に最適化されたインデントがその人の 頭の中では完成しているのではないか。つまり、細かいルールが宗派に分かれて しまうのは、ある意味仕方ないのだ。 そうは言っても、認知心理学の方面からアプローチしてみると、大きなルールと しての一定のパターンが守られていることが分かる。最も共通した感覚は、イン デントが等しい一連の行のコードは一連のものと認識する、というものである。 言い換えると、インデントが異なっていたら、それは上下とは別のレベルのもの として認識されるということだ。このルールが一番よく出ているのがif-elseが 多重になった場合である。List 5 のような書き方をしばしば見かける。 ---- List 5 ---- if ( ... ) { /* 処理1 */ } else if ( ... ) { /* 処理2 */ } else { /* 処理3 */ } ---- List 5 end ---- しかし、if文の構文を考えると、構造的には List 6 のように解釈すべきだろう。 ---- List 6 ---- if ( ... ) { /* 処理1 */ } else if ( ... ) { /* 処理2 */ } else { /* 処理3 */ } ---- List 6 end ---- では、なぜ List 5 のような書き方をしてしまうのだろうか。プログラマーが List 5 のように書く場合は、思考的には処理1、処理2、処理3を全て同一レベル だと解釈しているのだと思う。同じレベルの処理は同じ字下げになると考えれば、 List 5 のように書いても違和感がない。しかし、「処理1」と「処理2と処理3」 のレベルが異なるものだと考えているならば、プログラマーは List 6 のように 書くだろうし、また、その書き方に対する違和感はないはずである。実際、その ようなコードも見たこともあるし、書いたこともある。 そこまで考えるとList 2の特徴が一つ見えてくる。インデントされていないコー ドは、視覚的にはその前後と同一レベルになってしまうから、どこからどこまで か条件が成立した場合の処理なのか、直感的に識別できなくなるのだ。もちろん、 インデントを使えばそれは分かりやすくなる。List 5 のような感じだ。 ---- List 7 ---- if (x == 1 || x == 2 || x == 5) goto Exit; // この位置に // 処理を書いて // みるとか… Exit: ---- List 7 end ----- しかし、多くの人が、List 7 は猛烈な抵抗感があると言うような気がする。実 際によくある書き方として、ラベルを左方向にインデントするというものがあっ て、List 8 のようになる。 ---- List 8 ---- if (x == 1 || x == 2 || x == 5) goto Exit; // 処理は // インデントなしで // 書かれる Exit: ---- List 8 end ----- これだと、Exit: の場所は明白になるが、このExit:にどこから飛んでくるのか 分からないという問題が残る。特に、途中の処理にifがたくさんあったりしたら、 Exit にジャンプする個所はどんどん分かりにくくなる。 * ところで、(1) はどういう意味だろう?一つ考えてみたのは、処理の行数が多く なると、そこを実行している条件が短期記憶から追い出されてしまって、どうい う理由でそこを実行しているのか分からなくなるとか?実際それはよくある話で ある。しかし、goto を使って書いても、それは同じことではないのか? 実は違うのだ。goto で飛ばすというのは、その条件を例外として解釈したいと いう意志である。発想としては、本筋の処理は最初から最後まで唯一のレベルに あるのだ。先ほど、条件成立時に行う処理範囲が分からないことを「特徴」と表 現した。デメリットではなく特徴と書いたのは、この処理がもし「条件成立時」 に実行するのではなくて、一つの「通常時」の処理の流れがまずあって、何か問 題がある時だけ例外的に中断させたい、みたいな発想でプログラムを書くのなら、 まさに List 2 の書き方が最も自然な表現であって、見事にツボにはまっている かもしれないからである。 (フィンローダ @nifty FPROG SYSOP) ※参考 BM98'S ROOMつう http://bm98.yaneu.com/ Programming集中講義の、集中講義2.Programming Tips 1024、 Lesson 1 小ネタ集 0001-0050 を参考にしてください。 なお、このサイトの管理者は、今回書いたような話は全部把握している。内容 的にも面白く、個人的には超一押しサイト。 |