~ プログラミングって面白そうと思った方に向けた連載エッセイ ~
ソフトウェア業界には「動いているソースコードに触るな」という伝統的な掟がある。何かの改修のついでに、よかれと思って関係ない部分にまで改善を施す。それが仇となり、思わぬ動作の問題を引き起こす。よく見聞きするケースである。当のプログラマは善意で行ったにも関わらず、その責めを負い、二度とそのような罪は犯さないと上司に誓うはめになる。
「コピペ」による「コードクローン」がなくならない背景には、こうした事情もある。
一般的に言って、プログラムの変更が新たな問題を生まないことを確実に保証するのは難しい。些細な変更でも、その影響は無限に広がる可能性を秘めている。他人――半年以上前の自分を含めてもよいだろう――の書いたコードは、一見無駄に見える処理が実は重要な意味を持っていたりと、意図を正確に汲み取りづらいこともある。コードを変更すればテストもしなければならない。影響範囲を限定できなければ、自ずとテスト範囲も広がる。「触るな」の掟が支持され続けている現実には、それなりの理由があるのだ。
だが、この掟に従ってつぎはぎ的な変更を重ねていくと、プログラムの可読性、保守性は落ちる一方で、しまいには誰も見たくない、触りたくないという逆聖域になってしまう。このようなコードは、そのこんがらがっている様から「スパゲティ・コード」と呼ばれたりする。
「触るな」の掟に対し、勇気を持って積極的、継続的にコードを改善していくことを提唱するのが「リファクタリング」である。リファクタリングでは、原則として動作を変えずにソースコードを整理し直す。プログラムは美しくなるが、ソフトウェアの振る舞いは変わらない。そのためマネージャは「自己満足」と片づけてしまいがちである。しかしそうではないのだ。本当はもっと切実な問題なのである。リファクタリングの目的は可読性の向上であり、品質、生産性の向上である。品質のよくないコードは「技術的負債」と称されることがある。放置しておけば膨らむ一方であり、不具合リスクは上昇の、開発効率は下降の一途をたどる。その借金を、頑張って今返済してしまうのがリファクタリングである。
近年ではリファクタリングの価値も広く認められるようになり、それを安全かつ効率よく行うためのツールも整備されてきた。「触るな」の掟はいまだ健在だが、積極的にリファクタリングを採用する開発チームも増えてきている。プログラムの美しさを維持するためには、継続的なリファクタリングが不可欠である。そうして美しさを維持することによって、プログラムの品質や保守性も維持されることになる。
リファクタリングの手法自体は技術的なものだが、プログラマのやる気に与える影響も見逃せない。プログラマとは――どんな職種でもそうかもしれないが――、やる気を起こすと生産性の上がる種族である。そして普通は、スパゲティ・コードよりも美しいコードを前にした方がやる気が起きる。
余談だが、概念としてのリファクタリングは様々な分野に応用できると思う。プログラミングの世界だけに留めておくのはもったいない。何かを作り上げる、スキルを習得する、組織として成熟する――あらゆる変化の過程において、改善には慎重さ(ときに恐れ、諦め、惰性)という対抗勢力が立ちはだかる。それらに屈することなく、折り合いをつけながら堅実に成長を続けていくための方法論がリファクタリングである。たとえ関わるのが自分だけだとしても、始めたものが存続する限り、継続的に改善していくことには意味がある。文章もほかではない。
ここで一つ例を。
ヨルゲンはミシェルより酒が強く、エミールはヘンリーより酒が弱い。ミシェルはリディアほど酒が弱くない。ヨルゲンはエミールほど酒が強くない。
最も酒が強いのは誰だろう。
意地の悪いクイズのようだが、このように頭を悩ませるソースコードも実際に存在するのである。
まず強弱の表現が統一されていないのが問題だろう。比較する順序もばらばらだ。このままでは理解が難しいのでリファクタリングしてみよう。
リディアよりミシェルの方が酒が強い。
ミシェルよりヨルゲンの方が酒が強い。
ヨルゲンよりエミールの方が酒が強い。
エミールよりヘンリーの方が酒が強い。
正解はヘンリーだった。
強弱の表現を統一し、弱い順から並び替えることでかなりわかりやすくなった。規則性を持たせることで可読性が向上する一例である。
より美しくなったが、さらに短くしてみよう。
酒の強い順に、ヘンリー、エミール、ヨルゲン、ミシェル、リディアである。
無味にすぎるだろうか。心配ない。コンピュータもプログラマも、プログラムに趣を求めてはいない。プログラムにおいて、装飾は邪魔にしかならない。それは美しさをも阻害する。
次の例に移ろう。
トラックレースでは、ちょうど0・1秒で終わる以外は次の0・1秒として変換され記録される。すなわち、10秒11は10秒2と記録される。
日本陸上競技連盟ルール第3部から、第165条の引用である。
正確を期すためか、やや遠回りな表現で、失礼ながら一読で意味を正確に把握するのは困難である。これをリファクタリングしてみたい。
条文を噛み砕いていくと、「え、これって切り上げってことじゃないの?」という疑問が浮かぶ。が、すぐに断定するまでの自信はない。なぜこのような表現にしたのだろう。特別な理由があるのかもしれない。見落としているケースはないだろうか。
切り上げにならないケースがないか、境界値を使って検証してみよう。
【ケース1】小数第2位が0
変換前:10秒10
期待値:10秒1(「ちょうど0・1秒で終わる」ので変換不要)
変換後:10秒1
【ケース2】小数第2位が0以外(下限値=条文に挙げられた例)
変換前:10秒11
期待値:10秒2(切り上げ)
変換後:10秒2
【ケース3】小数第2位が0以外(上限値)
変換前:10秒19
期待値:10秒2(切り上げ)
変換後:10秒2
いずれのケースでも、条文の変換結果が切り上げの結果と一致することが確認できた。
参考までに、こうした局所的な動作の検証は、実際のプログラミングにおいて「単体テスト」という形で行われる。近年では、単体テストはテストプログラムで自動実行することが多い。製品プログラムの妥当性を、テスト用に記述したプログラムによって検証するのである。テストを自動化することで、人の手を介すことなく何度でも、素早く、正確に検証できるようになる。ソースコードをリファクタリングしたときも、単体テストを自動実行することにより、意図せず動作が変わっていないかを即座に確認することができる。
例に戻ろう。まだ確信は持てない。考慮漏れはないか。信頼性の高い根拠が欲しい。そこで「切り上げ」の定義について、特定非営利活動法人・数理検定協会のサイトを参照すると、「概数にする方法の一つで、必要な位より下の数がすべて0でないかぎり、求める位の数を1大きくすることを切り上げという」とある。
「必要な位より下の数がすべて0」→「ちょうど0・1秒で終わる」
「求める位の数を1大きくする」→「次の0・1秒として変換」
のように相当すると考えると、条文はほぼ切り上げの定義を説明していることになる。
真意は日本陸上競技連盟に問い合わせるのが確実なのだろうが、ここでの核心からは外れてしまう。コードを書いたプログラマがすでに開発チームを去っていて、聞きたくても聞けないというのはよくあることでもある。仮説は正しいことが証明できたとして、実際のリファクタリング作業へと進もう。
先ほどからただ「切り上げ」と書いているが、位を明示する必要がある。誤解のないように正確に記述したい。かといって「小数第1位までになるように第2位以下の端数を切り上げ」では長ったらしくてリファクタリングの意味が薄れる。「第1位に切り上げ」か、「第2位を切り上げ」か。どちらでも意味は通りそうだが、ストレートに目的を示す前者の方がより明解、美しいと言えるだろう。
トラックレースでは、秒数を小数第1位に切り上げて記録する。
ここで考え直す。仮に切り捨てや四捨五入に変わったときも、同じ形式で無理なく表現できた方がよい。再び数理検定協会の定義にあたると、「例:切り上げによって小数第一位までの概数を求める。」とある。これに倣おう。
トラックレースでは、切り上げによる小数第1位までの概数で秒数を記録する。
堅い気もするが、正確さと保守性を優先してよしとしよう。
…面倒くさい。そう思われただろうか。確かに。私もそう思いながらリファクタリングした。
直さずとも運用できているのだ。意味を狂わせてしまったら仇となる。得が少ない。やめてしまおうか。実際のプログラミング現場でも、そんなふうに葛藤することはよくある。特にリファクタリングの価値を認める文化のない組織では、孤立した正義感しか拠るところがない。そっとしておこうじゃないか。気持ちは傾く。
ここが美しいプログラムとそうでないプログラムの分かれ目である。
くじけてはいけない。歪みの上には歪みが乗る。放置した汚れが自然と落ちることはない。これは見解というより事実である。改善しないことの危険性を然るべき人に伝えるのは、それを知る者の責務だろう。上司が怒ったのは改善を図ったからではなく、リスクのあることを予告なしにやってしまったからではないか。「頭が固いからどうせ言っても」と頑なに決めつけるのはやめて、根気良くその利を説くところから始めてみよう。志に恥ずべきところはない。堂々と自信を持って、丁寧に話せばきっとわかってもらえる。とは限らないが、その努力を怠って流れに身を任せた者に、後で「そら見たこと」と他人事ぶる資格はない。
例に戻ろう。
忘れてはいけない。大事なまだ作業が残っている。リファクタリング後の定義に先ほどの「単体テスト」を通しておこう。
いずれもリファクタリング前と結果が変わらないことが確認できた。これで安心だ。
以上、日本語の文章を例に、リファクタリングの過程を、心の動きも含めて追ってみた。リファクタリングを施した後のすっきりした感じを、少し味わっていただけただろうか。
美しさを保つにはコストがかかる。だが、すぐに成果に表れないからと言って無駄と切り捨てるのは早計である。違いは将来確実に表れる。美しさを保ったコードが品質、生産性のうえでメリットを生み出すのは、ほぼ約束された未来と考えてよい。逆の表現がよりわかりやすいかもしれない。作りっぱなしのコードが品質、生産性のうえで大きな足かせとなるのは、ほぼ約束された未来である。
序章
第一章 その美の特徴
第二章 見た目と冗長性
第三章 ロジック
第四章 命名
第五章 アーキテクチャ
第六章 リファクタリング
第七章 デザインパターン
第八章 正規化
終章