Item 39. 필요하다면 방어적 복사본을 만들라

  • 자바는 안전한 언어이다.....이유가 어렵다.

    • This means that in the absence of native methods it is immune to buffer overruns, array overruns, wild pointers, and other memory corruption errors that plague unsafe languages such as C and C++
  • 하지만, 안전하다 해도 완전한 자유는 아닌데, 클라이언트가 불변식(invariant)를 망가뜨리기 위해 최선을 다할 거라는 가정하에, 최대한 방어적으로 프로그래밍 해야한다.

  • 해당 책에서서는 생성자/접근자 에 대한 방어적 복사본을 만들라고 언급

객체 불변

// Broken "immutable" time period class
public final class Period {
 private final Date start;
 private final Date end;
​
 /**
  * @param start the beginning of the period
  * @param end the end of the period; must not precede start 
  * @throws IllegalArgumentException if start is after end
  * @throws NullPointerException if start or end is null
  */
 public Period(Date start, Date end) {
  if (start.compareTo(end) >0)
     throw new IllegalArgumentException(start + " after " + end);
  this.start = start;
  this.end = end;
 }
​
 public Date start() { return start; }
 public Date end() { return end; }
 ...  // Remainder omitted
}
  • 첫번째 공격! (생성자 공격)

    // Attack the internals of a Period instance
    Date start = new Date();
    Date end = new Date();
    Period p = new Period(start, end); 
    end.setYear(78); // Modifies internals of p!
    ​
    // Repaired constructor - makes defensive copies of parameters
    public Period(Date start, Date end) {
     this.start = new Date(start.getTime());
     this.end = new Date(end.getTime());
     if (this.start.compareTo(this.end) > 0)
      throw new IllegalArgumentException(start +" after "+ end);
    }
    
    • 인자의 유효성을 검사하기 전에, 방어적 복사본을 만들어서 유효성 검사는 복사본에 대해서 시행

    • 취약 구간(인자를 검사한 직후 복사본이 만들어지기 직전까지의 기간) 동안에 다른 스레드가 인자 변경하는 것을 막기 위한 것

    • defensive copies are made before checking the validity of the parameters (Item 38), and the validity check is performed on the copies rather than on the originals

    • 인자로 전달 된 객체의 자료형이 제3자가 계승할 수 있을 경우, 방어적 복사본을 만들 때 clone을 사용하지 않는다.

      필드가 final이 아니므로 이미 공격을 위해 설계된 객체의 하위가 반환 될 수 있기 때문.

  • 두번째 공격! (생성자가 아닌 접근자 공격)

    // Second attack on the internals of a Period instance
    Date start = new Date();
    Date end = new Date();
    Period p = new Period(start, end); 
    p.end().setYear(78); // Modifies internals of p!
    ​
    // Repaired accessors - make defensive copies of internal fields
    public Date start() {
     return new Date(start.getTime());
    }
    public Date end() {
     return new Date(end.getTime());
    }
    
    • 접근자의 경우는 생성자와 달리 clone을 사용 해도 됨.

    • 경험이 많은 개발자들은 new Date() 인 reference 자료형 대신 primitive Long 타입인 Date.getTime() 을 주로 사용함

방어적 복사

  • 계속 나오는 "방어적 복사"라는 내용은, 변경 불가능 클래스에서 만 쓰이진 않는다.

  • 클라이언트가 제공한 객체를 내부 자료구조에 반영하는 경우 (Set, Map,,, 자료구죠형 등), 물론 클라이언트에게 내부 컴포넌트를 반환할 때도 마찬가지.

  • 길이가 0이 아닌 배열도 항상 변경 가능. 방어적 복사를 만들거나 변경 불가능 뷰를 만들어 반환 할 것

  • 객체의 컴포넌트는 가능하다면 변경 불가능 객체를 사용 해야 함

요약하자면,

  • 클라이언트로 받거나 반환되는 변경 가능 컴포넌트는 반드시 방어적으로 복사해야 한다

  • 하지만 복사의 오버헤드가 크고, 클라이언트가 부적절하게 사용하지 않는다는 보장이 있다면 해당 사실만 문서에 명시하고 넘어갈 수 있다…

results matching ""

    No results matching ""