본문 바로가기
우아한테크코스/공부

적시에 방어적 복사본을 만들라

by 자바지기 2022. 4. 4.
반응형

적시에 방어적 복사본을 만들라

 

자바는 안전한 언어다. 그러나 클라이언트가 불변식을 깨뜨리려 한다고 가정하고 방어적 프로그래밍을 해야 한다.

 

방어적 프로그래밍을 해야 하는 예:

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());
}

 

내부 객체를 클라이언트에 건네줄 때 방어적 복사본을 건네주자.

항상 내부 객체가 외부에서 잠재적으로 변경될 수 있는지 생각해보아야 한다.

변경될 수 있는 객체라면 그 객체가 외부에서 임의로 변경되어도 그 클래스가 문제없이 동작할지를 따져보라.

 

결론

모든 작업에서 되도록 불변 객체들을 조합해 객체를 구성해야 방어적 복사를 할 일이 줄어든다.

그러나 복사 비용이 너무 크거나 클라이언트가 잘못 수정할 일이 없음을 신뢰한다면 방어적 복사 대신

해당 요소를 수정했을 때의 책임이 클라이언트에 있음을 문서화하자.

반응형

댓글