ソリッドと早すぎる抽象化の回避


27

SOLIDが何を達成することになっているのかを理解し、モジュール性が重要であり、その目標が明らかに役立つ状況で定期的に使用します。ただし、次の2つの理由により、コードベース全体に一貫して適用できません。

  • 時期尚早な抽象化を避けたい。私の経験では、具体的なユースケース(現在または近い将来に存在する種類)なしで抽象化ラインを描画すると、間違った場所に描画されます。このようなコードを変更しようとすると、抽象化ラインが助けになるのではなく邪魔になります。したがって、どこに有用なのかがよくわかるまで、抽象化ラインを描画しないという側面を誤解する傾向があります。

  • 私のコードをより冗長にし、理解しにくくするなど、重複を排除しない場合、モジュール性を高めることを正当化するのは難しいと思います。フローが単純で線形であるため、非常に適切にファクタリングされたラビオリコードよりも、シンプルで密結合された手続き型またはGodオブジェクトコードの方が理解しやすい場合があります。書くのもずっと簡単です。

一方、この考え方はしばしば神の対象につながります。私は通常、これらを控えめにリファクタリングし、明確なパターンが出現する場合にのみ明確な抽象化ラインを追加します。明らかにモジュール性を必要とせず、大幅な重複がなく、コードが読み取り可能な場合、Godオブジェクトと密結合コードで何か問題があるとしたらどうでしょうか。

編集:個々のSOLID原則に関しては、Liskov Substitutionは常識の形式化であり、どこにでも適用されるべきであると強調するつもりでした。また、すべてのクラスは、何らかの抽象化レベルで単一の責任を負う必要がありますが、実装の詳細がすべて1つの巨大な2,000行クラスに詰め込まれた非常に高いレベルかもしれません。基本的に、抽象化は、抽象化を選択した場所で意味をなす必要があります。モジュール性が明確に役に立たない場合に私が疑問に思う原則は、オープンクローズ、インターフェース分離、特に依存関係の逆転です。これらは抽象化が意味をなさないだけでなく、モジュール性に関するものだからです。


10
@ S.Lott:キーワードは「もっと」です。これはまさにYAGNIが考案したものです。モジュール性を高めること、または単にそれ自体のために結合を減らすことは、単なる貨物カルトプログラミングです。
メイソンウィーラー

3
@ S.Lott:それは文脈から明らかです。少なくとも、「問題のコードには現在以上のものがあります」と読みました。
メイソンウィーラー

3
@ S.Lott:あなたがyou慢であると主張する場合、「もっと」とは、現在存在している以上のものを指します。その意味は、コードであれ大まかな設計であれ、何かがすでに存在し、それをリファクタリングする方法を考えているということです。
dsimcha

5
@ back2dos:そうですね。しかし、拡張の可能性について明確な考えがなく、拡張性のために何かを設計することに懐疑的です。この情報がなければ、抽象化ラインはおそらくすべて間違った場所に行き着くでしょう。抽象化ラインがどこに役立つかわからない場合は、いくつかの神オブジェクトがそこに投げ込まれている場合でも、機能し、読みやすい最も簡潔なコードを書くことをお勧めします。
-dsimcha

2
@dsimcha:コードを書いた後に要件が変わっても幸運です。通常、それはあなたがそれをしているに変わるからです。これが、スナップショット実装が実際のソフトウェアライフサイクルを生き残るのに適していない理由です。また、SOLIDは盲目的に抽象化することを要求しません。抽象化のコンテキストは、依存関係の反転によって定義されます。すべてのモジュールの依存関係を、それが依存する他のサービスの最も単純で明確な抽象化に減らします。それはarbitrary意的、人工的、または複雑ではありませんが、明確に定義され、もっともらしくシンプルです。
back2dos

回答:


8

