Java.use(better); Episode#23 ファインダーを作成する

《前の記事|記事一覧次の記事
Java.use(better, Scala);


Episode#23

ファインダーを作成するリファクタリング



未来というのは結局この一瞬一瞬の積み重ねなんだ
この今の延長に未来がある
遠い未来もこの瞬間の積み重ねなんだよな
Takeshi Kitano - Wikipedia

《関連記事》

《目的》同じアプリケーションを異なるプログラミング言語 Java/Jython/Scala で作成する過程(作譜:プログラミング)を通して、言語の違いに依らない概念を適用するための手法(算譜:プログラム)を紹介します。

《動機》ともすると、プログラミング言語の違いに目を奪われて、言語の違いに依らないプログラムの本質を見失いがちです。そこに、同じアプリケーションを異なるプログラミング言語で作成することの意義があります。その本質を理解できれば、新たなプログラミング言語を習得するときに役立ちます。

リファクタリング:メソッドの抽出

リファクタリングを「いつ実践すべきか」という問題に唯一の正解はありません。要求仕様の変更は、リファクタリングを実践する好機です。というのも、その要求に対して「何が影響を受け、何が影響を受けないか」が、明確になるからです。

■ 要求仕様の変更

$ scala cherry.pie.Tips


(左)ツリー項目を選択すると(右)それを頂点とするツリーが文字列で表示されます。表示した情報はプレーンテキストなので、他のアプリケーションでも再利用できます。

今回の要求仕様の変更は、ツリーを表現するとき「ファイルとフォルダーを区別したい」という些細なものです。その影響を受けるコードの断片を抽出する仕掛があると、プログラミング(作譜)の効率が上がります。リファクタリングの指針として「要求仕様の変更の影響を受けるコードの断片を、メソッドとして抽出する」が挙げられます。

■ 引数を介して情報を蓄積する

再帰呼び出しごとにバッファーを生成するのは、効率的ではありません。この問題を回避するために「リファクタリング」を実践します。さらに「再帰呼び出しに依存する」コードの断片を特定して、独立したメソッドを抽出します。
☞ 余録:Move Accumulation to Collecting Parameter

 15: import javax.swing.tree.TreeNode
 16: class Node(node: TreeNode) {
 17:   override def toString() = {
 18:     val buf = new StringBuffer
 19:     appendNode(buf, node, 0)
 20:     buf.toString
 21:   }
 22:   def appendNode(buf: StringBuffer,
 23:     node: TreeNode, level: Int): Unit = {
 24:     appendParent(buf, node, level)
 25:     appendChildren(buf, node, level)
 26:   }
 27:   def appendParent(buf: StringBuffer,
 28:     node: TreeNode, level: Int) =
 29:     buf.append("%s%s %s\n".format(
 30:       tabs(level),
 31:       if (node.isLeaf) "." else "+",
 32:       node))
 33:   def appendChildren(buf: StringBuffer,
 34:     node: TreeNode, level: Int) =
 35:     nodeIterator(node) foreach { appendNode(buf, _, level+1) }

[17-21]オブジェクトを表現する文字列を生成します。バッファー buf を用意して、再帰呼び出しごとに使い回します。メソッド appendNode を下請にして、特定のノード node を頂点 0 とするツリーを文字列で表現します。

[22-26]特定のノード node を頂点 level とするツリーを文字列で表現します。用意したバッファー buf に、各ノードの情報を追加します。このとき、メソッドの本体には、分割したメソッド呼び出しだけを列挙します。すると、書籍の目次と同様に、その内容(目的)を一覧できるので、プログラミング(作譜)の効率が上がります。ここでは、[24]具体的な処理と[25]再帰呼び出しを、独立したメソッドとして抽出しました。
☞ 余録:Composed Method

再帰呼び出しでは、リターン値の型を明記する必要があるので注意してください。さもないと、次のようにコンパイル時のエラーとなります。

$ scalac src/{_Tips,AppWindow,Node,TreeView}.scala
src/Node.scala:41: error: recursive method appendNode needs result type
    nodeIterator(node) foreach { appendNode(buf, _, level+1) }
                                 ^
one error found

[27-32]用意したバッファー buf を使って、具体的な処理を行います。先行するタブの数 tabs(level) が、ノードのレベルに対応します。"." の後にはファイルの名前を、"+" の後にはフォルダーの名前を続けます。

注目に値するのは「要求仕様を変更した影響を受ける」コードの断片がすべて、このメソッドに「限定」されることです。これが、リファクタリングによって抽出した、メソッドの正当性を裏付ける根拠になります。新たな要求に対処する前に、このリファクタリングを実践しておくと、プログラミング(作譜)の効率が良くなります。というのも、変更しなかった箇所の単体テストを「割愛」できるからです。

[33-35]再帰呼び出し appendNode がツリーの階層構造を反映するので、次のレベルを探索するときに、実引数の値 level+1 を増やします。ここでは、特定のデータ構造(文字列バッファー)に依存しない「抽象表現」になっています。その具体的な処理は、前述したメソッド appendParent に委ねます。

バッファーを導入した影響は、すべてのメソッドに及びます。逆にそれは、すべてのメソッドが「リファクタリングに恩恵に浴する」とも言えます。

》作業中です《

 ↑ TOP

Created: 2010/05/23|Last updated: 2013/06/18 22:25:13