贅肉を落としてスリムに

引き続き、他人が作ったプログラムの改訂作業です。

わかりにくいコードを整理して書きなおす作業は、 全体の構成を把握するためにも役に立ちます。 本来は「追加してほしい」と言われた機能だけを組み込めばいいのですが、 充分に時間をかけて整理しておくと、結局は時間の節約になるでしょう。

最初約68000行あり、使っていないファイルを消していったら 7000行も減った、と前々回書きました。 現在、約35000行です。まあ、使っていない機能を削除した分も多少はありますが、 大部分は重複したコードを整理し、単純な書き方にした結果です。 最近は、機能追加した分の増加と、整理作業で減った分とがつりあって、 ほぼ横ばいになっております。 変な話ですが、時々wcで行数を数えて

今日はこれだけ減った

と確認するのが一種の励みのようになってきています。


デジャヴューを感じるプログラム

直しても直しても、また修正前のコードが出てくる、という「賽の河原」的 プログラムをこう言います(嘘。今思いついた言葉です)。

例えば、ある1行に字下げのタブが1個多くついている、みたいなちょっとした 間違いに気がつき、修正したとしましょう。 しばらくすると、また同じような文脈で余分なタブに気がつき、 「あれ、直し忘れたかな」と思いつつ修正します。 さらに数分後、また同じようなのが目にとまって、そろそろ変に思い始めます。 直したはずなのに、全然直っていない...

ある函数をまるごとコピーして函数名を変え、数箇所書き換えて 別の機能の函数にする、という作り方を繰り返すとこうなります。 ですから、「既視感」ではなく本当に何回も同じ間違いを見ている訳ですが、 なぜか「いや、自分は今デジャヴューにとらわれているのだ」と感じてしまうのです。

ファイルを作って標題を書きこむ函数がファイルの個数分ある

というのが典型的な例。書きこむ標題はみな同じなので、 冷静に考えれば、ファイル名を引数とする函数が1個あれば足りる訳です。

で、圧巻が、約600行の巨大な函数が2つ、なぜか変数名を微妙に変えて 並んでいたやつです。 そっくりとはいえどこか違っている訳ですから、一つにまとめるにしても 差分をみつけなければならない、でも単純にdiffで比較することもできない、という トホホな状況なのです。


単純なことは単純に書こう

さてさて、次のリストをご覧ください。

List 1

        isFirst = TRUE;
        while (TRUE) {
                if (isFirst) {
                        p = strtok(text, delim);
                        isFirst = FALSE;
                }
                else {
                        p = strtok(NULL, delim);
                }
                if (p == NULL) {
                        break;
                }
                func(p);
        }

strtok() ってのは、文字列を区切り文字で順次区切って取り出す函数。 実際にあったのは別の函数だけど、考え方は同じなのでこれで例を示します。 データを順に取り出して処理する、という非常にありふれたプログラムで、 最初のデータの取り出し方と2つ目以降の取り出し方が違っているものです。

こういうのには定型的な書き方があって、

        for (p = strtok(text, delim); p != NULL; p = strtok(NULL, delim)) {
                func(p);
        }

のようにすればすっきりまとまります。

もう一つ、こんなのもあります。

List 2

        phase = 0;
        while (phase < 2) {
                switch (phase) {
                case 0:
                        f();
                        phase++;
                        break;
                case 1:
                        g();
                        phase++;
                        break;
                }
        }

こう書けばすぐにわかると思いますが、

        f();
        g();

と同じことです。実際には、f(); や g(); の部分が結構長かったり phase がもっとたくさんになっていたりしてわかりにくかったのですが、 各 phase のコードを個別の函数にまとめて整理していったら こういう単純な構造が浮かび上がってきたのでした。

この書き方をすると不必要に字下げが深くなるのも問題でして、 エディターで眺めると、かなりの範囲にわたって画面の右側にしか 文字が現れなかったりします。