클라이언트가 우리의 불변식을 깨뜨리려 혈안이 되어 있다고 가정하고 방어적으로 프로그래밍을 해야 한다.
Period.class
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 getStart() {
return start;
}
public Date getEnd() {
return end;
}
}
start와 end가 한번 값이 정해지면 변하지 않도록 의도된 클래스이다.
하지만 Date 클래스가 가변이기 때문에 불변식을 깨뜨릴 수 있다.
아래의 코드를 보면 end의 값을 수정할 수 있다.
public static void main(String[] args) {
Date start = new Date();
Date end = new Date();
Period period = new Period(start, end);
end.setTime(1);
period.getEnd().setTime(10);
}
불변 Period.class
final class Period {
private final Date start;
private final Date end;
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if (start.compareTo(end) > 0) throw new IllegalArgumentException("start가 end보다 늦다.");
}
public Date getStart() {
return new Date(start.getTime());
}
public Date getEnd() {
return new Date(end.getTime());
}
}
방어적 복사를 통해 불변 클래스로 만들 수 있다.
생성자에서 받은 매개변수는 각각 방어적으로 복사해 start와 end에 넣어준다.
생성자에서 방어적 복사를 clone 메서드를 사용하지 않는 점도 특징이다. Date 클래스는 final이 아니므로 상속이 가능한데, 악의를 가진 하위 클래스를 반환할 수 있기 때문이다.
추가로 주목할 것은 복사 후에 유효성 검사를 하고 있는데 검사시점/사용시점(TOCTOU) 공격을 막기 위함이다.
멀티스레드 환경이라면 원본 객체의 유효성을 검사한 후 복사본을 만드는 그 찰나의 취약한 순간에 다른 스레드가 원본 객체를 수정할 위험이 있기 때문이다.
접근자 메서드 또한 방어적 복사본을 반환하도록 하여 불면 클래스로 만든다.
참고로 접근자 메서드 같은 경우 반환 객체가 Date가 확실하기 때문에 clone 메서드를 사용 가능하다.
방어적 복사는 성능 저하가 따르고, 또 항상 쓸 수 있는 것도 아니다.
호출자가 컴포넌트 내부를 수정하지 않으리라 확신하면 방어적 복사를 생략할 수 있다.
대신 문서화로 수정하지 말아야 함을 명시한다.
'Java > Effective Java' 카테고리의 다른 글
[아이템 49] 매개변수가 유효한지 검사하라 (0) | 2022.02.14 |
---|---|
[아이템 26] 로 타입(raw type)은 사용하지 말라 (0) | 2022.02.07 |
[아이템 8] finalizer와 cleaner 사용을 피하라 (0) | 2022.01.01 |
[아이템 7] 다 쓴 객체 참조를 해제하라 (0) | 2021.12.23 |
[아이템 6] 불필요한 객체 생성을 피하라 (0) | 2021.12.21 |
댓글