- 메서드와 생성자 대부분은 입력 매개변수의 값이 특정 조건을 만족하기를 바란다.
- 인덱스 값은 음수일 수 없다
- 객체 참조는 null이면 안된다.
- 이러한 제약은 메서드 몸체가 시작되기 전에 검사되어야 한다.
- 이는 오류는 가능한 빨리 발생한 곳에서 잡아야 한다는 일반 원칙의 사례이기도 함
- 오류를 발생한 즉시 잡지 못하면 오류는 감지하기 어려워지고 발생 지점을 추적하기 또한 어려워진다.
- 반면 메서드 몸체가 실행되기 전에 매개변수를 확인한다면 잘못된 값이 넘어왔을 때 즉각적이고 깔끔한 방식으로 예외를 던질 수 있다.
- 메서드가 수행되는 중간에 모호한 예외를 던지며 실패할 수 있다.
- 예외를 던지지 않고 잘못된 결과를 반환할 수 있다 (1번 보다 심각)
- 메서드는 문제 없이 수행되었지만 어떤 객체를 이상한 상태로 만들어놓아서 미래의 알 수 없는 시점에 관련 없는 오류 발생 (2번 보다 심각)
- 이를 통해 매개변수 검사에 실패하면 실패 원자성을 어기는 결과를 낳을 수 있다는 것을 알 수 있음
- 매개변수 검사를 해야하는 이유를 알아보았다.
- 이어지는 요약에서는 매개변수 값을 검사하는 여러가지 방법이 등장한다.
- public, protected 메서드는 매개변수 값이 잘못됐을 때 던지는 예외를 문서화해야 한다.
@throws
자바독 태그를 사용하여 처리할 수 있다.- 그리고 매개변수의 제약을 문서화한다면 그 제약을 어겼을 때 발생하는 예외도 함께 기술해야 한다.
- 이런 간단한 처리는 API 사용자가 제약을 지킬 가능성을 크게 높일 수 있다.
/**
* 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이면 NPE 발생
- 그런데 m이 null일 때 NPE를 발생시킨다라는 말은 메서드 설명 어디에도 없다.
- 그 이유는 이 설명을 BigInteger 클래스 수준에서 기술했기 때문
- 클래스 수준의 주석은 그 클래스의 모든 public 메서드에 적용됨으로 일일히 표기하지 말도록 하자.
- 검사를 다음 처럼 수동으로 할 수도 있다.
if (param == null) {
throw new NullPointerException();
}
- 다만 다음의 기능들 처럼 이미 제공되고 있는 기능도 있으니 참고 해보자
java.util.Objects.requireNonNull
: null 검증java.util.Objects.checkFromIndexSize
: 범위 검사 - 1java.util.Objects.checkFromToIndex
: 범위 검사 - 2java.util.Objects.checkIndex
: 범위 검사 - 3
- 공개되지 않은 메서드라면 패키지 제작자인 우리가 메서드가 호출되는 상황을 통제할 수 있다.
- 따라서 오직 유효한 값만이 메서드에 넘겨지리라는 것을 우리가 보증할 수 있고, 그렇게 해야 한다.
- 이러한 경우 단언문(assert)를 사용할 수 있다.
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;
}
- 위는 단언문을 사용한 private 메서드 예시이다.
- 여기서 단언문들은 자신이 단언한 조건이 무조건 참이라고 선언한다.
- 단언문들은 실패하면 AssertionsError를 던지고 런타임에 아무런 성능저하도 없다는 것이 일반적인 유효성 검증과의 차이이다.
- 메서드가 직접 사용하지는 않으나 나중에 쓰기 위해 저장하는 매개변수는 특히 더 신경 써서 검사해야 한다.
- 생성자는 "나중에 쓰려고 저장하는 매개변수의 유효성을 검사하라"는 원칙의 특수한 사례다.
- 생성자 매개변수의 유효성 검사는 클래스 불변식을 어기는 객체가 만들어지지 않게 하는 데 꼭 필요하다.
-
유효성 검사 비용이 지나치게 높거나 실용적이지 않을 때, 혹은 계산 과정에서 암묵적으로 검사가 수행되는 경우 유효성 검사는 생략될 수도 있다.
-
이번 아이템 요약을 매개변수에 제약을 두는 게 좋다라고 해석해서는 안된다.
-
사실은 그 반대다.
-
메서드는 최대한 범용적으로 설계되어야 하며 제대로 된 일을 할 수 있다면 매개변수 제약은 적을수록 좋다.
-
하지만 구현하려는 개념 자체가 특정한 제약을 내재한 경우도 드물지 않다.