以下は、システム設計のバランスをとる方法の理解を支援するために適用できる簡単な原則です。

  • 単一責任の原則:Sソリッド。メソッドの数またはデータの量の点で非常に大きなオブジェクトを持つことができ、それでもこの原則を維持できます。たとえば、Ruby Stringオブジェクトを取り上げます。このオブジェクトには、棒を振ることができる以上のメソッドがありますが、それでも1つの責任しかありません。それは、アプリケーションのテキスト文字列を保持することです。オブジェクトが新しい責任を負い始めるとすぐに、それについて真剣に考え始めます。メンテナンスの問題は、「後で問題が発生した場合、このコードはどこで見つかると思いますか?」
  • シンプルさの重要性:アルバートアインシュタインは、「すべてをできるだけシンプルにするが、シンプルではない」と述べました。その新しい方法が本当に必要ですか?既存の機能で必要なことを達成できますか?新しいメソッドが必要だと本当に思う場合、既存のメソッドを変更して、必要なものすべてを満たすことができますか?本質的に、新しいものを作成するときにリファクタリングします。

本質的には、ソフトウェアをメンテナンスするときが来たときに自分の足で撃たないようにできることをしようとしています。大きなオブジェクトが合理的な抽象化である場合、クラスをX行/メソッド/プロパティ/などとすべきではないというメトリックを誰かが思いついたからといって、それらを分割する理由はほとんどありません。どちらかといえば、それらはガイドラインであり、厳格なルールではありません。


3
良いコメント。他の回答で説明したように、「単一の責任」の1つの問題は、クラスの責任をさまざまな抽象化レベルで記述できることです。
dsimcha

5

ほとんどの場合、あなたはあなた自身の質問に答えたと思います。SOLIDは、概念を抽象レベルの上位に移動する必要がある場合に、コードをリファクタリングする方法に関する一連のガイドラインです。

他のすべての創造的な分野と同様に、絶対的なものはありません-ただトレードオフ。あなたがそれをやればやるほど、あなたの現在の問題の領域や要件に十分であるかどうかを判断するのが「簡単」になります。

そうは言っても、抽象化はソフトウェア開発の心臓部です。したがって、それを行わない正当な理由がなければ、実践することでそれを上手にでき、トレードオフに対する直感を感じることができます。有害でない限り、それを支持します。


5
I want to avoid premature abstraction.In my experience drawing abstraction lines without concrete use cases... 

これは部分的に正しく、部分的に間違っています。

間違っている
これは、OO原則/ SOLIDの適用を妨げる理由ではありません。それらの適用が早すぎることを防ぐだけです。

それは...

Right
リファクタリング可能になるまでコードをリファクタリングしないでください。要件が完了したとき。または、あなたが言うように「ユースケース」がすべてあるとき。

I find simple, tightly coupled procedural or God object code is sometimes easier to understand than very well-factored...

単独でまたはサイロで作業する場合
オブジェクト指向を行わないことの問題は、すぐには明らかになりません。プログラムの最初のバージョンを作成した後:

Code is fresh in your head
Code is familiar
Code is easy to mentally compartmentalize
You wrote it

これらの良いものの3/4は、短時間ですぐに死にます。

最後に、Godオブジェクト(多くの機能を持つオブジェクト)があることを認識するため、個々の機能を認識します。カプセル化します。それらをフォルダに入れます。時々インターフェイスを使用してください。長期的なメンテナンスにご協力ください。特に、リファクタリングされていないメソッドは無限に膨張し、神のメソッドになる傾向があるためです。

チーム内
OOを行わないことに関する問題は、すぐに目立ちます。優れたオブジェクト指向コードは、少なくともある程度自己文書化され、読み取り可能です。特に、適切なグリーンコードと組み合わせる場合。また、構造の欠如により、タスクと統合をより複雑に分離することが難しくなります。


右。私のオブジェクトは複数のことを行うことを漠然と認識していますが、メンテナンスを行う必要があるときに抽象化ラインが適切な場所にあるように、それらを便利に分割する方法をより具体的に認識していません。メンテナンスプログラマが抽象化ラインを適切な場所に配置するために、おそらくさらに多くのリファクタリングを行う必要がある場合、リファクタリングは時間の無駄のようです。
-dsimcha

