私は何年にもわたって多くのJavaScriptを実行してきましたが、具体的にはモジュールパターンで、よりオブジェクト指向のアプローチを使用しています。より大きなコードベースがスパゲッティになるのを避けるために、どのようなアプローチを使用しますか? 「最善のアプローチ」はありますか?
Dan Wahlinは、JavaScriptで function spaghetti code を回避することについて具体的なガイダンスを提供しています。
ほとんどの人(私を含む)は、.jsファイルまたはHTMLファイルに関数を続けて追加することにより、JavaScriptコードの作成を開始します。このアプローチは仕事を完了するので間違いなく何の問題もありませんが、多くのコードを扱う場合、すぐに制御不能になる可能性があります。関数をファイルにまとめると、コードの検索が難しくなる可能性があり、コードのリファクタリングは非常に面倒です(Resharper 6.0のような素晴らしいツールがない限り)。あなたはもともとそれを書きませんでした。
彼は、コードに構造を与えるいくつかの JavaScript設計パターン について説明しています。
私は 明らかにするプロトタイプパターン を好みます。 2つ目のお気に入りは 公開モジュールパターン です。これは、public/privateスコープを宣言できるという点で標準モジュールパターンとは少し異なります。
OOP=を現代の言語で使用している場合、最大の危険は通常、「スパゲッティコード」ではなく「ラビオリコード」です。コードベースがそのビットピース、10の関数とオブジェクトで構成されている場所まで問題を分割して征服する可能性があります。これらはすべて疎結合であり、単一で10の責任を実行し、すべてユニットテストに対してテストされ、抽象的な相互作用のクモの巣が進行します副作用などについて何が起こっているのかを推測するのが非常に難しくなります。そして、個々の部分が実際に美しく、すべてがSOLIDに準拠している場合があるため、これを美しく設計したと頑固に考えるのは簡単です。システムを完全に理解しようとするときに、すべての相互作用の複雑さから爆発する寸前。
そして、これらのオブジェクトまたは関数のいずれかが個別に何を行うかについては、非常に簡単に推論できますが、これらは、DIを通じて少なくともそれらの抽象的な依存関係を表現しながら、そのような特異でシンプルかつおそらくは美しい責任を果たすため、全体像を分析するために、相互作用のクモの巣を使って何千もの小さなことを最終的に何ができるかを理解することは困難です。もちろん、ドキュメントに記載されている大きなオブジェクトと大きな関数を見て、小さなオブジェクトにドリルダウンしないでください。もちろん、少なくとも、高レベルの種類で発生するはずのことを理解するのに役立ちます。 ..
ただし、実際にコードをchangeまたはdebugする必要がある場合は、それほど役に立ちません。その時点で、これらのすべてが何を追加するのかを理解できなければなりません。 副作用のような下位レベルのアイデア、永続的な状態変化、およびシステム全体の不変条件を維持する方法まで。また、抽象的なインターフェースを使用して相互に通信しているかどうかに関係なく、何千もの小さなものの相互作用の間に発生する副作用をつなぎ合わせるのはかなり困難です。
[〜#〜] ecs [〜#〜]
したがって、この問題を緩和するために私が見つけた最終的なものは、実際にはエンティティコンポーネントシステムですが、多くのプロジェクトにとってはやり過ぎかもしれません。私はECSに夢中になって、小さなプロジェクトを書いているときでも、ECSエンジンを使用しています(その小さなプロジェクトには1つまたは2つのシステムしかないかもしれませんが)。しかし、ECSに興味がない人のために、ECSがシステムを理解する能力をそれほど単純化した理由を理解しようと努めてきました。そうでない場合でも、多くのプロジェクトに適用できるはずだと思います。 ECSアーキテクチャを使用します。
同種ループ
基本的には、より均一なループを優先することから始めます。これは、同じデータに対してより多くのパスを意味しますが、より均一なパスを意味します。たとえば、これを行う代わりに:
for each entity:
apply physics to entity
apply AI to entity
apply animation to entity
update entity textures
render entity
...代わりにこれを行うと、どういうわけか非常に役立つようです:
for each entity:
apply physics to entity
for each entity:
apply AI to entity
etc.
そして、同じデータを何度もループすることは無駄に見えるかもしれませんが、各パスは非常に均一です。それはあなたが考えることを可能にします"大丈夫、システムのこのフェーズの間、物理学を除いてこれらのオブジェクトで何も起こっていません。変更されているものがあり、副作用が起こっている場合、それらはすべて非常に均一な方法です。」そして、どういうわけか、それがコードベースについての推論にとても役立つことがわかりました。
無駄に見えるかもしれませんが、各ループのすべてに均一なタスクが適用されているときにコードを並列化する機会を見つけるのにも役立ちます。そしてtendsからencourageへのデカップリングの度合いも大きくなります。本質的に、1つのパスでオブジェクトに対してすべてを実行しようとしない離婚したパスがある場合、コードを簡単に分離し、分離したままにする機会が増える傾向があります。 ECSでは、システムは互いに完全に分離されていることが多く、外部の「クラス」または「機能」が手動で調整することはありません。 ECSは、同じデータを複数回ループする必要がないため、キャッシュミスが繰り返されることもありません(各ループは、メモリ内の他の場所にあるが、同じエンティティに関連付けられている異なるコンポーネントにアクセスする場合があります)。システムは自律的であり、ループ自体の責任があるため、システムを手動で調整する必要はありません。同じ中央データにアクセスする必要があるだけです。
したがって、これは、システム上でより均一で単純な種類の制御フローを確立するのに役立つ1つの方法です。
フラット化イベント処理
もう1つは、イベント処理への依存を減らすことです。多くの場合、ポーリングなしで発生した外部の事柄を把握するためにイベント処理が必要ですが、予測が非常に困難な制御フローや副作用につながるプッシュイベントのカスケードを回避する方法がしばしばあります。イベント処理は、本質的に、一度に多くのオブジェクトで発生する単純で均一なものに焦点を当てたい場合に、一度に1つの小さなオブジェクトで発生する複雑なものを処理する傾向があります。
したがって、たとえば、親コントロールのサイズを変更するOSサイズ変更イベントの代わりに、子ごとにサイズ変更イベントとペイントイベントのプッシュを開始し、who-knows-whereにさらにイベントをカスケードする場合は、サイズ変更イベントをトリガーして親と子にマークを付けるだけです。 dirty
として、再描画する必要があります。すべてのコントロールにサイズ変更が必要であるとマークするだけで、LayoutSystem
がそれを取得してサイズを変更し、関連するすべてのコントロールのサイズ変更イベントをトリガーできます。
次に、GUIレンダリングシステムが条件変数で起動され、ダーティコントロールをループし、それらを広範なパス(イベントキューではない)で再描画し、そのパス全体がUIの描画以外に焦点を当てていない場合があります。再描画に階層順序の依存関係がある場合は、ダーティな領域または四角形を把握し、それらの領域のすべてを適切なzオーダーで再描画して、ツリートラバーサルを実行する必要がなく、非常に再帰的で「深い」ファッションではなく、シンプルで「フラット」なファッション。
このような微妙な違いのように見えますが、何らかの理由で制御フローの観点からこれは非常に役立つと思います。これは、一度に個々のオブジェクトに発生することの数を減らし、SRPに似ているがループと副作用の観点から適用されるものを目指しているということです: "Single-Task Loop Principle" 、「ループ原理ごとの単一タイプの副作用 "。
このタイプの制御フローでは、ループ内で適用される大きな、多額の、しかし非常に均一なタスクの観点から、一度に個々のオブジェクトで実行できるすべての機能と副作用ではなく、システムについて考えることができます。これは大きな違いをもたらすようには見えないかもしれませんが、変更を加えるときに重要なすべての領域でコードベースの動作を理解する私の心の能力まで、それが世界にすべての違いを生むことがわかりましたまたはデバッグ(これもこのアプローチで行う必要がはるかに少ないことがわかりました)。
データへの依存フロー
これはおそらくECSの中で最も物議を醸している部分であり、一部のドメインにとっては悲惨なことになるかもしれません。 SOLIDの依存関係の逆転の原則に直接違反しています。これは、低レベルのモジュールであっても、依存関係が抽象化に向かって流れる必要があることを示しています。情報の非表示にも違反していますが、少なくともECS 、通常、1つまたは2つのシステムのみが特定のコンポーネントのデータにアクセスするため、見た目ほどではありません。
そして、抽象化に向かって流れる依存関係の考え方は、抽象化がstableである場合(変化しないように)であれば、うまく機能すると思います。依存関係は安定性に向かって流れる必要があります。ただし、少なくとも私の経験では、抽象化はしばしばwere n't安定しています。開発者はそれらを完全に正しくすることは決してなく、関数を変更または削除する必要があり(追加はそれほど悪くはありませんでした)、1〜2年後に一部のインターフェイスを非推奨にします。クライアントは、開発者が構築した慎重な概念を破る方法で心を変え、抽象的なカードの抽象的な家の抽象的なファクトリーをダウンさせます。
その間、dataの方がはるかに安定していることがわかります。例として、ゲームでモーションコンポーネントに必要なdataは何ですか?答えは非常に簡単です。ある種の4x4変換マトリックスが必要であり、モーション階層を作成できるようにするには、親への参照/ポインターが必要です。それでおしまい。その設計上の決定は、ソフトウェア全体の寿命を持続させる可能性があります。
マトリックスに単精度浮動小数点を使用するか、倍精度浮動小数点を使用するかなど、いくつかの微妙な点があるかもしれませんが、どちらも適切な決定です。 SPFPを使用する場合、精度は困難です。 DPFPを使用する場合、速度は課題ですが、どちらも変更する必要がない、または必ずしもインターフェースの背後に隠されている必要がない優れた選択肢です。どちらの表現も、私たちがコミットして安定を保つことができる表現です。
ただし、all抽象IMotion
インターフェースに必要な関数とは何か、さらに重要なことに、すべてのニーズに対して効果的に物事を実行するために提供する必要のある理想的な最小限の関数セットは何かモーションを処理するサブシステム?そのため、アプリケーションの設計全体を事前に把握する必要があるため、理解することなく答えることは非常に困難です。そして、コードベースの非常に多くの部分がこのIMotion
に依存することになると、これを最初から正しく行うことができない限り、設計の反復ごとに多くを書き直さなければならないことがあります。
もちろん、場合によっては、データ表現が非常に不安定になることがあります。データ構造に関連するシステムの機能的なニーズは事前に容易に予測できる一方で、データ構造の不備のために将来置換が必要になる可能性がある複雑なデータ構造に依存するものがあります。したがって、実用性を高め、依存関係が抽象化に向かうのかデータに向かうのかをケースバイケースで決定することは価値がありますが、少なくとも、抽象化よりもデータを安定化する方が簡単な場合があり、ECSを採用するまでは、依存関係を主にデータに流すことも検討しました(効果を驚くほど単純化および安定化します)。
したがって、これは奇妙に思えるかもしれませんが、抽象的なインターフェイスを介してデータの安定した設計を作成する方がはるかに簡単な場合は、実際に依存関係をプレーンな古いデータに向けることをお勧めします。これにより、書き換えを何度も繰り返す必要がなくなります。ただし、制御フローとスパゲッティとラビオリのコードに関しては、関連するデータに最終的に到達する前にそのような複雑な相互作用を行う必要がない場合、これは制御フローを単純化する傾向もあります。
コード標準は一般的に役立ちます。
つまり:
モジュールは絶対に必要です。 「クラス」の実装方法、つまり「プロトタイプのメソッド」と「インスタンスのメソッド」の一貫性も必要です。また、ECMAScriptのどのバージョンをターゲットにするか、つまりECMAScript 5をターゲットにする場合は提供されている言語機能を使用、(ゲッターやセッターなど)も決定する必要があります。
関連項目:TypeScript。これは、標準化に役立ちます。クラス。現時点では少し新しいですが、ロックインはほとんどないため(JavaScriptにコンパイルされるため)、使用に不利な点はありません。
作業コードとデプロイされたコードを分離すると役立ちます。ツールを使用してJavaScriptファイルを結合および圧縮します。そのため、特定のモジュールで作業しているときのために、フォルダ内に任意の数のモジュールをすべて個別のファイルとして含めることができます。しかし、展開時間のために、これらのファイルは圧縮ファイルに結合されます。
私はChirpy http://chirpy.codeplex.com/ を使用していますが、これはSASSとcoffeeScriptもサポートしています。