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