[Effective Java] 49.매개변수가 유효한지 검사하라
메서드와 생성자 대부분은 입력 매개변수 값이 특정 조건을 만족하길 바란다.
이런 제약은 반드시 문서화하고 메서드 몸체가 시작되기 전에 검사해야한다.
오류는 가능한 빨리 잡아야한다.
매개변수 검사를 제대로 하지 못했을 시 문제점
실패 원자성(failure atomicity)을 어기는 결과 초래 : 메서드 수행 중간에 모호한 예외를 던지며 실패하거나 잘 수행되더라도 잘못된 결과를 반환할 수 있다.
실패 원자성 : 호출된 메서드가 실패하더라도 해당 객체는 호출 전의 상태를 유지해야한다.
매개변수 제약을 어길 시 발생하는 예외 문서화
public이나 protected 메서드는 매개변수 값이 잘못됐을 때 던지는 예외를 문서화해야한다. (@throws 자바독 태그 사용)
보통 IllegalArgumentExcepiton, IndexOutOfBoundsException, NullPointerException 중 하나다.
문서화함으로써 사용자가 제약을 지킬 가능성을 높일 수 있다.
/**
* Returns a BigInteger whose value is {@code (this mod m}). This method
* differs from {@code remainder} in that it always returns a
* <i>non-negative</i> BigInteger.
*
* @param m the modulus.
* @return {@code this mod m}
* @throws ArithmeticException {@code m} ≤ 0
* @see #remainder
*/
public BigInteger mod(BigInteger m) {
if (m.signum <= 0)
throw new ArithmeticException("BigInteger: modulus not positive");
BigInteger result = this.remainder(m);
return (result.signum >= 0 ? result : result.add(m));
}
위 메서드는 m이 null이면 NullPointerException 을 던지는데도 불구하고 메서드 설명에 그런 말이 없다.
그 이유는 이 설명을 BigInteger 클래스 수준에서 기술했기 때문이다.
클래스 수준 주석을 통해 그 클래스의 모든 public 메서드에 적용할 수 있어서 더 깔끔한 방법이다.
매개변수의 유효성 검사
1) java.util.Objects.requireNonNull
자바 7에 추가된 java.util.Objects.requireNonNull 메서드를 이용하면 편하게 null 검사를 수동으로 하지 않아도된다.
(원하는 예외 메시지도 지정 가능)
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
this.strategy = Objects.requireNonNull(strategy, "전략");
2) assert
공개되지 않은 메서드라면 패키지 제작자인 사람이 메서드가 호출되는 상황을 통제할 수 있다.
public이 아닌 메서드라면 단언문(assert)를 사용하여 매개변수 유효성을 검증할 수 있다.
//재귀 정렬용 private 도우미 함수
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;
//생략..
}
- assert 문은 실패하면 AssertionError를 던짐
- 런타임에 아무런 효과도, 아무런 성능 저하도 없다.
3) 나중에 쓰려고 저장하는 매개변수의 유효성을 검사해라
- 메서드가 직접 사용하진 않으나 나중에 쓰기위해 저장하는 매개변수는 더 신경써서 검사해야함
- 입력받은 int 배열의 List를 반환하는 메서드가 requireNonNull을 이용해 null 검사를 한다면?
클라이언트가 이 메서드에 null을 건네면 NullPointerException을 던질 것이다. - 그런데, 이 검사를 생략한다면? 이 메서드는 새로 생성한 List 인스턴스를 반환하게 되고 클라이언트가 그 List를 사용하려할때 그때가 되어서야 NullPointerException이 발생하게된다. 이 때는 List를 어디서 가져왔는지 추적하기 어려워 디버깅이 괴로워진다.
- 생성자의 매개변수 유효성 검사는 클래스 불변식을 어기는 객체가 만들어지지 않게 하는데 꼭 필요
- 유효성 검사에도 예외가 있음
- 검사 비용이 너무 높거나 실용적이지 않을때
- 계산 과정에서 암묵적으로 검사가 수행될 때 → 암묵적 유효성 검사에 너무 의존하면 실패 원자성을 해칠 수 있음
- 매개변수에 제약을 두는게 무조건 좋다는게 아니다. 메서드는 최대한 범용적으로 설계해야함 → 건네받은 값으로 제대로된 일을 한다면 제약은 적을수록 좋음
결론
메서드나 생성자를 작성할때면 그 매개변수들에 어떤 제약이 있을지 생각해야함
제약들을 문서화하고 메서드 시작 부분에서 유효성 검사를 하자. → 오류는 최대한 빨리 발생한 곳에서 잡는 것이 좋다.