21

この質問が主観的で申し訳ありませんが、少し行き詰まっており、以前にこの問題に対処しなければならなかった人からのガイダンスとアドバイスをいただければ幸いです。

C# 2.0 で記述された非常に大規模な RESTful API プロジェクトがあり (その後どうなったか)、私のクラスのいくつかは巨大なものになりました。私のメイン API クラスはこの例です。数十のメンバーとメソッド (おそらく数百に近い) があります。ご想像のとおり、このコードを維持するだけでなく、コードをナビゲートするだけでも雑用になり、小さな悪夢になりつつあります。

私は SOLID の原則についてかなりの初心者であり、デザイン パターンの大ファンです (しかし、私はまだそれらを実装できる段階にありますが、それらをいつ使用するかを知るには十分ではありません - それほど明白ではない状況で) .

クラスのサイズを分割する必要がありますが、それを行う最善の方法がわかりません。私の仲間の StackOverflow'ers は、既存のコード モノリスを利用してサイズを縮小する方法を提案できますか?

4

5 に答える 5

29

単一責任の原則- クラスを変更する理由は 1 つだけにする必要があります。モノリシックなクラスがある場合は、変更する理由が複数ある可能性があります。変更する理由を 1 つ定義するだけで、合理的な範囲で細かく設定できます。「大」から始めることをお勧めします。コードの 3 分の 1 を別のクラスにリファクタリングします。それができたら、新しいクラスからやり直してください。1 つのクラスから 20 のクラスに直接移行するのは非常に困難です。

オープン/クローズの原則- クラスは、拡張に対してオープンである必要がありますが、変更に対してはクローズされている必要があります。妥当な場合は、メンバーとメソッドを仮想または抽象としてマークします。各項目は比較的小さく、基本的な機能や動作の定義を提供する必要があります。ただし、後で機能を変更する必要がある場合は、コードを変更して新しい機能や別の機能を導入するのではなく、コードを追加できます。

Liskov Substitution Principle - クラスは、その基底クラスを置換可能でなければなりません。ここでの鍵は、私の意見では、継承を正しく行うことです。膨大な case ステートメント、またはオブジェクトの派生型をチェックする 2 ページの if ステートメントがある場合は、この原則に違反しており、アプローチを再考する必要があります。

インターフェイス分離の原則- 私の考えでは、この原則は単一責任の原則によく似ています。高レベル(または成熟した)クラス/インターフェースに特に適用されます。大規模なクラスでこの原則を使用する 1 つの方法は、クラスに空のインターフェイスを実装させることです。次に、クラスを使用するすべての型をインターフェイスの型に変更します。これにより、コードが壊れます。ただし、クラスをどのように消費しているかを正確に指摘します。それぞれ独自のメソッドとプロパティのサブセットを使用する 3 つのインスタンスがある場合、3 つの異なるインターフェイスが必要であることがわかります。各インターフェイスは、集合的な機能セットを表し、変更する理由の 1 つです。

依存性逆転の原則- 親と子の比喩は、私にこれを理解させました。親クラスを考えてみましょう。動作を定義しますが、汚い詳細には関係ありません。頼もしいです。ただし、子クラスは詳細がすべてであり、頻繁に変更されるため依存できません。あなたは常に親、責任あるクラスに依存したいと考えており、決してその逆ではありません。子クラスに依存する親クラスがある場合、何かを変更すると予期しない動作が発生します。私の考えでは、これは SOA の考え方と同じです。サービス コントラクトは、入力、出力、および動作を定義しますが、詳細はありません。

もちろん、私の意見や理解は不完全または間違っている可能性があります。ボブおじさんのように、これらの原則を習得した人から学ぶことをお勧めします。私にとって良い出発点は、彼の著書Agile Principles, Patterns, and Practices in C#でした。もう 1 つの優れたリソースは、Hanselminutes の Uncle Bob です

もちろん、Joel と Jeff が指摘したように、これらは原則であって規則ではありません。それらはあなた方を導くためのツールであって、その土地の法律ではありません。

編集:

本当に興味深いこれらのSOLID スクリーンキャストを見つけました。1回につき約10~15分です。

于 2009-04-23T23:52:21.797 に答える
4

Martin Fowlerによる古典的な本、Refactoring: Improving the Design of Existing Code があります。