カプセル化と抽象化は2つの異なる概念です。カプセル化は基本的なオブジェクト指向の概念であり、基本的にはクラスがあることを意味します。クラスは、それ自体でモジュール性と読みやすさを提供します。それは便利です。
P.Brian.Mackey

適切に適用すると、抽象化によりメンテナンスを減らすことができます。不適切に適用すると、メンテナンスが増加する可能性があります。いつどこで線を引くかを知るには、ビジネス、フレームワーク、顧客、および「工場パターンを適用する」方法自体の基本的な技術的側面をしっかりと理解する必要があります。
P.Brian.Mackey

3

大きいオブジェクトと密結合されたコードが適切であり、より優れた設計が必要な場合は、何も問題はありません。これは、経験則が教義に変わることの単なる別の現れです

疎結合と小さく単純なオブジェクトは、多くの一般的なケースで特定の利点を提供する傾向があるため、一般にそれらを使用することをお勧めします。問題は、原則の背後にある理論的根拠を盲目的に適用しようとしても、適用しない場合でもその原理を理解していない人々にあります。


1
+1。「適切な」とはどういう意味かを適切に定義する必要がある。
jprete

2
@jprete:なぜですか?絶対的な定義を適用しようとすることは、このような概念的な混乱の原因の一部です。優れたソフトウェアの構築には、多くの職人技が関係しています。IMOに本当に必要なのは、経験と判断力です。
メイソンウィーラー

4
まあ、はい、しかし広義のいくつかの並べ替えはまだ有効です。
jprete

1
適切な定義があると便利ですが、それがポイントです。サウンドバイトに当てはめることは困難です。なぜなら、それはケースバイケースでしか適用できないからです。各ケースで適切な選択をするかどうかは、主に経験にかかっています。なぜあなたはあなたがあなたの高い凝集力、モノリシックな解決策が低凝集力、高度にモジュール化された解決策よりも優れているかを明確にできないなら、あなたは「おそらく」低凝集力とモジュール化の方が良いでしょう。リファクタリングは常に簡単です。
イアン

1
私は、誰かがスパゲッティコードを盲目的に書くのではなく、盲目的に分離したコードを書くことを好みます:)
ボリスヤンコフ

2

You Ai n't Gonna Need Itの観点からこれにもっとアプローチする傾向があります。この投稿では、特に継承という1つの項目に焦点を当てます。

最初の設計では、2つ以上の階層があることを知っています。多くの場合同じコードが必要になるため、最初から設計する価値があります。最初のコードを配置し、さらに機能/機能を追加する必要がある場合、自分が持っているものを見て、「同じまたは類似の機能を既に実装しているものはありますか?」もしそうなら、それはおそらく、リリースされることを懇願する新しい抽象化です。

これの良い例は、MVCフレームワークです。ゼロから始めて、コードビハインドを持つ1つの大きなページを作成します。ただし、別のページを追加する必要があります。ただし、背後にある1つのコードは、新しいページに必要な多くのロジックを既に実装しています。したがって、そのページに固有Controllerのロジックを実装するクラス/インターフェースを抽象化し、一般的なものを元の「ゴッド」コードビハインド残します


1

コードの将来のために原則を適用しない場合を開発者として知っている場合は、それで良いでしょう。

SOLIDがあなたの心の奥に残っていて、あなたが述べた値が低下し始めている場合、複雑さを避け、コードの品質を改善するために物事を抽象化する必要があるときを知ることを願っています。

さらに重要なことは、開発者の大部分が日中の仕事をしていて、あなたほど気にかけないことを考慮してください。長時間実行するメソッドを最初に設定した場合、コードにアクセスする他の開発者は、それを維持または拡張するためにどのようなことを考えますか?...神のオブジェクト、および彼らは成長するコードをキャッチし、それを正しくカプセル化することができることを念頭に置いていない。あなたは「判読可能な」コードを腐敗の混乱にしています。

