著作一覧 |
コピペはコードを書くときに必要な技術の1つでそれはうまく使わなきゃならない。
で、実は正しくはコピペではなくカトペであるべきだ。
public class Foo { public void bar(Baz baz) { ... if (baz.getA() > 0) { baz.bazzzzzzz(Goo.newInstance()); baz.cazzzzzzz(Hoo.newInstance()); baz.dazzzzzzz(Ioo.newInstance()); } ... } ... public void car(Baz baz) { ... if (baz.getB() > 0) { baz.eazzzzzzz(Goo.newInstance()); baz.fazzzzzzz(Hoo.newInstance()); baz.gazzzzzzz(Ioo.newInstance()); } ... } ... }
というコードがあったとする。で、実はこれが半分しか正しくなくて、barもbazのgetB()の処理(carでやってるやつ)が、carもgetA()の処理が必要だと判明する。
で、なぜかこういうふうに直す人がいる。
public class Foo { public void bar(Baz baz) { ... if (baz.getA() > 0) { baz.bazzzzzzz(Goo.newInstance()); baz.cazzzzzzz(Hoo.newInstance()); baz.dazzzzzzz(Ioo.newInstance()); } if (baz.getB() > 0) { baz.eazzzzzzz(Goo.newInstance()); baz.fazzzzzzz(Hoo.newInstance()); baz.gazzzzzzz(Ioo.newInstance()); } ... } ... public void car(Baz baz) { ... if (baz.getA() > 0) { baz.bazzzzzzz(Goo.newInstance()); baz.cazzzzzzz(Hoo.newInstance()); baz.dazzzzzzz(Ioo.newInstance()); } if (baz.getB() > 0) { baz.eazzzzzzz(Goo.newInstance()); baz.fazzzzzzz(Hoo.newInstance()); baz.gazzzzzzz(Ioo.newInstance()); } ... } ... }
ここで利用されるのがコピペだ。同じテキストなので、手で打ち込む手間の軽減とタイプミスの防止のために範囲を指定して一時的にヤンクバッファにテキストを覚えさせてそれを指定した個所に埋め込むことである(とか自明のことをだらだら書いてみたり)。
が、これはカトペのほうが良かろうと思う。
public class Foo { public void bar(Baz baz) { ... setupBaz(baz); ... } ... public void car(Baz baz) { ... setupBaz(baz); ... } void setupBaz(Baz baz) { if (baz.getA() > 0) { baz.bazzzzzzz(Goo.newInstance()); baz.cazzzzzzz(Hoo.newInstance()); baz.dazzzzzzz(Ioo.newInstance()); } if (baz.getB() > 0) { baz.eazzzzzzz(Goo.newInstance()); baz.fazzzzzzz(Hoo.newInstance()); baz.gazzzzzzz(Ioo.newInstance()); } } ... }
カトペは元のテキストをヤンクバッファにコピーした後、削除する。つまり、テキストのクローンを生まない方法だ。
なんでこうしないんだろう?
それなりに説得力があるコピペの理由は、途中で「やっぱり元通り」とか「実はbarではgetB、carではgetAですた。すまん」とかがあった場合に、コピペのほうが対応が楽だし、どうも仕様が信頼ならないというものだ。確かに途中で変わったわけだからまた変わる可能性は十分にある。(にもかかかわらずこれはだめだろう)。
(追記)カトペだとキーボードに対して操作が発生するが(上の例だとメソッド定義と呼び出しの部分)、コピペだとうまくやると一切キーボードに手を触れなくて済むから疲労度が小さくて済むとか……というか名前重要と強調し過ぎると名前をつけるのを怖がってしまうとか?
実際は、最初の時点で間違っている(なんかコピペはどうでも良くなってきたので論点をずらしてみたり)。
最初から意味単位でばらしときゃ済む話だ。
public class Foo { public void bar(Baz baz) { ... setupBazA(baz); ... } ... public void car(Baz baz) { ... setupBazB(baz); ... } void setupBazA(Baz baz) { if (baz.getA() > 0) { baz.bazzzzzzz(Goo.newInstance()); baz.cazzzzzzz(Hoo.newInstance()); baz.dazzzzzzz(Ioo.newInstance()); } } void setupBazB(Baz baz) { if (baz.getB() > 0) { baz.eazzzzzzz(Goo.newInstance()); baz.fazzzzzzz(Hoo.newInstance()); baz.gazzzzzzz(Ioo.newInstance()); } } ... }
で最初の仕様変更が来たら
public class Foo { public void bar(Baz baz) { ... setupBazA(baz); setupBazB(baz); ... } ... public void car(Baz baz) { ... setupBazA(baz); setupBazB(baz); ... } ...
ほら、コピペで問題なし。
追記:
いや、問題なしとは言えないかも。だいたい、5行のコピペはだめで1行なら問題なしとはなんじゃらほい。それにもしsetupBazA()+setupBazB()のペアで1つの処理ならば、それをさらに束ねておく意味はある。
public void bar(Baz baz) { ... setupBaz(baz); // Bazで終えてよいかどうかは別問題 ... } ... void setupBaz(Baz baz) { setupBazA(baz); setupBazB(baz); } ... }
そうでないと、一連の処理である「べき」だった場合に、ばらけた1つだけを呼び出したせいで不完全な状態になることだって考えられる。
public void dar(Baz baz) { // 良く理解しないでdarを追加 ... setupBazA(baz); // setupBazBを呼び忘れている ... }
で、もし一連の処理ならば、setupBazAとsetupBazBの2つとそれを束ねたsetupBaz、という組み合わせではなく、setupBazAとsetupBazBをカトペしたsetupBazを作りsetupBazAとsetupBazBは発展的に解消(妙な言い回し)させてしまうほうが良いかも知れない。でも、そうでないかも知れない(やっぱり、あの修正依頼はうそー、とかなった場合のことを想定)。
常に統合するのではなく、多段階にメソッドを束ねる作戦を取ると、良く似た細かなメソッドがわらわらと生まれてくるかも知れない。
void setupBazAB(Baz baz) { setupBazA(baz); setupBazB(baz); } void setupBazAC(Baz baz) { setupBazA(baz); setupBazC(baz); // こんだ、こんなのが出てきました } void setupBazBC(Baz baz) { setupBazB(baz); setupBazC(baz); // こんだ、こんなのが出てきました(コメントで、コピペがばれたり) } ... }
こうなると収集がつかなくなってくる。
そうなるくらいなら、setupBazというメソッドで束ねたのが実はあまりうまい方法ではなくて、setupBaz?の粒度で揃えておくほうが良かったということになる。どのsetupBaz?を呼ぶかは個々のpublicなメソッドの仕様として考えておいて、それ以上の深入りは避けておくということだ。
実際には、ABだのBCだのという組み合わせが出て来たら、そこで一度構成を考え直すのが良い。
具体的には、Baz側に局面に応じた状態設定メソッドを用意しておくということだ(setupBazAとかをFoo―呼び出し側―に持たせるのではなくBazの側にsetupByA、setupByB……を用意するということ)。
かくして、クラスをまたがるリファクタリングということになる。
実際には、FooとBazの関連(Bazはブラックボックスで変更できないという場合もあり得る)やその他の局面(どういう変更がありえるか、ありえないか)に応じて、使い分けることになる。
で、5行のコピペはだめで1行ならOKなのかとか、どの粒度で揃えるかとかって、機械的に決定できるか? と言うと、多分できない。見てくれは揃えることはできるかも知れないが(おそらく徹底的にメソッドを細分化して多段階の束ねたメソッドを用意すればできるが、まじめにやればやるほどそれは羹に懲りて膾斬の刑と等しい)ろくでもないソースになるはずだ。となれば最後にモノを言うのはつまり属人性ですよ。カタカナ使うとセンスということだ。
そこで究極のコピペという手段が1つの解決策となる。Wizardとか自動生成。コピペ単位の設定に人間が介入するだけなので、生成されたソースが最初に挙げたようなものでもそれほど問題はない(読むのはイヤだけど)。
木目のある板を利用して壁を張ると、職人のセンスが問われる(木目をどうあわせるかどうか、微妙な色味の違いの組み合わせとかも)。でも合板を使えば何も考える必要はない。
という結論になるのかなぁ。
人間はできるだけカトペにする。
意味あるまとまりを作る(基本的に)。
複雑になりそうな場合には、組み合わせを記述する言語を作り、機械にコピペソースを作らせる。
ジェズイットを見習え |
私の仕事場のコーディング規約に「コピー & ペーストは悪、カット & ペーストは善」というのがあります。<br>カットアンドペーストは重複を生みにくいというのが理由です。<br><br>で、コピペしてしまう理由は、「とりあえず動けばいいから」だと思います。カットアンドペーストだと、コードの意味をちゃんとわかった上で再構成する手間がかかるから、その場しのぎに流れやすい人は好まないんでしょうね。
>コードの意味をちゃんとわかった上で再構成する手間がかかる<br>それ、良い指摘ですね。逆に1行単位の意味をわかる必要がないようにあらかじめまとめた単位にしておくのが重要だということの理由にもなりそうです。(メソッド=クラス内コンポーネントと考えさせる)
カトちゃんペッ