ThoughtWorksアンソロジー その2 / Antビルドファイルのリファクタリング

yyamano2009-01-05

ThoughtWorksアンソロジー ―アジャイルとオブジェクト指向によるソフトウェアイノベーション

ThoughtWorksアンソロジー ―アジャイルとオブジェクト指向によるソフトウェアイノベーション

タイトル通り、Antビルドファイルもリファクタリングしましょうという話です。ビルドファイルのリファタリングがあまり行われないのは、XUnitのような単体テストフレームワークが存在しないこと、IDEのサポートがないという技術的な問題もありますが、実行されるコードだけを成果物と考えてしまう開発者のメンタリティによるところも多いと思います。そして、ソフトウェアの規模が大きくなってくると、CIビルド時に全てのターゲットを実行をおこなうのが難しいという問題もあります。とはいえ、リファクタリングなしでは、いつの間にか複雑で混沌としたビルドファイルができあがり、メンテできなくなってしまいます。

では、この章で扱われているリファクタリングのパターンを紹介しましょう。

  • macrodefの抽出 - コードブロックをmacrodefとして抽出する
  • ターゲットの抽出 - 大きなターゲットを小さなターゲットに分割し、依存関係を定義する
  • 宣言の導入 - if条件分岐のかわりに、プロパティとimportを使う。
  • 依存によるcallの置き換え - 明示的なantcallのかわりに宣言的な依存関係を使う
  • プロパティによるリテラルの置き換え - 頻繁に使うリテラル文字列をプロパティに置き換える
  • filtersfileの導入 - filter要素のかわりにプロパティファイルとfiltersfile属性を使う
  • プロパティファイルの導入 - プロパティ定義を外部ファイルに移動する
  • ターゲットのラッパービルドファイルへの移動 - CI用のターゲットは別ビルドファイルに移動する
  • descriptionによるコメントの置き換え - XMLコメントのかわりにdescription属性を使う
  • デプロイ用コードのimport先への分離 - デプロイ用のコードは別ビルドファイルに移動する
  • 要素のantlibへの移動 - 複数のプロジェクトで使われるコードをantlib.xmlに移動し配布する
  • filesetによる多数のライブラリ定義の置き換え - pathelement要素のかわりにfileset要素を使う
  • 実行時プロパティの移動 - ビルドに使うプロパティと実行時に使うプロパティを
  • IDを用いた要素の再利用 - 重複したpathのような要素をidを使い共通化する
  • プロパティのターゲット外部への移動 - ターゲット内部で定義されているプロパティをターゲットの外に移動する
  • locationによるvalue属性の置き換え - パスを表現するプロパティではvalue属性のかわりにlocation属性を使う
  • build.xml内へのラッパースクリプトの取り込み - シェルスクリプトで定義しているパスやオプションをビルドファイルに移動する
  • taskname属性の追加 - タスクの意図を明確にし、実行状況を簡単に把握できるようにtaskname属性を使う
  • 内部ターゲットの強制 - 内部ターゲットの先頭にハイフンを追加し、コマンドラインから呼べないようにする
  • 出力ディレクトリの親ディレクトリへの移動 - 生成される成果物の出力先を一カ所にまとめる
  • applyによるexecの置き換え - execのかわりにapplyを使う
  • CI Publisherの利用 - ?
  • 明確なターゲット名の導入 - ターゲットとプロパティで異なる命名規則を使う
  • ターゲット名の名詞への変更 - ターゲット名にはプロセスではなく成果物の名前を使う

全体を読んで感じるのは、antのビルドファイルは手続き的に書くな、宣言的に書けという著者の主張です。ついつい手続き的に書きたくなってしまうんですけどね。
この中で使ったことがないのは、filtersfileの導入、要素のantlibへの移動、locationによるvalue属性の置き換え、taskname属性の追加です。どれも機会があれば使ってみたいですね。
CI Publisherの利用は僕には理解できませんでした。Cruise ControlのようなCIツールからしか、あるいはCIツールでビルドに成功した場合しかタグ付けできないようにするということなんでしょうか。
僕の経験では大規模ビルドファイルの場合、macrodefの抽出、ビルドファイルの分割(ターゲットのラッパービルドファイルへの移動、デプロイ用コードのimport先への分離)、命名規則(明確なターゲット名の導入、ターゲット名の名詞への変更)を使うとかなり見通しがよくなります。

macrodefの抽出

macrodefはマクロを定義するタスクです。1.6でmacrodefやimportのような大規模ビルドのために機能が導入されていますが、macrodefはその中で最も強力なものだと思います。macrodefを使うと、コードを共通化することができます。値を属性として、実行するコードの断片を要素として渡すこともできるのも便利です。

from http://ant.apache.org/manual/CoreTasks/macrodef.html

<macrodef name="testing">
   <attribute name="v" default="NOT SET"/>
   <element name="some-tasks" optional="yes"/>
   <sequential>
      <echo>v is @{v}</echo>
      <some-tasks/>
   </sequential>
</macrodef>

<testing v="This is v">
   <some-tasks>
      <echo>this is a test</echo>
   </some-tasks>
</testing>

Antのマニュアルで取り上げられている例だと、vを属性として定義しており、渡すコードブロックのための親要素をsome-tasksとして定義しています。同様のタスクでpresetdefというのもあり、こっちは既存のタスクの特定の属性や要素をあらかじめ設定した新しいタスクを定義することができます。

ターゲットのラッパービルドファイルへの移動、デプロイ用コードのimport先への分離

基本的にはプログラムを書くのと同じで、関心事を分離するのがポイントです。昔かいたコードが参照できないので、記憶に頼ってますが、ビルドとデプロイ、実行でまず分割して、ビルドの処理が大きくなったら自動生成、共通コードなどを別ビルドファイルにしていたような気がします。

明確なターゲット名の導入、ターゲット名の名詞への変更

これも基本的にはプログラムを書くのと同じですね。ここではあげられていませんがプロパティ名の命名規則も重要です。ツリー構造を使うのか、英文を意識したスタイルにするのかなど。
一貫した命名規則は重要です。昔、あるプロジェクトで途中から別の命名規則を使って書かれたビルドファイルがあり、これにはものすごく悩まされました。結局、かなり時間をかけてひとつの規則に統一しましたが、本当に泣きそうな経験でした。


続く(かな?)・・・