タオルケット体操

サツバツいんたーねっとでゲームとかガジェットのレビューとかをします

共通化という考え方はアンチパターンを生み出すだけ説

共通化を指針にするのはおすすめできない

「共通化」というワードはプログラマーであれば誰しもが一度は聞いたことがあるだろう。そしてもうひとつ、それと対称であるかのように語られるのが「コピペは悪」だ。
ここで僕が異議を唱えたいのは共通化を善とする教義についてだ。世間的に共通化を良いものだとする風潮があるようなので石を投げるために書き殴ろうとおもう。

さて、ぶっちゃけた話、共通化なんてものを念頭にしてコードを書いてはいけない。そんなことをしたら見通しの悪いクソの山が世の中に一つ増えるだけである。
じゃあコピペしろというのかというとそういう話をしているわけではない。僕が言いたいのはコードを設計*1する際に、共通化なんていうゴミみたいな方針を用いるのはやめろということだ。

ならどうすればいいのかというのを頑張って言語化してみると、「抽象化」するというのが僕の語彙の中では正しい。

なぜ共通化するのか

共通化の言及はおおむねコピペコーディングへのアンチとしてなされる。
コピペコーディングの弊害は、コードの量自体の増大やそれに伴う見通しの悪さもさることながら、コピペ元の誤り(if文の条件漏れなど)がそのまま伝播していき、修正していくには同じような部分を全てgrepして手で直していく必要があるというところに尽きる。
あるいはマジックナンバーを使ったロジックの修正コストを下げるようなケースで引き合いに出されることもあるかもしれない。

消費税の変更に多大な修正コストが必要なシステムの噂が聞こえてくるようなこの業界においては、実際のところ共通化をしているだけでもマシというような現実があるのは否定しないが、より堕落した快適なプログラミング生活を送るためにはそういう底辺レベルの話は頭から消し去る必要があるのでみなさん忘れましょう。

共通化と抽象化

さきにああいうようなことを言っておいて何だが、共通化と抽象化は思考のアプローチの違いであって、結果として同じようなコードが生まれるような可能性はないではない。
どちらも「コードを何らかの単位でまとめる(関数やクラス、モジュールが使われる)」という点では何の違いもない。

ただし共通化という名の下におこなわれるのは「同じロジックを持つコードをまとめる」行為であって、抽象化のようにそのコード単位の意味を捉える作業はその範疇にない。

抽象化というのはロジックを意味単位ごとにひとくくりにしていく行為で、これがどういうことなのかは次以降で述べていく。

共通化による弊害、生まれがちなアンチパターン

共通化だけを主眼においたプログラマーが生み出しがちな良くないコードをリストアップしていくと

  • 関数やクラスの名前から機能が推測しにくい

    「同じロジックをまとめる」という目的で関数やクラスを作ってしまうと、一つの関数が複数の目的の元に動作するようなコードになりがちだ。またそういう人は長い関数名を嫌う傾向にあるので、「XXXをしてYYYだった場合にメンバ変数αにその結果を代入する」引数のないメソッドの名前がsetAlfaだったりする。

  • 関数名が嘘をつく

    機能が推測しにくい名前の亜種になるが、俗に言う「嘘つきメソッド」のアンチパターンをなぞる人が非常に多い。

  • 神クラス、超万能Utils

    共通化のみを考えているとクラスは「メソッドをまとめられる、なんかそういうモノ」という発想でしか捉えられなくなる。その発想の行き着く先は全てを司る神クラスのアンチパターンだ。共通化の性質が悪いところは、むしろ神クラスや万能Utilsを「よく機能がまとめられている」と肯定してしまいかねないところにある。

  • 継承で差分プログラミングをしてしまう

    コードの量を減らすためにクラスを作り始めた場合高確率で何かが間違っている可能性が高い。

  • 本来一緒にすべきでないロジックをひとつまとめにしてしまう

    これは非常に難しい問題で、うまい例が見当たらないんだけども僕が共通化という方針を憎む大きな理由だ。
    「たまたま共通化できるロジックを含んでいるだけで関心ごとが異なる」パターンでは下手に共通化せずにコードの重複を許した方が良い場合があると僕は感じている。そういった場所は後々でどちらかだけに修正が入るものだし、そもそも命名が困難なコード片である場合無理やりに関数にしてしまうと見通しが悪くなる。

  • 複数回出現しないコードは関数にしないのでコードが長くなる and or コードが長くなってしまったので漠然と関数に切り出す

