非常に単純ですが、同じプロセスを数回繰り返すだけで、すべてのケースとスケーラブルなデータで機能するコードを作成する必要があるのはなぜですか。
私はこれをすぐにもう一度編集する必要はほとんどありません。
たったこれだけで済むのは、はるかに少ない作業のようです...
function doStuff1(){/*.a.*/}
function doStuff2(){/*.b.*/}
function doStuff3(){/*.c.*/}
そして、何かを追加する必要がある場合は...
function doStuff4(){/*.d.*/}
削除する必要がある場合は、削除します。
これらすべてを1つの単純なパターンにして、データをフィードしてすべてのケースを処理できるようにする方法を理解するのは難しいです。する。
なぜDRYにすると、簡単な切り取りと貼り付けで作業量が大幅に減るのでしょうか?
繰り返すと、保守性の問題が発生する可能性があります。 doStuff1-3のすべてに同様の構造のコードがあり、1つの問題を修正すると、他の場所で問題を修正するのを忘れがちになります。また、処理する新しいケースを追加する必要がある場合は、場所全体にコピーアンドペーストするのではなく、1つの関数に異なるパラメーターを渡すだけで済みます。
ただし、DRYは、賢いプログラマーによって極端に解釈されることがよくあります。自分自身を繰り返さないようにするには、抽象化を作成しなければならず、チームメイトがそれをフォローできないようにする必要があります。2つのものの構造が漠然としているだけの場合もあります。 doStuff1-4が十分に異なっているため、リファクタリングを繰り返さないようにして、不自然なコードを記述したり、巧妙なコーディングバックフリップを行ったりして、チームをまぶしくさせる必要がある場合は、繰り返してもかまいません。不自然な方法で何度か自分自身を繰り返さないように後方に曲がり、最終製品を後悔しました。
私はいつもDRYの側に誤りを犯します。まれに、可読性の利点は、誰かが複数の場所でバグを修正するのを忘れるリスクに見合うと自分が繰り返す場合です。
そのアドバイスを考慮すると、あなたの場合のように聞こえます
いくつかのマイナーな微調整で同じプロセスを数回繰り返します
私は間違いなくあなたのケースで自分自身を繰り返さないように努力します。最小限の「微調整」を前提としています。これらは、動作に影響を与えるさまざまなパラメーターで処理したり、依存性を注入してさまざまなサブタスクを実行したりできます。
なぜDRYにすると、簡単な切り取りと貼り付けで作業量が大幅に減るのでしょうか?
有名な最後の言葉。ジュニアエンジニアが1つのdoStuffを微調整/修正/リファクタリングすると、他のdoStuffが存在することにさえ気付かないと考えるのを後悔します。陽気さが続く。ほとんど胸焼けは起こりません。コードのすべての行のコストが高くなります。非常に多くの関数を繰り返してテストする必要があるコードパスの数は? 1つの関数の場合、いくつかの動作の変更を加えて1つのメインパスをテストする必要があります。コピーして貼り付けた場合は、すべてのdoStuffを個別にテストする必要があります。見落としがあり、顧客が不愉快なバグを抱えていたり、受信トレイに不快なメールが届いたりしている可能性があります。
DRYは後で作業が少なくなるためです。
DRY:(自分を繰り返さないでください)
引数を取る1つの関数。
def log(arg):
print(arg)
C&P:(コピー&ペースト)
基本的に同じことを実行する26ガジリオンの関数ですが、2文字の違いがあります。
def logA():
print('a')
def logB():
print('b')
...ad infinitum...
印刷を正確に指定するために印刷を更新するのはどうでしょうか?
乾燥:
def log(arg):
print(arg + "Printed from process foo")
できました。
C&P:
あなたは戻って変更する必要がありますすべての単一の関数。
デバッグする方が簡単だと思いますか?
理由、例に適用:
+読みやすさ
Less codeは、しばしばless noiseに変換されます。 (常にではない...)
+柔軟性
doStuffX
の動作を変更する必要が生じた場合は、自分またはそれを書いた人を殺したいと思うでしょう。
+拡張性
選択したデータ構造に個別の部分を抽出し、ジェネリックdoStuff
を呼び出してそれを反復した場合、新しいエントリが必要な場所にデータ構造に1行追加することもできます。 、または1つ削除して、動作を変更すると、単にdoStuff
を編集することになります。 維持するのが簡単です。
+コスト効率
コードなしここでの意味:
+(可能な)管理された最適化
言語によっては、コンパイラー/インタープリターは、ジェネリックdoStuff
が常にほぼ同じことを次々に呼び出し、inlineそれを最適化しようとします。おそらく、XのdoStuffX
には対応しません。
+テストと品質
テストは簡単です:doStuff
にはテストが必要ですが、それだけです。まあ、正確ではありませんが、すでにmoreをカバーしています。 IO期待値は異なり、さまざまな条件下でテストする必要がありますが、それでもテストがはるかに簡単ですおよびdoStuffX
のすべてのバリエーションよりも保守しやすい。
全体これはより多くのことを説明しますmaintainableコードと改善された開発効率チームにとって、それは多くの良い習慣の1つ to help yoより堅牢で信頼できるソフトウェアを作成します。
他の誰もが重複したコードの保守性の問題を説明するのに素晴らしい仕事をしたので、私はこれをただ言うだけにしておきます:
プログラミングの多くは、現在の現在だけでなく、将来について考える必要があります。コピーと貼り付けの方が簡単になりましたが、これをすぐにもう一度編集する必要はほとんどありません) "は、正しく考えていないことを示しています。自分で少し時間をかけてコピー/貼り付けを行いますが、そうすることで、差し迫った問題を越えて明日を考えることができないことを示しています。このコードを再確認する必要がないと確信していますか?そこにバグがないことが確実にわかりますか?次の一連の機能を実装する必要がある場合に、再度確認する必要がないことを100%保証できますか?これらは明日の問題であり、考慮に入れる必要があります今日の再設計。
もちろん、コピー/貼り付けが必要になる場合があります。 UI開発者として、DRYの原則に違反しなければならない場合があることを発見しました。それはうんざりする、それが起こるたびに私はうんざりします。そしてありがたいことに、それはまれです。しかし- する起こります。
違いは、DRYに違反する場合、veryの説得力のある理由が必要であること、およびステートメントそれらすべてを1つに単純化する方法を理解するのが難しいことです)パターンは実際にはそれらの1つではありません。あなたが大規模な時間の危機に瀕していて、ボスが次の数時間で何かを取得するために悲鳴を上げている場合、またはあなたが仕事を失う場合を除いて、これは正当な根拠ではないと思います。
これを間違った方法で行わないでください。私はあなたを非難したり、貞操にしたりするのではなく、むしろあなたの考え方が間違っているところを見てもらいます。プログラマーは将来の怠惰に投資します。 DRYはそれを達成するための方法です。困難な設計問題を解決するために今日行う作業は、明日利益をもたらします。
私はこれをすぐにもう一度編集する必要はほとんどありません。
これが本当に当てはまる場合、問題を回避できる可能性がありますが、多くの場合、維持する必要のあるコードに取り組んでいます。これは、機能の拡張、バグの修正、その他の改善を意味します。 10か所に同じコードの小さなバリエーションがあり、ある日そのコードに戻って変更を加える必要がある場合、10か所に同じ変更を加えるというエラーが発生しやすいタスクがあります(申し訳ありませんが、 11か所ありましたが、1つ忘れてバグが発生しました)。
解決しようとしている問題を一般化できれば、コードが拡張しやすくなり、バグが発生した場合の修正が容易になります。
別の質問への回答で述べたように、私のアプローチは次のとおりです。
つまり2つまで、別の原則(YAGNI)がDRYに勝ちます。しかし、3(または私が本当に怠惰な場合は4)から始めて、それが必要になると思われるので、DRYに従います。
更新
私の最近の経験からのいくつかのさらなるアイデア。別のチームによって開発された2つのコンポーネントAとBを私たちの製品に適応/統合する必要がありました。まず、2つのコンポーネントAとBは非常によく似ているため、アーキテクチャが多少異なるという事実にすでに不安を感じていました。 2つ目は、サブクラスを使用して本当に必要なものだけをオーバーライドしてよかったので、それらを適応させる必要がありました。
そこで、これら2つのコンポーネント(それぞれ約8つのC++クラスで構成されます)のリファクタリングを開始しました。AとBの両方に共通のアーキテクチャを用意し、サブクラスを定義して必要な機能を追加したいと考えました。このようにして、2つの新しいコンポーネントA 'とB'は、既存のコンポーネントから派生したものになります。
2週間後、既存のコードから一般的で明確な構造を取得しようとし、毎日の会議中に、元のコードが乱雑であるのでほとんど進歩していないことを説明する必要があった後、上司に話しました。これらの2つの新しいコンポーネントA 'およびB'以外は必要ないことがわかりました(4つまたは6つではなく、これら2つのみ)。
わかりました、そうです。AとBのクラスの大規模なコピーと名前の変更を行い、コードのコピーを適応させ始めました。さらに2週間で動作するようになりました(今でもバグ修正を行っています)。
利点:機能がほぼ終了し、すべてのバグを修正した時点で終了です。 AとBのすべてのリファクタリングとテストを保存しました。
短所:2週間前に、他のチームがAとBで使用される別のコンポーネントCを変更しました。AとBを適合させましたが、A 'とB'も壊れており、自分で変更する必要がありました。これにより、修正が必要な新しいバグが発生しました。 A 'およびB'がコードの大部分をAおよびBと共有している場合、この余分な作業はおそらく不要でした。
したがって、コードの重複は常に危険です。それは常にトレードオフを見つけることの問題であり、しばしばそれは容易ではないと思います。
明確にするために、他の答えにはこれが見つからないので:
[〜#〜] dry [〜#〜] は、すべての種類の情報の繰り返しを減らすことを目的としたソフトウェア開発の原則です。
すべての知識は、システム内で単一の明確で信頼できる表現を持つ必要があります。
Andy HuntとDave Thomasが述べたDRY原則は、コードの重複防止に限定されていません。また、コード生成と任意の自動化プロセス皮肉なことに、コード生成の結果は重複したコードになる可能性さえあります...
他の回答ですでに完全に説明されている理由ですが、ファルコンのコメントはそれを十分に要約していますIMHO:
単一の変更点を維持するのが簡単です。
DRYが多すぎるなどの事があります。これが発生すると、ある時点でファクタリングコード(1)を保証するのに十分に類似しているように見える2つの概念が、別々の実装に値するほど十分に異なることが後で判明する場合があります。
つまり、DRYと疎結合が競合する場合があります。doStuff1や友人がソフトウェアの新しいリリースごとに分岐することを期待する場合は、コードを複製しても問題ありません。
私の経験では、ソフトウェアの将来の方向性を判断するのは難しい場合があるため、DRYが安全な選択であることがよくあります。
過度に「乾燥」しているコードは、通常、制御フローが複雑で、パラメーターが多すぎます。最初は単純な関数であったものが後で拡張され、追加のパラメーターによって制御される新しい機能がサポートされました。 2〜3回の反復の後、関数は保守できなくなります。設定で発生するバグを修正し、他の設定で新しいバグを導入します。
コードの進化に伴ってコードの品質が低下することはよくありますが、本文にif-then-elseスパゲッティが含まれるマルチパラメーター関数が、うまく機能しているものの不十分なリファクタリング作業の結果であるケースを見てきました。
(1)「コード」という言葉を使用していますが、これはデザインにも当てはまります。
リレーショナルデータベースの世界でのDRYの問題について言及しなければなりません。データベースは、セットベースのロジックを使用して、検索可能なクエリを通じて迅速かつ適切に実行するように設計されています。DRY =原則により、開発者はSargable以外のクエリを作成したり、Row-by-agonizing-Rowロジックを使用して複数の状況で既存のコードを活用したりすることがよくあります。DRYそしてパフォーマンスの最適化は、多くの場合、データベースの世界では、パフォーマンスは通常、保守性よりもはるかに重要です。これは、DRYの原則をまったく使用しないことを意味するのではなく、全体的なユーザビリティにどのように影響するかを認識する必要があります。アプリケーション開発者のことDRY最初にパフォーマンス、次にデータベース開発者がデータの整合性を最初に、パフォーマンスを2番目、データのセキュリティを3番目に考える(パフォーマンスとセキュリティは一部のシステムでは場所が入れ替わる可能性があります)。したがって、DRYは遠い4番目です。
一般的に、データベースクエリに挿入する抽象化の層が多いほど遅くなることに気づきました。データベースベースのプログラムに影響を与えることなく、開発者がDRYを使用できるようにするというより良い仕事をdatbaseプログラムを設計する人々がやらないことを望んでいないとは言っていませんが、私はそのレベルでデータベースソフトウェアを設計しないでください。データベースの抽象化とパフォーマンスの競合は、私が思うよりも修正するのが難しいかもしれません。しかし、現在構築されているシステムで作業する必要があります。より良い実装を求めることができますDRYパフォーマンスを低下させない将来のリリースの原則(そしてそれは何年にもわたって改善されましたが、それでも問題があります)ですが、当面はDRYは現時点でこのデータベースの正しい動きです。
しかし、DRYの原則が満たされていることを確認するために使用したい機能そのものが、データベースに多大な問題を引き起こすものです。DRYしかし、それを使いすぎないでください。
私が話していることの例。月に一度、100万件のレコードのデータインポートを行う必要があります。ストアドプロシージャを呼び出すユーザーインターフェイスを介して、レコードを手動で追加できます。このプロシージャは、単一レコードのインポート用に設計されているため、一度に1つのレコードのみを追加します。 DRYを使用して、挿入コードが2か所に配置されないようにするには、必要なセットベースのインポートを書き込むのではなく、カーソルを記述してprocを繰り返し呼び出します。インポートの時間は30分から始まりますセットベースのロジックを18時間に使用する必要があります。ここでDRYに準拠する正しい方法は、複数のレコードのインポートを処理するようにprocを修正することです。残念ながら、それはしばしば不可能ですまたは、配列をprocに送るのは非常に困難です(dbバックエンドによって異なります)。procを変更すると、アプリケーションが壊れてしまいます。
スカラー関数とテーブル値関数は、DRYの原則を実装するためにも使用されます。特に、インデックスが役に立たない方法で使用する必要がある場合は、パフォーマンスに深刻な影響を与える可能性があります。
ビューは、DRYの実装にも適しています。ただし、他のビューを呼び出すビューを呼び出すビューを使用してDRYを実装すると、負荷がかかったときにクエリがタイムアウトするポイントにすぐに到達します。実際には、最後に3つだけ必要なときに数百万のレコードのデータセットを生成します。したがって、DRYを実装するための複雑な結合セットの1レベルのビューは優れています(私たちはすべての財務報告が同じベーステーブルセットと特定のものの計算を使用することを確認するために使用します)、2つ以上のレベル、およびパフォーマンスの混乱を作成している場合は考慮する必要があります。
上記の回答の要点はわかりません。 DRYルールと同じくらいに対して何かをしているのを見ないでください。それはそのように表現されているかもしれませんが、実際にはまったく異なる、前向きな目的に役立ちます。それは停止し、考え、そしてより良い答えを見つけるための信号です。それは、より良いソリューションを設計する機会を探すことを私に挑戦します。それは私に私の設計を再考させ、私にそれを実行させるコードの悪臭の良い面ですDRYは、ちょっとした構文違反だけではありません。モジュール化するように要求します。コンポーネント化するように要求します。これは、テンプレートとコードの使用について考えるように思い出させる繰り返しを示します力ずく無知の代わりに世代。それは私が私の自動化を自動化する時間を見つける必要があることを理解するのに役立ちます。詳細、そしてマナー、呼吸、そして健康的なライフスタイルを提供します! ....
はい、DRY書いている場合は気にしないでください使い捨てコード。
ただし、コードを保持する場合は、DRYはもちろん重要です。
私は古いレガシープロジェクトを使用しており、以前の開発者の一部はDRYをまったく気にしていませんでした。そのため、コードベース全体がGetSystemTimeAsString()、LogToFile()などのヘルパーメソッドでいっぱいになりました。一部のメソッドは特別なニーズに合わせてわずかにカスタマイズされましたが、ほとんどは単にコピーアンドペーストでした。
残念ながら、一部のメソッドには、char配列などの微妙なバグがあり、場合によってはstrcpy()などの安全でないものを使用するなど、十分に長くはありません。
したがって、すべてのコードフラグメントを見つけ、それらを調和させ、バグを修正するのは本物のPITAでした。そして、私たちはまだ物事を調和させ、修正しています。
コピーしただけなので、最初の方法を間違え、それを何度も修正する必要がある場合は、決してわかりません。そして、後でいくつかのメソッドを使用したい場合、どのようにしてコードベース内の5つのメソッドのうちどれがあなたのケースのためのものであるかをどのようにして知っていますか?つまり、1つをコピーしてカスタマイズし、ここで再び開始します...
「自分を繰り返さないでください」という表現は、少し単純すぎます。重要なのは、「2つのindependent場所にカプセル化された1つの変更可能な情報が含まれないようにすること」です。
プログラムがウィジェットを処理することになっている場合、それぞれに3つのウーゼルがあり、フォームの多くのループです。
for (i=0; i<3; i++)
thisWidget.processWoozle(i);
次に、ウィジェットに3つのウーズルが含まれると予想されるという期待は、それらのループのそれぞれにカプセル化され、ウィジェットごとの他の数のウズルに対応するようにコードを更新することは困難な場合があります。それとは対照的に、
#define WOOZLES_PER_WIDGET 3
そして各ループは書き直されました
for (i=0; i<WOOZLES_PER_WIDGET; i++) ...
そのようなデザインmightは、ウィジェットごとのウーゼルの数を非常に簡単に変更できます。
ただし、ウィジェットごとのウーゼルの数などの情報を1つのポイントに統合することが望ましい一方で、常に実用的であるとは限らないことに注意することが重要です。場合によっては、特定のサイズの場合にのみ機能するロジックをハードコーディングする必要があります。たとえば、各woozleに値があり、特定のウィジェットに関連付けられた中央値を見つけたい場合、値を並べ替えて中央の値を取ることができます。そのようなアプローチは任意の数のwoozleで機能しますが、ロジックこれは、3つの項目の中央値を見つけるために特別に手書きされたもので、大幅に速くなる可能性があります。
WOOZLES_PER_WIDGET定数を使用するとコードが読みやすくなりますが、プログラムロジックに他の調整を行わない限りその値を変更できないことを明確にするためにコメント化する必要があります。その場合、3つの項目に対してハードコードされたロジックと定数WOOZLES_PER_WIDGETはどちらも「各ウィジェットには3つのウーゼルがある」という情報を複製しますが、そのような複製の利点(実行速度が大きい)はコストを上回る可能性があります。
私は実際に他のポスターに同意しますが、保守性などについてのコメントはすべて有効です。
私は議論に小さな反対意見を加えたいと思います。
_<tl;dr>
_
繰り返されるすべての回答を読むことができなかったため、何かを見逃した可能性があります(自分で繰り返します<=ここで何をしたかを確認しますか?)。
これは、コードの重複を防ぐことについて素晴らしいことのリストです!
doFoo1(a, b)
を使用する場合、その厄介な障害とEdgeケースの多くが明らかにされて解決される可能性が高くなります。全員がコードをコピーしてdoFoo2(specialA)
... doFuu2^n(a, b, c)
を作成すると、問題は_doFoo1
_に複製され、より多くの作業が具体的に作成されます。_</tl;dr>
_
ロングバージョン:
コードの重複に関する問題は、コードが「指数関数的に成長する」(つまり、急速に拡大する)ことです。コードを複製すると、知らないうちに他のユーザーに許可を与えます(1つには、それらを判断する立場にありません)。あなたは彼らに同じことをするよう勧めます。また、ソースに冗長な冗長な繰り返しがたくさんあると、有用なコードを見つけて再利用することが難しくなるので、そうしないのも難しくなります。特に、コードが適切な名前の関数にまだ抽出されていない場合。したがって、一般的な単純な問題を解決する場合は、自分でコードを記述してそれを解決する可能性があります。また、いくつかのEdgeケースのチェックに失敗し、バグのある未テストのコードが追加されます。
もう1つは、初心者にはこれは大企業にのみ影響を与える問題のように聞こえるかもしれませんが、小さなスタートアップをひどく悩ませることがわかりました(重複したサーバー側コードの10,000行のように)。心の状態です。 DRYを習得するだけでなく、他の人にも同じことをするように奨励する必要があります。そうしないと、ほとんどの場合、コードを複製することになります。DRY複製されたコードが大量にある場合、コピーとペーストのソリューションを適用する方がはるかに簡単です。
コードの複製で有害だと思うもの:
熱狂的なコード重複防止と要約についての最後のメモ:
これは以前にも述べられましたが、重複を回避すると、「逆方向に曲がって」しまい、他の人には理解できないほど洗練された(または重要ではない)ことを行う場合があります。読み取り不可能なコードを書くこと(または冗談で「ジョブ保存」コードと呼んでいるように)は、コードの重複防止を行わなくても、それ自体が問題です。ただし、適切なインフラストラクチャとベストプラクティスを最初から導入すると、コードの重複を防ぐのがはるかに簡単になり、多くの場合、直感的でないことを回避できるため、コードの重複防止のために行われる将来の不要で読めない作業のヒープを防ぐことができます。あなたはその最初から正しいことをします。
正しいことは何ですか?それは答えるのが難しい質問ですが、1つは、プロジェクトに必要なメソッドを定義し、社内(社外)によって既に実装されているものを確認し、可能な場合はそれを再利用することです。コードベースに追加するすべてのことを文書化し、必要以上に一般的にすることを試みますが、それだけです。必要のない場所でコードを柔軟にするためだけに設計パターンをやり過ぎないでください。