Java.use(better); Episode#23 ファインダーを作成する
┃余録:Move Accumulation to Collecting Parameter
■ 事例:class Accumulate1/Accumulate2
次の事例は、その理解を深めるための便宜的なものです。文字列を列挙したリスト、を列挙したリストという二重構造について考えます。
scala> val seq = """AB C | DEF""" ... scala> var buf = new ListBuffer[List[String]] ... scala> seq.split("\n") foreach { buf += _.split(" ").toList } scala> print( buf ) ListBuffer(List(AB, C), List(DEF)) scala> print( seq ) AB C DEF scala> print( new Accumulate1(buf) ) AB C DEF scala> print( new Accumulate1(buf)+"" == seq ) true
seq は、空白で区切られた文字列を、改行で区切って並べた文字列です。これをもとにして、空白で分割した部分文字列を列挙した List[String] を、列挙した ListBuffer[] に分解します。Accumulate1 は、これを再び連結させて、もとの文字列を復元します。同様に、
scala> print( seq ) /** Iterators are data structures that allow to iterate over a sequence * of elements. They have a `hasNext` method for checking * if there is a next element available, and a `next` method * which returns the next element and discards it from the iterator. */ scala> print( new Accumulate1(buf)+"" == seq ) true
複数行からなるコメントを部分文字列に分解したものを、Accumulate1 が復元するのを確認できます。これは次のように実現します。
class Accumulate1(seq: ListBuffer[List[String]]) { override def toString() = accumulate(seq) def accumulate(seq: ListBuffer[List[String]]) = { var buf = new StringBuffer() seq foreach { s => buf.append(accumulate2(s)+"\n") } val r = buf.toString r.take(r.size-1) } def accumulate2(s: List[String]) = { var buf = new StringBuffer() s foreach { e => buf.append("%s ".format(e)) } val r = buf.toString r.take(r.size-1) } }
accumulate は、指定した seq から文字列を復元するときに、accumulate2 を下請にします。このとき、メソッドを呼び出すたびにバッファー StringBuffer を用意するのは、効率が良くありません。そこで、
class Accumulate2(seq: ListBuffer[List[String]]) { override def toString() = accumulate(seq) def accumulate(seq: ListBuffer[List[String]]) = { var buf = new StringBuffer() seq foreach { s => accumulate2(buf, s) } val r = buf.toString r.take(r.size-1) } def accumulate2(buf: StringBuffer, s: List[String]) = { s foreach { e => buf.append("%s ".format(e)) } val n = buf.length buf.replace(n-1, n, "\n") } }
バッファー buf を用意するのは accumulate だけにして、accumulate2 を下請にするときの引数に指定すると効率的です。このとき、メソッドを呼び出すたびに、バッファー buf に文字列が累積されます。
┃余録:Composed Method, idiom#54
■ メソッドの整理
メソッドが大きく複雑になると、その内容を理解するのは困難です。そこで、小さなメソッドに分割すると、その理解も容易です。メソッドの本体に、分割したメソッド呼び出しだけを列挙したものが「目次」です。すると、メソッドを実現する手段(how)には依存せずに、メソッドを利用する目的(what)が明確になるので、コードが簡潔で見通しも良くなります。
メソッドを抽出する前のコードは、次のようなものでした。
: def appendNode(buf: StringBuffer, : node: TreeNode, level: Int): Unit = {: // appendParent(buf, node, level): buf.append("%s%s %s\n".format( : tabs(level), : if (node.isLeaf) "." else "+", : node)): // appendChildren(buf, node, level): nodeIterator(node) foreach { appendNode(buf, _, level+1) } : }
抽出したメソッド呼び出しをコメントにすると、それ以外のコードの断片を見ただけでは、その目的を理解するのは困難です。なにより、作成者(プログラマー)の意図を、利用者(プログラマー)が理解しているか「確証」が持てません。逆に、
: def appendNode(buf: StringBuffer, : node: TreeNode, level: Int): Unit = { : // appendParent(buf, node, level): buf.append("%s%s %s\n".format( : tabs(level), : if (node.isLeaf) "." else "+", : node)): // appendChildren(buf, node, level): nodeIterator(node) foreach { appendNode(buf, _, level+1) }: }
コメント以外のコードの断片を見なくても、メソッドの目的を理解できます。ここから、ひとつの教訓が得られます。「コメントが必要だ」と感じたなら、それは「リファクタリングの好機」です。コメントを記述する労力を、リファクタリングに投資するだけで、プログラミング(作譜)の効率が良くなります。
》作業中です《
↑ TOP