こんなところだろうか。
共通化の考え方はこれらのアンチパターンと共存することが可能であり、より良い設計を目指すには不足であると言わざるをえない。

対して抽象化であれば、まず何を持っても正しく性質を説明する名前がついていることが重要になるので最初の二つは発生してはならない。神クラスやUtilsついても、まずは各機能が正しいドメインに属していることが求められるので起こりえない(言うは易しだけど)。差分プログラミングも同様で、そもそも継承そのものが7割方アンチパターンだ。
残りも抽象化の考え方の元では発生しえないのがお分りいただけるかとおもう。

Ruby on Railsのようなフレームワークを作る(あるいは上手に使う)には、共通化だけではなく、高レベルな視野が必要となる。

抽象化の問題点

抽象化も万能ではない

  • 難易度が高い

    「神クラスを作るのをやめろ」と言われた人がいきなり正しく役割分担されたコード設計をはじめるかというとそうなればどうにも苦労はないわけで、共通化のような機械的に判断可能な目的があるわけでもなく、気持ちだけでどうこうなるものではない問題があまりに致命的。

  • やりすぎると意味不明になる

    共通化もそうだが、例えばメタプログラミングなんかを駆使しまくって抽象的なコードを書きまくると常人には理解できないものが出来上がる可能性が高い。
    ……が、難解なわけではなくただ単にそいつが見慣れていないだけという可能性も捨てきれない。SIerを通過したことのあるお友達はみんな一度くらいLINQでラムダ式を使ったコードにいちゃもんをつけられた経験があるはずで、図らずも同じ行為を自分がしてしまう可能性を念頭におく必要がある。モナドとか。

  • 抽象化という言葉が指し示す範囲が広すぎる上に流派がたくさんある

    オブジェクト指向とかデザインパターンとか関数型とかクラスとかプロトタイプとかDDD(ラノベじゃないよ)とかMVCとかFluxとか。
    初心者を抜け出したプログラマーが必ずどこかでぶち当たるフワッとしてて具体的には何言ってんだかわからないような理論というのは、乱暴に言ってしまえばコードをわかりやすさを保ったまま抽象化していくための方法論で、そこに完璧な答えは存在しない。

共通化とは使いやすいコードの条件、あるいは結果であって用いる手法や指針としては完全に間違っている反面、抽象化は正しい設計の方向性で完璧な回答が存在していないということになる。

抽象化と共通化についてのまとめ

「共通化された状態が使いやすい」というのは正しい。
ただし共通化は指針として使うにはあまりに単純すぎる。

「抽象化は理想の状態を作り出すための指針」である。
ただしアプローチは多岐にわたり、また言語機能の高度な部分を使いこなす必要がある。そして完璧な答えは存在しない。

「結局理想論かよーッ! 明日使えるテクニックを書きやがれッ」とおもった貴方。
だが我々プログラマーは安易に「結果」だけを求めてはならない。
「結果」だけを求めると人は近道をしたがるものだ……。近道をしたとき、真実を見失うかもしれない。やる気も次第に失せていく。
大切なのは「真実に向かおうとする意思」だとおもっている。
向かおうとする意思さえあれば、たとえ今回はXXX駆動設計の本が分厚すぎて途中で積んだとしても、いつかは読み終わるだろう? 向かっているわけだからな…… 違うかい?

to be continued…


元ネタ

ジョジョの奇妙な冒険 (59) (ジャンプ・コミックス)

ジョジョの奇妙な冒険 (59) (ジャンプ・コミックス)

*1:ここでいう設計とはマクロからミクロまでを漠然と指し示す。アーキテクチャの設計から方針の検討、そして実装に落とし込むまでが設計だ。プログラマーなら全部やるのは当然だよね?