最近、ビデオゲーム開発におけるOOPについて友人と話し合いました。
私は私のゲームの1つのアーキテクチャを説明していました。これには、友人が驚いたことに、多くの小さなクラスといくつかの抽象化レイヤーが含まれていました。これは、すべてに単一の責任を与えることに集中し、コンポーネント間の結合を緩めることに焦点を当てた結果だと私は主張しました。
彼の懸念は、多数のクラスがメンテナンスの悪夢につながることでした。私の見解では、それは正反対の効果をもたらすということでした。私たちは何世紀にもわたって見えたものについてこれを議論し、最終的には同意しませんでした。おそらく、SOLIDの原則と適切なOOPが実際にはうまく混合しなかったケースがあったと言います。
SOLID原則の Wikipediaエントリ でも、それらはガイドラインであり、保守可能なコードの記述に役立ち、全体的な一部であると述べていますアジャイルおよび適応型プログラミングの戦略。
だから、私の質問は:
OOPで、SOLIDの原則の一部またはすべてがクリーンなコードに適さない場合がありますか?
リスコフの代替原則が安全な継承の別のフレーバーと競合する可能性があることはすぐに想像できます。つまり、継承を通じて実装された別の有用なパターンを考案した場合、LSPがそれと直接競合する可能性があります。
他にありますか?おそらく、特定のタイプのプロジェクトまたは特定のターゲットプラットフォームは、より少ないSOLIDアプローチでよりよく機能しますか?
編集:
私は自分のコードを改善する方法を尋ねているのではないことを指定したいだけです;)この質問でプロジェクトについて言及した唯一の理由は、少しコンテキストを与えることでした。私の質問はOOPとgeneralの設計原則についてです。
私のプロジェクトに興味があれば、 this を参照してください。
編集2:
この質問には、次の3つの方法のいずれかで答えると思いました。
オプション1と2は、長くて興味深い回答を生成する可能性があります。一方、オプション3は、短く、興味をそそらないが、全体的に安心させる答えになるでしょう。
オプション3に収束しているようです。
OOPにSOLID原則の一部またはすべてがクリーンなコードに向いていない場合がありますか?
一般的には違います。歴史は、SOLID原則のすべてがデカップリングの増加に大きく貢献していることを示しています。これにより、コードの柔軟性が向上し、変更に対応し、コードをより簡単にする能力が示されました推論し、テストし、再利用します...要するに、コードをよりクリーンにします。
SOLIDの原則がDRY(自分自身を繰り返さないでください)と衝突する)、KISS(単純な愚かさを保つ)または良い他の原則OOデザイン。そしてもちろん、要件の現実、人間の制限、プログラミング言語の制限、その他の障害と衝突する可能性があります。
要約すると、SOLID=原則は常にクリーンなコードに適していますが、シナリオによっては、競合する代替案よりも役立つ場合があります。それらは常に優れていますが、他のことはもっと良い。
ファイナンスとゲームの両方で働いてきたという点で、私は変わった視点を持っていると思います。
ゲームで出会ったプログラマーの多くはソフトウェアエンジニアリングでひどいものでしたが、SOLIDのような練習は必要ありませんでした。伝統的に、彼らはゲームを箱に入れ、それで終わりです。
財務面では、開発者はあなたがゲームで行うようなパフォーマンスを必要としないため、ずさんで規律のないことがあることがわかりました。
上記の両方のステートメントはもちろん一般化されすぎですが、実際には、どちらの場合もクリーンなコードが重要です。最適化のためには、明確で理解しやすいコードが不可欠です。保守性のために、あなたは同じことを望みます。
SOLIDに批判がないわけではありません。それらは原則と呼ばれていますが、ガイドラインのように守られているはずです?それで、いつ正確に従うべきですか?ルール?
おそらく2つのコードを見て、どれがSOLIDに最も適合するかを言うことができます。しかし、単独では、コードをSOLIDに変換する客観的な方法はありません。それは間違いなく開発者の解釈にあります。
「クラスの数が多いとメンテナンスの悪夢につながる可能性がある」と言うのは不公平ではありません。ただし、SRPが正しく解釈されない場合、簡単にこの問題が発生する可能性があります。
私は、ほとんど責任のない多くの層を持つコードを見てきました。あるクラスから別のクラスに状態を渡す以外に何もしないクラス。間接的なレイヤーが多すぎるという古典的な問題。
SRPが悪用された場合、コードにまとまりがなくなる可能性があります。これは、機能を追加しようとしたときにわかります。常に複数の場所を同時に変更する必要がある場合は、コードにまとまりがありません。
Open-Closedには批評家がいるわけではありません。たとえば、Jon Skeetによる Open Closed Principal のレビューを参照してください。ここでは彼の議論を再現しません。
LSPについてのあなたの議論はかなり仮説のようであり、私はあなたが安全な継承の別の形で何を意味するのか本当に理解していませんか?
ISPはSRPをいくらか複製しているようです。確かにそれらは同じように解釈されなければならないので、あなたはあなたのインターフェースのすべてのクライアントが何をするか予想できない。今日のまとまりのあるインターフェースは、明日のISP違反者です。唯一の非論理的な結論は、インターフェースごとに1つのメンバーのみを持つことです。
DIPを使用すると、コードが使用できなくなる可能性があります。 DIPの名前に多数のパラメーターがあるクラスを見てきました。これらのクラスは通常、それらの複合パーツを「更新」しますが、私はテスト能力の名前であるので、再び新しいキーワードを使用することはできません。
SOLIDだけでは、クリーンなコードを作成するには不十分です。 Robert C. Martinの本「 Clean Code:A Handbook of Agile Software Craftsmanship "を見てきました。それに関する最大の問題は、この本を読み、それをルールとして解釈するのが簡単なことです。そうするなら、あなたは要点を逃しています。これには、匂い、ヒューリスティック、および原則の多数のリストが含まれています。SOLIDの5つだけではありません。これらの「原則」はすべて、実際には原則ではなくガイドラインです。ボブ叔父さんは、自分たちをコンセプトの「カビーホール」として説明しています。彼らはバランスをとる行為です。ガイドラインを盲目的に実行すると問題が発生します。
これが私の意見です。
SOLIDの原則は非冗長で柔軟なコードベースを目的としていますが、クラスとレイヤーが多すぎる場合、これは可読性とメンテナンスのトレードオフになる可能性があります。
特に abstractions の数についてです。少数の抽象化に基づいている場合、多数のクラスが問題ない場合があります。プロジェクトのサイズと詳細がわからないのでわかりにくいですが、さらにいくつかのレイヤーについて言及するのは少し心配です多数のクラスに。
SOLID原則はOOPに同意しないと思いますが、それらに付着したクリーンでないコードを書くことはまだ可能です。
彼の懸念は、多数のクラスがメンテナンスの悪夢につながることでした。私の見解では、それは正反対の効果をもたらすということでした。
私は絶対にあなたの友人の側にいますが、それは私たちのドメインと私たちが取り組む問題と設計の種類、そして特にどのような種類のものが将来変更を必要とする可能性が高いかという問題である可能性があります。さまざまな問題、さまざまな解決策。私は正しいか間違っているとは考えていません。特定の設計上の問題を解決する最善の方法を見つけようとするプログラマーだけです。ゲームエンジンとあまり変わらないVFXで作業しています。
しかし、少なくとも多少はSOLID準拠のアーキテクチャ(COMベース)と呼ばれるもので苦労した問題は、「クラスが多すぎる」または「関数が多すぎる」に大まかに要約されている可能性があります。あなたの友人が説明するかもしれません。具体的には、「やり取りが多すぎる、誤動作する可能性のある場所が多すぎる、副作用を引き起こす可能性のある場所が多すぎる、変更が必要な場所が多すぎる、私たちが思っていることを実行できない場所が多すぎる」
私たちは、サブタイプのボートロードによって実装された抽象的な(そして純粋な)インターフェースをいくつか持っていました(この図をECSの利点について説明するコンテキストで作成し、左下のコメントは無視してください)。
モーションインターフェースまたはシーンノードインターフェースは、数百のサブタイプ(ライト、カメラ、メッシュ、物理ソルバー、シェーダー、テクスチャ、ボーン、プリミティブシェイプ、カーブなど)によって実装される場合があります(多くの場合、それぞれに複数のタイプがありました)。そして究極の問題は、それらのデザインがそれほど安定していないことでした。要件の変更があり、場合によってはインターフェース自体も変更する必要がありました。200のサブタイプによって実装される抽象インターフェースを変更したい場合、それは非常にコストのかかる変更です。このような設計変更のコストを削減する抽象ベースクラスを使用することで、この問題の緩和に着手しましたが、それらは依然として高価でした。
代わりに、ゲーム業界でかなり一般的に使用されているエンティティコンポーネントシステムアーキテクチャの調査を開始しました。それはすべてをこのように変えました:
そしてすごい!それがメンテナンス性の面での違いでした。依存関係はabstractionsではなくdata(components)に向かって流れていました。そして、少なくとも私の場合、データははるかに安定しており、要件の変化にもかかわらず、設計の面で前もって正しく理解することが容易でした(同じデータで実行できることは、要件の変化に伴って常に変化しています)。
また、ECSのエンティティは継承ではなく構成を使用するため、実際に機能を含める必要はありません。それらは、単なる類似した「コンポーネントのコンテナ」です。これにより、モーションインターフェースを実装する類似の200サブタイプが200エンティティインスタンスに変わります(個別のコードで個別のタイプではありません)単純にモーションコンポーネントを格納します(モーションに関連するデータにすぎません)。 PointLight
は、別個のクラス/サブタイプではなくなりました。クラスではありません。これは、空間内の場所(モーション)に関連するいくつかのコンポーネント(データ)とポイントライトの特定のプロパティを組み合わせるエンティティのインスタンスです。それらに関連付けられている唯一の機能は、RenderSystem
などのシステム内にあり、シーン内のライトコンポーネントを探してシーンのレンダリング方法を決定します。
ECSアプローチでの要件の変化により、多くの場合、そのデータで動作する1つまたは2つのシステムを変更するか、新しいシステムを導入するか、新しいデータが必要な場合は新しいコンポーネントを導入するだけで済みました。
したがって、少なくとも私のドメインでは、だれもがそうであるとは限らないと確信しています。これにより、依存関係が安定性(頻繁に変更する必要のないもの)に向かって流れていたため、状況が非常に簡単になりました。依存関係が抽象化に向かって一様に流れていたCOMアーキテクチャでは、これは当てはまりませんでした。私の場合、モーションで実行できるすべてのことよりも、事前にモーションに必要なデータを把握する方がはるかに簡単です。多くの場合、数か月または数年で少しずつ変化し、新しい要件が発生します。
OOPにSOLID原則の一部またはすべてがクリーンなコードに向いていない場合がありますか?
まあ、クリーンなコードはSOLIDとクリーンなコードを同一視している人がいるのでわかりませんが、ECSのように機能からデータを分離し、抽象化からデータへの依存関係をリダイレクトすることにより、物事を非常に簡単にできる場合があります。データが抽象化よりもはるかに安定している場合は、明らかな結合の理由で変更します。もちろん、データへの依存は不変量の維持を困難にする可能性がありますが、ECSは、特定のタイプのコンポーネントにアクセスするシステムの数を最小限にするシステム構成でそれを最小限に抑える傾向があります。
DIPが示唆するように、依存関係が抽象化に向かって流れる必要はありません。依存関係は、将来の変更が必要になる可能性が非常に低いものに向かって流れる必要があります。それはすべての場合において抽象化であるかもしれませんし、そうでないかもしれません(それは確かに私のものではありませんでした)。
- はい、存在しますOOP SOLIDと部分的に競合する設計原則
- はい、OOP SOLIDと完全に矛盾する設計原則が存在します。
ECSが本当にOOPのフレーバーかどうかはわかりません。一部の人々はそれをそのように定義していますが、私は本質的にカップリング特性と機能(システム)からのデータ(コンポーネント)の分離、およびデータのカプセル化の欠如とは非常に異なると思います。これがOOPの形式と見なされる場合、SOLID(少なくともSRP、オープン/クローズ、リスコフ置換、およびDIPの最も厳密なアイデア)と非常に競合していると思います。しかし、私はこれがSOLIDの最も基本的な側面が、少なくとも人々がより認識しやすいOOPコンテキストでそれらを解釈するので、それほど適用できないかもしれない場合の1つのケースとドメインの合理的な例であることを願っています。
ティーンクラス
私は私のゲームの1つのアーキテクチャを説明していました。これには、友人が驚いたことに、多くの小さなクラスといくつかの抽象化レイヤーが含まれていました。これは、すべてに単一の責任を与えることに集中し、コンポーネント間の結合を緩めることに焦点を当てた結果だと私は主張しました。
ECSは私の見解に異議を唱え、大きく変えました。あなたと同じように、私は保守性のまさにその考えが可能なことのための最も単純な実装を持つことを考えていました、それは多くのものを意味し、さらに多くの相互依存性のものです(相互依存関係が抽象化の間であっても)。 1つのクラスまたは関数だけを拡大して最も簡単で単純な実装を確認したい場合に最も意味があります。1つも表示されない場合は、リファクタリングし、さらに分解することさえできます。しかし、結果として外界で何が起こっているのかを見逃しがちです。比較的複雑なものを2つ以上に分割するときはいつでも、これら2つ以上のものは必然的に相互に作用しなければならない*(下記参照)場合があります。方法、または外部の何かがそれらのすべてと対話する必要があります。
最近、私は、何かの単純さとそこにあるものの数と必要なやり取りの量との間にバランスのとれた行動があることに気づきました。 ECSのシステムは、PhysicsSystem
やRenderSystem
やGuiLayoutSystem
のように、データを操作するための重要な実装ではかなり高額になる傾向があります。ただし、複雑な製品に必要なものが非常に少ないため、コードベース全体の全体的な動作を簡単に後退させて推論する傾向があります。保守するクラスと推論するクラスが少なく、相互作用全体が少ない場合は、クラスが少なくてかさばる(依然として単一の責任を果たしている)側に寄りかかるのは悪い考えではない可能性があることを示唆している可能性があります。システム。
相互作用
抽象化を使用して2つの具象オブジェクトを分離することはできますが、それらは相互に通信するため、「相互作用」ではなく「相互作用」と言います(相互作用を減らすと、両方を減らすことになります)。それでも、この間接的なコミュニケーションの過程で副作用が発生する可能性があります。また、システムの正確さを推論する私の能力は、「カップリング」よりもこれらの「相互作用」に関連していることがよくあります。相互作用を最小限に抑えると、鳥瞰図からすべてについて推論することがずっと簡単になります。つまり、物事はまったく対話しないことを意味します。その意味で、ECSは「相互作用」を最小限に抑え、結合だけでなく最小限に抑える傾向があります(少なくとも、他の一般的な建築デザインは見つかりませんでした)私のドメインではこれをさらに削減します。システムが非常に少ない複雑な製品の設計をキャプチャできるため、およびシステムが互いに直接通信しないためです(中央の「ECSデータベース」のみ)。
とはいえ、これは少なくとも私と私の個人的な弱点かもしれません。巨大な規模のシステムを作成し、それについて自信を持って推論し、ナビゲートし、予測可能な方法で任意の場所に潜在的な望ましい変更を加えることができると感じる最大の障害を見つけました。それは、状態とリソースの管理です。副作用。自分で完全に作成したコードであっても、数万のLOCから数十万のLOC、数百万のLOCに移行するときに発生する最大の障害です。何かが何よりもクロールの速度を落とすとしたら、アプリケーションの状態、データ、副作用の観点から何が起こっているのか理解できなくなっているのはこのためです。システムがそれについて推論する私の能力を超えて成長した場合、変更の完全な影響を理解できないほど、私を遅くするような変更を加えるのに必要なロボットの時間ではありません。そして、私にとって相互作用を最小限に抑えることは、個人的にこれらのことに圧倒されることなく、はるかに多くの機能を備えた製品をより大きく成長させるための最も効果的な方法でした。アプリケーションの状態を変更し、実質的に副作用を引き起こす可能性さえあります。
それはこのようなものを変えることができます(図のすべてに機能があり、明らかに実際のシナリオにはオブジェクトの数の何倍もあり、これは「相互作用」図であり、結合図ではなく、結合図です)間に抽象化があるでしょう):
...これだけで、システムにのみ機能があります(青いコンポーネントは単なるデータになり、これは結合図になります)。
そして、これらすべてと、これらの利点のいくつかをより準拠したOOP SOLIDとより互換性のあるコンテキストでフレーム化する方法が考えられますが、私はデザインをまだ見つけていません。と言葉だけですが、OOPに直接関連するすべての用語を使い回すのに慣れていたので、難しいと思います。私は、ここで人々の回答を読んで理解し、自分の考えを定式化するように最善を尽くしていますが、 ECSの性質について非常に興味深い点があります。完全に指を置くことができなかったため、それを使用しないアーキテクチャにもより広く適用できる可能性があります。この答えが出ないことを願っていますECSプロモーションとして!メンテナンス性に不可欠だと思った多くのことに挑戦することから始まったECSの設計は私の考えを本当に劇的に変えたので、私はそれを非常に興味深いと思います。
開発の初期の頃、私はC++でローグライクを書き始めました。私は教育から学んだ優れたオブジェクト指向の方法論を適用したかったので、すぐにゲームアイテムがC++オブジェクトであることがわかりました。 Potion
、Swords
、Food
、Axe
、Javelins
など、すべて基本的なコアItem
コードから派生し、名前、アイコン、重量などを処理します。
次に、bagロジックをコーディングし、問題に直面しました。 Items
をバッグに入れることはできますが、それを選択した場合、それがPotion
であるかSword
であるかをどのようにして知ることができますか?アイテムをダウンキャストする方法をインターネットで調べました。コンパイラーオプションをハックしてランタイム情報を有効にして、抽象化されたItem
true型が何であるかを把握し、型のスイッチロジックを開始して、許可する操作(たとえば、Swords
を飲まないようにしたり、私の足にPotions
を付けます)
それは本質的に、半分のコピーペーストされた泥の大きなボールでコードを変えました。プロジェクトが進むにつれて、私は設計の失敗が何であるかを理解し始めました。起こったことは、値を表すためにクラスを使用しようとしたことです。私のクラス階層は意味のある抽象化ではありませんでした。何をすべきかすべきだったは、Equip ()
、Apply ()
、Throw ()
などの関数のセットをItem
に実装させています。そして、それぞれが値に基づいて動作するようにします。列挙型を使用して装備スロットを表す。列挙型を使用してさまざまな武器の種類を表す。私のサブクラス化には、これらの最終的な値を埋める以外の目的がなかったため、より多くの値を使用し、クラス化を少なくしました。
あなたは抽象化レイヤーの下でオブジェクトの乗算に直面しているので、私の洞察はここで価値があると思います。オブジェクト型が多すぎる場合は、型と値を混同している可能性があります。
SOLIDの原則は適切ですが、KISSおよび競合の場合はYAGNIが優先されます。SOLIDの目的は複雑さを管理することですが、SOLID自体は、コードをより複雑にし、目的に反します。例を挙げれば、クラスが少ない小さなプログラムの場合、DIの適用、インターフェースの分離などにより、プログラム全体の複雑さが非常に高まる可能性があります。
オープン/クローズの原則は特に物議を醸しています。基本的には、同じインターフェースのサブクラスまたは個別の実装を作成してクラスに機能を追加する必要があるが、元のクラスはそのままにしておく必要があると述べています。調整されていないクライアントが使用するライブラリまたはサービスを維持している場合は、既存のクライアントが依存する可能性のある動作を中断したくないため、これは理にかなっています。ただし、独自のアプリケーションでのみ使用されるクラスを変更する場合は、単にクラスを変更するほうがはるかに簡単でわかりやすい場合があります。
私の経験では:-
大きなクラスは、大きくなるにつれて、維持するのが指数関数的に難しくなります。
小さなクラスは維持が簡単で、小さなクラスのコレクションを維持することの難しさは、クラスの数に比例して増加します。
大きなクラスは避けられないことがある大きな問題は大きなクラスを必要とすることがありますが、それらを読んで理解するのははるかに困難です。適切に名前が付けられた小さいクラスの場合、名前だけで十分に理解できます。クラスに非常に具体的な問題がない限り、コードを調べる必要もありません。
私は、物体の「S」と、オブジェクトにそれ自体のすべての知識を持たせる必要がある(旧式の可能性がある)必要性との間に線を引くのが常に難しいと感じています。
15年前、私はすべてを含むクラスを持つために彼らの聖杯を持っていたDeplhi開発者と協力しました。したがって、住宅ローンの場合は、保険と支払い、収入とローン、税金情報を追加し、納税額、控除額を計算して、シリアル化したり、ディスクに永続化したりできます。
そして今、私は同じオブジェクトをたくさんの小さなクラスに分けて持っていますが、それらはユニットテストをはるかに簡単にそしてより良くできるという利点がありますが、ビジネスモデル全体の良い概観を与えません。
そして、はい、私はあなたがあなたのオブジェクトをデータベースに結び付けたくないことを知っています、しかしあなたはそれを解決するために大きな住宅ローンのクラスにリポジトリを注入することができます。
その上、小さなことだけを行うクラスの適切な名前を見つけるのは必ずしも容易ではありません。
したがって、「S」が常に良いアイデアであるかどうかはわかりません。