そこで彼は、一連の設計テクニックと、既存のコードベースをより管理しやすく保守しやすいものにするための決定の例を提供します (そして、SOLID プリンシパルとは何か)。リファクタリングにはいくつかの標準的なルーチンがありますが、それは非常にカスタムなプロセスであり、1 つのソリューションをすべてのプロジェクトに適用することはできませんでした。

単体テストは、このプロセスを成功させるための重要な柱の 1 つです。既存のコードベースを十分なコード カバレッジでカバーして、変更中に何かを壊さないようにする必要があります。モッキングをサポートする最新の単体テスト フレームワークを実際に使用することで、より優れた設計を行うことができます。

退屈なコード変更を支援する ReSharper (私のお気に入り) や CodeRush などのツールがあります。しかし、それらは通常、取るに足らない機械的なものであり、設計上の意思決定ははるかに複雑なプロセスであり、サポートされるツールはそれほど多くありません。クラス図と UML を使用すると役立ちます。実際、それから始めます。すでにそこにあるものを理解し、そこに何らかの構造をもたらすようにしてください。そこから、さまざまなコンポーネント間の分解と関係について決定を下し、それに応じてコードを変更できます。

これがお役に立てば幸いです。リファクタリングをお楽しみください。

于 2009-04-24T00:01:55.643 に答える
3

これは時間のかかるプロセスになります。コードを読んで、SOLID の原則を満たしていない部分を特定し、新しいクラスにリファクタリングする必要があります。Resharper ( http://www.jetbrains.com ) のような VS アドインを使用すると、リファクタリング プロセスに役立ちます。

理想的には、自動化された単体テストを適切にカバーして、変更によってコードに問題が発生しないようにすることができます。

詳しくは

メイン API クラスでは、互いに関連するメソッドを識別し、メソッドが実行するアクションをより具体的に表すクラスを作成する必要があります。

例えば

番地、名前などを含む個別の変数を持つ Address クラスがあるとします。このクラスは、挿入、更新、削除などを担当します。住所を特定の方法で書式設定する必要がある場合は、書式設定された住所を返す GetFormattedPostalAddress() というメソッド。

または、このメソッドを AddressFormatter というクラスにリファクタリングすることもできます。このクラスは、コンストラクターで Address を受け取り、書式設定された住所を返す PostalAddress という Get プロパティを持ちます。

アイデアは、異なる責任を別々のクラスに分けることです。

于 2009-04-23T23:33:29.597 に答える
2

この種のことを提示されたときに私が行ったことは (これまで SOLID の原則を使用したことがないことはすぐに認めますが、私が知っている限りでは、それらは良さそうです)、既存のコードベースを次のように調べます。コネクティビティの観点。基本的に、システムを調べると、内部的に高度に結合されている (多くの頻繁な相互作用) が、外部的に疎結合されている (まれな相互作用はほとんどない) 機能のサブセットを見つけることができるはずです。通常、大規模なコードベースにはこれらの部分がいくつかあります。彼らは切除の候補です。基本的に、候補を特定したら、それらがシステム全体に外部的に結合されているポイントを列挙する必要があります。これにより、関連する相互依存のレベルがよくわかります。通常、かなりの相互依存関係が関係しています。リファクタリングのためにサブセットとその接続ポイントを評価します。多くの場合 (常にではありません)、デカップリングを促進できるいくつかの明確な構造的リファクタリングが行われることになります。これらのリファクタリングに注意して、既存のカップリングを使用して、サブシステムがシステムの残りの部分と連携できるようにするために必要な最小限のインターフェイスを定義します。これらのインターフェースの共通点を探してください (多くの場合、予想以上の共通点が見つかります!)。最後に、特定したこれらの変更を実装します。既存のカップリングを使用して、サブシステムがシステムの残りの部分と連携できるようにするために必要な最小限のインターフェイスを定義します。これらのインターフェースの共通点を探してください (多くの場合、予想以上の共通点が見つかります!)。最後に、特定したこれらの変更を実装します。既存のカップリングを使用して、サブシステムがシステムの残りの部分と連携できるようにするために必要な最小限のインターフェイスを定義します。これらのインターフェースの共通点を探してください (多くの場合、予想以上の共通点が見つかります!)。最後に、特定したこれらの変更を実装します。

このプロセスはひどいように聞こえますが、実際には非常に簡単です。これは、完全に完全に設計されたシステムに到達するためのロードマップではありません (そのためには、ゼロから始める必要があります)。

于 2009-04-23T23:48:48.343 に答える