2007-08-07

最近読んだ本

だるい. 英辞郎によれば, 夏ばてを英語で "suffering from the summer heat" というらしい. 冷房負けは "suffering from the summer cool" かな ... 何もする気が起きない. 消去法で日記を書くことに.

The Art Of Unix Programming を読んだ. (原書は オンライン版 もある.) 主題については良くかけており, 得るものも多かった. プログラミングというとクラスや関数がどうといったコードの話を想像するけれど, この本の重点はもう少し外側. プロセス同士の協調の仕方 (パイプとか) やファイルフォーマットの設計, コマインドライン・インターフェイスの流儀などについて詳しく説明している. Unix はプロセス同士の協調を好む. だからその繋ぎ方には気を使うんだろうね.

Unix の歴史など文化的な話題にもページを割いている. ただ語り口は内輪受けの空気が濃く, 好みがわかれそう. 個人的には退屈だった. 似たような話はあちこちで読むからね. また Unix 外の文化に対する悪態ぶりには腹が立つ. Unix 信者(とあえて言おう)と話している時に感じる, あの絶望的に話の通じない無力さ. Windows も Java も C++ も GUI もマルチスレッドも好きな私にとっては悲しい. Unix 好きか寛容な心の持ち主なら楽しめると思う.

Unix とリファクタリング

さて, artu の著者は直交性を語る中でリファクタリングに触れている. 目のつけどころは悪くない. ただ深く掘り下げることなく話は先に進んでしまう. 悲しい.

仕事の中でも, やっつけスクリプトのうち生き延びたものの "やっつけ指数" を下げていくことはよくある. その手口はリファクタリングに似ていると思う.

artu にもある通り, Unix はモジュール分割の単位としてプロセスを積極的に使う. 小さなスクリプトやプログラムが協調して仕事をする. オブジェクト指向ではクラスが協調して動く. リファクタリングはオブジェクト指向の言葉で説明されることが多いけれど, モジュール同士の関係を整理する作業だと考えてもいい. だから Unix プログラミングをオブジェクトのメタファで眺めると, 広く知られるリファクタリングのパターンをあてはめやすくなる. プログラムやシェルスクリプトがクラス, プロセスやデータファイルがオブジェクト, 引数つきでのプログラム起動やパイプがメソッド呼び出し ... というかんじ.

ケーススタディ: テスト用やっつけスーパーサーバのリファクタリング

感覚がつかみにくい向きもあるだろうから, 以下で少し実例 (やや誇張あり) を. パターン名の詳細は 原典 をあたってください.

私はこのごろ仕事でよくスクリプトを書いている. もっぱら自動テストを支援するためだ. ネットワークのミドルウェアをテストすべくストプログラム同士を通信させたい.

通信する C/S の対はテストケースの数だけある. そこで各テストクライアントに対して 起動するテストサーバの対応を管理するスーパーサーバ を ruby と webrick で作った. inetd みたいのね. テストクライアントは HTTP でスーパーサーバに問合せ, 該当テストサーバを起動する. HTTP はミドルウェア自身がサポートしている. だからクライアントはゲーム機のように比較的融通の利かない環境でも動く.

最初は Windows のループバックやゲーム機相手にテストを動かしていたが, ある日から隣にある Unix 機との通信もテストすることになった. テストクライアントのビルド はその Unix 上で行う必要がある. でもリモートでビルドをする良い手がない. 困った.

そこで Unix 上に先のスーパーサーバを動かし, 少し手を加えて make をキックすることにした. Windows 上からスーパーサーバに make を依頼する. URL やポート番号といった定数を使い回す都合で, スーパーサーバを叩く Windows 上のスクリプトは スーパーサーバ自身のコードに寄生させることにした. コマンドラインの引数でサーバにもなるしクライアントにもなる. クライアントとして起動したスクリプトは まず Unix 側にソースコードをコピーし, それからスーパーサーバを叩いて make を呼び出す. 即席リモートビルドのできあがり.

これはまあまあうまく動いた. 味を占めた私は更に拡張を進め, Unix 側で色々やる機能を加えていく. 気がつくとスーパーサーバのコードは酷いことになっていた.

