외부의 의도치 않은 수정을 막기 위해, 방어적 복사본을 만들어라.
in Development on Java
클라이언트가 여러분의 불변식을 깨트리려 혈안이 되어 있다고 가정하고 방어적으로 프로그래밍해야 한다. 어떤 객체든 그 객체의 허락 없이는 외부에서 내부를 수정하는 일은 불가능하다. 하지만 주의를 기울이지 않으면 자기도 모르게 내부를 수정하도록 하는 경우가 생긴다.
외부의 의도치 않은 수정을 허용한 예시
문제점
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 + "after" + end);
}
}
public Date start() {
return start;
}
public Date end() {
return end;
}
...
}
얼핏 위 클래스는 불변처럼 보이고, 시작 시각이 종료 시각보다 늦을 수 없다는 불변식이 무리 없이 지켜질 것 같다. 하지만 Date가 가변이라는 사실을 이용하면 어렵지 않게 그 불변식을 깨트릴 수 있다. 밑의 코드를 보자.
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // 불변식인 줄 알았는데, p의 내부를 수정해버렸다.
해결책
- 외부 공격으로부터 Period 인스턴스의 내부를 보호하려면 생성자에서 받은 가변 매개변수 각각을 방어적으로 복사해야 한다.
- 접근자 메서드(필드에 접근하는 메서드)가 방어적 복사본을 반환하도록 만들어야 한다.
public 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 + "after" + end);
}
}
// 접근자 메서드
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
...
}
매개변수의 유효성을 검사 하기 전에 방어적 복사본을 만들고, 이 복사본으로 유효성을 검사한 점에 주목하자. 순서가 부자연스러워 보이겠지만 반드시 이렇게 작성해야 한다. 멀티스레딩 환경이라면 원본 객체의 유효성을 검사한 후 복사본을 만드는 그 찰나의 취약한 순간에 다른 스레드가 원본 객체를 수정할 위험이 있기 때문이다. 방어적 복사를 매개변수 유효성 검사 전에 수행하면 이런 위험에서 해방될 수 있다.
References
- Effective Java(이펙티브 자바) - 조슈아 블로크, 인사이트 → [아이템 50. 적시에 방어적 복사본을 만들라.]