방어적으로 프로그래밍하라
항상 우리는 클라이언트가 우리의 불변식을 깨뜨리려고 혈안이 되어 있다고 가정하고 방어적으로 프로그래밍해야 한다.
아래와 같은 클래스를 불변식으로 만들려고 의도하고 코드를 작성하였다고 해보자.
위 클래스는 불변인가? 그렇지 않다.
기본적으로 final은 기본형인 int와 같은 타입은 변경이 불가능하도록 한다. 하지만 Date와 같은 가변 클래스는 변경이 가능하게 된다. 그 말인 즉슨 Date를 final로 선언하여도 Date 내부의 값을 변경할 수 있다는 뜻이다.
이러한 이유로 Date보다는 자바 8 이후에 등장한 Java.time.Instant나, LocalDateTIme과 같은 불변으로 정의된 클래스를 사용하는 것이 좋다.
하지만 현재 위 코드의 상태로 클라이언트로부터 Period 클래스를 불변으로 만들고 싶다면 아래와 같이 방어적으로 복사본을 생성하여 이용하면 된다.
Date의 생성자를 통하여 start의 내부 정보만을 가지고 새로운 객체를 선언하여 그것을 Period의 필드로 정의한 것을 볼 수 있다.
이렇게 설계하게 된다면, 사용자가 외부에서 전달한 파라미터의 start나 end를 변경하여도 Period 클래스의 필드 값인 start와 end는 변하지 않게 된다.
또한 방어적 복사본을 만드는 절차는 매개변수의 제약 조건의 시점보다 앞서야 한다. 이는 멀티 스레딩 환경에서 고려해야할 사항이다. 만약 우리가 제약 조건을 확인하는 코드를 복사본 생성 코드보다 앞서서 배치하였다면, 제약 조건 확인 이후 순간의 찰나에 사용자가 매개변수의 start와 end의 값을 바꾸어 우리가 원하는 매개변수 제약 조건을 만족하지 못하게 변경할 수 있기 때문이다.
또한 여기서 clone 메서드를 사용하지 않았음에 집중하자. clone 메서드는 재정의가 가능한 메서드이다. 만약 매개변수로 전달받은 Date를 상속한 하위 클래스를 전달 받는 상황에서 사용자가 그 하위 클래스 내에 clone 메서드를 재정의하여 Period 객체의 불변식을 방해하도록 만들 수 있다.
마찬가지로 접근자 또한 해당 필드의 값을 그대로 반환하는 것은 앞서 말한 것과 같은 이유로 불변식이 보장되지 않을 수 있다. 따라서 아래와 같이 접근자 또한 복사본을 전해주어야한다. 이 때는 clone을 사용해도 무방하다. 우리가 작성한 Period내에서 사용한 Date 클래스는 clone을 재정의하지 않았음을 보장할 수 있기 때문이다.
방어적으로 복사본을 만드는 것은 불변 객체를 만들기 위한 것만은 아니다. 메서드 또는 생성자든 사용자가 제공한 객체의 참조를 내부의 자료구조에 보관해야할 때면 항상 그 객체가 변경될 수 있는지를 생각해보아야 한다.
Map이나 Set의 자료구조에 사용자에게 전달받은 객체를 저장한 후에, 사용자가 임의로 자신의 객체를 바꾸게 된다면 자료구조의 불변식이 깨지게 된다. 이러한 것을 방지하기 위해서도 방어적 복사본을 만드는 습관은 필요하다.
하지만 방어적 복사본을 만드는 관습은 필수적이지 않다. 방어적 복사본을 만드는 일은 성능 저하를 동반하기 때문이다. 따라서 안심할 수 있는 상황(본인이 작성하는 메서드같이 통제 가능한 상황)에는 생략해도 좋다. 또한 사용자에게 공개된 메서드일 때에도 불변식이 깨졌을 때의 영향이 온전히 클라이언트에 있을 때도 생략할 수 있다.
'Java' 카테고리의 다른 글
[이펙티브 자바] 매개변수가 유효한지 검사하라. (Item 49) (0) | 2024.02.27 |
---|---|
[Java/자바] 자바 프로젝트와 mysql을 docker-compose로 묶어보자 (0) | 2023.08.30 |
[Java/자바] 싱글톤 패턴? 싱글톤 컨테이너? 그게 뭔데!! (0) | 2023.07.30 |
[Java/자바] 문자열을 "+"로 split 해보자(BOJ 1541 잃어버린 괄호) (0) | 2023.07.26 |
[자바/Java] 추상 클래스(Abstract Class) (0) | 2023.07.10 |