2010年12月3日金曜日

ifを書いたら負け

筆者の持論というか、
最近の見解で「条件分岐を書いたら負け」というものがあります。

具体的にはif,for,while,switchです。

プログラムと条件分岐は、切っても切れません。
しかも要件定義者は、プログラムに都合の悪い仕様を考えてくれるので、
馬鹿正直に実装すると、条件分岐で大変な事になります。

何が大変って、デバッグがです。
極論すれば、条件分岐を一つ書くと、バグの可能性が一つ増えることになります。

20年前には、ソースプログラム上のif文の数等を数えて、バグ件数を見積もって、
それに満たなければ「枯れてない」と判断する。そんな昔話もあるらしいです。

面倒な仕様は、実は仕方有りません。
面倒な仕事を面倒なままやるなら、誰にでも出来ます。

困難な問題は分割せよ、と昔の偉い人も言ったらしい。

もう少し具体的な話を書きます。
例えば、非連続値が関わってるタイプの条件分岐は、工夫次第で条件分岐を省けます。

簡単な話では、「「入力値」を幾つかの範囲にグルーピングするもの」。集計ではよくありますね。

perlではフツーに書くと、こんな感じになると思われます。

my @threshold = ( 20,60,100,150,200,500);
sub islevel{
my($value)=@_;
my $level = 0;
foreach my $ii (@threshold){
last if( $ii > $value ) ;
$level = $ii;
}
return $level;
}


#一応デバッグしたから正しい筈

  • 各範囲の閾値のうち、
  • 入力値より大きいもの
  • 直前が、範囲値

直前の値、が曲者で、地味に面倒な処理です。

この手の実装をするときには、大きい方から回した方が、
条件文はスムーズだったかな。

結論からいいましょう。
perlらしく書くとこうです。

my @threshold = ( 20,60,100,150,200,500);
sub islevel{
my $value = shift;
my @levels = grep { $_ <= $value } @threshold; return $levels[$#levels]; }


後者の考え方は実にシンプルです。

  • 各範囲の閾値のうち、
  • 入力値以下のもの全てを、
  • のうち、もっとも大きいものが、範囲値

余り変わってない?そうですね。「直前の値」の解釈を変えただけです。

本稿で論じたいことの肝は、
処理は多めにやっちゃったほうがプログラムはシンプルになる。
というところです。

ループと条件分岐は、grep に任せています。
第2引数に配列、第1引数に条件式を指定すると、
それにマッチした要素を全て抜き出してくれるという便利関数です。
関数型プログラミングの発想らしい?

これがサンプルをperlで書いている理由だったりしますが、
似たような考え方は他の言語でも出来ます。

grepで回せるように、要件を意訳した。と言った方が正しいでしょう。
性能についての議論はややこしくなるから割愛します。

って言うか、grep に相当する機能が無かったら
自分で作るぐらいが本当でしょう。
毎回、前者のループをコピペしまくるんじゃ頭悪すぎます。