少しでもメンテナンスするならコードの抽象化は行った方が良い

「すべてのソースコードが手元にあるのに不要な抽象化を行うのはよくない」

http://blog.sigbus.info/2014/12/blog-post_29.html

を読んで。良いこと言っていると思ったのですが、どこまで抽象化するべきなのかについての議論がもう少しないと、見方によっては「抽象化を全然しない」言い訳にもなりうるかな、という印象も持ってしまいました。ひと昔前に流行ったコメント不要論に近いものがあるかな、と。

自分は人より抽象化をする方だと思っているので、こういうこと言われたらちゃんと反論できないとな〜、と思ったのが発端なのは秘密…。

なんてことから始まりつらつら考えていたら、抽象化に対する今の個人的な考えが一通りまとまってきたので書いてみます。

そもそも、誤った抽象化とは?

余計な抽象化をしているコードは、例えば以下のような印象を与えるコードじゃないかと思います。

  • 時間をかけてコードを追った結果、大した事をしてなかった
  • 修正量が無駄に多かった、または問題の原因が追いにくかった
  • 書いた人の独りよがりを感じた

直接的に他の人に影響がなくても、抽象化しないほうが良いというケースもあります。例えば、抽象化を考えたり実装することは意外とコストがかかるので、その時間自体が効果に見合わなかった、ということもあるでしょう。メタプログラミングや内部 DSL などを使ったりする場合は、デバッガや IDE などのツールがうまく使えなくなる事も多いです。

抽象化はある種の投資だと思います。多かれ少なかれ、大抵は一定以上のコストをチームのメンバーに強いることになります。一方で実際の利益が得られるかは曖昧になる傾向にあるのではないかと思います。

抽象化によるメリット・モチベーション

一方で、抽象化によるメリットとは何でしょうか。

問題の分割

抽象化を行う一番素朴なモチベーションは「そのまま書くとわかりづらくなってしまう」という事なのではないかと思います。ある程度以上の大きさや複雑さのある処理に対しては、抽象化を行わないと例えば以下のようなことが起こります。

  • コードが密結合しすぎて修正時の影響範囲が見えにくくなる
  • コードが煩雑になり可読性が下がる
  • 想定ケースに漏れが発生する

特に、試行錯誤をしながらコーディングを行った後などは、動作したタイミングで満足してしまい、ブラッシュアップに時間をかけるのが面倒になりがちです。また、コードだけでは他人に伝わらないような暗黙的な知識や意図などが残っている事も多いです。こういう場合は丁寧に抽象化を行い、時には作り直すくらい時間をかけてもいいのではないかと思います。

パターン化された実装方法の導入

一箇所だけではそこまで複雑ではなくても、何箇所も同じような処理を行っていると全体としてのコードが大きくなり、メンテナンスがしづらくなってしまう事もあります。

例えば、一覧画面を表示するようなアプリを考えます。一覧画面が一箇所か二箇所くらいなら、それほど抽象化をする必要はないかもしれません。しかし、複数の場所で一覧を表示するような場合は、その都度一から作っていたら同じようなバグを作り込んでしまったり、使用感が異なってしまう恐れがあります。

この場合、一覧表示のためにモジュールやクラスを用意する事で、チームで統一された方法で開発ができるようになります。バグを減らしたり使用の統一感をあげるのに有効でしょう。場合によっては独自の内部 DSL などの導入を検討しても良いかもしれません。

慣れた手法での実装

プログラマにはそれぞれ得意の実装方法というのがあると思います。抽象化自体に関しても、考えながら抽象度の高い実装をするのが得意な人もいれば、スピード感を持って書くのが得意な人もいます。

あまりに独特な手法でコーディングするのも考えものですが、ある程度はその人に合ったやり方でコーディングができないと、効率が大きく下がってしまいます。チーム全体の統率がかなり取れていない限りは、ある程度の「気に入らないコード」は許容する必要が出てくると思います。

誤った抽象化をしないためには

上に書いたような誤った抽象化をしないために、個人的に重要だと思う点をまとめました。

YAGNI (You ain’t gonna need it) を意識する

実装方法を色々検討した結果、良い感じのクラス構成等を設計する事が出来たとしても、実装する前にそれが本当に今必要なのかを考えたほうが良いでしょう。現在の要件ではそこまで必要としなかった、なんて事も多いです。

完全にするために使用されないコードを書くよりは、下位互換性を持った不完全なクラスを作ったほうが良いでしょう。それで元の設計の意図が失われるのであれば、意図をコメントに書いてカバーすることもできます。

チームの人とコミュニケーションをとる

先ほども書きましたが、得意な実装方法は人によって違います。同様に読みやすいコードも人によって違います。

コードレビューを行えば、他人から見たコードの問題点を早期に発見できるでしょう。また、コードの書き方に関する他人の考え方を理解するためのきっかけにもなります。

また、設計をした時点で他の人に相談や了承を得る事で、後々になってトラブルになる事も防げるのではないかと思います。「話すほどの事ではない」と思った場合でも、明確な利点を説明できるように考えるだけでも独りよがりなコードを防げると思います。

コードが使用される範囲を意識する

コードの要素には、その修正が様々な箇所のコードに影響を与える種類の物と、ほとんど影響を与えない物があります。

具体的には、シングルトンで使いまわされるようなクラス(サービスなど)は、仮にそれが様々な場所で使われていたとしても、修正が多くの場所に伝播する事はあまりありません。最初適当な設計だったとしても後で意外と簡単に修正できたりする事が多いと思います。

一方で、様々な処理に引数として渡されるようなクラス(状態を格納するクラスなど)は、抽象化を誤った事によって使い勝手が悪かった場合のコストが大きくなります。後で修正するコストも莫大になります。設計は慎重に考えたほうがいいでしょう。

時には習慣を疑う

習慣となっているものでも実は利点があまり無い、なんてものも時々あります。

例えば Java では「フィールドは getter / setter でラップすべき」とされていた節がありました。今でも「とにかく getter / setter でラップする」コードを見かけます。しかし最近ではこの習慣の存在価値が疑問視され、限定的ではあれフィールドを public にする人も増えているように思います。

getter / setter をつけない人を見て「こんな事も知らないのか!」などと嘆いていたらいつの間にか「老害」になっていた、なんて事もあり得ます。

自分の習慣を疑うことも必要だと思います。

まとめ

  • 抽象化はある種の投資
  • 今行っている抽象化の利点を見失わない
  • 自分の思考と他人の思考は割と違う
  • 重要な点とそうでない点のメリハリをつける

あたりが重要だと思います。