8

防御的なゲッター/セッターを実際に使用する人はいますか? 私にとっては、99% の確率で、別のオブジェクトに設定したオブジェクトが同じオブジェクト参照のコピーになることを意図しており、それに加えた変更は、それが設定されていたオブジェクトでも行われることを意図しています。あなたsetDate ( Date dt )と後で dt を変更します。誰が気にしますか? プリミティブと Date のような単純なものだけを持つ基本的な不変データ Bean が必要でない限り、私はそれを使用しません。

クローンに関しては、コピーがどれだけ深いか浅いかという問題があるため、オブジェクトをクローンしたときに何が出るかを知ることは、一種の「危険」に思えます。clone()別のスレッド (つまり、セッション内の同じオブジェクトにアクセスする別の HTTP 要求) がオブジェクトを変更している可能性があるため、オブジェクトの現在の状態をコピーするために、1 回か 2 回しか使用したことがないと思います。

編集 - 私が以下に行ったコメントは、より多くの質問です:

しかし、繰り返しになりますが、あなたは日付を変更したので、それはあなた自身のせいであり、「防御的」という用語の全体的な議論です. 小規模から中規模の開発者グループの間ですべてのアプリケーション コードが自分の管理下にある場合、オブジェクトのコピーを作成する代わりに、クラスを文書化するだけで十分でしょうか? または、セッター/ゲッターを呼び出すときに何かがコピーされていないと常に想定する必要があるため、これは必要ありませんか?

4

8 に答える 8

12

Josh Bloch の効果的な Java から:

クラスのクライアントがその不変条件を破壊するために最善を尽くすという前提で、防御的にプログラミングする必要があります。誰かがシステムのセキュリティを破ろうとした場合、これは実際に当てはまるかもしれませんが、API を使用するプログラマー側の正直なミスに起因する予期しない動作にクラスが対処しなければならない可能性が高くなります。いずれにせよ、行儀の悪いクライアントに直面しても堅牢なクラスを作成するために時間を割く価値はあります。

項目 24: 必要に応じて防御コピーを作成する

于 2009-05-29T15:47:32.357 に答える
5

これは重要な質問です。基本的に、ゲッターを介して、または別のクラスのセッターを呼び出して、他のクラスに与えるクラスの内部状態について考える必要があります。たとえば、次のようにします。

Date now = new Date();
someObject.setDate(now);
// another use of "now" that expects its value to not have changed

次に、2 つの問題が発生する可能性があります。

  1. someObject" " の値を変更する可能性がありますnow。つまり、後でその変数を使用するときに、上記のメソッドが予想とは異なる値を持つ可能性があります。
  2. now" " を渡した後でsomeObjectその値を変更しsomeObject、防御的なコピーを作成しなかった場合は、 の内部状態を変更したことになりますsomeObject

コードのクライアントが誰であるかに応じて、両方のケースから保護するか、何が許可され、何が許可されないかを文書化する必要があります。もう 1 つのケースは、クラスに があり、それ自体Mapにゲッターを提供する場合ですMap。がオブジェクトMapの内部状態の一部であり、オブジェクトが の内容を完全に管理することを期待している場合はMap、 を決して外に出してはなりませんMap。マップに getter を提供する必要がある場合は、Collections.unmodifiableMap(myMap)代わりに を返しますmyMap。ここでは、コストがかかる可能性があるため、おそらくクローンまたは防御コピーを作成したくないでしょう。Map を変更できないようにラップして返すことで、内部状態が別のクラスによって変更されるのを防ぎます。

多くの理由で、clone()多くの場合、適切なソリューションではありません。いくつかのより良い解決策は次のとおりです。

  1. ゲッターの場合:
    1. a を返す代わりに、クライアント コードが必要なことを実行できるようにするものに、 または にMapのみIterators を返します。つまり、本質的に内部状態の読み取り専用ビューである何かを返すか、またはkeySetMap.Entry
    2. 次のような不変のラッパーにラップされた可変状態オブジェクトを返しますCollections.unmodifiableMap()
    3. を返すのではなく、キーを受け取り、マップから対応する値を返すメソッドをMap提供します。getすべてのクライアントがそれを使用して値を取得する場合は、クライアントにそれ自体Mapを提供しないでください。代わりに、のメソッドMapをラップする getter を提供してください。Mapget()
  2. コンストラクターの場合:
    1. オブジェクトコンストラクターでコピーコンストラクターを使用して、渡された変更可能なもののコピーを作成します。
    2. 変更可能な量ではなく、可能な場合はコンストラクターの引数として不変の量を取るように設計します。new Date().getTime()オブジェクトではなく、たとえばによって返される long を取得することが理にかなっている場合がありDateます。
    3. できるだけ多くの状態を作成しますが、オブジェクトは変更可能であり、配列は変更可能であることfinalを忘れないでください。finalfinal

どのような場合でも、誰が可変状態を「所有」しているかについて疑問がある場合は、ゲッター、セッター、またはコンストラクターについて文書化してください。どこかに文書化してください。

悪いコードの簡単な例を次に示します。

import java.util.Date;

public class Test {
  public static void main(String[] args) {
    Date now = new Date();
    Thread t1 = new Thread(new MyRunnable(now, 500));
    t1.start();
    try { Thread.sleep(250); } catch (InterruptedException e) { }
    now.setTime(new Date().getTime());   // BAD!  Mutating our Date!
    Thread t2 = new Thread(new MyRunnable(now, 500));
    t2.start();
  }

