開発しているときに、アプリケーションに不要なクラスがたくさんあるかどうかをいつ判断できますか?クラスの数に一定の制限はありますか?
9 に答える
「クラスが多すぎる」ということは実際にはありません。問題になる可能性があるのは、「同じことを行うクラスが多すぎる」ことです。
コードベースにクラスが多すぎると感じた場合、それを監査する良い方法は、いくつかの新しい要件を追加することです。コードにいくつかの変更を加えることを強制するもの。(もちろん、ソース管理の別のブランチで。)これらの変更を行うのはどのくらい難しいですか?比較的単純な変更では、何トンものクラスを変更する必要がありますか?その場合は、数が多すぎる可能性が非常に高くなりますが、問題は数そのものではありません。
多くの場合、それは主に個人的な好みの問題です。多くの場合、コードの再利用とコードの分離の間にはトレードオフがあります。考えられるすべての懸念を分離し、多数の小さなクラスを用意することで、すべてを他のすべてから切り離します。ただし、多くのコードが「同じこと」を実行している可能性があるため、このような場合はコードを繰り返さなければならないことがよくありますが、理由は少し異なります。
一方、コード内で何も繰り返さないことを主張する場合、クラスが少なくなる一方で、単一のクラスが同様の機能を必要とするコードに対して複数の責任を持つため、多くの場合、結合が多くなります。
ほとんどの場合、肝心なのは変化への抵抗です。結合と再利用は、人々が長い間議論できることですが、ソフトウェアの硬直性は、議論が実際の努力(お金)に変わるところです。コードに変更を加えることがどれほど難しいかをテストします。次に、変更をより受け入れやすいと思われる方法でクラス/ロジックを再配置して、もう一度テストしてみてください。大幅な改善はありましたか?
一般的に、クラスがたくさんあるということは、問題を非常に一般的に解決した可能性が高いことを意味します。これは、最終的に必要になったときに動作を変更する時間が簡単になることを意味するため、通常は適切です。
小規模なプロジェクトを開発する場合、物事をより速く達成するために、より具体的(つまり一般的ではない)にする方が良い場合があります。これにより、クラスが少なくなる可能性がありますが、必要に応じて変更するのが難しい場合があります。
クラスが適切に順序付けられ、明確に定義された目的を持っている限り、多くのクラスがあることは問題ではありません。
クラスが緊密に結合されている場合、または一部のクラスの責任が明確に定義されていない場合、これまでに問題になる可能性があります。カップリングの詳細については、こちらをご覧ください。
発生する可能性のある別の問題は、以下のコメントに記載されています。多くのクラスに同様のコードがある場合、重複の問題があります。複製されたコードに変更が必要な場合は、何度も変更を加える必要があるため、これは通常、システムの保守性の低下につながります。これは通常、継承によって解決されます。
ケントベックはあなたの質問に答えます。著書「CleanCodeAHandbook of Agile Software Craftsmanship」のジェフ・ラングルは、ケント・ベックによって指定された4つの設計規則について説明しています。
(重要度の高い順に)
- すべてのテストを実行します
- 重複は含まれていません
- プログラマーの意図を表現します
- クラスとメソッドの数を最小限に抑えます
Kentは、クラスとメソッドの数を少なく保つために、実用的なアプローチを採用することを提案しています。彼は、すべてのクラスにインターフェースが必要であるなど、独断的なルールを順守する例を示しています。通常はそうですが、これが必要ない場合もあります。ただし、このルールは、シンプルなデザインの4つのルールの中で最も優先度の低いものです。
(注:これはケントベックスの意見であり、私の意見ではありません!)
私が現在取り組んでいるプロジェクトでは、間違いなく、使用しているクラスが多すぎると思います。少なくとも、オブジェクト/インスタンスが多すぎます。
PHP / MySQLに基づいてCMSを構築しました。この場合、データベース内のすべてのレコードとフィールドがPHPのオブジェクトとして表されます。これにより、同時に数万のインスタンスが発生する可能性があり、パフォーマンスの問題やメモリ不足などが継続的に発生しています。
もちろん、これは他のプログラミング言語や他の要件では問題にならないかもしれませんが、私の意見では、パフォーマンスも考慮する必要があります。
他の多くの人が示唆しているように、「それは...に依存します」
通常、それはあなたの方法、あなたの目標、そしてあなたのチームメンバーの好みと能力の組み合わせに依存します。単体テストに非常に厳しい場合は、おそらく多くの小さな一般的なクラスと依存性注入になってしまうでしょう。これはまた、非常に一般的なすべてのパーツから、個々のチームメンバーが構築している具体的な全体を確認するのが非常に難しいことを意味します。
個人的には、2つのレベルで構築されたAPIの観点から考えることを好みます。1つは一般的な独立したパーツで構成される低レベル、もう1つはファサードやディレクターなどを使用して他のコーダーに具体的で役立つものを提示する高レベルです。これは、iOSライブラリのIMHOの設計によく似ています。
哲学的:
何かが多すぎるということもあります。クラスが多すぎたり少なすぎたりする可能性があります。検索などのツールを、過度に乱雑で、ナビゲートが難しく、大きな検索スペースを作成するための言い訳として使用できるため、より多くのものが無料であると偽るのが好きな人もいます。長期的には、常に測定可能な赤字が見つかります。
クラスの数に上限があるかどうかはわかりません。クラスを無期限に、技術的に言えば、無限に追加して物事を望遠鏡で見る方法を見つけることができます。クラスの数が多すぎなくても、無限に追加できる場合は、必要なクラスの数が原因でプログラムを終了することはできません。したがって、クラスの数が多すぎる可能性があります。
多くのIDEを使用すると、多数のクラスを非常に簡単に作成し、定型文の生成、オートコンプリートなどと一緒にそれらを文字列化できます。コピーパスタは常に存在します。多くのツールは、本質的に役に立たないコードの作成コストを削減しますが、肥大化のコストをそれほど削減しません。ヘルパーがいる場合でも、肥大化していないコードは常に肥大化したコードよりも安価に機能します(税金を減らすことはできますが、排除することはできません)。気にしないとやがて問題になります。コードスキャン、ファイル内での細かい置換などがある場合でも、10倍以上は10倍以上です。これは、変更するのに10倍、失敗するのに10倍、1行に費やす労力の10分の1を意味します。
多くの人は、クラスを追加することで複雑さを軽減していると考える罠に陥ります。実際、それらは単に複雑さを解消し、関連するものから物事を遠ざけ、間接的な形で複雑さの層を追加する傾向があります。線形コードは、
不必要に非線形(これは段落が多すぎる例ですが、公平を期すために、文または単語ごとに1つの段落が多すぎる場合があります。段落が文になると、実際には2つの別個のものがなくなります。文が段落とは異なるものでなくなったとき、それはおそらく2つの多くの段落の証拠です)。
検出:
これを確認する簡単な方法は、パスA(単一ノード/ 1つの関数/クラスなど)があり、それをA-> Bに分割した場合、実際には何も得られなかった場合です。1枚の紙を取り、2枚に引き裂き、2枚の封筒に入れて、目的地に郵送しました。実際に複数のエッジを持つノードが本当に必要であることが判明した場合は、何かを得ることができます。たとえば、A-> B、A->Cになります。グラフ分析を使用して、あまりにも多くのオブジェクトをスニッフィングできます。大きな長いリンクリストまたは多数の小さなリンクリスト(またはおそらくいくつか)がある場合は、クラスが多すぎると言うことができます。すべての形式のオブジェクトの超過がそれほど簡単に検出されるわけではありません。クラスが多すぎると、あるレベルの柔軟性と、ごく一部しか使用しないモデルをサポートすることになり、メンテナンスが非常に複雑になります。これは、コードの多くが実際に実行する必要のあることに対応していないことを意味します。そのコードの目的は客観的ではなく主観的であるため、これだけでは維持が困難になります。それは恣意的である可能性もあります。
コードベースを使用して、実際に必要なクラスだけになるまでクラスの数を減らすことができます。これは、移植性(データの受け渡し)、動作の違い(メソッドの受け渡し)、および合理的な分離(永続性、表示などの主要な概念の処理)に必要なもの、および重複排除に必要なもののみを意味します。優れた設計に取り組むことができない場合、多くのプログラマーはそれを逆方向に行い、コードを記述し、必要に応じて特定の目的を果たすために必要な場合にのみコードを分割します。
測定可能ですが、クラスが多すぎるという正確な測定値や完全な兆候はありません。ヒントだけがあります。たとえば、必要なクラスの最小数と最大数の比率が大きいことはヒントです。何が大きいですか?100回は間違いなく疑わしい、10回はかなり疑わしい、5回はわずかに疑わしいと思います。これは、いくつかのパラメーターに基づいて変更される可能性があります。
奇妙な方法は、コードをgzipで圧縮することです。圧縮率が高いほど、膨満の可能性が高くなります。ただし、参照ポイントが必要なため、これは完全な手段ではありません。圧縮率を下げる特定の方法も非生産的である可能性があり、特定の数値に単純にコーディングすることは決して機能しません。
多くのクラス(またはインターフェース)があなたに仕事をさせているのか、それがあなたの最終的な目標を達成するのに本当に役立たないのか、それとも彼らが物事をスピードアップしているよりも遅くしているのかを知ることができます。ただし、これは主観的な場合があります。誰かがあまりにも多くのクラスを作った場合、それは彼らが彼らの習慣を変えなければならないことを意味します、それはコーディングへのより良いアプローチのために通常エントリー料金があることを意味します。プロジェクトの開始時には、コードの追加は通常非常に安価であるため、これを検出するのは困難です。まだそれほど依存しているものはなく、層が浅いなどです。プロジェクトが膨れ上がったり、組織が貧弱だったりするのは、少なくとも何ヶ月も、あるいは1年も経ってからでないと、コストが明らかになりません。人々が注目するのは、プロジェクトが実質的に行き詰まるまではありません。多くの人は、1年かかるものが本当に1年または6か月かかるべきかどうかを知りません。比較のポイントはめったにありません。
あなたがあなたのクラスを見れば、あなたはいくつかのことを拾うことができます。コードのどのくらいがオブジェクト指向で、どれくらいがFO(オブジェクト指向と機能指向)ですか?
機能指向とは、実際に何かを実行し、最終結果に直接寄与するコードを意味します。これはあなたの必要なコードを構成します。割り当てとキャスト以外の演算子で構成される可能性があります。通常、条件付きステートメント、分岐、データの読み取り、一連のアクションの選択、およびデータ生成、変更、IOに対するフェッチ/保存などの適切な一連のアクションの実行。
オブジェクト指向とは、クラスを使用して概念を単純に表現することを意味します。このコードは、手続き型コードを宣言型言語にほぼ変換します。ほとんどのクラスが単純に重い型のチェックや表現などを支援している場合は、多すぎる可能性があります。その兆候は、それらのクラスを縮小できる変数となる可能性のある名前の一部を持つクラスとメソッドです。これの非常に強い兆候は、これらのクラスが行っていることのほとんどがボクシングであるかどうかです。単に変数を割り当てるだけで、他のことはほとんどしません。これは実際には、過剰な構造体があり、通常、重複排除、動的コーディング、または抽象化が欠如している場合です。
明らかなケースでは、クラスがまったく使用されていない場合、そのクラスは多すぎます(デッドコードの場合)。同一であるが名前が異なるクラスのようなものも良い兆候です。
原因:
これは、物事の作成と接続を非常に簡単にするメカニズム以外にも、さまざまな要因によって促進されます(IDEに取って代わるが、IDEを破る良い習慣の回避を動的に促進するものを抽象化して実行すると、破綻する傾向があります)。私はすべてを完全に表現しようとすることでこれにとらわれてしまうことがよくありますが、OOは実際にはこれを行うのに十分な柔軟性がなく、YAGNIであることがよくあります。一般的な問題は、前述のように、通常、トップレベルの言語構造に変数を展開する抽象化の欠如です(これは、すべてをIDEに直接公開するための設計にもリンクしています)。これは、動的コーディングの欠如を裏切るだけでなく、プリプロセッサなどの使用を裏切る可能性があります。これがどのように見えるかは、基本的にすべての葉が定義された木です。クラスでは、可能な限りすべてのリーフに対してクラスを定義する必要がないようにします。極端なツリー拡張の兆候は、プリミティブのすべての可能な使用法のためのクラスがある場合です。これは、より極端なことの兆候でもあります。すべてのクラスの展開されたデカルト積。この場合、Legのクラスを取得するのではなく、CatLeg、DogLegなどのクラスを取得しますが、通常、2つの間に実際の差異はありません。一部の人々は、タイプチェックの過激主義からこれを実行して、誰かがDogLegをCatLegに置くのを防ぐことができます。これは厄介で一般的なアンチパターンです。これは、より極端なことの兆候でもあります。すべてのクラスの展開されたデカルト積。この場合、Legのクラスを取得するのではなく、CatLeg、DogLegなどのクラスを取得しますが、通常、2つの間に実際の差異はありません。一部の人々は、タイプチェックの過激主義からこれを実行して、誰かがDogLegをCatLegに置くのを防ぐことができます。これは厄介で一般的なアンチパターンです。これは、より極端なことの兆候でもあります。すべてのクラスの展開されたデカルト積。この場合、Legのクラスを取得するのではなく、CatLeg、DogLegなどのクラスを取得しますが、通常、2つの間に実際の差異はありません。一部の人々は、タイプチェックの過激主義からこれを実行して、誰かがDogLegをCatLegに置くのを防ぐことができます。これは厄介で一般的なアンチパターンです。
あまりにも多くのクラスの最大の要因の1つは、実際には状況に当てはまらない、クラウド内の標準に準拠しようとする試みです。この場合、問題に対応してプログラミングを行っているわけではありません。あなたは他の人の問題に対応してプログラミングしています。
これは、SOLIDなどで非常に一般的です。SOLIDなどの原則を理解して適用できるようにすることは非常に重要ですが、適用しない場合を知ることも重要です。
この原則は、大規模なライブラリを備えたOOP言語を教えるときによく使用されます。世界に配布したいOOPライブラリを作成している場合、考えられるすべてのユースケースで数百万の人々がいる可能性がある場合は、OOPの原則に従い、多くのことをmakeインターフェイスとクラスに分割してそれらを作成する必要があります。さまざまな方法で使用できるため、ある機能が、必要のない別の機能を引き込む可能性が高くなりません。この世界では、人々がフォークしなければならない可能性のあるライブラリを作成したくないことを考慮する必要があります。人々はそれを望んでいません。そうしないと、総所有コストが大幅に失われてしまう、再利用しているコードのメンテナーになってしまうからです。
これらの原則はまた、非常に多くのオーバーヘッドを追加します。コードベースのユーザースコープが制限されている場合、完全に制御されている場合など、分散するための「必要な方法」で実行している場合は、コードが多すぎる可能性があります。コード。配布されているコードがある場合でも、すべてのユースケースに事前に対応するには極端すぎる場合があります。必要になる可能性が最も高いものを見つけ出す必要があり、それ以外はすべてオンデマンドで変更されます。小さな図書館の場合、多くの余分な作業を行う余裕があります。大規模なコードベースの場合、オーバーヘッドがそれ自体の代償を払う可能性が最も高い場所でトレーニングする必要があります。
反例:
理想的な世界では、最小限に抑えて、差し迫ったニーズに応じてのみコーディングします。過剰が自明ではないため、この方法が優れているというバイアスがあります。欠陥があります。少なすぎる場合は、直接表示されます。これは、DRYでよく見られます。1つの関数を追加した後、別の関数を追加します。最初にコピーして貼り付けてから、下半分を変更します。2つの関数の共通の上半分は重複コードであり、重複を排除する必要があることがすぐにわかります。これを行うには、3番目の関数を作成します。あなたはそれを作成する客観的な証明可能な理由があるので、それはあまりにも多くの1つの関数ではないことを知っています。このアプローチは、他の人が使用するコードを書くときにさらに難しくなります。他の人によって、私は必ずしも誰かを意味するわけではありません。コードベースに直接アクセスできない人、通常は見知らぬ人を意味します。基本的に、必要なときにコードを簡単に/すばやく/安く分割できない人。そのようなオーディエンスに対応しない場合は、コードを途中で分割することを心配する必要はありません。
最近、クラスが少なすぎるオンラインのライブラリを使用しました。複数の責任を持つ単一のクラスが含まれていました。書き込むには(プリミティブ型として)ファイルハンドルが必要であり、呼び出されたメソッド(addImage、addTextなど)に基づいて生成されたストリームに適したHTTPヘッダーも自動的に出力されます。
理想的な世界では、このクラスは出力についての仮定を行うべきではありませんでした。ファイルシステム、メモリ、TCPストリームなどに出力したい場合があります。単純な書き込みメソッドを備えたインターフェイスを提供するか、標準ライブラリのインターフェイスを使用するだけで済みます。私の場合、文字列連結を介して出力するだけで済みましたが、これを実現するには、メモリへの疑似ファイルマッピングを開く必要がありました(通常は不可能ですが、言語ではハックとして許可されています)。
私は、すべてのソースからのランダムライブラリを使用してこの問題を何度も経験しました。場合によっては、分離をどこに適用すべきかが明らかになることもあれば、そうでないこともあります。疑わしい場合は、最終的にそれについて知ることが保証されているので、少なすぎるとまだ多すぎます。私は、あなたが何かを追加すると、あなたが本当に肥大化した領域になってしまうことについて本当に確信が持てないことに気付く傾向があります。一度やれば、たぶん二度やると、それが習慣になります。
それはすべてあなたのプロジェクトに依存します。それはあなたの要件に依存します。
クラスは最小である必要があります。不要なクラスがない
という意味で、クラスは最大である必要があります。つまり、すべてのクラスに個別に属性が含まれている必要があります。
アプリケーションはすべて1つのコードファイルに含めることも、各アトマイズされた関数を独自のファイルに含めることもできます。影響を受けるのは保守性だけです。保守性とは、コードをナビゲートする独自の能力を意味する場合もあれば、他の人がコードを理解する方法を意味する場合もあります。または、新しいリリースを構築できるかどうかを意味する場合もあります。
これに関して常に適用される一般的なガイドラインはないと思います。それは非常に多くのことに依存します。たとえば、JavaScriptでコーディングする場合、通常、C#またはC ++でコーディングする場合よりも、関連性のない機能を含むファイルの数が少なくなります(大きくなります)。
Visual Studio 2012を使用している場合は、 http://msdn.microsoft.com/en-us/library/bb385910.aspxおよびhttp://msdn.microsoft.com/en-us/library/dd264939.aspxに次の情報があります。コードメトリクスとコード分析のしくみ。
これは、私自身のアプリに基づくVisual Studio2012のCodeMetricsからのレポートの例です。値については、http://msdn.microsoft.com/en-us/library/bb385914.aspxで説明されています。
Project: <<Projectname>>
Configuration: Debug
Scope: Project
Assembly: <<Path>>
Maintainability Index: 84
Cyclomatic Complexity: 479
Depth of Inheritance: 8
Class Coupling: 189
Lines of Code: 903
どのエリアにクラスが多いかによると思います。一般的なビジネスロジックを含む静的クラスが多数ある場合、静的クラスは一般的なヘルパーメソッドにのみ使用する必要があるため、これは不適切と見なされます。静的クラスには、一般的なビジネスロジックを含めることはできません。
本質的に同じデータを保持するための異なるレイヤーに異なるクラスがある場合。DTOクラスはレイヤー間で複製されるべきではないため、これは悪いと見なされます。
ただし、要件を適切に分解してクラスを作成した場合は、実際にはクラスを多数持つのが良いと思います。