ですから、悪い開発者がそれをやろうと主張するかもしれませんが、少なくとも物事が最初からシンプルでうまく組織化されていれば、より良い利点があります。

グローバルな「レジストリマップ」や「神オブジェクト」は単純なものであれば特に気にしません。それは本当にシステム設計に依存します。時にはそれで済ませることができますが、シンプルなままである場合もあります。

また、大きな機能とゴッドオブジェクトはテストが非常に難しいことを忘れないでください。アジャイルのままで、コードをリファクタリングおよび変更するときに「安全」に感じたい場合は、テストが必要です。関数またはオブジェクトが多くのことを行う場合、テストを書くのは困難です。


1
基本的には良い答えです。私の唯一の不満は、メンテナンス開発者が来て混乱する場合、そのような変更がそれらの計画を正当化するのに十分に先見の明があるか、コードがあまり文書化/テストされていないなどの場合を除き、リファクタリングしないことの彼/彼女のせいです。そのリファクタリングは一種の狂気だったでしょう。
-dsimcha

それが本当のdsimchaです。開発者が「RequestHandlers」というフォルダーを作成し、そのフォルダーの下の各クラスがリクエストハンドラーであるシステムで、次の開発者が「新しいリクエストハンドラーを配置する必要がある」と考えただけです。現在のインフラストラクチャは、彼を慣例に従わせるために彼を強制的にルートに追い込みます。これは私が言おうとしていたことだと思います。最初から明確な規則を設定することで、最も経験の浅い開発者でもパターンを継続することができます。
マーティンブロア

1

神のオブジェクトの問題は、通常、それを有益に断片化できることです。定義により、それは複数のことを行います。それをより小さなクラスに分割することの全体的な目的は、1つのことをうまく行うクラスを持つことができるようにすることです(また、「1つのこと」の定義を広げるべきではありません)。これは、どのクラスでも、行うべき1つのことを理解したら、それをかなり簡単に読み、その1つのことを正しく行っているかどうかを言うことができることを意味します。

私はそこだと思うあるようにあまりにも多くのモジュール性を持つようなもの、とあまりにも多くの柔軟性が、それより多くのあなたも、顧客の欲求を知らないという要件のためにあなたがしている会計オーバーデザインやオーバーエンジニアリング問題、のです。多くのデザインアイデアは、コードの実行方法の変更を簡単に制御できるように方向付けられていますが、誰も変更を期待していない場合、柔軟性を含めることは無意味です。


「複数のことを実行」は、作業している抽象化のレベルによっては、定義が難しい場合があります。2行以上のコードを持つメソッドは、複数のことを実行していると言えます。スペクトルの反対側では、スクリプトエンジンのような複雑なものには、互いに直接関係のない個別の「もの」をすべて実行するが、それぞれが「メタこれは、スクリプトを適切に実行するためのものであり、スクリプトエンジンを壊さずに行うことができるのは、非常に多くの分解だけです。
メイソンウィーラー

そこ概念レベルのスペクトルは確かですが、私たちがすることができ、それ上の点を選ぶ:「一つのことをするクラス」のサイズは、ほとんどすぐに実装を理解できるようにすべきです。つまり、実装の詳細は頭の中に収まります。それが大きくなると、クラス全体を一度に理解することはできません。小さい場合は、抽象化しすぎています。しかし、別のコメントで述べたように、多くの判断と経験が関係しています。
-jprete

私見で考えるのに最適な抽象化レベルは、オブジェクトの消費者が使用することを期待するレベルです。Queryerデータベースクエリを最適化し、RDBMSをクエリし、結果を解析して返すオブジェクトと呼ばれるオブジェクトがあるとします。アプリでこれを行うQueryer方法が1つだけで、この1つの方法で密閉されてカプセル化されている場合、それは1つのことだけを行います。これを行う方法が複数あり、誰かがその一部の詳細に関心がある場合、複数のことを行うため、分割することもできます。
-dsimcha

