Home日記コラム書評HintsLinks自己紹介
 

フィンローダのあっぱれご意見番 第117回「思った通りに書こう」

← 前のをみる | 「フィンローダのあっぱれご意見番」一覧 | 次のをみる →

昨年ブレイクしたものは多数あるのだろうと思うが、 自分の中で最もインパクトがあったのは、 家庭からの高速インターネット接続環境ではないかと思う。 特に、ADSLの急激な普及が予想外で、 Webの世界に意外な影響を与えつつあるようだ。

正月頃に1週間ほどCATV接続の環境を使う機会があったのだが、 普段使っているフレッツISDNの常時接続64kbpsと、 CATVの数Mbpsという環境の差は、 数値では表現できないものがあった。 単に速度が上がったというよりは、 全く別のものを使っているような感じがしたのだ。

相手サイトの性能にもよるのだが、 大雑把に考えれば、数百KBのサイズのデータは、ADSLなら1秒程度で全部持ってくることができる。 実際にそういうサイズのページがあるのかというと、 例えば、大抵のニュース系のサイトのトップページは、 バナー広告まで全部込みでその程度になるのではないかと思う。

すると、64kbpsだと数秒~10秒以上かかってやっと表示完了するトップページが、 高速環境だと一瞬で表示される、 つまり、まさに新聞を読む程度のストレスで読めるわけで、 これが実は当たり前の世界なのかもしれないが、 体験してみるとカルチャーショックである。

新聞の1面を見る時にペーパーメディアなら一瞬で済むが、 感覚的に「一瞬」というのはどの程度なのだろうか? 1/100秒とか、1/10秒というのは当然そうだとして、 1秒とか3秒を一瞬とみなしてよいかどうか。 3秒というのは短いようだが、 実際、無意識的な操作の途中ではかなりの長時間である。 全てのページが1秒以内に表示できる、 というのが一つの目安だろうかと思う。

§

  

とあるサイトにこういうネタがあった。

 

※ このページの最後で紹介させていただきました。

---- 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 7 のような感じだ。

---- 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 の書き方が最も自然な表現であって、 見事にツボにはまっているかもしれないからである。

※参考

BM98'S ROOMつう
http://www.sun-inet.or.jp/~yaneurao/
Programming集中講義の、集中講義2.Programming Tips 1024、
Lesson 1 小ネタ集 0001-0050 を参考にしてください。
なお、このサイトの管理者は、今回書いたような話は全部把握している。 内容的にも面白く、個人的には超一押しサイト。

  

(C MAGAZINE 2002年3月号掲載)
内容は雑誌に掲載されたものと異なることがあります。

修正情報:
2006-03-03 裏ページに転載。

(C) Phinloda 2002-2006, All rights reserved.