変数はどのように状態を導入しますか?


11

「C ++ Coding Standards」を読んでいて、この行がありました:

変数は状態を導入するため、可能な限り短いライフタイムで、できるだけ少ない状態を処理する必要があります。

変異するものは最終的に状態を操作しませんか?できるだけ少ない状態に対処する必要があるとはどういう意味ですか?

C ++などの不純な言語では、状態管理は本当にあなたがしていることではありませんか?そして、変数の寿命を制限する以外に、できるだけ少ない状態対処する他の方法は何ですか?

回答:


16

変更可能なものは本当に状態を操作しませんか?

はい。

「小さな状態に対処する必要がある」とはどういう意味ですか?

これは、状態が少ないほど状態が多いことを意味します。状態が増えると、複雑さが増す傾向があります。

C ++のような不純な言語では、状態管理は本当にあなたがしていることではありませんか?

はい。

変数の寿命を制限する以外に、「状態をほとんど持たずに対処する」他の方法は何ですか?

変数の数を最小限にします。他のコードセクションが無視できるように、ある状態を操作するコードを独立したユニットに分離します。


9

変更可能なものは本当に状態を操作しませんか?

はい。C ++では、変更可能なものは(非const)変数のみです。

「小さな状態に対処する必要がある」とはどういう意味ですか?

プログラムの状態が少ないほど、プログラムの動作を理解しやすくなります。したがって、必要のない状態を導入しないでください。また、不要になった状態を保持しないでください。

C ++のような不純な言語では、状態管理は本当にあなたがしていることではありませんか?

C ++のようなマルチパラダイム言語では、多くの場合、「純粋な」機能的アプローチまたは状態駆動型アプローチ、またはある種のハイブリッドの選択があります。歴史的に、関数型プログラミングの言語サポートは一部の言語に比べてかなり弱いものでしたが、改善されています。

変数の寿命を制限する以外に、「状態をほとんど持たずに対処する」他の方法は何ですか?

スコープとライフタイムを制限して、オブジェクト間の結合を減らします。グローバル変数よりもローカル変数を優先し、パブリックオブジェクトメンバーよりもプライベート変数を優先します。


5

状態とは、何かがどこかに保存されているので、後で参照できることを意味します。

変数を作成すると、データを保存するためのスペースが作成されます。このデータはプログラムの状態です。

あなたはそれを使って何かをしたり、変更したり、計算したりするために使用します。

これはstateですが、あなたすることはstateありません。

関数型言語では、ほとんどの場合、関数のみを扱い、関数をオブジェクトのように渡します。これらの関数は状態を持たず、関数を渡しますが、状態を導入しません(おそらく関数自体の内部を除く)。

C ++では、オーバーロードされた関数オブジェクトstructまたはclass型を作成できますoperator()()。これらの関数オブジェクトはローカル状態を持つことができますが、これは必ずしもプログラム内の他のコード間で共有されるわけではありません。ファンクタ(関数オブジェクト)は非常に簡単に渡せます。これは、C ++の機能パラダイムを模倣できる程度に近いものです。(私の知る限り)

状態がほとんどまたはまったくないということは、スレッドまたはCPUの間で共有できるものが何もないため、プログラムを並列実行用に簡単に最適化できることを意味します。


2

他の人は、最初の3つの質問に良い答えを提供しました。

そして、変数の寿命を制限する以外に、「できるだけ少ない状態で対処する」他の方法は何ですか?

質問#1の鍵答えはイエス、何でもそれで変異するが、最終的に影響を与える状態。その際の鍵は、物事を変異させないことです。ある関数の結果が他の関数に直接渡され、保存されない関数型プログラミングスタイルを使用した不変の型。

そうでなければ、状態の影響を制限することになります。可視性または存続期間のいずれかを介して。


1

「小さな状態に対処する必要がある」とはどういう意味ですか?

これは、クラスをできるだけ小さくし、単一の抽象化を最適に表現する必要があることを意味します。クラスに10個の変数を配置した場合、おそらく何か間違ったことをしているので、クラスをリファクタリングする方法を確認する必要があります。


1

プログラムの動作を理解するには、状態の変化を理解する必要があります。状態が少なく、それを使用するコードに対してローカルであるほど、これは簡単になります。

多数のグローバル変数を持つプログラムを使用したことがある場合、暗黙的に理解できます。


1

状態は単に保存されたデータです。すべての変数は実際にはある種の状態ですが、通常「状態」を使用して、操作間で永続的なデータを参照します。簡単な、無意味な例として、あなたは内部的に保存するクラスがありintとありincrement()decrement()メンバ関数を。この場合、内部値はこのクラスのインスタンスの存続期間中保持されるため、状態です。つまり、値はオブジェクトの状態です。

