続 糞プログラムをやっつけろ!
前回の続き。このすさまじいif - then -elseチェーン。なぜこんなことになってしまったのだろうか。
一言で言えば、モジュール分割方向がまちがっているということだ。たとえば、こんなWebアプリケーションがあったとしよう。ある商品の取引で、画面からは「買い」「売り」「取消」の3つが入力できる。株の売買みたいなのを想像してくれれば良い。さて、このユーザー入力の「買い」「売り」「取消」の選択に対して、プログラムはどのように設計するだろうか。
自分だったら、おそらく最初に「買い」「売り」「取消」の3つの処理に振り分け、それぞれ「買い」なら買いの処理特化した処理にすると思う。オブジェクト指向で言えば、ObjectFactory作って、「買い」「売り」「取消」共通のインターフェイスクラスを生成して…、といった感じか。ここでいいたいのは、処理の設計はユーザーの立場---クライアント側の立場で作られているということだ。あたりまえと思うかもしれない。なぜならクライアント側の処理だから、クライアント側の立場でプログラム設計したほうが、きっとスムーズにつくれるだろう。
しかし、だ。ヘタレプログラムはこういう風にならない。先ほどの処理で、ユーザー入力にたいして注文テーブル(RDBを使っていれば)に1件追加するとした場合、注文テーブルに対する処理をサブルーチン化する。ユーザー入力だから入力チェックも必要だろう。入力チェック処理も、ひとつのサブルーチンにする。もちろんレスポンス画面用の文言を生成する部分も、1つのサブルーチンだろう。
これの何が悪いのか? 個々のサブルーチンは「買い」「売り」「取消」に限らず同じサブルーチンが走る。だからこれらのブルーチンは次のようになっているだろう。
void init(int kbn) { switch(kbn) { case 1: // 買いの場合 …買いの初期化 case 2: // 売りの場合 …売りの初期化 case 3: // 取消の場合 …取消の初期化 } } void updateData(int kbn) { switch(kbn) { case 1: // 買いの場合 …買いのテーブル更新 case 2: // 売りの場合 …売りのテーブル更新 case 3: // 取消の場合 …取消のテーブル更新 } } …以下同様
もちろん、これを呼び出すmain側も同様だ。
int main() { string msg; if( kbn == 1 ) { msg = "買い処理"; }else if(kbn == 2) { msg = "売り処理"; }else if(kbn == 3) { msg = "取消処理"; } init(kbn); updateData(kbn); … }
これでめでたく、すべてのサブルーチンに同じ条件のif-elseが入る訳だ。これがよく見かける if-then-elseチェーンの正体である。
なぜこのような分割をするのか。それは、サービスを提供する側からの視点でモジュール分割をしているからだ。注文テーブル更新があるなら、「注文テーブル更新」というサブルーチンを作る。レスポンスの文言生成なら、レスポンス文言サブルーチンを作る。その後で買い」「売り」「取消」の処理振り分けをするから、ほとんどすべてのサブルーチンで、同じif条件が入るわけだ。この考え方はまさしくホスト系の考え方であろう。
こんな風に、クライアント側の立場で処理を分割しないから、ちょっと区分やコードが増えるだけで、簡単にifネストが深くなる。今の、いわゆる売買区分のほかに、午前/午後のような時間区分、すぐ処理するか予約かの処理区分みたいに、区分が3つになろうものなら、この地点でifは3重ネストである。いや、エラー処理もあるからifの4重ネストになる。これでさらにforループも加われば、インデントは5マスになる。あまりにインデントが深くなるので、インデントが半角空白1文字とか、ふざけたプログラムができるのもこんな背景があるからだろう。
しかし、こんな考え方をするプログラマ(?)は、再教育できないようだ。機能ごとに処理を分ける(だって分けてるじゃん)とか、グローバル変数を減らせ(だって関数の引数がふえるじゃん)とか、なぜダメかを説明しても理解しない/しようとしない/できないのだ。ヤレヤレ、だからソコ Javaで数値をStringで比較するのヤメれ