おそらく多くの人と同じように、設計の問題が頭痛の種になっていることがよくあります。たとえば、問題に直観的に当てはまり、望ましい利点があるデザインパターン/アプローチがいくつかあります。多くの場合、何らかの回避策がないとパターン/アプローチを実装するのが困難になる警告があり、パターン/アプローチの利点が無効になります。多くのパターン/アプローチを繰り返し処理してしまう可能性があります。実際には、簡単な解決策がない実際の状況では、ほぼすべてのパターンに非常に大きな注意事項があるためです。
最近遭遇した実際のものに大まかに基づいた架空の例を紹介します。継承階層が過去のコードのスケーラビリティを妨げていたため、継承ではなくコンポジションを使用したいとしましょう。私はコードをリファクタリングするかもしれませんが、スーパークラス/ベースクラスがサブクラスの機能を呼び出さなければならない状況がいくつかあります。
次善のアプローチは、半分のデリゲート/オブザーバーパターンと半分の構成パターンを実装して、スーパークラスが動作をデリゲートできるようにするか、サブクラスがスーパークラスのイベントを監視できるようにすることです。その場合、クラスを拡張する方法が不明確であるため、クラスの拡張性と保守性が低下します。また、既存のリスナー/デリゲートを拡張するのも難しいです。また、スーパークラスを拡張する方法を確認するために実装を知る必要があるため、情報は十分に隠されていません(コメントを非常に広範囲に使用しない限り)。
したがって、この後は、オブザーバーまたはデリゲートを完全に使用して、アプローチを過度に混同することに伴う欠点を回避することを選択する場合があります。ただし、これには独自の問題があります。たとえば、事実上すべての行動にオブザーバー/デリゲートが必要になるまで、行動の量を増やすためにオブザーバーまたはデリゲートが必要になることに気付く場合があります。 1つのオプションは、すべての動作に対して1つの大きなリスナー/デリゲートを持つことですが、実装するクラスは、多くの空のメソッドなどで終了します。
それから私は別のアプローチを試すかもしれませんが、それと同じくらい多くの問題があります。それから次のもの、そして次のものなど。
各アプローチが他のアプローチと同じくらい多くの問題を抱えているように思われ、一種の設計決定麻痺につながる場合、この反復プロセスは非常に困難になります。また、使用する設計パターンやアプローチに関係なく、コードが同じように問題を抱えてしまうことを受け入れるのも困難です。このような状況になった場合、問題自体を再検討する必要があるということですか。彼らがこの状況に遭遇したとき、他の人は何をしますか?
編集:明確にしたい質問の解釈がいくつかあるようです。
そのような難しい決断をしたとき、私はたいてい3つの質問をします。
all利用可能なソリューションの長所と短所は何ですか?
解決策はありますか、まだ検討していませんか?
最も重要なこと:
正確には私の要件は何ですか?紙の要件ではなく、本当の基本的な要件ですか?
問題を何らかの形で再定式化できますか/その要件を微調整して、シンプルで簡単な解決策を可能にしますか?
データをいくつかの異なる方法で表すことができるので、そのような単純で直接的なソリューションが可能になりますか?
これは、知覚された問題の基本についての質問です。あなたが実際に間違った問題を解決しようとしていたことが判明するかもしれません。問題には、一般的な場合よりもはるかに簡単な解決策を可能にする特定の要件がある場合があります。 問題の定式化についての質問!
続行する前に、3つすべての質問について一生懸命考えることは非常に重要だと思います。散歩をしたり、オフィスのペースを調整したりして、これらの質問への回答を検討するために必要なことは何でもしてください。ただし、これらの質問への回答に不適切な時間がかかることはありません。適切な時間は、15分から1週間程度の範囲であり、すでに見つけたソリューションの問題とその全体への影響に依存します。
このアプローチの価値は、驚くほど良い解決策が見つかることがあることです。エレガントなソリューション。これらの3つの質問に答えるために費やした時間に見合う価値のあるソリューション。そして、すぐに次の反復に入るだけでは、これらのソリューションは見つかりません。
もちろん、良い解決策が存在しないように見えることもあります。その場合、あなたは質問1への答えに行き詰まっており、善は単に最悪です。この場合、時間をかけてこれらの質問に答えることの価値は、失敗するはずの反復を回避できる可能性があることです。適切な時間内にコーディングに戻るようにしてください。
まず最初に-パターンは有用な抽象化であり、デザインのすべてではなく、OOデザインです。
第二に-現代OOは、すべてがオブジェクトではないことを知るのに十分賢いです。時々、プレーンな古い関数、またはいくつかの命令スタイルスクリプトを使用することで、特定の問題に対するより良い解決策が得られます。
さて、物事の肉に:
これは、各アプローチが他のアプローチと同じくらい多くの問題を抱えているように見える場合、非常に難しくなります。
どうして?同様のオプションがたくさんあると、あなたの決定はより簡単になるはずです! 「間違った」オプションを選択しても、多くを失うことはありません。そして、実際には、コードは修正されていません。何かを試して、それが良いかどうかを確認してください。 反復。
また、使用するデザインパターンやアプローチに関係なく、コードに問題が発生することを受け入れるのも困難です。
タフなナッツ。難しい問題-実際の数学的に難しい問題はただ難しいです。おそらく難しい。彼らにとって文字通り良い解決策はありません。結局のところ、簡単な問題はあまり価値がありません。
しかし、注意してください。特定の方法で問題を見て立ち往生しているため、または当面の問題に対して不自然な方法で責任を削減しているために、良い選択肢がないことに不満を感じる人がよくいます。 「良い選択肢がない」というのは、あなたのアプローチに根本的に何か問題があるという匂いがすることがあります。
これにより、「クリーン」なコードがオプションでなくなる場合があるため、モチベーションが失われます。
完璧は善の敵です。何かを動作させ、それをリファクタリングします。
この問題を最小限に抑えるのに役立つ設計方法はありますか?このような状況で何をすべきかについて認められた実践はありますか?
すでに述べたように、反復的な開発は一般にこの問題を最小限に抑えます。何かが機能するようになったら、問題をよりよく理解して、問題を解決するためにより良い立場に立つことができます。見て評価する実際のコードがあり、正しくないように見える抽象デザインではありません。
あなたが説明している状況は、ボトムアップアプローチのように見えます。葉を取り、それを修正して、それが別のブランチにも接続されているブランチに接続されていることを確認します。
タイヤから車を作るようなものです。
必要なのは、一歩下がって、全体像を見ることです。その葉はデザイン全体にどのように配置されていますか?それはまだ最新で正しいですか?
最初から設計して実装する場合、モジュールはどのように見えますか?現在の実装は、その「理想」からどれだけ離れているか。
そうすることで、何に取り組むべきかについての全体像を把握できます。 (または、作業量が多すぎると判断した場合、問題は何ですか)。
例では、レガシーコードの大きな部分で通常発生する状況、および「大きすぎる」リファクタリングを作成しようとしている状況について説明します。
この状況について私があなたに与えることができる最良のアドバイスは:
1つの「ビッグバン」で主な目標を達成しようとしないでください、
小さなステップでコードを改善する方法を学びましょう!
もちろん、書くのは書くより簡単なので、実際にこれを実現する方法は?まあ、あなたは練習と経験を必要とします、それはケースに非常に依存します、そして、すべてのケースにふさわしい「これまたはそれをしてください」と言う厳格な規則はありませんしかし、架空の例を使用してみましょう。 「組成より継承」は、全か無かの設計目標ではなく、いくつかの小さなステップで達成するのに最適な候補です。
「継承より継承」がケースに適したツールであることに気付いたとしましょう。これが賢明な目標であることを示す兆候がなければなりません。そうでなければ、これを選択しなかったでしょう。したがって、サブクラスから単に「呼び出された」スーパークラスには多くの機能があると想定しましょう。そのため、この機能は、そのスーパークラスにとどまらない候補です。
サブクラスからスーパークラスをすぐに削除できないことに気付いた場合は、まず、スーパークラスを上記の機能をカプセル化する小さなコンポーネントにリファクタリングすることから始めます。ぶらさがりの少ない果物から始めて、いくつかの単純なコンポーネントを最初に抽出します。これにより、スーパークラスの複雑さが緩和されます。スーパークラスが小さくなるほど、追加のリファクタリングが容易になります。これらのコンポーネントは、サブクラスおよびスーパークラスから使用します。
運が良ければ、スーパークラスの残りのコードはこのプロセス全体で非常に単純になるため、それ以上問題なくサブクラスからスーパークラスを削除できます。または、継承なしで再利用したいコンポーネントに十分なコードを既に抽出しているため、スーパークラスを保持することはもはや問題ではないことに気付くでしょう。
どこから始めればよいかわからない場合は、リファクタリングが単純であるかどうかわからないため、場合によっては scratch refactoring を実行するのが最善の方法です。
もちろん、実際の状況はさらに複雑になる可能性があります。だから、学び、経験を集め、忍耐強く、これを正しく行うには何年もかかります。ここでお勧めできる本が2冊あります。役立つと思います。
リファクタリング by Fowler:非常に小さいリファクタリングの完全なカタログについて説明します。
レガシーコードを効果的に使用する フェザーによる:不適切に設計されたコードの大きなチャンクを処理し、小さなステップでテストしやすくする方法についての優れたアドバイスを提供
時には、最高の2つの設計原則がKISS *とYAGNI **です。 「Hello world!」を印刷するだけのプログラムに、既知のすべてのデザインパターンを詰め込む必要を感じないでください。
* Keep It Simple, Stupid
** You Ain't Gonna Need It
質問の更新後に編集します(そして、Pieter Bの発言をある程度反映します):
時々、それを実装しようとするときにあらゆる種類の醜さをもたらす特定の設計に導く早い段階で、アーキテクチャの決定を下すことがあります。残念ながら、その時点での「適切な」解決策は、一歩下がって、その位置にたどり着いた方法を理解することです。まだ回答が表示されない場合は、表示されるまで後戻りしてください。
しかし、それを行うための作業が不均衡になる場合は、問題に対して最も醜い回避策を思い付くには実際的な決定が必要です。
私がこの状況にあるとき、私が最初にすることは停止です。私は別の問題に切り替えて、しばらくその問題に取り組みます。たぶん1時間、多分1日、多分もっと。それは必ずしも選択肢ではありませんが、私の潜在意識は、私の意識的な脳がより生産的な何かをする間、物事に取り組みます。やがて、新鮮な目で戻ってやり直します。
私がするもう1つのことは、私より賢い人に尋ねることです。これは、Stack Exchangeで質問したり、トピックに関するWeb上の記事を読んだり、この分野での経験が豊富な同僚に質問したりするという形をとります。多くの場合、私が正しいアプローチだと思う方法は、私がやろうとしていることに対して完全に間違っていることが判明しています。問題の一部の特徴を誤解しましたが、実際には、パターンと一致しません。それが起こったとき、誰かが「ほら、これはもっと似ている…」と言わせることは大きな助けになるでしょう。
上記に関連して、告白による設計またはデバッグがあります。あなたは同僚のところに行き、「私が抱えている問題について説明し、次に私が持っている解決策を説明します。それぞれのアプローチで問題を指摘し、他のアプローチを提案します」私が説明しているように、他の人が話す前に、私が最初に思ったよりも、他の人と同じように見えたパスが実際にははるかに良いまたは悪いことに気づき始めます。結果として生じる会話は、その認識を強めるか、私が考えていなかった新しいことを指摘するかもしれません。
TL; DR:休憩をとって、無理に押し込まず、助けを求めてください。