※ 色々と誤解を招くというご指摘を受けたためタイトルを変更しました
早すぎる抽象化の危険性
↓
早すぎる抽象化の危険性(その抽象化、今のタイミングで大丈夫ですか?)
元の記事の趣旨としては、
抽象化をするな
という訳ではなく、
その抽象化は本当に今すべきなのか一歩立ち止まって考えろ
ということだと思っております。
何か不適切な点などございましたらご指摘頂けますと幸いですm(_ _)m
~~ 以下本文 ~~
ちょっと前の記事なのですが
とても印象深く
今後も気をつけていきたいと思い
自分なりにまとめてみました。
早すぎる抽象化とは?
問題になっていることを十分に理解する前に
可能性のあるすべてのパターンを把握しきる前に
抽象化をしてしまうこと
※コメントでのご指摘がありましたように
「早すぎる抽象化」はの結果として
「誤った抽象化」に陥ってしまうことが問題であり、
定義を下記のように修正しました。
問題になっていることを十分に理解する前に
可能性のあるすべてのパターンを把握しきる前に
無理に抽象化をしてしまった結果
誤った抽象化になってしまうこと
元の記事の中でも
Premature Abstractions
Wrong Abstractions
Bad Abstractions
という言葉を使っていたり
If you abstract too early or too aggressively, it’s easy to create overly restrictive abstractions (where you’ll have to work around them later) or overly general abstractions (where you’ll have difficulty maintaining them).
とあるように
早すぎる抽象化が
すべて誤った抽象化に繋がるとは言いたいのではないかと思っています。
私の書き方が誤解を与えてしまい、申し訳ございません。
悪循環の始まり
例えば
新しい機能の追加などの大きな問題を解決しようとしているとき
それを小さな問題に分割して解決していくと思います。
その際に共通して出てくる解決策
いわゆる重複コードを見かけた時に
共通処理(ここでは抽象化と呼びます)に切り出していきます。
そして
また大きな問題から分割された小さな問題に取り組み
また似たような問題を見つけて新たに抽象化を行います。
しかし
その時に具体的な実装間の小さい違いを吸収するために
抽象に小さな修正を加えていきます。
そしてこれを繰り返す。。。繰り返す。。。
気がつくと
抽象化がサポートしなければいけないパターンが増えすぎていて
手に負えないほどに巨大化していってしまいます。
再利用可能で
バグの修正やメンテナンスが楽になることを期待して導入した抽象化は
かえって複雑で追いづらくもろくなってしまいます。
この変遷は急激にすぐに起きることもあれば
何ヶ月、何年後と経過したあとに起きることもあります。
早かれ遅かれ
早すぎる抽象化はシステムのメンテナンスを困難にしてしまいます。
どうやって早すぎる抽象化に気づけるか?
よくある例としては
ユーティリティやヘルパーと命名される関数を導入した場合や
ViewやComponentと呼ばれるUIに関わるもの
が挙げられます。
データ構造を構築する際にも
早すぎる抽象化を行ってしまうことで
いわゆる「神オブジェクト」を生み出してしまいます。
他の例としてはRxなどの複雑なライブラリを使用することで
密かに早すぎる抽象化が起きることもあります。
あまりにも早く導入することで
十分に知識がないままに使いはじめ
誤った使い方をした結果
利益以上に負担を増やしてしまいかねません。
特に新規参入者の方で起きやすいかもしれません。
こういった早すぎる抽象化に気づくことは
科学的に数値で判断できるものではなく
感覚的なもので主観的なってしまいます。
誤った抽象化の真のコスト
抽象化は適切な時に適切な抽象化をしていても
コードの複雑度を減らすと同時に増やしもするという
矛盾を抱えています。
適切な抽象化を行うことで減るもの
- バグの修正が共通のコードになるので修正箇所が減って楽になる
- 詳細を追わなくてもコードが理解しやすくなる
適切な時に適切な抽象化を行うことで逆に増える複雑度
- コードを修正する箇所を探すために、抽象化された中身を紐解き、
修正する箇所を探すために深く掘っていく必要がある
増える点に関してはある程度は仕方がないことだと思っています。
早すぎる抽象化の場合だともっとひどいことになります。
さらにテストがない場合はより危険性を高めます。
抽象化を変更することで
未知の影響を与える可能性があるからです。
テストのないコードの中で抽象化を多く行うのは避けた方が良いでしょう。
ただし完全にやめる必要はなく
単純に避けるのです。
早すぎる抽象化を避けるには?
疑わしいと思った時はコードを重複させたままにしておくこと
※ここも私の訳し方が誤解を招く表現だと思いましたので修正しました。申し訳ございません。
もし疑わしいと思った時は一度立ち止まり、きちんと検討する時間を設けよう
間違った抽象化の方が
重複したコードを扱うよりも何倍も負担になります。
この忠告は
DRY(don’t repeat yourself)などの原則に反しているように思われますが
こうした原則は重複したコードを書くよりも
関数のような抽象概念を加えることを
**「推奨」**しているだけなのです。
理想としてはちょうど中間くらいが良いでしょう。
重複しても大丈夫です。
基本的には繰り返すべきだと思っています。
ただ
書いているコードが本当に複数の場所で使われる必要があって
抽象化としてまとめた個々のパターンが
大きな意味を持たない(特別に扱わなければいけないものがない)ことが
「本当に」明確だとわかった時は抽象化をすること
コードレビューの時間を設け
ほぼ同じロジックやデータ構造を持ち
再利用可能だとはっきりとしているケースのみを抽出していくようにしましょう。
早すぎる抽象化や積極的すぎる抽象化は
過度に限定的な抽象化(あとで応急処置が必要になるもの)や
過度に一般的すぎる抽象化(メンテナンスが難しくなるもの)を
いとも簡単に生み出してしまいます。
もちろん抽象化が当然のものもあります。
実装をしていく中で直接SQL文を書いたりはしないでしょう。
まとめ
大事なことは
抽象化をする目的を忘れないこと
- もっと簡単に再利用可能にすること
- 一箇所でバグの修正を抑えること
- 実装の詳細を隠し、処理の理解を早めること
抽象化は必要で私たちの身近に存在しています。
決して悪いものではありません。
ゴールは
適切なタイミングでちょうど良い抽象化を行う
という一見不可能に思えるバランスを達成することです。
参考として
過去にも近い議論はあったようです。
https://www.sandimetz.com/blog/2016/1/20/the-wrong-abstraction