これを始める前に、抽象化と依存性注入の概念をよく知っているとしましょう。ここで目を開ける必要はありません。
まあ、私たちのほとんどは、(あまりにも)本当に理解せずに「グローバル変数を使用しないでください」または「シングルトンはグローバルであるため、悪である」と何度も言います。しかし、本当に不吉なグローバル状態についてisは何が悪いのでしょうか?
たとえば、システムフォルダーのパスやアプリケーション全体のデータベース資格情報など、アプリケーションのグローバル構成が必要だとします。
その場合、これらの設定をアプリケーション全体で一般的に利用できる、ある種のグローバルスペースで提供する以外に、良い解決策はありません。
虐待それは悪いことですが、グローバルスペースは本当に[〜#〜] that [〜#〜]悪ですか?もしそうなら、どんな良い代替案がありますか?
非常に簡単に言うと、プログラムの状態を予測できなくなります。
詳しく説明すると、同じグローバル変数を使用するオブジェクトが2つあるとします。いずれかのモジュール内でランダム性のソースを使用していない場合、メソッドを実行する前にシステムの状態がわかっていれば、特定のメソッドの出力を予測(したがってテスト)できます。
ただし、いずれかのオブジェクトのメソッドが、共有グローバル状態の値を変更する副作用を引き起こした場合、もう一方のオブジェクトでメソッドを実行すると、開始状態がわかりません。メソッドを実行したときにどのような出力が得られるかを予測できなくなったため、テストすることはできません。
学術レベルでは、これはそれほど深刻に聞こえないかもしれませんが、コードを単体テストできることは、その正しさ(または少なくとも目的への適合性)を証明するプロセスの主要なステップです。
現実の世界では、これはいくつかの非常に深刻な結果をもたらす可能性があります。グローバルデータ構造にデータを入力する1つのクラスと、そのデータ構造のデータを使用し、その状態を変更するか、プロセスでデータを破棄する別のクラスがあるとします。 populatorクラスが完了する前にプロセッサクラスがメソッドを実行すると、その結果、プロセッサクラスは処理するデータが不完全になる可能性があり、populatorクラスが処理していたデータ構造が破損または破壊される可能性があります。これらの状況でのプログラムの動作は完全に予測不可能になり、おそらく壮大な損失につながります。
さらに、グローバルな状態はコードの読みやすさを損ないます。コードに明示的に導入されていない外部依存関係がコードにある場合、コードの保守を担当する人は誰でも、コードの出所を見つけるためにコードを探しに行く必要があります。
どのような選択肢が存在するかについては、グローバル状態をまったく持たないことは不可能ですが、実際には、通常、グローバル状態を、他のすべてをラップする単一のオブジェクトに制限することが可能であり、スコープルールに依存して参照することはできません使用している言語の特定のオブジェクトに特定の状態が必要な場合は、コンストラクターに引数として渡すか、セッターメソッドで明示的に要求する必要があります。これは、依存性注入と呼ばれます。
使用している言語のスコープルールにより、すでにアクセスできる状態で渡すのはばかげているように見えるかもしれませんが、その利点は非常に大きいです。誰かがコードを分離して見れば、それが必要とする状態とそれがどこから来るのかは明らかです。また、コードモジュールの柔軟性、したがってさまざまなコンテキストで再利用する機会に関して、大きなメリットがあります。状態が渡され、状態への変更がコードブロックに対してローカルである場合、任意の状態を渡し(それが正しいデータ型である場合)、コードで処理できます。このスタイルで記述されたコードは、簡単に交換できる緩く関連付けられたコンポーネントのコレクションのように見える傾向があります。モジュールのコードは、どのようにそれを処理するかだけで、状態がどこから来るのかを気にする必要はありません。状態をコードブロックに渡す場合、そのコードブロックは分離して存在できますが、グローバル状態に依存している場合はそうではありません。
他の多くの理由があり、国家の通過が地球規模の国家に依存するよりもはるかに優れている理由はたくさんあります。この答えは決して包括的なものではありません。なぜグローバルな状態が悪いのかについての本全体を書くことができるでしょう。
可変グローバル状態は多くの理由で悪です。
変更可能なグローバル状態の代替:
grep
にして、どの関数がそれを使用しているかを見つけることができるよりもはるかに保守上の悩みです。参照を必要とする関数に参照を渡すだけです。それほど難しくありません。
「状態」と言った場合、通常は「変更可能な状態」を意味します。そして、グローバルな変更可能な状態は完全に悪です。なぜなら、それはプログラムの任意の部分が他の部分に影響を与える可能性がある(グローバル状態を変更することによって)だからです。
未知のプログラムのデバッグを想像してみてください。関数Aは特定の入力パラメーターに対して特定の動作をしますが、同じパラメーターに対しては動作が異なる場合があります。グローバル変数xを使用していることがわかります。
xを変更する場所を探し、それを変更する場所が5つあることを確認します。次に、関数Aがどのような場合に何を行うかを確認してください。
あなたは自分の質問に答えました。それらは「乱用」されると管理するのが困難ですが、それらを封じ込める方法を知っている人が適切に使用されると、有用であり、[ある程度]予測できます。グローバルのメンテナンスやグローバルへの変更は、通常、悪夢であり、アプリケーションのサイズが大きくなるにつれて悪化します。
グローバルが唯一のオプションであり、簡単な修正であるという違いを理解できる経験豊富なプログラマcanを使用すると、問題が最小限になります。しかし、それらの使用に伴って発生する可能性のある無限の可能性のある問題は、それらの使用に対するアドバイスを必要とします。
編集:私の意味を明確にするために、グローバルは本質的に予測不可能です。予測不能なものと同様に、予測不能性を抑えるための対策を講じることができますが、実行できることには常に制限があります。これに加えて、比較的未知の変数を処理する必要があるプロジェクトに参加する新しい開発者の面倒、グローバルの使用に対する推奨事項は理解できるはずです。
シングルトンには多くの問題があります-私の頭の中で2つの最大の問題があります。
単体テストは問題になります。グローバルな状態は、あるテストから次のテストへと汚染される可能性があります
「1つだけ」というハードルールを適用します。これは、変更できなかったとしても、突然適用されます。次に、グローバルにアクセス可能なオブジェクトを使用したユーティリティコード全体を変更する必要があります。
そうは言っても、ほとんどのシステムにはビッググローバルオブジェクトが必要です。これらは、大きくて高価なアイテム(例:データベース接続マネージャー)、または広範囲にわたる状態情報(ロック情報など)を保持するアイテムです。
シングルトンの代わりに、起動時にこれらのビッググローバルオブジェクトを作成し、このオブジェクトにアクセスする必要のあるすべてのクラスまたはメソッドにパラメーターとして渡すことができます。
ここでの問題は、あなたが「小包を渡す」という大きなゲームに終わることです。コンポーネントとその依存関係のグラフがあり、一部のクラスは他のクラスを作成し、生成されたコンポーネント(または生成されたコンポーネントのコンポーネント)がそれらを必要とするために、それぞれが一連の依存関係コンポーネントを保持する必要があります。
新しいメンテナンスの問題が発生します。例:突然、「WidgetFactory」コンポーネントのグラフの奥に、モックアウトしたいタイマーオブジェクトが必要になります。ただし、「WidgetFactory」は「WidgetCreationManager」の一部である「WidgetBuilder」によって作成され、1つだけが実際に使用する場合でも、このタイマーオブジェクトを認識する3つのクラスが必要です。あなたは自分をあきらめてシングルトンに戻したいと思っていることに気づき、このタイマーオブジェクトをグローバルにアクセス可能にするだけです。
幸いなことに、これはまさに依存性注入フレームワークによって解決される問題です。作成する必要のあるクラスをフレームワークに伝えるだけで、リフレクションを使用して依存関係グラフがわかり、必要なときに各オブジェクトが自動的に構築されます。
したがって、要約すると、シングルトンは不適切であり、代替案は依存性注入フレームワークを使用することです。
キャッスルウィンザーをたまたま使用しましたが、あなたは選択の自由を失います。利用可能なフレームワークのリストについては、2008年から このページ を参照してください。
まず最初に、依存性注入が「ステートフル」であるためには、シングルトンを使用する必要があるため、これが何らかの形で代替案であると言っている人々は誤っています。人々は常にグローバルコンテキストオブジェクトを使用しています...たとえば、セッションステートでさえ、本質的にグローバル変数です。依存性注入によるかどうかに関係なくすべてを渡すことが常に最良の解決策とは限りません。現在、大量のグローバルコンテキストオブジェクト(IoCコンテナーを介して挿入されたシングルトン)を使用する非常に大規模なアプリケーションで作業しており、デバッグが問題になることはありません。特にイベントドリブンアーキテクチャでは、変更されたものを渡すのではなく、グローバルコンテキストオブジェクトを使用することが推奨されます。あなたが尋ねる人に依存します。
あらゆるものが悪用される可能性があり、それはアプリケーションのタイプにも依存します。たとえば、Webアプリで静的変数を使用することは、デスクトップアプリとはまったく異なります。グローバル変数を回避できる場合は、そうする必要がありますが、場合によっては、それらの用途があります。少なくとも、グローバルデータが明確なコンテキストオブジェクトに含まれていることを確認してください。デバッグに関する限り、コールスタックと一部のブレークポイントは解決できません。
グローバル変数を盲目的に使用することは悪い考えであることを強調したいと思います。関数は再利用可能である必要があり、データがどこから取得されるかを気にする必要はありません。グローバル変数を参照すると、関数が特定のデータ入力に結合されます。これが渡される必要がある理由であり、依存関係の注入が役立つ理由ですが、(シングルトンを介して)集中化されたコンテキストストアを処理しています。
ところで、一部の人々は、Linqの作成者を含め、依存性注入は悪いと思っていますが、それでも、私を含め、人々がそれを使用するのを妨げるものではありません。最終的に経験はあなたの最高の教師になります。ルールに従う時とそれらを破る時があります。
ここで他のいくつかの答えはmutableとimmutableglobalを区別するため状態、私はevenimmutableglobal variables/settings is an ananyanceであると主張します。
質問について考えてみましょう:
...たとえば、システムフォルダーのパスやアプリケーション全体のデータベース資格情報など、アプリケーションのグローバル構成が必要だとします。 ...
そう、小さなプログラムの場合、これはおそらく問題ではありませんが、少し大きい大きなシステムのコンポーネントでこれを行うとすぐに、自動化された書き込みすべてのテスト(同じプロセス内で実行される)が同じグローバル構成値で動作する必要があるため、テストは突然難しくなります。
すべての構成データが明示的に渡される場合、コンポーネントのテストがはるかに簡単になり、複数のテストのグローバル構成値bootstrapを並列で行う方法を心配する必要はありません。
なぜグローバルステートはそんなに悪いのですか?
Mutableグローバル状態は悪です。なぜなら、私たちの脳が一度に複数のパラメーターを考慮に入れ、それらをタイミングの観点と値の観点の両方から組み合わせて何かに影響を与える方法を理解するのは非常に難しいためです。
したがって、プログラムの実行中に変更される外部の理由がいくつかあるオブジェクトのデバッグまたはテストは、非常に困難です。何十ものこれらのオブジェクトをまとめて考える必要がある場合はもちろんです。
実際のアプリケーションでは、状態は避けられません。好きなようにまとめることができますが、スプレッドシートにはセル内のデータが含まれている必要があります。インターフェースとしての機能のみでセルオブジェクトを作成できますが、セルのメソッドを呼び出してデータを変更できる場所の数は制限されません。オブジェクト階層全体を構築してインターフェースを非表示にしようとすると、コードの他の部分できないデフォルトでデータを変更できます。それは、それを含むオブジェクトへの参照が勝手に渡されるのを防ぎません。また、それだけでは同時実行性の問題が解消されません。データへのアクセスを拡大することは困難になりますが、実際にはグローバルで認識されている問題が解消されるわけではありません。誰かが状態の一部を変更したい場合、グローバルな状態または複雑なAPIを介してそれを実行します(後者は阻止せず、阻止するだけです)。
グローバルストレージを使用しない本当の理由は、名前の衝突を避けるためです。同じグローバル名を宣言する複数のモジュールをロードする場合、未定義の動作(単体テストがパスするためデバッグが非常に難しい)またはリンカーエラー(C-リンカーが警告するか、これで失敗するのでしょうか?).
コードを再利用したい場合は、別の場所からモジュールを取得し、同じ名前のモジュールを使用しているために誤ってグローバルに踏まないようにする必要があります。または、幸運でエラーが発生した場合、コードの1つのセクションのすべての参照を変更して衝突を防ぐ必要はありません。
安全で堅牢なシステム設計のために設計された言語は、グローバルmutable状態を完全に取り除くことがよくあります。 (おそらく、これはグローバルがないことを意味します。不変オブジェクトはある意味で、状態遷移を持たないため、実際にはステートフルではありません。)
Joe-E は1つの例であり、David Wagnerがその決定を次のように説明しています。
オブジェクトにアクセスできる人の分析と最小特権の原則は、機能がグローバル変数に格納されている場合、両方とも破壊され、プログラムのどの部分でも読み取り可能になる可能性があります。オブジェクトがグローバルに利用可能になると、分析の範囲を制限することはできなくなります。オブジェクトへのアクセスは、プログラム内のどのコードからも差し控えることができない特権です。 Joe-Eは、グローバルスコープに機能がなく、不変のデータのみが含まれていることを確認することで、これらの問題を回避します。
それについて考える一つの方法は
したがって、グローバルに変更可能な状態では、
グローバルな変更可能な状態は DLL hell に似ています。時間の経過とともに、大規模なシステムのさまざまな部分には、共有されている可変状態の部分とは微妙に異なる動作が必要になります。 DLL地獄と共有された変更可能な状態の不整合を解決するには、異種チーム間の大規模な調整が必要です。これらの問題は、最初にグローバルな状態が適切にスコープ指定されていれば発生しません。
1つには、シングルトンの場合とまったく同じ問題が発生する可能性があります。今日の「私が1つだけ必要なグローバルなもの」のように見えるものは、突然、将来もっと必要なものに変わります。
たとえば、システム全体に対して1つのグローバル構成が必要なため、今日、このグローバル構成システムを作成します。数年後、別のシステムに移植すると、誰かが「一般的なグローバル構成が1つとプラットフォーム固有の構成が1つあれば、うまくいくかもしれません」と言います。突然あなたはあなたのグローバル構造をグローバルではなくするためにこのすべての仕事をしているので、あなたは複数のものを持つことができます。
(これはランダムな例ではありません...これは私が現在いるプロジェクトの設定システムで起こりました。)
非グローバルなものを作成するコストは通常取るに足らないものであることを考えると、そうするのはばかげています。あなたはただ未来の問題を生み出しています。
不思議なことに、もう1つの問題は、アプリケーションが「グローバル」ではないため、アプリケーションのスケーリングが困難になることです。グローバル変数のスコープはプロセスです。
複数のプロセスを使用したり、複数のサーバーで実行したりして、アプリケーションをスケールアップしたい場合はできません。少なくとも、すべてのグローバルを除外して、それらを他のメカニズムで置き換えるまでは。
グローバルはそれほど悪くありません。他のいくつかの回答で述べられているように、それらの本当の問題は、今日、あなたのglobalフォルダパスが、明日、いくつかのうちの1つになる可能性があることです。または何百もの。簡単な1回限りのプログラムを作成している場合は、簡単な場合はグローバルを使用してください。ただし、一般的には、1つだけが必要だと思っている場合でも、複数を許可する方法があります。突然twoデータベースと通信する必要がある大規模で複雑なプログラムを再構築する必要があるのは楽しいことではありません。
しかし、それらは信頼性を損なうことはありません。プログラム内の多くの場所から参照されているAnyデータは、予期せず変更された場合に問題を引き起こす可能性があります。列挙子は、列挙しているコレクションが列挙の途中で変更されると窒息します。イベントキューイベントは互いにトリックを再生できます。スレッドは常に大混乱を引き起こす可能性があります。ローカル変数または不変フィールドでないものはすべて問題です。グローバルはこの種の問題ですが、非グローバルにすることで修正することはできません。
ファイルに書き込もうとしていて、フォルダーパスが変更された場合、変更と書き込みを同期させる必要があります。 (問題が発生する可能性のある1000のことの1つとして、パスを取得すると、そのディレクトリが削除され、フォルダパスが適切なディレクトリに変更され、削除されたディレクトリに書き込もうとします。)フォルダパスはグローバルであるか、プログラムが現在使用している1000のうちの1つです。
キューのさまざまなイベント、さまざまなレベルの再帰、またはさまざまなスレッドからアクセスできるフィールドには、実際の問題があります。単純にするため(そして単純化するため):ローカル変数は適切で、フィールドは不適切です。しかし、以前のグローバルはまだフィールドになるので、この(非常に重要ですが)この問題はグローバルフィールドの善または悪のステータスには適用されません。
追加:マルチスレッドの問題:
(イベントキューや再帰呼び出しでも同様の問題が発生する可能性がありますが、マルチスレッド化は明らかに最悪です。)次のコードを検討してください。
if (filePath != null) text = filePath.getName();
filePath
がローカル変数またはなんらかの定数である場合、filePath
がnullであるため、プログラムの実行時にnotが失敗します。チェックは常に機能します。他のスレッドはその値を変更できません。 その他の場合、保証はありません。私がJavaでマルチスレッドプログラムを書き始めたとき、私はいつもこのような行でNullPointerExceptionsを受け取りました。 Any他のスレッドはいつでも値を変更でき、多くの場合変更されます。他のいくつかの答えが指摘するように、これはテストに深刻な問題を引き起こします。上記のステートメントは10億回も機能し、広範囲にわたる包括的なテストを経て、本番環境で1度爆発します。ユーザーは問題を再現することはできません。また、ユーザーが何かを見ていると確信し、それを忘れてしまうまで、問題は再発しません。
グローバルには間違いなくこの問題があり、完全に排除するか、定数またはローカル変数で置き換えることができる場合、それは非常に良いことです。ステートレスコードをWebサーバーで実行している場合は、可能です。通常、マルチスレッドの問題はすべてデータベースで発生します。
ただし、プログラムでユーザーアクションから次のユーザーアクションまでを記憶する必要がある場合は、実行中のスレッドからアクセスできるフィールドがあります。グローバルをそのような非グローバルフィールドに切り替えても、信頼性は向上しません。
グローバル変数が良いか悪いかは分からないが、グローバルステートを使用していない場合は、おそらく多くのメモリを浪費しているということを説明する。特に、クラスを使用してそれらの依存関係をフィールドに格納する場合。
グローバルな状態の場合、そのような問題はなく、すべてがグローバルです。
例:次のシナリオを想像してください:「ボード」と「タイル」のクラスで構成される10x10グリッドがあります。
OOPのようにしたい場合は、おそらく「ボード」オブジェクトを各「タイル」に渡します。「タイル」には、座標を格納する2つの「バイト」タイプフィールドがあるとしましょう。 。1つのタイルに対して32ビットマシンで必要なメモリの合計は、(1 + 1 + 4 = 6)バイトになります。x座標用に1、y座標用に1、ボードへのポインタ用に4となります。これにより、合計600になります。 10x10タイル設定のバイト
ボードがグローバルスコープにある場合、各タイルからアクセス可能な単一のオブジェクトは、各タイルごとに2バイトのメモリ(xおよびy座標バイト)を取得するだけで済みます。これは200バイトしか与えません。
したがって、この場合、グローバル状態のみを使用すると、メモリ使用量の1/3が得られます。
これは、他のものに加えて、グローバルスコープがC++のような(比較的)低レベルの言語にまだ残っている理由だと思います
すべてのグローバルな状態を簡単に確認してアクセスできる場合、プログラマーは必ずそうすることになります。得られるのは暗黙のうちにあり、依存関係を追跡するのが困難です(int blahblahは、配列fooが何でも有効であることを意味します)。基本的に、すべてを個別にいじることができるため、プログラムの不変条件を維持することはほぼ不可能です。 someIntにはotherIntとの関係があります。これは管理が難しく、いつでも直接変更できるかどうかを証明することが困難です。
とはいえ、それは可能ですが(一部のシステムではそれが唯一の方法であったときに遡ります)、それらのスキルは失われます。彼らは主にコーディングと命名規則を中心に展開しています-フィールドは正当な理由で進んでいます。コンパイラーとリンカーは、クラス/モジュールの保護された/プライベートなデータの不変条件をチェックする作業を、マスタープランに従ってソースを読み取るために人間に依存するよりも優れています。
グローバルな状態で考慮すべきいくつかの要因があります。
グローバルが多いほど、重複が発生する可能性が高くなり、重複が同期しなくなったときに問題が発生します。すべてのグローバルをあなたの虚偽の人間の記憶に保つことは、必要であり苦痛の両方になります。
イミュータブル/書き込みは1回で十分ですが、初期化シーケンスエラーに注意してください。
可変グローバルは不変グローバルと間違われることが多い…
グローバルを効果的に使用する関数には、追加の「隠し」パラメーターがあり、リファクタリングが困難になります。
グローバルな状態は悪いことではありませんが、確かなコストがかかります。利益がコストを上回っているときに使用してください。