Item49. 매개변수가 유효한지 검사하라!
Intro
- 메서드와 생성자 대부분은 입력 매개변수의 값이 특정 조건을 만족하기를 바란다.
- 이러한 제약은 반드시 문서화해야 하며 메서드 몸체가 시작되기 전에 검사해야 한다.
- 이는 “오류는 가능한 한 빨리 (발생한 곳에서) 잡아야 한다“는 일반 원칙의 한 사례이기도 하다.
- 메서드 몸체가 실행되기 전에 매개변수를 확인한다면 잘못된 값이 넘어왔을 때 즉각적이고 깔끔한 방식으로 예외를 던질 수 있다.
- 이는 “오류는 가능한 한 빨리 (발생한 곳에서) 잡아야 한다“는 일반 원칙의 한 사례이기도 하다.
매개변수 검사를 제대로 하지 못했을 때
- 메서드가 수행되는 중간에 모호한 예외를 던지며 실패할 수 있다.
- 메서드가 잘 수행되었지만 잘못된 결과를 반환할 수도 있다.
- 메서드는 문제없이 수행됐지만, 어떤 객체를 이상한 상태로 만들어 놓아서 미래의 알 수 없는 시점에 이 메서드와는 관련 없는 오류를 낼 수 있다.
- => 매개변수 검사에 실패하면 실패 원자성(failure atomicity)을 어기는 결과를 낳을 수 있다.
문서화
- public과 protected 메서드는 매개변수 값이 잘못됐을 때 던지는 예외를 문서화해야 한다.
- @throws 자바독 태그를 사용하면 된다.
- 매개변수의 제약을 문서화한다면 그 제약을 어겼을 때 발생하는 예외도 함께 기술해야 한다.
문서화의 예
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 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이면 m.signum() 호출 시 NullPointerException을 던진다.
- 이러한 사항은 메서드 어디에서도 찾을 수 없지만 BigInteger 클래스 수준에서 찾아보면 있을 것이다.
- 클래스 수준 주석은 그 클래스의 모든 public 메서드에 적용되므로 각 메서드에 일일이 기술하는 것보다 훨씬 깔끔한 방법이다.
유효성 검사 API
자바 7에서 추가된 java.util.Objects.requireNonNull 메서드는 유연하고 사용하기도 편하다.
- 이를 사용하면 더 이상 null 검사를 수동으로 하지 않아도 된다.
1
this.strategy = Objects.requireNonNull(startegy, "전략");
자바 9에서는 Objects에 범위 검사 기능도 더해졌다.
- checkFromIndexSize, checkFromToIndex, checkIndex라는 메서드들이다.
- null 검사 메서드만큼 유연하지는 않다.
- checkFromIndexSize, checkFromToIndex, checkIndex라는 메서드들이다.
공개되지 않은 메서드
- public, protected 이외의 메서드라면 개발자가 스스로 메서드가 호출되는 상황을 통제할 수 있다.
- 유효한 값이 들어온다는 것을 보장할 수 있다.
단언문(assert)
- 단언문을 사용해 매개변수 유효성을 검증할 수 있다.
- 단언문들은 자신이 단언한 조건을 무조건 참이라고 선언한다.
- 단언문은 몇 가지 면에서 일반적인 유효성 검사와는 다르다.
- 실패하면 AssertionError를 던진다.
- 런타임에 아무런 효과도, 아무런 성능 저하도 없다.
생성자 매개변수
- 메서드가 직접 사용하지는 않으나 나중에 쓰기 위해 저장하는 매개변수는 특히 더 신경 써서 검사해야 한다.
- 검사를 소홀히 하여 새로 성성한 인스턴스에 null이 할당되었다면, 그 값을 사용함으로 인해 NullPointerException을 발생시킬 수 있다.
- 이렇게 되면 사용한 인스턴스를 어디서 가져왔는지 추적하기 어려워 디버깅이 힘들 수 있다.
- 생성자는 “나중에 쓰려고 저장하는 매개변수의 유효성을 검사하라”는 원칙의 특수 사례다.
- 생성자 매개변수의 유효성 검사는 클래스 불변식을 어기는 객체가 만들어지지 않게 하는 데 있어 필수적이다.
예외
- 메서드 몸체 실행 전에 매개변수 유효성을 검사해야 한다는 규칙에도 예외는 존재한다.
- 유효성 검사 비용이 지나치게 높거나 실용적이지 않을 때
- 계산 과정에서 암묵적으로 검사가 수행될 때
예외 번역 관용구
- 계산 중 잘못된 매개변수 값을 사용해 발생한 예외와 API 문서에서 던지기로 한 예외가 다를 수도 있다.
- 이러한 경우 아이템 73에서 설명하는 예외 번역 관용구를 사용해 API 문서에 기재된 예외로 번역해줘야 한다.
주의할 점
- 이번 아이템을 읽고 “매개변수에 제약을 두는 게 좋다”고 해석해서는 안 된다.
- 메서드는 최대한 범용적으로 설계해야 하고, 메서드가 건네받은 값으로 무언가 제대로 된 일을 할 수 있다면 매개변수 제약을 적을수록 좋다.
- 하지만 구현하려는 개념 자체가 특정 제약을 내재한 경우도 드물지 않게 존재한다.
핵심 정리
- 메서드나 생성자를 작성할 때면 그 매개변수들에 어떤 제약이 있을지 생각하자.
- 그 제약들을 문서화하고 메서드 코드 시작 부분에서 명시적으로 검사하자.
- 이러한 습관을 들인다면, 그 노력은 유효성 검사가 실제 오류를 처음 걸러낼 때 충분히 보상받을 것이다.
Comments powered by Disqus.