理想的には、クラスが定義する状態は、最小限の冗長性で可能な限り小さくする必要があります。これにより、クラスが単一の責任原則を満たし、カプセル化が改善され、複雑さが軽減されます。オブジェクトの状態は、そのオブジェクトへのインターフェースによって完全にカプセル化される必要があります。これは、そのオブジェクトに対する操作の結果が、そのオブジェクトのセマンティクスを考慮して予測可能であることを意味します。状態にアクセスできる関数の数を最小限に抑えることで、カプセル化をさらに改善できます

これは、グローバルステートを回避する主な理由の1つです。グローバル状態は、オブジェクトを表現するインターフェースなしでオブジェクトの依存関係を導入し、この状態をオブジェクトのユーザーから隠します。グローバルな依存関係を持つオブジェクトで操作を呼び出すと、さまざまな予測不能な結果が生じる可能性があります。


1

変異するものは最終的に状態を操作しませんか?

はい。ただし、プライベート状態を操作できるシステム全体の唯一のエンティティである小さなクラスのメンバー関数の背後にある場合、その状態のスコープは非常に狭くなります。

できるだけ少ない状態に対処する必要があるとはどういう意味ですか?

変数の観点から:できるだけ少ないコード行が変数にアクセスできる必要があります。変数のスコープを最小限に絞ります。

コード行の観点から:可能な限りそのコード行からアクセスできる変数はできるだけ少なくする必要があります。コード行がアクセスできる可能性のある変数の数を絞り込みます(アクセスするかどうかさほど重要ではありません。重要なのはアクセスできるかどうかだけです)。

グローバル変数は最大のスコープを持つため、非常に悪いです。コードベースの2行のコード、コードのPOV行からアクセスされた場合でも、グローバル変数は常にアクセス可能です。変数のPOVから、外部リンケージを持つグローバル変数は、コードベース全体のすべてのコード行(またはヘッダーを含むコードのすべての行)にアクセスできます。実際に2行のコードでしかアクセスされていないにもかかわらず、グローバル変数が400,000行のコードから見える場合、無効な状態に設定されていることが判明した場合の容疑者の即時リストには、400,000エントリがあります(おそらくすぐに減少します)ツールを含む2つのエントリがありますが、それでも直近のリストには400,000人の容疑者が含まれており、それは励みになる出発点ではありません)。

同様に、グローバル変数がコードベース全体で2行のコードだけで変更されたとしても、コードベースが後方に進化するという不幸な傾向により、その数が劇的に増加する傾向があります。開発者は、期限に間に合わせるために必死で、このグローバル変数を見て、彼らがそれを介してショートカットを取ることができることに気付きます。

C ++などの不純な言語では、状態管理は本当にあなたがしていることではありませんか?

大部分、はい、C ++を非常にエキゾチックな方法で使用しており、カスタムメイドの不変のデータ構造と純粋な関数型プログラミングをすべて処理している場合を除きます。多くの場合、その状態の可視性/露出の関数です。

そして、変数の寿命を制限する以外に、できるだけ少ない状態に対処する他の方法は何ですか?

これらはすべて、変数のスコープを制限する領域にありますが、これを行うには多くの方法があります。

  • ペストのような生のグローバル変数を避けてください。いくつかの愚かなグローバルなセッター/ゲッター関数でさえ、その変数の可視性を劇的に狭め、少なくとも不変式を維持する何らかの方法を許可します(例:グローバル変数が負の値になってはいけない場合、セッターはその不変式を維持できます)。もちろん、そうでなければグローバル変数になるようなセッター/ゲッターデザインでさえ、かなり貧弱なデザインです。
  • 可能な場合はクラスを小さくします。数百のメンバー関数、20のメンバー変数、および30,000行のコードを実装するクラスには、「グローバル」プライベート変数があります。これらの変数はすべて、3万行のコードで構成されるメンバー関数からアクセスできるためです。その場合の「状態の複雑さ」は、各メンバー関数のローカル変数を割り引いていると言うことができます30,000*20=600,000。その上にアクセス可能なグローバル変数が10個ある場合、状態の複雑さはのようになり30,000*(20+10)=900,000ます。健康的な「状態の複雑さ」(私の個人的な種類の発明されたメトリック)は、数万ではなく、数十、あるいは数十ではなく、クラスで数千以下でなければなりません。無料の機能については、メンテナンスで深刻な頭痛を感じるようになる前に、数百以下と言ってください。
  • 上記と同じ方法で、クラスのパブリックインターフェイスのみを使用して、非メンバー、非フレンドになり得るメンバー関数またはフレンド関数として何かを実装しないでください。このような関数はクラスのプライベート変数にアクセスできないため、これらのプライベート変数のスコープを縮小することでエラーの可能性を減らします。
  • 関数で実際に必要になるずっと前に変数を宣言することは避けてください(つまり、必要な行が数行しかない場合でも関数の先頭ですべての変数を宣言するレガシーCスタイルを避けます)。とにかくこのスタイルを使用する場合は、少なくとも短い機能に努めてください。

