じぶん対策

日々学んだことをアウトプットして備忘録にしています。

DRY原則とそのトレードオフ

はじめに

おそらくもっとも有名なソフトウェア工学の原則の1つに、DRY(Don't Repeat Yourself)原則があります。DRY原則は、同じコードを何度も書かないようにすることを目指しています。この記事では、DRY原則の重要性とそのトレードオフについて考えてみます。

参考

ソフトウェア設計のトレードオフと誤り

DRY原則とは?

DRY原則(Don't Repeat Yourself)は、ソフトウェア開発における原則の一つです。

重複したコードを避け、同じコードを複数の場所で書かないようにすることを推奨しています。

DRY原則を遵守することで得られる主なメリットは次のとおりです。

  1. 保守性の向上: 同じコードが複数の場所に存在しないため、修正が必要な場合に一箇所だけ変更すれば済みます。これにより、コードの保守性が向上します。
  2. 可読性の向上: 重複するコードがないため、コードの構造が明確になり、読みやすくなります。
  3. バグの減少: 重複したコードを修正する際に、一部の場所を修正し忘れることがなくなるため、ケアレスミスによるバグが減少します。
  4. 開発効率の向上: コードの重複を避けることで、新しい機能の追加や既存機能の改善が容易になり、開発効率が向上します。

DRY原則トレードオフ

現在のソフトウェア開発において、コードの重複を減らそうとすると、いくつかのトレードオフが生じることがあります。
コンポーネント間が密結合いなったり、チームの開発速度が低下したりします。
例えば、同じコードを複数の場所で使い回すことで、コードの再利用性が向上する一方で、コードの変更による影響範囲が大きくなります。

DRY原則は非常に重要ですが、過度に適用すると逆に問題が生じることがあります。異なる文脈で同じコードを無理に使おうとしてしまい、適切な抽象化が行われないこともあります。

ここからは、コードの重複が合理的になるパターンと、逆にどのような場合に避けるべきなのかについて考えてみます。

アムダールの法則

今回参考にした書籍では、まずアムダールの法則について触れています。

アムダールの法則とは、

ある計算機システムとその対象とする計算についてのモデルにおいて、その計算機の並列度を上げた場合に、並列化できない部分の存在、特にその割合が「ボトルネック」となることを示した法則である

引用元: wikipedia

この法則をソフトウェア開発に置き換えて考えてみると、必要な同期処理が少ない(つまり並列処理部分が多い)ほど、問題解決のためにリソースを追加することで得られる利益が大きいということが言えます。もっと単純に、待ち状態が発生しにくいほどリソース追加の効果が大きいといってもいいでしょう。

例: 認証ライブラリ

参考書籍でも紹介されていますが、例えば、認証ライブラリを開発する場合を考えてみましょう。書籍の内容をできるだけ簡潔にしながらまとめるので、より詳細な説明が必要な場合は書籍を参照してください。

この例では、ビジネス的に分断され、開発チームとしても分かれている2つのプロジェクトがあります。それぞれのプロジェクトで認証機能が必要になりました。

先に紹介したアムダールの法則通り、それぞれのチームで認証機能を開発することを選択した場合のことを考えてみます。

この場合、認証機能はそれぞれのプロジェクトで独立して開発されるため、コードの重複が発生します。同じ認証という機能を独立して開発しているため、コードや作業に重複が存在します。

この時発生する事象として、下記のようなものがあります。

  • 多くのバグやミスにつながる
    • たとえば、片方のチームがバグを修正した場合、もう片方のチームも同じバグを抱えている可能性があります。
  • 知見は共有されない
    • 片方のチームがバグを発見し、彼らのコードベースでそのバグを修正したとします。しかし、もう片方のチームのコードベースにその修正が反映されることはありません。お互いのチームはそれぞれこのバグを修正する必要があります。
  • チーム間での調整なしの作業はより早く進みます

この例からわかるように、コードの重複は問題を引き起こす可能性があります。このような場合、DRY原則を適用することで、コードの重複を避けることが重要です。

続いて、この二つのチームは、コードベース間で重複している部分を別のライブラリに抽出することを決定しました。これにより、認証機能は共通のライブラリとして提供され、両方のプロジェクトで再利用されることになります。

重複を除くことで、コード全体の品質が向上します、共通のライブラリを蓄積することで、双方のチーム間での協力が可能になり、共通のコードベースが改善されます。

バグを修正する際の作業の重複もなくなります。

このライブラリとして抽出するアプローチを選択する場合のトレードオフについて考えてみましょう。

  • 抽出したライブラリ自身は元のコードベースとは別の様式、デプロイ方法、コードの慣例を持つ
  • そのため、最初に共有ライブラリを追加するコストは高くなるが、2つ目を追加するコストは低くなる
  • 共有ライブラリを選択した場合、ほとんどのケースではそれを利用するクライアントと同じ言語を利用する必要がある
    • ネイティブインターフェースのような技術でラップすることも可能だが、中間層が増え、例えばOS間での移植が難しくなったり、別の問題が発生する
  • ライブラリを開発する場合はバージョンに寄る互換性の維持やシンプルな設計など、ライブラリの品質にも注意が必要
  • ライブラリをインポートした場合、ライブラリのコードは利用者のコードの一部となり、責任が発生する

上記のライブラリの利用による問題をいくつか解決するために、参考書籍ではマイクロサービスのアーキテクチャを提案しています。

マイクロサービスとして切り出して、そのサービスをHTTPなどのプロトコルを通して利用する場合は、ライブラリを利用する場合に比べると結合が緩やかになります。言語による制限や、ライブラリの品質による問題をブラックボックスとして扱うことができ、APIのみが唯一の結合箇所になります。

ただし、現在のコードを実行するための新しい依存関係として、クライアントライブラリを追加する必要があります。

また、ライブラリの場合と同様に、マイクロサービスの場合も、サービスの品質やバージョン管理に注意する必要があり、独自のデプロイの仕組みが必要になります。また、サービス自体をデプロイするインフラの整備や、監視の仕組みなど、多くの追加の実装が必要になります。

DRY原則トレードオフバランス

適切なバランスを見つけるためには、以下の点を考慮することが重要です。

  1. 文脈の理解: コードを再利用する際には、そのコードが適用される文脈を理解することが重要です。異なる文脈で同じコードを無理に適用することは避けましょう。
  2. 抽象化の適切なレベル: 抽象化は適切なレベルで行う必要があります。過度に抽象化すると、コードの理解が難しくなります。
  3. 可読性と再利用性のバランス: 可読性と再利用性は両立しないことがあります。どちらを優先するかは、プロジェクトの状況やチームの方針によって異なります。

また、本質的な重複か、偶然の重複かを意識することが重要です。

参考書籍には、ソフトウェア開発者はパターンマッチングに過剰適合する傾向があります。という記述があります。これは、同じコードを見つけると、それを抽出して再利用しようとする傾向があるということです。2つのものが同一に見える場合には、それらが同じビジネス目標を解決する場合と、作業しているコード内における偶然の重複の場合に分けられます。

通常、2つの概念が異なることが判明したときに分離する場合と、2つの別の概念が同じことが判明した時に1つに統合する場合を比べると、後者のほうが簡単です。一度抽象化を行い、複数の場所で利用され始めると、分離する際に影響範囲が広がり、さまざまな調整が必要になります。

抽象化された状態から考え始めて、発生しうる全ての利用法を、決めた抽象化の枠組みに適合させていくのは、大抵のケースで最適ではない、と書籍内で記述がありますが、これは私個人の経験則とも合致するものです。

まずは独立したコンポーネントを作成し、多少コードの重複があったとしても、しばらくの間独立させたままシステムを実装できます。

このように、独立した実装を維持するか、ライブラリとして共有するか、マイクロサービスとして切り出すかは、それぞれのユースケーストレードオフが発生します。適切なバランスを見つけるためには、文脈の理解、適切な抽象化のレベル、可読性と再利用性のバランスを考慮することが重要です。

まとめ

DRY原則はソフトウェア開発において非常に重要な原則ですが、大抵の場合は分離するほうが難易度が高くなりがちなので過度に適用することは避けるべきです。適切なバランスを見つけることで、高品質で保守しやすいコードを実現できると思いました。

参考書籍の中で、まとめられている箇所がありましたので、引用します。

  • コードベース間での共通コードの共有は、ライブラリとして抽出することで実現できます。しかし、ライブラリ経由でのコードの再利用は密結合や柔軟性の低下などのさまざまな問題が伴います。
  • 共通のビジネスロジックを分離したサービスに抽出することは、より複雑な問題に対しては正しい選択かもしれませんが、保守コストが高くなります。
  • 継承は、コードの重複を取り除くことや子クラス間でのコードの共有に役立ちます。しかし、継承はコードの柔軟性を制限するなど、多くのトレードオフがあります。
  • 時として、重複したコードを残しておくことで柔軟性が維持され、チーム間の調整を減らす価値があります。

所感

DRY原則は、エンジニアになって一番最初に覚えた原則でしたが、その内容を本当の意味で理解するようになったのは意外と最近のことでした。
ソフトウェア開発は常に様々なトレードオフが存在するため、手法ごとにその適用範囲を理解し、トレードオフを可視化したうえで判断を繰り返していくことが重要だと感じました。
社内でもライブラリやマイクロサービスとしての切り出しは行われているため、それぞれのケースについて今一度トレードオフの内容を考え直すきっかけになりました。