매개변수가 유효한지 검사하라.
메서드와 생성자 대부부은 입력 매개변수의 값이 특정 조건을 만족하여야 한다. 오류를 발생한 즉시 잡지 못하면 해당 오류를 감지하기 어려워지고, 감지하더라도 오류의 발생 지점을 찾기 어려워진다.
매개변수가 잘못되었을 때 문제점
- 메서드가 수행되는 중간에 모호한 예외를 던지며 실패 할 수 있다.
- 메서드가 잘 수행되지만 잘못된 결과를 반환할 때다.
- 어떤 객체를 이상한 상태로 만들어놓아서 미래에 알 수 이 없는 시점에 이 메서드와 관련 없는 오류를 낸다.
public과 protected 메서드는 매개변수 값이 잘못됐을 때 던지는 예외를 문서화해야 한다. ( @throws 자바독 태그를 사용하면 된다. )
/**
* ( 현재 값 mod m ) 값을 반환한다. 이 메서드
* 항상 음이 아닌 BigInteger를 반환한다는 점에서 remainder 메서드와 다르다.
*
* @param m 계수 (양수여야 한다.)
* @return 현재 값 mod m
* @throws ArithmeticException m이 0보다 작거나 같으면 발생한다.
*/
public BigInteger mod(BigInteger m) {
if(m.signum() <= 0)
throw new ArithmeticException("계수(m)는 양수여야 합니다." + m);
}
java.util.Objects.requireNonNull 메서드는 유연하고 사용하기도 편해 더 이상 null 검사를 수동으로 하지 않아도 된다.
this.strategy = Objects.requireNonNull(strategy, "전략");
public이 아닌 메서드라면 assert를 통해 매개변수 유효성을 검증 할 수 있다. 아래 조건이 참이 아니면 AssertionError가 발생하고 런타임에 아무런 효과도, 성능저하도 없다.
private static void sort(long a[], int offset, int length) {
assert a != null;
assert offset >= 0 && offset <= a.length;
assert length >= 0 && length <= a.length - offset;
}
매개변수 유효성을 검사해야 한다는 규칙의 예외
- 유효성 검사 비용이 지나치게 높다
- 계산 과정에서 암묵적으로 검사가 수행될 때
적시에 방어적 복사본을 만들라
클라이언트가 불변식을 깨뜨린다고 가정하고 방어적으로 프로그래밍해야 한다.
Period 클래스는 불변식이 쉽게 깨지는 예제이다.
public class Period {
private final Date start;
private final Date end;
public Period(Date start, Date end) {
this.start = start;
this.end = end;
}
public Date start() {
return start;
}
public Date end() {
return end;
}
}
end.setYear에서 Period의 내부 값을 수정되도록 바뀌었다.
public static void main(String[] args) {
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(780);
}
이것을 해결하기 위해 생성자에서 받은 가변 매개변수 각각을 방어적 복사를 해야 한다. 방어적 복사본을 만들고 나서 유효성을 검사하였다.
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(
this.start + " after " + this.end);
}
public Date start() {
return start;
}
public Date end() {
return end;
}
}
* 매개변수가 제 3자에 의해 확장 될 수 있는 타입이라면 clone을 사용해서는 안된다.
하지만, 여전히 end()메서드를 호출하여 p의 내부를 바꿀 수 있다.
public static void main(String[] args) {
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
p.end().setYear(780);
}
메서드들도 방어적 복사본을 반환해주자.
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
방어적 복사를 해야 하는 이유 중 하나는 클라이언트가 제공한 객체의 참조를 내부의 자료구조에 보관해야 할 때면 항시 그 객체가 잠재적으로 변경 될 수 있는지를 생각해야 한다. Set이나 Map같은 자료구조에 보관하는 동안 객체가 변경된다면 불변식이 깨질 것이다.
방어적 복사에는 성능 저하가 따르고, 또 항상 쓸 수 있는 것도 아니다.
- 호출자가 컴포넌트 내부를 수정하지 않으라 확신하면 방어적 복사를 하지 않아도 되지만, 문서화를 해놓자
- 다른 패키지에서 넘겨받은 가변 매개변수에 경우 이전하는 클라이언트가 더 이상 객체를 직접 수정하는 일이 없다면 방어적 복사는 필요없다.
- 방어벅 복사를 생략해도 되는 상황에서 불변식이 깨지더라도 영향도는 클라이언트로 국한되어야만 한다. 래퍼클래스의 경우에는 래퍼에 넘긴 객체는 접근 할 수 있지만 클라이언트에만 영향을 받는다.
댓글