HN🔥 414
💬 279

「誤った抽象化」をするくらいなら、コードの重複を恐れるな!

rafaepta
1日前

ディスカッション (11件)

0
rafaeptaOP🔥 414
1日前

この記事では、ソフトウェア開発において「DRY原則(Don't Repeat Yourself)」を過度に意識するあまり、不適切な抽象化を行ってしまうリスクについて警鐘を鳴らしています。将来的な変更の可能性を見越して無理に共通化を図るよりも、まずは重複を許容し、設計が明確になってからリファクタリングを行う方が、長期的にはメンテナンスコストが低く、コードの柔軟性も保たれるという教訓が説かれています。

1
cryo32
1日前

マイクロサービスならどっちもできるよ!

2
bhouston
1日前

OOP(オブジェクト指向プログラミング)時代は抽象化に苦労したものだけど、純粋関数型のアプローチに完全に移行してからは、コードの重複なんてめったにないね。関数を作って、必要な場所で呼び出せばいいだけ。抽象化の主な問題はデータ構造にあるけど、TypeScriptのインターフェースは実質的にダックタイピングだから、そこで困ることも少ない。だから、抽象化の問題によるコードの重複なんて珍しいんだよ。むしろ、開発者が孤立してサイロ化しているせいで発生する重複のほうが、はるかに一般的だね。

3
znkr
1日前

+1。これまで保守した中で最悪のコードは、DRY原則の本来の意図を理解せずに無理やり従おうとしたものだった。その地獄から抜け出す唯一の方法は、あえてコードを広範囲で重複させることだったよ。

4
strongpigeon
約24時間前

記事に同感。どっちも経験した人なら納得するはずだけど、過剰に設計されたコードベースよりも、未設計に近いコードベースのほうがはるかに扱いやすいんだよね。

5
Rendello
約24時間前

この件に関して2つのトークを思い出した。Mike Actonの『Data-Oriented Design and C++』[1]と、Brian Cantrillの『The Complexity of Simplicity』[2]だね。

Mikeのトークでは、コードの解決策は必ずしも現実世界をモデル化する必要はなく、異なるデータが異なる問題を生み出し、それには異なる解決策が必要だと主張されている。うまく説明しきれないけど、自分には大きな影響があったよ。

Brianのトークは抽象化そのものについてで、「正しい」抽象化を見つけることがいかに難しいかという話だ。

  1. https://www.youtube.com/watch?v=rX0ItVEVjHc
  2. https://www.youtube.com/watch?v=Cum5uN2634o
6
ultim8k
約24時間前

誰も話を聞こうとしないんだ。本当に誰も。企業の90%には、新しい抽象化を作るたびに有頂天になる、いわゆるシニア開発者がいるものさ。

過剰なエンジニアリング、抽象化、早期最適化は、エンジニアリングにおける最悪の3つの災厄だよ。

とはいえ、彼らが存在するのは嬉しいことだ。おかげで僕らの仕事がなくなることはないからね。

7
lg5689
約24時間前

「Single Source of Truth(単一の信頼できる情報源)」は常に従うべき原則だと思う。もし重複したコードがあって、それが乖離することでバグになるなら、リファクタリングすべきだね。そうしないと、コード内に遠隔的な結合が生まれて、バグが出るまで将来の開発者からは見えなくなってしまうから。

ただ、それを念頭に置いたとしても、記事には概ね同意するよ。「Single Source of Truth」に違反しないなら、抽象化なんて単なる便利機能に過ぎない。もし不便になり始めたら、それは役割を果たせていないわけで、無理に使う理由なんてない。関数にカスタム動作のためのフラグがいくつも必要なのは、重大なコードの不吉な臭い(コードスメル)だ。それは、抽象化が間違っているか、単一責任の原則に違反している可能性が高い。もし本当に多くのカスタマイズが必要なら、関数やファンクタを引数として渡すのがいい解決策になる。例えば、solve(f:double -> double, max_iters = 99, x_abs_tol = 1e-15, x_rel_tol = 1e-15, ...) と書くより、solve(f:double -> double, stopping_criteria: StoppingCriteriaClass) のようにする感じさ。

8
Waterluvian
約21時間前

時々このことについて考えるよ。最近、個人のプロジェクトで問題に直面したんだ。RTSユニットの2Dスプライトが、一貫したルールでスプライトシートにパックされていた。8方向に対して5つのスプライト(3つは反転を利用)。順番は「立ち・移動・攻撃・死亡」でパックされていたから、アクションと方向を受け取って、再生すべきスプライト配列を返すローダーを作ったんだ。

でもその後、別のケースにぶつかった。方向性がないスプライト(爆発など)や、死体のスプライト(4方向のみ、2つは反転、ほとんどはオークと人間で共有されている)などだ。

これらすべてに対する共通の抽象化とは一体何なのか、少し悩んだよ。結局、ロード処理の一部を切り出して、UnitLoader、CorpseLoader、EffectLoaderを作ることで対処した。今となっては、3つのローダーすべてが同じことを少しずつ考慮する必要があるから、もっと良い抽象化があるかもしれない。でも、その抽象化は後から見つければいいんだ。今、無理に複雑なEverythingLoaderを作ってすべてのケースを処理しようとするより、後でコードの重複を解消するほうがずっと楽だからね。

9
aftbit
約21時間前

同じように、インラインの文字列や数値定数は何でも悪だと考えている開発者を見かけたことがある。あるプルリクエストでこんなコードを見たよ。

HTTPS_SCHEME = 'https'
DOMAIN = 'www.example.com'
url = HTTPS_SCHEME + '://' + DOMAIN

「定数を埋め込むな」というカーゴ・カルト的な教条以外に、これが何の得になるのか理解できないよ。しかも、定数の定義はファイルの一番上にあるのに、URLの構築コードは何百行も離れた場所にあったんだ。

10
ketozhang
約19時間前

大半のシニアエンジニアはDRY原則を盲信してはいけないと知っていると思いたいね。ただ、重複したコードを保守し続ける必要があるという考えに居心地の悪さを感じる人も多いだろう。

それを解決するために、共通コードに依存する2つの呼び出し元という単純なモデルを精査する必要があると思う。もし呼び出し元の一方だけのために共通コードを変更しなければならないなら、それは共通コードに置くべきじゃない。

DRYを適用する際の間違ったゴールは、カプセル化で解決しようとすることだ。カプセル化は、リファクタリングの作業を呼び出し元から共通コードへと押し付ける。でも、共通コードの更新は呼び出し元を修正するよりもはるかに大きな影響があるから、これは望ましいことじゃない。

カプセル化を避けつつDRYを維持するには、呼び出し元が把握しておくべき薄い抽象化を複数持つほうがいい。OOPではこれをSRP(単一責任の原則)やIoC(制御の反転)で教わるけど、手続き型プログラミングでは、単にヘルパー関数を順に呼び出すコードとして自然に実現できることだね。