1

私はより単純なガイドラインを使用します。単体テストを記述でき、コードの重複がない場合は、十分に抽象的です。さらに、後でリファクタリングするのに適した位置にいます。

それ以外にも、SOLIDを念頭に置く必要がありますが、それはルールというよりもガイドラインとしてです。


0

あなたが明らかにより多くのモジュール性を必要とせず、重要な重複がなく、コードが読める場合、Godオブジェクトと密結合コードで何かが間違っているのは何ですか?

アプリケーションが十分に小さい場合は、何でも保守可能です。大規模なアプリケーションでは、神のオブジェクトがすぐに問題になります。最終的に新しい機能を実装するには、17個のGodオブジェクトを変更する必要があります。17ステップの手順を実行しても、人々はあまりうまくいきません。神のオブジェクトは常に複数の開発者によって変更されており、それらの変更は繰り返しマージする必要があります。あなたはそこに行きたくありません。


0

過度かつ不適切な抽象化についてのあなたの懸念を共有しますが、私は必ずしも時期尚早な抽象化についてそれほど心配しているわけではありません。

それはおそらく矛盾のように聞こえますが、抽象化を使用しても、必要に応じてリファクタリングする意思があり、可能な限り早すぎると、問題を引き起こす可能性は低くなります。

これが意味することは、コードのある程度のプロトタイピング、実験、およびバックトラッキングです。

とはいえ、すべての問題を解決するために従うべき単純な決定論的なルールはありません。経験は非常に重要ですが、間違いを犯して経験を積まなければなりません。そして、以前の間違いがあなたを準備していなかったことをするために、常により多くの間違いがあります。

それでも、教科書の原則を出発点として扱い、多くのプログラミングを学び、それらの原則がどのように機能するかを見てください。SOLIDのようなものよりも優れた、より正確で信頼性の高いガイドラインを提供することができた場合、誰かがおそらくそうしているでしょう。 。

また、プログラムの設計とコーディングに明確で決定的なアルゴリズムを提供できる人がいれば、人間が書く最後のプログラムは1つだけです。これは、人間の介入なしにすべての将来のプログラムを自動的に書くプログラムです。


0

適切な抽象化線を引くことは、経験から得たものです。間違いを犯すことは否定できません。

SOLIDは、この経験を苦労して得た人々によってあなたに手渡される、その経験の集中的な形です。抽象化が必要になる前に、抽象化の作成を控えると言うとき、あなたはそれをほとんど釘付けにしました。さて、SOLIDが提供する経験は、あなたが決して新しいものではなかった問題解決するのに役立ちます。しかし、私を信じて...非常に現実的です。

SOLIDはモジュール性に関するものであり、モジュール性は保守性に関するものであり、保守性はソフトウェアが本番環境に到達し、クライアントがあなたが知らないバグに気づき始めたときに人道的な作業スケジュールを維持できるようにすることです。

モジュール性の最大の利点は、テスト容易性です。システムのモジュール化が進むほど、テストが容易になり、問題のより困難な側面に取り組むための強固な基盤をより迅速に構築できます。したがって、あなたの問題はこのモジュール性を要求しないかもしれませんが、より良いテストコードをより速く作成できるという事実は、モジュール性を追求するための簡単なことではありません。

アジャイルであることとは、迅速な出荷と高品質の提供との微妙なバランスをとろうとすることです。アジャイルは、私たちが角を切り取るべきだという意味ではありません。実際、私が関与した最も成功したアジャイルプロジェクトは、そのようなガイドラインに細心の注意を払ったものです。


0

見過ごされていると思われる重要な点は、ソリッドがTDDとともに実践されていることです。TDDは、特定のコンテキストで「適切な」ものを強調し、アドバイスにあると思われる多くの不公平を軽減するのに役立ちます。


1
あなたの答えを拡大することを検討してください。現状では、2つの直交する概念をまとめているため、結果がOPの懸念にどのように対処しているかは明確ではありません。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.