티스토리 뷰
클라이언트가 불변식을 깨뜨리려 혈안이 되어 있다고 가정하고 방어적으로 프로그래밍해야 한다.
// 50-1. 불변 클래스인가 ??
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가 end보다 늦다."
);
}
this.start = start;
this.end = end;
}
public Date getStart() {
return start;
}
public Date getEnd() {
return end;
}
}
// 50-2. Period 내부 변경해보기
public static void main(String[] args) {
Date start = new Date();
Date end = new Date();
System.out.println("end.year: " + end.getYear());
Period period = new Period(start, end);
System.out.println("---------------------");
end.setYear(78);
System.out.println("end.year: " + period.getEnd().getYear());
}
// 121, 78 출력
- Period가 불변식인줄 알았지만 Date가 가변객체이기에 불변식이 아니다.
- 여기서는 Date를 쓰지만, 자바8이후에 등장한 Instnat, LocalDateTime, ZonedDateTime 을 쓰면 쉽게 해결된다. Date는 쓰지 않도록 한다. (Deprecated 되어있음)
외부 공격으로부터 인스턴스 내부를 보호하려면 생성자에서 받은 가변 매개변수 각각을 방어적으로 복사(defensive copy)해야 한다.
// 50-3. 수정한 생성자 - 방어적 복사
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가 end보다 늦다."
);
}
}
Item49에서는 매개변수의 유효성을 먼저 검사하라 했는데 Item50 에서는 방어적 복사본을 만드는 작업을 먼저하고 그 다음에 유효성 검사를 한다.
멀티스레딩 환경이라면 원본 객체의 유효성을 검사한 후 복사본을 만드는 그 찰나의 취약한 순간에 다른 스레드가 원본 객체를 수정할 위험이 있기 때문이다.
방어적 복사를 매개변수 유효성 검사 전에 수행하면 이런 위험에서 해방될 수 있다.
이를 검사시점/사용시점(time-of-check/time-of-use) 공격 혹은 TOCTOU 공격이라 한다.
- 매개변수가 제 3자에 의해 확장될 수 있는 타입이라면 방어적 복사본을 만들 때 clone 을 사용해서는 안된다.
- 이전 아이템 13 에서 clone 을 다뤘었는데 기본 원칙은 '복제 기능은 생성자와 팩토리를 이용하는게 최고' 이고 배열만은 clone 메서드 방식이 가장 깔끔한 방법이라고 하였다.
// 50-4. Period 내부 변경해보기
public static void main(String[] args) {
Date start = new Date();
Date end = new Date();
System.out.println("end.year: " + end.getYear());
Period period = new Period(start, end);
System.out.println("---------------------");
period.end.setYear(78);
System.out.println("end.year: " + period.end.getYear());
}
// 121, 78 출력
// 50-5. getter 에서도 방어적 복사본을 반환한다.
public Date getStart() {
return new Date(start.getTime());
}
public Date getEnd() {
return new Date(end.getTime());
}
- 되도록 불변 객체들을 조합해 객체를 구성해야 방어적 복사를 할 일이 줄어든다.
- 방어적 복사에는 성능 저하가 따르고, 또 항상 쓸 수 있는 것은 아니다. (같은 패키지에 속하는 등의 이유로) 호출자가 컴포넌트 내부를 수정하지 않으리라 확신하면 방어적 복사를 생략할 수 있다.
- 이러한 상황이라도 호출자에서 해당 매개변수나 반환값을 수정하지 말아야 함을 명확히 문서화하는게 좋다.
- 방어적 복사를 생략해도 되는 상황은 해당 클래스와 그 클라이언트가 상호 신뢰할 수 있을 때, 혹은 불변식이 깨지더라도 그 영향이 오직 호출한 클라이언트로 국한될 때로 한정해야 한다.
- 후자의 예로는 래퍼 클래스 패턴 (아이템 18)을 들 수 있다.
- 래퍼 클래스의 특성상 클라이언트는 래퍼에 넘긴 객체에 여전히 직접 접근할 수 있다.
- 따라서 래퍼의 불변식을 쉽게 파괴할 수 있지만 그 영향을 오직 클라이언트 자신만 받게된다.
결론
- 클래스가 클라이언트로부터 받는 혹은 클라이언트로 반환하는 구성요소가 가변이라면 그 요소는 반드시 방어적으로 복사해야 한다.
- 복사 비용이 너무 크거나 클라이언트가 그 요소를 잘못 수정할 일이 없음을 신뢰한다면 방어적 복사를 수행하는 대신 해당 구성요소를 수종했을 때의 책임이 클라이언트에 있음을 문서에 명시하도록 하자.
'책 > 이펙티브자바' 카테고리의 다른 글
아이템40. @Override 애너테이션을 일관되게 사용하라 (0) | 2021.08.31 |
---|---|
아이템39. 명명 패턴보다 애너테이션을 사용하라 (0) | 2021.08.31 |
아이템49. 매개변수가 유효한지 검사하라 (0) | 2021.08.30 |
아이템38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라 (0) | 2021.08.29 |
아이템35. ordinal 메서드 대신 인스턴스 필드를 사용하라 (0) | 2021.08.28 |
링크
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- ㅇㄷㅇㅈ
- http
- Spring Security
- GCP
- 김영한 JPA
- 백기선 스터디
- HTTP 완벽 가이드
- 이펙티브자바 아이템60
- 프로그래머스
- 가상 면접 사례로 배우는 대규모 시스템 설계 기초
- dreamcoding
- 프로그래머스 SQL
- 김영한 http
- 모던자바스크립트
- 이펙티브자바 스터디
- 패스트캠퍼스 컴퓨터공학 완주반
- js promise
- JPA 연관관계 매핑
- js array
- js api
- HTTP 완벽가이드
- BOJ
- 이펙티브자바 아이템59
- java
- JS 딥다이브
- 킹수빈닷컴
- 드림코딩
- 백준
- 이펙티브자바
- REST API
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 |
글 보관함