私が一緒に仕事をしている優れた開発者は、私たちが継承したいくつかのコードに機能を実装する際にいくつかの困難があったことを最近教えてくれました。彼は問題はコードに従うのが難しいことであると言いました。それから、私は製品をより深く調査し、コードパスを確認することがいかに難しいかを理解しました。
それは非常に多くのインターフェースと抽象的なレイヤーを使用しており、物事の始まりと終わりを理解することは非常に困難でした。過去のプロジェクトを調べたとき(クリーンなコードの原則に気付く前に)を考えると、主にコードナビゲーションツールが常にインターフェイスにたどり着くため、プロジェクトを回避するのが非常に困難であることがわかりました。具体的な実装を見つけたり、プラグインタイプのアーキテクチャで何かが配線されている場所を見つけるには、多くの余分な労力が必要になります。
この理由から、依存関係注入コンテナを厳格に拒否する開発者もいます。ソフトウェアのパスを非常に混乱させ、コードナビゲーションの難易度が指数関数的に増加します。
私の質問は次のとおりです。フレームワークまたはパターンがこのように多くのオーバーヘッドをもたらす場合、それは価値がありますか?実装が不十分なパターンの症状ですか?
開発者は、その抽象化がプロジェクトにもたらすものの全体像を見て、欲求不満を乗り越えられるようにすべきだと思います。しかし、通常、その全体像を見せるのは困難です。 IOCおよびTDDを使用したDIのニーズを売り込むことに失敗したことは知っています。これらの開発者にとって、これらのツールの使用はコードの可読性を過度に制限するだけです。
これは、@ kevin clineの回答に対する長いコメントです。
言語自体がこれを必ずしも引き起こしたり妨げたりするわけではありませんが、とにかくそれが言語(または少なくとも言語コミュニティ)にある程度関係しているという彼の考えには何かがあると思います。特に、さまざまな言語で同じような問題が発生する可能性がありますが、多くの場合、さまざまな言語ではかなり異なる形式になります。
たとえば、C++でこれに遭遇した場合、可能性は、抽象化が多すぎる結果ではなく、巧妙さが多すぎる結果である可能性があります。たとえば、プログラマーは、発生している(見つけられない)重大な変換を特別なイテレーターで隠しているため、ある場所から別の場所にデータをコピーしているだけのように見えるものには、実際には何の影響もない多くの副作用がありますデータのコピーを行います。面白くするために、これは、あるタイプのオブジェクトを別のタイプのオブジェクトにキャストする過程で一時オブジェクトを作成する副作用として作成される出力とインターリーブされます。
対照的に、Javaで実行すると、よく知られている「エンタープライズhelloワールド」の変種が表示される可能性が高くなります。この場合、単純な処理を行う単一の簡単なクラスではなく、抽象基本クラスが取得されます。インターフェースXを実装する具体的な派生クラス。DIフレームワークのファクトリクラスなどによって作成されます。実際の作業を行う10行のコードは、5000行のインフラストラクチャの下に埋め込まれています。
その一部は、少なくとも言語と同じくらい環境に依存します。X11やMS Windowsのようなウィンドウ環境で直接作業することは、些細な「hello world」プログラムを300行以上のほぼ解読不可能なゴミに変えることで悪名高いです。時間の経過とともに、私たちはさまざまなツールキットを開発して、それからも私たちを隔離しました-しかし、1)それらのツールキットはそれ自体が非常に重要であり、2)最終結果は依然として大きくて複雑であるだけでなく、通常は柔軟性が低い同等のテキストモードよりも(たとえば、テキストを印刷しているだけの場合でも、ファイルにリダイレクトすることはほとんど不可能です/サポートされています)。
元の質問(の少なくとも一部)に答えるために:少なくとも私がそれを見たとき、手元のタスクに不適切なパターンを単に適用することよりも、パターンの不適切な実装の問題ではありませんでした。多くの場合、避けられないほど巨大で複雑なプログラムで役立つ可能性のあるパターンを適用しようとしますが、小さな問題に適用すると、サイズと複雑さは本当に回避できたとしても、結果として巨大で複雑になります。 。
[〜#〜] yagni [〜#〜] アプローチをとっていないことが原因であることがよくあります。具体的な実装は1つしかなく、他を導入する現在の計画はありませんが、インターフェイスを通過するすべてのものは、You Ai n't Gonnaに必要な複雑さを追加する主な例です。それはおそらく異端ですが、依存関係注入の多くの使用法について私は同じように感じています。
まあ、十分な抽象化ではなく、どの部分が何をしているのかを分離できないため、コードを理解するのは困難です。
抽象化が多すぎると、抽象化は表示されますがコード自体は表示されず、実際の実行スレッドをたどることが難しくなります。
優れた抽象化を実現するには、K.I.S.S。 : これらの種類の問題を回避するために従うべきことを知るには、この質問への私の回答を参照してください 。
深い階層とネーミングを避けることが、あなたが説明するケースを見るための最も重要なポイントだと思います。抽象化に適切な名前が付けられていれば、何が起こるかを理解する必要がある抽象化レベルまで、深く掘り下げる必要はありません。ネーミングにより、このレベルの抽象化がどこにあるかを識別できます。
問題は、すべてのプロセスを理解する必要がある低レベルのコードで発生します。次に、明確に分離されたモジュールによるカプセル化が唯一の助けになります。
私にとっては、それはカップリングの問題であり、設計の粒度に関連しています。最も緩い結合の形式でさえ、あるものから別のものへの依存関係を導入します。それが数百から数千のオブジェクトに対して行われる場合、たとえそれらがすべて比較的単純であっても、SRPに準拠し、すべての依存関係が安定した抽象化に向かって流れていても、相互に関連する全体として考えるのが非常に難しいコードベースが生成されます。
コードベースの複雑さを測定するのに役立つ実用的なものがあり、理論的なSEではあまり取り上げられていません。たとえば、コールスタックの最後まで到達するまでの深さ、到達するまでに必要な深さなどがあります。かなりの自信を持って、例外のイベントを含め、コールスタックのそのレベルで発生する可能性のあるすべての副作用を理解してください。
そして、私が経験したところ、コールスタックが浅いフラットなシステムは、理由を説明するのがはるかに簡単になる傾向があることがわかりました。極端な例は、コンポーネントが単なる生データであるエンティティコンポーネントシステムです。システムのみが機能を備えており、ECSの実装と使用の過程で、何十万行ものコードにまたがる複雑なコードベースが基本的に数十のシステムに沸騰するときを考えることが、これまでで最も簡単なシステムであることがわかりましたすべての機能が含まれています。
機能が多すぎます
以前のコードベースで作業する前の代替手段は、数百から数千のほとんど小さいオブジェクトを持つシステムでしたが、いくつかのオブジェクトが1つのオブジェクトから別のオブジェクト(Message
オブジェクト、たとえば、独自のパブリックインターフェイスがありました)。これは基本的に、コンポーネントが機能を備え、エンティティ内のコンポーネントの一意の組み合わせごとに独自のオブジェクトタイプが生成されるポイントにECSを戻すと、類推して得られるものです。そして、それは小さなアイデアをモデル化するオブジェクトの無限の組み合わせによって継承され提供される、より小さくてシンプルな関数を生み出す傾向があります(Particle
オブジェクトvs Physics System
、など)。ただし、相互依存関係の複雑なグラフを生成する傾向もあります。これは、コードベースに実際に何かを実行でき、それによって何か間違ったことを実行できる非常に多くのことがあるためです。 -「データ」タイプではなく、関連機能を持つ「オブジェクト」タイプのタイプ。関連する機能を持たない純粋なデータとして機能する型は、それ自体では何もできないため、問題が発生する可能性はありません。
純粋なインターフェースは、この理解可能性の問題をそれほど助けません。なぜなら、「コンパイル時の依存関係」が複雑でなくなり、変更と拡張の余地が多くなったとしても、「ランタイムの依存関係」と相互作用がそれほど複雑にならないためです。クライアントオブジェクトは、IAccount
を介して呼び出されている場合でも、最終的には具体的なアカウントオブジェクトの関数を呼び出します。ポリモーフィズムと抽象インターフェースには用途がありますが、特定の時点で発生する可能性のあるすべての副作用について実際に推論するのに役立つ方法でそれらを分離することはありません。このタイプの効果的なデカップリングを実現するには、機能を含むものがはるかに少ないコードベースが必要です。
より多くのデータ、より少ない機能
ECSアプローチは、完全に適用しなくても非常に役立つことがわかりました。これは、何百ものオブジェクトを、粗い設計の粗いシステムで生データに変え、すべてのオブジェクトを提供するためです。機能性。 「データ」タイプの数を最大化し、「オブジェクト」タイプの数を最小化するため、実際に問題が発生する可能性のあるシステム内の場所の数を完全に最小化します。最終結果は、依存関係の複雑なグラフのない、非常に「フラットな」システムです。システムからコンポーネントへ、決してその逆、決してコンポーネントから他のコンポーネントへのリンクはありません。コードベースの機能を一元化し、重要な領域であるキーの抽象化にフラット化する効果を持つのは、基本的にはより多くの生データとはるかに少ない抽象化です。これらの集中化された領域ではより高密度のコードが生成されますが、機能が提供された場合に物理システムを構成する可能性のある30の相互に関連するオブジェクトタイプよりも、その複雑さを分離して集中化する物理システムについて考える方がはるかに簡単です。
単純な30の事柄は、複雑なものが単独で存在している間に相互に関連している場合、必ずしも1つの複雑な事柄よりも簡単に推論できるわけではありません。したがって、私の提案は、実際には複雑さをオブジェクト間の相互作用から離れて、質量分離を達成するために他のものと相互作用する必要のないより大きなオブジェクトに向けて、「システム」全体(モノリスや神オブジェクトではなく、 200のメソッドを持つクラスではありませんが、ミニマリストなインターフェイスを備えているにもかかわらず、Message
またはParticle
よりもかなり高いレベルです。そして、よりプレーンな古いデータ型を優先します。それらに依存するほど、得られるカップリングは少なくなります。それがいくつかのSEのアイデアに矛盾する場合でも、それが本当に非常に役立つことがわかりました。
設計パターンの理解不足は、この問題の主な原因となる傾向があります。非常に具体的なデータのないインターフェース間を行き来するこのヨーヨーやバウンスで私が見た中で最悪の1つは、Oracleのグリッドコントロールの拡張機能でした。
正直なところ、私のJavaコード全体に、誰かが抽象的なファクトリメソッドとデコレータパターンオーガズムを持っているように見えました。そして、それは私がちょうど中空で一人でいるように感じさせた。
私の質問は、フレームワークまたはパターンがこのように多くのオーバーヘッドをもたらす場合、それだけの価値があるのでしょうか?実装が不十分なパターンの症状ですか?
多分それは間違ったプログラミング言語を選択する症状です。