電通総研 テックブログ

電通総研が運営する技術ブログ

DRY 原則再解釈:「誤った共通化」を責任駆動で回避する


こんにちは!
X(クロス)イノベーション本部 プロダクトイノベーションセンターの佐藤です。電通総研 Advent Calendar 2024、12日目の今日は、DRY 原則の再解釈を通して、設計上の落とし穴である「誤った共通化」に陥らないためにはどうするべきかを考察していきたいと思います。
本稿は、設計の世界に足を踏み入れたばかりの方から鍛え抜かれた達人プログラマーまで、幅広い方々に気づきを提供できるような内容を目指しました。
初学者の方は手元に参考書を、達人の方はくつろぎのコーヒーを携えながら、気軽にお読みいただければ幸いです。

DRY

DRY 原則は、プログラミングや設計の初学者が最初期に学ぶ考えだと思います。
そして、それはしばしば
コピペをしてはいけない」「同じコードを繰り返し重複して書いてはいけない」
といった説明がなされることから、「DRY 原則はコードそれ自体を対象とする」と理解されている方々は決して少なくないのではないでしょうか。
しかし、筆者としては「この理解は、ともすれば誤解につながるケースが多いのではないか」と考えています。
実は、DRY 原則を世に広めた名著 達人プログラマー の第2版においても、この点について言及されています。

本書の初版では、「DRY 原則」が意味することについて書き足りない部分がありました。多くの人々はこれがコードの話だと受け取ってしまったのです。つまり、DRY を「ソースコードのコピー&ペーストをしてはいけない」と解釈してしまったのです。
これも DRY 原則の一部ですが、ほんの些細な一部でしかありません。 [1], p.40

確かに、コードのコピペや繰り返しが好ましくない結果をもたらす可能性があることは事実です。
しかし、この「コード」に着眼した理解は、その分かり易さがゆえに
「であれば、同じコードは通化しよう
といった拡大解釈を招いている可能性が、一定以上あるように思います。
達人プログラマー は以下のように続けます。

DRY 原則は「知識」や「意図」の二重化についての原則です。 [1], p.40

本記事では、この忘れ去られがちな DRY の本質について「知識」や「意図」とは別のもう一つの観点、「責任」の観点から捉え直してみたいのです。

SRP

ここで一度 DRY から離れて、SOLID 原則のイニシャルを務める、SRP(単一責任の原則) を思い出してみます。

クラスを変更する理由は1つ以上存在してはならない。 [2], p.122

ここではクラスと書かれていますが、設計思想としてはクラスに限ったことではありません。
なぜ、1つ以上(厳密には2つ以上ですが、ここでは引用元の訳に倣います)存在してはいけないのでしょうか。
それは、設計に硬さと脆さを与えることにつながるからです。
エンティティ(本記事では、メソッド、クラス、モジュール、コンポーネントといったシステムの構成要素を意味します)が独立した複数の責任を担っている時、ほとんどの場合において、そのエンティティを変更する理由もまた、責任ごとに独立して生じます
すると、ある責任に関する変更により他の責任が意図せず果たせなくなり、予想外の欠陥が発生する(=脆さ)可能性があります。そして、往々にしてこのような実装は変更し辛い(=硬さ)ものです。

一般に、責任と変更理由は対応します。
アジャイルソフトウェア開発の奥義(以下、奥義本) は、そこに一つ「役割」という概念も合わせて、SRP における言葉を以下のように定義しています。

役割(責任)= 変更理由 [2], p.123

再び、DRY

奥義本は、「腐敗するソフトウェアの兆候」の一つ「不必要な繰り返し」について、以下のように説明しています。

システム内に重複するコードがあると、システムの変更はとても骨の折れる作業になる。[...] コード中にある同じような部分をすべて修正しなければならないからだ。 [2], p.111

また、プリンシプル オブ プログラミング にも、DRY を適用すべき理由として同様のことが記載されています。

同じようなコードが複数あると、その複数箇所を正確に修正しなければ、全体としての整合が取れません。慎重に作業しないと、修正漏れの危険性があります。 [3], p.50

これら、DRY 適用の動機(=DRYを破った場合の問題)から逆算すると、そこには「それらが同じ変更理由である」という前提が浮かび上がってきます。
すなわち、DRY とは「同じ理由で変更されてしまうエンティティは、重複して存在するべきではない」と説明されるのが、より適切ではないでしょうか。
筆者は、ここであえて飛躍してコードをエンティティと言い換えましたが、なぜコードという記述を避けるのかについては後述します。

奥義本は、SRP における言葉を以下のように定義しています。

役割(責任)= 変更理由 [2], p.123

これは、重要さを伝えるための意図的な繰り返しです。
DRY と SRP の間に、責任を軸とした関連を見出だせる気がします。