変数を超えて:副作用

上記のガイドラインの多くは、生の可変状態(変数)への直接アクセスに取り組んでいます。しかし、十分に複雑なコードベースでは、生の変数の範囲を狭めるだけでは、正確性について簡単に推論するのに十分ではありません。

たとえば、完全に固定された不変式を完全に維持できる完全に抽象化されたインターフェースの背後にある中央データ構造がありますが、この中央状態が広く露出しているために多くの悲しみに陥ります。必ずしもグローバルにアクセスできるわけではなく、単に広くアクセスできる中央状態の例は、ゲームエンジンの中央シーングラフまたはPhotoshopの中央レイヤーデータ構造です。

そのような場合、「状態」の概念は生の変数を超えて、データ構造とそのようなものにだけ行きます。同様に、スコープを縮小するのに役立ちます(間接的に変更する関数を呼び出すことができる行の数を減らします)。

ここに画像の説明を入力してください

ここでは、間接的にではあるが、広範なズームアウトされたアーキテクチャレベルから、そのインターフェイスにアクセスする状態が変化しているため、ここでもインターフェイスを意図的に赤でマークしたことに注意してください。このクラスは、インターフェースの結果として不変式を維持できますが、それは、正確性について推論する能力の観点からはこれまでのところです。

この場合、中央のデータ構造は、グローバルにアクセスできない抽象インターフェースの背後にあります。単に注入され、複雑なコードベースの関数のボートロードから(メンバー関数を介して)間接的に変更される場合があります。

そのような場合、データ構造が独自の不変条件を完全に維持していても、より広いレベルで奇妙なことが起こります(例:オーディオプレーヤーは、ボリュームレベルが0%から100%、ただし、ユーザーが再生ボタンを押して、最後にロードしたもの以外のランダムなオーディオクリップがイベントのトリガーとして再生を開始することから保護されないため、プレイリストは有効な方法で再シャッフルされますが、幅広いユーザーの観点からは、依然として望ましくないグリッチな動作)。

これらの複雑なシナリオで身を守る方法は、未加工の状態やインターフェースを超えたシステムのこの種のより広い視野からさえ最終的に外部副作用を引き起こす関数を呼び出すことができるコードベース内の場所を「ボトルネック」にすることです。

ここに画像の説明を入力してください

これは奇妙に見えますが、多くの場所から「状態」(赤で表示され、これは「生変数」を意味せず、「オブジェクト」を意味し、場合によっては抽象インターフェースの背後にさえも)がアクセスされていないことがわかります。各機能は、中央アップデーターからもアクセス可能なローカル状態にアクセスできます。中央状態は、中央アップデーターのみがアクセスできます(本質的に中央ではなく、むしろローカルになります)。

これは、1,000万行のコードにまたがるゲームのような本当に複雑なコードベースにのみ適用されますが、ソフトウェアの正確さを推論し、数を大幅に制限/ボトルネックした場合に変更が予測可能な結果を​​もたらすことを見つけるのに非常に役立ちますアーキテクチャ全体が適切に機能するように旋回しているクリティカルな状態を変化させる可能性のある場所。

生の変数を超えて外部の副作用があり、外部の副作用は少数のメンバー関数に限定されていてもエラーの原因です。関数のボートロードがそれらの一握りのメンバー関数を直接呼び出すことができる場合、外部の副作用を間接的に引き起こす可能性のある関数のボートロードがシステムにあり、それが複雑さを高めます。コードベースにそれらのメンバー関数にアクセスできる場所が1つだけあり、その実行の1つのパスがその場所全体で散発的なイベントによってトリガーされるのではなく、非常に制御された予測可能な方法で実行される場合、複雑さが軽減されます。

状態の複雑さ

状態の複雑さであっても、考慮すべき重要な要素です。抽象インターフェースの背後で広くアクセス可能な単純な構造は、混乱させることはそれほど難しくありません。

複雑なアーキテクチャのコア論理表現を表す複雑なグラフデータ構造は、簡単に台無しにでき、グラフの不変条件にも違反しません。グラフは単純な構造よりも何倍も複雑であるため、そのような場合、コードベースの知覚される複雑さを減らして、そのようなグラフ構造にアクセスできる場所の数を絶対最小に減らすことがさらに重要になります。そして、散発的な回避のためにプルパラダイムに反転するこの種の「中央アップデーター」戦略は、どこからでもグラフデータ構造への直接プッシュが本当に成果を上げることができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.