「static」キーワードを「final」キーワードなしで使用している場合、これは設計を慎重に検討するための合図となるはずです。変更可能な静的最終オブジェクトも同様に危険である可能性があるため、「最終」の存在でさえフリーパスではありません。
私は「ファイナル」なしで「スタティック」を見る時間の約85%を見積もるでしょう、それは間違っています。多くの場合、私はこれらの問題を隠蔽または非表示にする奇妙な回避策を見つけます。
静的な可変を作成しないでください。特にコレクション。一般に、コレクションは、それらが含まれているオブジェクトが初期化されるときに初期化され、それらが含まれているオブジェクトが忘れられたときにリセットまたは忘れられるように設計する必要があります。
静力学を使用すると、非常に微妙なバグが発生する可能性があり、エンジニアに何日も苦痛を強いることになります。私が知っているのは、私はこれらのバグを作成し、追跡したからです。
詳細については、次をお読みください...
Staticsを使用しないのはなぜですか?
テストの作成と実行、およびすぐには明らかではない微妙なバグなど、静的に関する多くの問題があります。
静的オブジェクトに依存するコードは簡単に単体テストすることはできず、静的オブジェクトは(通常)簡単にモック化することはできません。
静的を使用する場合、より高いレベルのコンポーネントをテストするためにクラスの実装を交換することはできません。たとえば、データベースからロードするCustomerオブジェクトを返す静的CustomerDAOを想像してください。これで、CustomerFilterクラスができました。これは、いくつかのCustomerオブジェクトにアクセスする必要があります。CustomerDAOが静的な場合、最初にデータベースを初期化して有用な情報を入力しないと、CustomerFilterのテストを作成できません。
また、データベースの作成と初期化には長い時間がかかります。私の経験では、DB初期化フレームワークは時間とともに変化します。つまり、データが変化し、テストが失敗する可能性があります。IEは、顧客1が以前はVIPでしたが、DB初期化フレームワークが変更され、顧客1がVIPでなくなったとしますが、テストは顧客1をロードするようにハードコーディングされました…
より良いアプローチは、CustomerDAOをインスタンス化し、構築時にCustomerFilterに渡すことです。(さらに優れたアプローチは、Springまたは別のInversion of Controlフレームワークを使用することです。
これを行うと、CustomerFilterTestで代替DAOをすばやくモックまたはスタブ化できるため、テストをより詳細に制御できます。
静的DAOがない場合、テストはより高速になり(db初期化なし)、信頼性が高くなります(db初期化コードが変更されても失敗しないため)。たとえば、この場合、テストに関する限り、顧客1がVIPであり、常にVIPになります。
テストの実行
一連の単体テストを一緒に実行すると(たとえば、継続的インテグレーションサーバーで)、静力学は実際の問題を引き起こします。あるテストから別のテストまで開いたままのネットワークソケットオブジェクトの静的マップを想像してください。最初のテストではポート8080でソケットを開く可能性がありますが、テストが終了したときにマップをクリアするのを忘れていました。これで、2番目のテストが起動したときに、ポート8080がまだ占有されているため、ポート8080の新しいソケットを作成しようとするとクラッシュする可能性があります。また、静的コレクション内のソケット参照は削除されず、(WeakHashMapを除いて)ガベージコレクションの対象とはならず、メモリリークが発生することも想像してください。
これは過度に一般化された例ですが、大規模なシステムでは、この問題は常に発生します。同じJVMで繰り返しソフトウェアの開始と停止を行う単体テストについては考えられませんが、これはソフトウェア設計の優れたテストであり、高可用性への願望がある場合は、注意が必要です。
これらの問題は、多くの場合、フレームワークオブジェクト(DBアクセス、キャッシング、メッセージング、ロギングレイヤーなど)で発生します。Java EEまたはいくつかの最高のフレームワークを使用している場合、おそらくこれらの多くを管理しますが、私のようにレガシーシステムを扱っている場合、これらのレイヤーにアクセスするためのカスタムフレームワークがたくさんあるかもしれません。
これらのフレームワークコンポーネントに適用されるシステム構成がユニットテスト間で変更され、ユニットテストフレームワークがコンポーネントを破棄して再構築しない場合、これらの変更は有効にならず、テストがそれらの変更に依存していると失敗します。
フレームワーク以外のコンポーネントでもこの問題の影響を受けます。OpenOrdersという静的マップを想像してください。いくつかの未処理注文を作成する1つのテストを記述し、それらがすべて正しい状態にあることを確認してから、テストを終了します。別の開発者は、必要な注文をOpenOrdersマップに入れる2番目のテストを記述し、注文の数が正確であることを表明します。個別に実行すると、これらのテストは両方とも成功しますが、スイートで一緒に実行すると失敗します。
さらに悪いことに、失敗はテストが実行された順序に基づいている可能性があります。
この場合、静的要素を回避することで、テストインスタンス間でデータが保持されるリスクを回避し、テストの信頼性を向上させます。
微妙なバグ
高可用性環境、またはスレッドが開始および停止される可能性のある場所で作業している場合、コードが本番環境で実行されているときにも、上記の単体テストスイートに関する同じ懸念が当てはまります。
静的オブジェクトを使用してデータを格納するのではなく、スレッドを処理する場合は、スレッドの起動フェーズ中に初期化されたオブジェクトを使用することをお勧めします。このようにして、スレッドが開始されるたびに、オブジェクトの新しいインスタンス(新しい構成となる可能性があります)が作成され、スレッドの1つのインスタンスから次のインスタンスへのデータの流出を防ぎます。
スレッドが停止しても、静的オブジェクトはリセットされず、ガベージコレクションも行われません。「EmailCustomers」というスレッドがあり、それが開始すると、静的なStringコレクションに電子メールアドレスのリストが入力され、各アドレスへの電子メールの送信を開始するとします。スレッドが何らかの理由で中断またはキャンセルされたため、高可用性フレームワークがスレッドを再起動するとします。次に、スレッドが起動すると、顧客のリストが再ロードされます。ただし、コレクションは静的であるため、前のコレクションのメールアドレスのリストを保持する場合があります。現在、一部のお客様は重複したメールを受け取る可能性があります。
余談:静的ファイナル
「static final」の使用は、技術的な実装の違いはありますが、Cの#defineと実質的にJavaで同等です。AC / C ++ #defineは、コンパイル前にプリプロセッサによってコードからスワップアウトされます。Javaの「静的ファイナル」は、スタックに常駐するメモリになります。このように、それは#defineよりもC ++の「静的const」変数に似ています。
概要
これが静力学が問題になるいくつかの基本的な理由の説明に役立つことを願っています。Java EEやSpringなどの最新のJavaフレームワークを使用している場合は、これらの状況の多くは発生しない可能性がありますが、レガシーコードの大きな本体で作業している場合は、はるかに頻繁になる可能性があります。