우아한테크코스/공부

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

자바지기 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());
}

 

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

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

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

 

결론

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

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

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

반응형