  static public class MyRunnable implements Runnable {
    private final Date date;
    private final int  count;

    public MyRunnable(final Date date, final int count) {
      this.date  = date;
      this.count = count;
    }

    public void run() {
      try { Thread.sleep(count); } catch (InterruptedException e) { }
      long time = new Date().getTime() - date.getTime();
      System.out.println("Runtime = " + time);
    }
  }
}

各ランナブルが 500 ミリ秒スリープするはずですが、代わりに間違った時間情報が取得されます。コンストラクターを変更して防御的なコピーを作成する場合:

    public MyRunnable(final Date date, final int count) {
      this.date  = new Date(date.getTime());
      this.count = count;
    }

その後、正しい時刻情報を取得します。これは簡単な例です。複雑な例をデバッグする必要はありません。

注:状態を適切に管理できなかった場合の一般的なConcurrentModificationException結果は、コレクションを反復処理する場合です。

防御的にコーディングする必要がありますか? エキスパートプログラマーの同じ小さなチームが常にプロジェクトの作成と保守を行うこと、プロジェクトの詳細の記憶を保持するために継続的に作業すること、同じ人々がプロジェクトのために作業することを保証できる場合.プロジェクトの存続期間が短く、プロジェクトが「大規模」になることは決してないということであれば、そうしなくても済むかもしれません。しかし、最もまれなケースを除いて、防御的プログラミングのコストは大きくなく、メリットは大きいです。さらに、防御的なコーディングは良い習慣です。変更可能なデータを、それを持ってはいけない場所に渡すという悪い習慣の開発を助長したくはありません。これいつかあなたをかみます。もちろん、これはすべて、プロジェクトに必要な稼働時間によって異なります。

于 2009-05-29T17:02:29.137 に答える
3

これらの問題の両方について、ポイントは状態の明示的な制御です。ほとんどの場合、これらのことを考えずに「逃げる」ことができます。これは、アプリケーションが大きくなり、状態とそれがオブジェクト間でどのように伝播するかについての推論が難しくなるにつれて、あまり当てはまらない傾向があります。

これを制御する必要がある主な理由については既に述べました。別のスレッドがアクセスしている間、データを安全に使用できるようにするためです。また、次のような間違いを犯しやすいです。

class A {
   Map myMap;
}


class B {
   Map myMap;
   public B(A a)
   {
        myMap = A.getMap();//returns ref to A's myMap
   }
    public void process (){ // call this and you inadvertently destroy a
           ... do somethign destructive to the b.myMap... 
     }
}

重要なのは、常にクローンを作成したいということではありません。それは愚かで高価です。要点は、そのようなことが適切な場合について一律に述べることではありません。

于 2009-05-29T15:28:49.197 に答える
1

Clone() を使用してオブジェクトの状態をユーザーのセッションに保存し、編集中に元に戻すことができるようにしました。単体テストでも使用しました。

于 2009-05-29T15:57:31.627 に答える
0

clone() メソッドは好きではありません。型キャストが常に必要だからです。このため、ほとんどの場合、コピー コンストラクターを使用します。それが何をするか (新しいオブジェクト) をより明確に述べており、その動作やコピーの深さを細かく制御できます。

私の仕事では、防御的プログラミングについて心配することはありませんが、それは悪い習慣です。しかし、ほとんどの場合は問題ありませんが、詳しく見てみようと思います。

于 2009-05-29T19:27:32.990 に答える
0

私は次の練習を使い始めました:

  1. クラスにコピー コンストラクターを作成しますが、それらを保護します。これは、new 演算子を使用してオブジェクトを作成すると、派生オブジェクトを操作するときにさまざまな問題が発生する可能性があるためです。

  2. 次のように Copyable インターフェイスを作成します。

     パブリック インターフェイス Copyable<T> {
            public T copy();
     }

Copyable を実装するクラスの copy メソッドで、保護されたコピー コンストラクターを呼び出すようにします。その後、派生クラスは super.Xxx(obj_to_copy); を呼び出すことができます。基本クラスのコピー コンストラクターを活用し、必要に応じて機能を追加します。

Java が共変の戻り値の型をサポートしているという事実により、これが機能します。派生クラスは単に copy() メソッドを適切に実装し、特定のクラスのタイプ セーフな値を返します。

于 2010-04-08T23:13:59.073 に答える
0

「ディフェンシブ コピーの議論」で私がいつも見逃していることの 1 つは、パフォーマンスの側面です。その議論は、私見であり、パフォーマンスと読みやすさ/セキュリティ/堅牢性の完璧な例です。

ディフェンス コピーはロブバスト オードに最適です。ただし、アプリのタイム クリティカルな部分で使用すると、パフォーマンスに重大な問題が生じる可能性があります。最近、データ ベクトルがそのデータを double[] 値に格納するという議論がありました。getValues() は values.clone() を返しました。私たちのアルゴリズムでは、getValues() がさまざまなオブジェクトに対して呼び出されました。なぜこの単純なコードの実行に時間がかかるのか疑問に思ったとき、コードを調べました - 戻り値.clone() を戻り値に置き換えたところ、突然、合計実行時間が元の 1/10 未満に短縮されました。価値。ええと、防御をスキップすることを選択したと言う必要はありません。

注: 私は一般的に防御的なコピーではありません。ただし、clone() するときは頭を使いましょう。

于 2009-09-07T08:45:04.993 に答える