結合を増加させずにDRYを適用することは可能ですか?


14

関数Fを実装するソフトウェアモジュールAがあるとします。別のモジュールBは、F 'と同じ関数を実装します。

重複するコードを取り除くには、いくつかの方法があります。

  1. AにBのF 'を使用させます。
  2. BにAのFを使用させます。
  3. Fを独自のモジュールCに入れ、AとBの両方に使用させます。

これらのオプションはすべて、モジュール間に追加の依存関係を生成します。カップリングを増加させる代わりに、DRY原則を適用します。

私が見る限り、DRYを適用すると、カップリングは常に増加するか、リースでより高いレベルに移動します。ソフトウェア設計の最も基本的な2つの原則の間には矛盾があるようです。

(実際、そのような競合があることは驚くことではありません。これはおそらく、優れたソフトウェア設計をそれほど難しくしていることです。これらの競合は通常、入門書では扱われていません。

編集(明確化のため):FとF 'の等価性は単なる偶然ではないと思います。Fを変更する必要がある場合、F 'も同様に変更する必要があります。


2
... DRYは非常に有用な戦略になると思いますが、この質問はDRYの非効率性を示しています。一部の人(たとえば、OOPファン)は、AとBの概念的な自律性を維持するためだけに、FをBにコピー/ペーストする必要があると主張するかもしれませんが、私はそれを行うシナリオに至りませんでした。コードのコピー/貼り付けは最悪のオプションに過ぎないと思います。何かを行うためのメソッド/関数をすでに作成したと確信しているという「短期間のメモリ損失」に耐えられません。ある機能のバグを修正し、別の機能を更新するのを忘れることも、別の大きな問題です。
jrh

3
相互に矛盾するこのオブジェクト指向の原則がたくさんあります。ほとんどの場合、合理的なトレードオフを見つける必要があります。しかし、IMHO the DRY原則が最も価値があります。@jrhが書いたように:複数の場所で同じ振る舞いを実装することはメンテナンスの悪夢であり、どんな犠牲を払っても避けるべきです。本番環境で冗長コピーの1つを更新するのを忘れたことが判明すると、ビジネスが停止する可能性があります。
ティモシートラックル

2
@TimothyTruckle:他のプログラミングパラダイムにも適用されるため、OO原則と呼ぶべきではありません。そして、はい、DRYは価値がありますが、やりすぎると危険です。それは依存関係を作成する傾向があるためだけでなく、それゆえ複雑さを引き起こします。また、変更理由が異なる偶然の一致によって引き起こされる重複にもしばしば適用されます。
フランクパファー

1
...また、このシナリオに遭遇したときに、Aが必要とするものとBが必要とするものに使用できる部分にFを分割することもできました。
jrh

1
カップリングは本質的に悪いことではなく、多くの場合、エラーを減らして生産性を高めるために必要です。関数内で言語の標準ライブラリのparseInt関数を使用する場合、関数を標準ライブラリに結合します。私は何年もこれをしないプログラムを見たことがありません。重要なのは、不要な結合を作成しないことです。ほとんどの場合、このような結合を回避/削除するためにインターフェイスが使用されます。たとえば、私の関数はparseIntの実装を引数として受け入れることができます。ただし、これは必ずしも必要ではなく、常に賢明でもありません。
ジョシュアジョーンズ

回答:


14

これらのオプションはすべて、モジュール間に追加の依存関係を生成します。カップリングを増加させる代わりに、DRY原則を適用します。

なぜそうですか。ただし、回線間の結合は減少します。カップリングを変更する力が得られます。カップリングには多くの形式があります。コードを抽出すると、間接性と抽象性が高まります。それを増やすことは良いことも悪いこともあります。どちらを取得するかを決定する一番の理由は、使用する名前です。名前を見て、中を見て驚いたなら、あなたは誰にも好意的ではありません。

また、真空状態でDRYを追跡しないでください。重複をなくすと、そのコードのこれら2つの使用が一緒に変わることを予測する責任があります。それらが独立して変更される可能性がある場合、混乱と余分な作業を引き起こし、ほとんど利益はありません。しかし、本当に良い名前はそれをより口当たりの良いものにすることができます。あなたが考えることができるすべてが悪い名前であるならば、どうぞ、今すぐやめてください。

カップリングは、システムが非常に隔離されていて、それが機能するかどうか誰にもわからない場合を除き、常に存在します。したがって、カップリングのリファクタリングは、毒を選択するゲームです。DRYに従うことで、変更するのが非常に困難になるまで、多くの場所で何度も同じ設計決定を表現することによって作成されるカップリングを最小限に抑えることができます。ただし、DRYを使用すると、コードを理解できなくなります。その状況を救う最善の方法は、本当に良い名前を見つけることです。良い名前が思いつかない場合は、意味のない名前避けるのが上手であることを願います


あなたのポイントを正しく理解するために、少し違った言い方をしましょう:抽出されたコードに良い名前を割り当てると、名前がすべてを(理想的には)言っているので、抽出されたコードはソフトウェアを理解するのにもはや関係ありません。結合は依然として技術レベルで存在しますが、認知レベルでは存在しません。したがって、比較的無害ですよね?
フランクパファー


@FrankPufferより良い?
candied_orange

3

明示的な依存関係を解除する方法があります。人気のあるものは、実行時に依存関係を注入することです。このようにして、DRYを取得し、静的安全性を犠牲にしてカップリングを削除します。今日では非常に人気があり、人々も理解していないので、トレードオフです。たとえば、アプリケーションコンテナは、複雑さを隠すことで、ソフトウェアを非常に複雑にする依存関係管理を定期的に提供します。型システムが不足しているため、単純な古いコンストラクターのインジェクションでさえ、一部のコントラクトを保証できません。

タイトルに答える-はい、可能ですが、ランタイムのディスパッチの結果に備えてください。

  • AのインターフェイスF Aを定義し、Fの機能を提供します
  • BのインターフェイスF Bを定義します
  • FをCに入れる
  • モジュールDを作成して、すべての依存関係を管理します(A、B、Cに依存します)
  • DでFをF AおよびF Bに適応させる
  • AおよびBにラッパーを挿入(パス)します

このように、依存関係の唯一のタイプは、他の各モジュールに依存するDです。

または、組み込みの依存性注入を使用してアプリケーションコンテナにCを登録し、ゆっくりと成長する実行時クラスローディングループとデッドロックの自動配線の幸運を味わってください。


1
+1ですが、これは通常悪いトレードオフであるという明確な警告があります。静的な安全性はあなたの保護のためにあり、遠くで不気味な行動の束でそれを回避することは、プロジェクトの複雑さが少し大きくなったときに、追跡が困難なバグをさらに求めています...
Mason Wheeler

1
DIは本当に依存性を破ると言えるのでしょうか?正式な場合でも、実装するにはFの署名付きのインターフェイスが必要です。しかし、それでもまだ、モジュールAはrwally CからFを使用している場合、それは関係なく、Cの実行時に注入されるか、または直接dependencが提供されていない場合linked.DIのみ故障延期依存、バグを破壊しない、それにdepwnds
max630

@ max630は、実装の依存関係を契約上の依存関係に置き換えます。
バシレフ

@ max630あなたは正しいです。DIは依存関係を壊すとは言えません。実際、DIは依存関係を導入する方法であり、カップリングに関して尋ねられる質問とは正反対です。Fの変化(またはそれをカプセル化インタフェース)は依然としてAとBの両方の変化を必要とするであろう
キング側スライド

1

それ以上の文脈のない答えが意味をなすかどうかはわかりません。

Aすでに依存していますBか、その逆ですか?—この場合、私たちはのためのホームの明らかな選択をするかもしれませんF

良いホームになる可能性のある一般的な依存関係を共有しABいますかF

大きさ/複雑さはF?他に何がF依存していますか?

モジュールAB同じプロジェクトで使用されていますか?

ウィルABとにかく、いくつかの一般的な依存関係を共有して終わりますか?

使用されている言語/モジュールシステム:新しいモジュールは、プログラマの苦痛やパフォーマンスのオーバーヘッドにどの程度苦しみますか?たとえば、モジュールシステムがCOMでC / C ++で記述している場合、ソースコードに苦痛を与え、別のツールを必要とし、デバッグに影響を与え、(モジュール間呼び出しの)パフォーマンスに影響を与えます。いくつかの深刻な一時停止を取ります。

一方、単一の実行環境でかなりシームレスに結合するJavaまたはC#DLLについて話している場合、それは別の問題です。


関数は抽象化であり、DRYをサポートします。

ただし、優れた抽象化は完全である必要があります。不完全な抽象化は、基礎となる実装の知識を使用して、消費クライアント(プログラマー)が不足を補う可能性が非常に高くなります。

そのためAB1つの関数を新しいモジュールに単純に移動するよりも、より優れた抽象化を作成し、依存するようにしたいと考えていますC。 

新しい抽象化を引き出すための一連の関数を探しています。つまり、ベースとなるよりも完全/より完全な抽象化リファクタリングを識別するために、コードベースがさらに進むまで待つかもしれません単一の機能コードで伝えます。


2
抽象化と依存関係グラフを結合することは危険ではありませんか?
バシレフス

Does A already depend on B or vice versa? — in which case we might have an obvious choice of home for F.これは、Aが常にBに依存している(またはその逆)ことを前提としています。これは非常に危険な前提です。OPがFを本質的にA(またはB)の一部ではないと見なしているという事実は、Fがどちらのライブラリにも固有ではなく存在することを示唆しています。Fが1つのライブラリ(DbContext拡張メソッド(F)およびEntity Frameworkラッパーライブラリ(AまたはB)など)に属している場合、OPの問題は議論の余地があります。
Flater

0

この問題を「最小化」することができるすべての方法に焦点を当てたここでの答えは、あなたを傷つけています。また、結合を作成するためのさまざまな方法を単に提供する「ソリューション」は、真のソリューションではありません。

真実は、あなたが作成したまさにその問題を理解していないということです。あなたの例の問題は、DRYとは関係なく、アプリケーション設計と(より広く)関係ありません。

両方が同じ関数Fに依存している場合、モジュールAとBが別々である理由を自問してください。もちろん、貧弱なデザインにコミットすると、依存関係の管理/抽象化/結合/あなたの名前の問題が発生します。

適切なアプリケーションモデリングは、動作に従って行われます。そのため、Fに依存するAとBの部分は、独自の独立したモジュールに抽出する必要があります。これが不可能な場合は、AとBを組み合わせる必要があります。いずれの場合も、AとBはシステムにとってもはや有用ではなく、存在しなくなるはずです。

DRYは、不適切なデザインを公開するために使用できるプリンシパルであり、その原因ではありません。アプリケーションの構造のためにDRYを達成できない場合(実際に適用される場合 -編集に留意する場合)、それは構造が不利になったという明確な兆候です。これが、「継続的なリファクタリング」も従うべき原則である理由です。

他のデザインプリンシパル(ソリッド、ドライなど)のABCはすべて、アプリケーションの変更(リファクタリングを含む)をより簡単にするために使用されます。それに注目する、他のすべての問題が消え始めます。


すべてのアプリケーションに1つのモジュールを含めることをお勧めしますか?
バシレフス

@Basilevs絶対にそうではありません(保証されない限り)。完全に分離されたモジュールをできるだけ多く持つことをお勧めします。結局のところ、それがモジュールの全体的な目的です。
キングサイドスライド

さて、質問はモジュールAとBが無関係な機能を含み、それに従って既に抽出されていることを意味します。なぜ、どのようにそれらが存在しなくなるのでしょうか?記載されている問題の解決策は何ですか?
バシレフス

@Basilevsこの質問は、AとBが適切にモデル化されていないことを意味します。そもそも問題を引き起こしているのはこの固有の欠陥です。確かにそれら存在するという単純な事実は、それら存在するべきであるという証拠ではありません。それが私が上で述べているポイントです。明らかに、DRYの破損を避けるために、代替設計が必要です。これらすべての「設計原則」の目的は、アプリケーションの変更を容易にすることであるということを理解することが重要です。
キングサイドスライド

完全に異なる依存関係を持つモデル化された他の多くのメソッドは不良であり、この単一の偶発的でないためだけにやり直す必要がありましたか?または、モジュールAとBにはそれぞれ1つのメソッドが含まれると仮定しますか?
バシレフ

0

これらのオプションはすべて、モジュール間に追加の依存関係を生成します。カップリングを増加させる代わりに、DRY原則を適用します。

少なくとも3番目のオプションについては、私は異なる意見を持っています。

あなたの説明から:

  • AニーズF
  • BはFが必要
  • AもBもお互いを必要としません。

AとBの両方がすでにCの機能を必要としているため、CモジュールにFを配置しても結合は増加しません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.