• 우리는 클라이언트가 자바의 불변식을 깨트리려 혈안이 되어 있다고 가정하고 방어적으로 프로그래밍 해야한다.
  • 어떤 객체는 그 객체의 허락 없이는 외부에서 내부를 수정하는 일이 불가능하게 해야한다.

예를 들어 기간을 표현하는 다음 클래스는 한번 값이 정해지면 변하지 않도록 할 생각이었다.

//불변식을 지키지 못한 클래스

public final class Period {
    private final Date start;
    private final Date end;

    public Period(Date start, Date end) {
        if(start.compareTo(end) > 0) {
            throw new IllegalArgumentException(start + " 가 " + end + " 보다 늦다.");
        }
        this.start = start;
        this.end = end;
    }
    public Date start() { return start; }
    public Date end() { return end; }
    // ... 생략

} 

Date는 가변이기 때문에 불변식처럼 보이는 이 객체를 깨트릴 수 있다.

//Period 인스턴스를 향한 공격
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // p의 내부를 수정함
  • Date 대신 불변인 Instant를 사용하면 해결 할 수 있다. (혹은 LocalDateTime 이나 ZoendDateTime)
  • Date는 낡은 API이기 때문에 새로운 코드를 작성할 때는 더 이상 사용하면 안된다.
  • 하지만 사용하게 된다면 생성자에서 받은 가변 매개변수 각각을 방어적으로 복사해 내부를 보호해야 한다.
public Perid(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
            (this.start + " 가 " + this.end + " 보다 늦다.");
    }
}
  • 이렇게 만들면 앞선 코드(end.setYear) 는 위협이 되지 않는다.
  • 매개변수의 유효성 검사( if문 )전에 방어적 복사본을 만들었고 이것으로 유효성 검사를 했기 때문이다.
  • 검사시점/사용시점 공격 = TOCTOU을 막을 수 있다.
  • 또한 매개변수가 제 3자에 의해 확장될 수 있는 타입이라면 방어적 복사본을 만들 때 clone을 사용하면 안된다.
//Period 인스턴스를 향한 공격
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
p.end().setYear(78); // 이렇게 변경이 또 된다.
  • 접근자가 가변 필드의 방어적 복사본을 반환하면 막을 수 있다.
public Date start() {
    return new Date(start.getTime());
}

public Date end() {
    return new Date(start.getTime());
}
  • 매개변수를 방어적으로 복사하는 목적이 불변 객체를 만들기 위해서만은 아니다.
  • 메소드는 생성자든 클라이언트가 제공한 객체의 참조를 내부의 자료구조에 보관해야 할 때면 항시 그 객체가 잠재적으로 변경될 수 있는지를 생각해야 한다.
  • 방어적 복사에는 성능 저하가 따르고, 또 항상 쓸 수 있는 것도 아니다.
  • 호출자가 컴포넌트 내부를 수정하지 않으리라 확신하면 생략 할 수 있다.

+ Recent posts