誤った共通化

SRP と DRY。
この二つの原則は、いずれも設計における構成要素としてのエンティティについて、責任の観点から、その凝集性と一意性を論ずるものだと言えます。
SRPは

  • エンティティは単一の責任を担うべきである

ということを説いています。
一方で DRY は

  • 同じ責任を担うエンティティを重複して定義すべきでない(繰り返してはならない)

と説いています。

DRY の表層だけを捉え、責任の考慮なしに安易な共通化に走ると、それは SRP に違反しかねません
通化されるエンティティは、SRP に準拠、すなわち単一の責任のみを担っている必要があります。そして本来、責任というのは意図的な設計のうえに見出され与えられるものです。
したがって、「ここはこの前実装したロジックと(コード的に)同じだから、メソッドに切り出して双方から使おう」といった後づけの共通化や再利用は、高確率で失敗すると思われます。
EC サービスを例に考えてみましょう。
このサービスには会員限定割引が存在し、その割引率の計算について責任を担う calcMemberDiscountRate メソッドが存在するとします。その計算ロジックの一部が、後から実装されるクリスマスセール割引率計算ロジックと偶然にも重複することが分かりました。
この時、calcMemberDiscountRate メソッドの該当コードをコピペして、calcXmasDiscountRate メソッドを実装することは、果たして悪でしょうか。
むしろ「それは DRY に違反している」として共通化された場合に誕生する calcMemberAndXmasDiscountRate メソッドの方が、よっぽど恐ろしく感じられます。
これは説明のための極端な例に過ぎません。実際にはもっと巧妙に、例えばそれらしい抽象的な名前を隠れ蓑にして、複数の責任を担うエンティティが人知れず実装されるのです。

DRY をコードで捉えない

前節では、DRY を以下のように捉えました。

同じ責任を担うエンティティを重複して定義すべきでない(繰り返してはならない)

ところで、オブジェクト指向の世界において、責任を担えるエンティティの最小単位は何でしょうか。
レガシーコードからの脱却 に、以下のような記載があります。

デジタルの領域では、ものごとはラベルによって定義されるのではなく、ふるまいによって定義されるのである。 [4], p.144

これは、凝集性の文脈におけるオブジェクト(クラス)に関する説明ではありますが、この「ふるまい」(いわばインターフェース)こそ責任の基本であり、その最小単位はメソッドではないでしょうか。
だとすれば、そもそもそれ以下のコード断片については、実装の詳細のそのまた一部である可能性が高く、そういった責任の伴わない単なる欠片としてのコードについては、DRY 適用を論ずるには時期尚早だとも思えるのです。これが、「再び、DRY」節にてコードをエンティティと言い換えた理由であり、そのようなコードのコピペの良し悪しは、その断片だけからは判断が難しいでしょう。
前節で述べたような誤った共通化に陥らないためには、常に責任の観点から DRY を捉え、以下のように自問自答することが有効です。
「今、自分が実装しているこのエンティティの責任は、既にほかのエンティティが担ってくれていないか、あるいは他にこの責務の適任者はいないだろうか」
そして、特に何かを共通化し始めようという時には、まずは一呼吸おいて、そのエンティティが担う責任を注意深く観察し、隠れ蓑をまとった意図しない責任(変更理由)がいないかを、慎重に確認することが重要だと言えるでしょう。

まとめ

本記事では、DRY 原則の本質を SRP における責任の観点から紐解き、誤った共通化に陥らないための新たな DRY 原則の捉え方を検討しました。
設計原則は奥深く、たとえ表面上は単純なルールのように思えても、深く検討することで新しい気づきや解釈を与えてくれます。
本記事の解説が、皆様が今後取り組む設計やコーディングの一助となれば、嬉しい限りです。

参考文献

[1] David Thomas 他, 村上訳. 達人プログラマー 第2版 熟達に向けたあなたの旅. オーム社, 2020.
[2] Robert C. Martin 他, 瀬谷訳. アジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技. SBクリエイティブ株式会社, 2008.
[3] 上田勲. プリンシプル オブ プログラミング 3年目までに身につけたい 一生役立つ101の原理原則. 株式会社秀和システム, Kindle版.
[4] David Scott Bernstein, 吉羽 他 訳. レガシーコードからの脱却 ソフトウェアの寿命を延ばし価値を高める9つのプラクティス. 株式会社オライリー・ジャパン, 2019.
※ 本稿に掲載された画像は AI により生成されました


私たちは一緒に働いてくれる仲間を募集しています!

電通総研グループ キャリア採用サイト:電通総研

執筆:@satorin、レビュー:@nakamura.toshihiro
Shodoで執筆されました