요약
•
생성자나 setter의 매개변수를 받을 때는 방어적 복사를 진행하고 복사본을 저장하자.
•
매개변수의 유효성 검사는 방어적 복사 이후 진행하자.
•
매개변수가 확장 가능한 타입이면 복사본을 받을 때 clone을 사용하지 말자.
•
접근자의 반환값도 방어적 복사를 해서 반환하자.
방어적으로 프로그래밍하기
•
자바는 네이티브 메서드를 사용하지 않으니 안전한 언어지만, 불변식을 깨뜨리는 게 불가능한 것은 아니기 때문에 가급적 최대한 방어적으로 프로그래밍 해야한다.
•
어떤 객체든 그 객체의 내부를 임의적으로 외부에서 수정하는 것을 막자.
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;
}
}
Java
복사
•
위 코드는 불변식처럼 보이지만, 아래와 같이 불변식을 깨뜨릴 수 있다.
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78);
Java
복사
•
이러한 외부 공격으로부터 내부를 보호하려면, 생성자에서 받은 매개변수 각각을 방어적으로 복사(defensive copy)하고 복사본을 사용해야 한다.
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 + "가 " + end + "보다 늦다.");
}
Java
복사
•
이처럼 작성하면 외부 공격으로부터 내부를 보호할 수 있다. 다만 매개변수의 유효성 검사를 방어적 복사 이후에 복사본을 통해 진행해야, 멀티스레딩 환경에서 복사본을 만드는 찰나의 순간에 다른 스레드가 원본 객체를 수정하는 일이 발생해도 내부를 보호할 수 있다. 이러한 공격을 검사시점/사용시점(TOCTOU, time-of-check time-of-use) 공격이라 부른다.
•
Date는 final이 아니므로 clone이 Date가 정의내린 게 아닐 수 있다. 이런 이유로 매개변수가 제 3자에 의해 확장될 수 있는 타입이라면 방어적 복사본을 만들 때 clone을 사용해서는 안된다.
•
위와 같은 이유로 접근자의 필드도 방어적 복사본을 반환하자.
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
Java
복사
•
LocalDateTime을 사용하면, 내부적으로 방어적 복사가 되어있기 때문에 방어적 복사 없이도 불변성을 깨뜨리지 않을 수 있다.