우아한테크코스/공부
적시에 방어적 복사본을 만들라
자바지기
2022. 4. 4. 16:21
반응형
적시에 방어적 복사본을 만들라
자바는 안전한 언어다. 그러나 클라이언트가 불변식을 깨뜨리려 한다고 가정하고 방어적 프로그래밍을 해야 한다.
방어적 프로그래밍을 해야 하는 예:
public class Period {
private final Date start;
private final Date end;
public Period(Date start, Date end) {
if (start.compareTo(end) > 0) {
throw new IllegalArgumentException();
}
this.start = start;
this.end = end;
}
}
위의 클래스는 불변처럼 보인다. 하지만 Date가 가변이므로 어렵지 않게 불변식을 깨뜨릴 수 있다.
public static void main(String[] args) {
Date start = new Date();
Date end = new Date();
System.out.println(end); // Mon Apr 04 11:22:38 KST 2022
Period p = new Period(start, end);
end.setYear(78);
System.out.println(end); // Tue Apr 04 11:22:38 KST 1978
}
외부 공격으로부터 이 클래스 내부를 보호하려면 생성자에서 받은 가변 매개변수 각각을 방어적으로 복사해야 한다.
public 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 (this.start.compareTo(this.end) > 0) {
throw new IllegalArgumentException();
}
}
public Date start() {
return start;
}
public Date end() {
return end;
}
}
이때 방어적 복사본을 만들고, 이 복사본으로 유효성을 검사한 점에 주목!!
멀티스레딩 환경에서, 유효성을 검사하고 복사본을 만드는 순간 다른 스레드가 원본 객체를 수정할 수도 있기 때문에 복사본을 먼저 만든다.
방어적 복사 -> 복사본을 이용해 유효성 검사
매개변수가 제삼자에 의해 확장될 수 있는 타입이라면 clone을 사용해서 방어적 복사를 진행하면 안 된다.
위의 예시에서 Date 클래스는 final로 선언되지 않았다. 따라서 clone이 Date가 정의한 게 아닐 수 있다.
따라서 clone이 악의를 가진 하위 클래스를 반환할 수도 있다.
public class BadDate extends Date {
@Override
public Object clone() {
...
}
}
접근자 메서드가 가변 정보를 직접 드러나게 하면 안 된다.
public static void main(String[] args) {
Date start = new Date();
Date end = new Date();
System.out.println(end); // Mon Apr 04 17:47:19 KST 2022
Period p = new Period(start, end);
p.end().setYear(78);
System.out.println(p.end()); //Tue Apr 04 17:47:19 KST 1978
}
위와 같은 경우에 생성자에서의 공격은 막아낼 수 있지만 접근자에 대한 공격은 막을 수 없다.
따라서 다음과 같이 접근자가 가변 필드의 방어적 복사본을 반환하면 된다.
public Date start() {
return new Date(start.getTime());
}
public Date end() {
new Date(end.getTime());
}
내부 객체를 클라이언트에 건네줄 때 방어적 복사본을 건네주자.
항상 내부 객체가 외부에서 잠재적으로 변경될 수 있는지 생각해보아야 한다.
변경될 수 있는 객체라면 그 객체가 외부에서 임의로 변경되어도 그 클래스가 문제없이 동작할지를 따져보라.
결론
모든 작업에서 되도록 불변 객체들을 조합해 객체를 구성해야 방어적 복사를 할 일이 줄어든다.
그러나 복사 비용이 너무 크거나 클라이언트가 잘못 수정할 일이 없음을 신뢰한다면 방어적 복사 대신
해당 요소를 수정했을 때의 책임이 클라이언트에 있음을 문서화하자.
반응형