そろそろ潮時か. 帽子をかぶりなおしてリファクタリングをしよう.

長すぎるスクリプト, スクリプトの抽出, 定数の引き上げ

前置きがながくてすみません.

さて, やっつけスーパーサーバのコードは "長すぎるメソッド" ならぬ "長すぎるスクリプト" の 臭いを発している. "メソッドの抽出" にあたる "スクリプトの抽出" で対処しよう. まずスーパーサーバ server.rb に寄生している クライアントのコードを client.rb に切り出すことにした.

とはいえもともと寄生したのにも理由がある. いくつかの定数を使いまわすためだ. その定数は一旦 client.rb にコピーしたけれど, "コードの重複" が臭う. (要は DRY でない.) そこで定数やユーティリティメソッドをまとめた const.rb を作って require し, "メソッドの引き上げ" にあたる "定数ファイルの引き上げ" をした.

少し短くなったとはいえ, server.rb はまだ長過ぎる. よく見ると client.rb に答える webrick ハンドラの一部が複雑だ. make をキックする以外の仕事をしている. 別スクリプトに抽出しよう. そこで新しく sometask.rb を作り, server.rb からは system() で呼ぶことにする.

仲介プログラム, 怠け者スクリプト, スクリプトのインライン化, 引数スクリプトの導入

ふと気付く. そもそも sometask.rb や make を HTTP 経由で呼び出す必要はあるんだろうか. もともと HTTP を使ったのはゲーム機からアクセスするためだった. 相手が Unix なら SSH を使えばこれらを直接起動できる. ここでの webrick は余計な "仲介人" というか "仲介プロセス" だ. そこで client.rb を書き換えて ssh をキックするよう修正, "仲介プロセスの除去" を行った.

client.rb を書き換えていると, このスクリプトはほとんど ssh 呼び出しだけしかしていないことに気付いた. "怠け者クラス" ならぬ "怠け者スクリプト" だ. 複雑に見えたのはオプション引数の解釈と設定ファイル読み込みのせい. client.rb に引数を渡すのはローカル用の Makefile なので, その Makefile から直接 ssh を呼んでしまえばいい. そんなわけで "スクリプトのインライン化" を行った. 元のスクリプトにはもう用がない. svn delete client.rb.

勢いあまって client.rb を消したはいいが, ssh の接続先ホスト名を Makefile にハードコードしてしまった. これはもともと const.rb に書かれており, client.rb はそれを利用していた. ハードコードからは悲しい臭いがする. そこで const.rb を呼んで所定の定数を出力する print-const.rb を書いた. Makefile では ssh `print-const.rb foo` ... と逆クオートで使う. "引数オブジェクト" ならぬ "引数スクリプト". 少しすっきりした.

最初から SSH を使っておけばよかったけれど, うっかり変なコードを書いてしまうことはよくある. (私は.) 気付いた所からリファクタリングで直していこう.

引数によるポリモーフィズム

やれやれと一息つくのも束の間, ゲーム機用コードでの不具合がみつかった.

しかし Unix のリモート呼び出しに対応すべくコードを書き散らかしたせいで Makefile が混乱してしまった. 似たようで違うルールもちらほら. たとえばゲーム機用の build や clean の他に unix-build, unix-clean などがある. バグをとるまえにちょっと整理しておこう.

環境ごとに手順の詳細は違っても, 自動化したいものに違いはない. 要はビルドと実行がしたい. 操作を共通化すべく "引数によるポリモーフィズム" を使おう. とりあえず環境毎のルールを子のファイルに分離する. ("スクリプトの抽出" ね.) 既存の build と clean や famicom.mk に, unix-build と unix-clean は remote-unix.mk に切り出した.

次に famicom.mk と remote-unix.mk のそれぞれで build, run, clean とルール名を統一する. 親の Makefile は子のファイル名を変数 (仮に PLATFORM_MAKEFILE とする) にしまい, ${PLATFORM_MAKEFILE} run などと呼び出す. 変数の値は const.rb あたりに書いておけばいいだろう.

... 最後はオーバーエンジニアリングかもなあ. まあいいや. バグとりしよ.

まとめなど