Home [Effective Java] Item49. 매개변수가 유효한지 검사하라!
Post
Cancel

[Effective Java] Item49. 매개변수가 유효한지 검사하라!

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} &le; 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 검사 메서드만큼 유연하지는 않다.

공개되지 않은 메서드

  • public, protected 이외의 메서드라면 개발자가 스스로 메서드가 호출되는 상황을 통제할 수 있다.
    • 유효한 값이 들어온다는 것을 보장할 수 있다.

단언문(assert)

  • 단언문을 사용해 매개변수 유효성을 검증할 수 있다.
  • 단언문들은 자신이 단언한 조건을 무조건 참이라고 선언한다.
  • 단언문은 몇 가지 면에서 일반적인 유효성 검사와는 다르다.
    • 실패하면 AssertionError를 던진다.
    • 런타임에 아무런 효과도, 아무런 성능 저하도 없다.

생성자 매개변수

  • 메서드가 직접 사용하지는 않으나 나중에 쓰기 위해 저장하는 매개변수는 특히 더 신경 써서 검사해야 한다.
  • 검사를 소홀히 하여 새로 성성한 인스턴스에 null이 할당되었다면, 그 값을 사용함으로 인해 NullPointerException을 발생시킬 수 있다.
    • 이렇게 되면 사용한 인스턴스를 어디서 가져왔는지 추적하기 어려워 디버깅이 힘들 수 있다.
  • 생성자는 “나중에 쓰려고 저장하는 매개변수의 유효성을 검사하라”는 원칙의 특수 사례다.
    • 생성자 매개변수의 유효성 검사는 클래스 불변식을 어기는 객체가 만들어지지 않게 하는 데 있어 필수적이다.

예외

  • 메서드 몸체 실행 전에 매개변수 유효성을 검사해야 한다는 규칙에도 예외는 존재한다.
  • 유효성 검사 비용이 지나치게 높거나 실용적이지 않을 때
  • 계산 과정에서 암묵적으로 검사가 수행될 때

예외 번역 관용구

  • 계산 중 잘못된 매개변수 값을 사용해 발생한 예외와 API 문서에서 던지기로 한 예외가 다를 수도 있다.
    • 이러한 경우 아이템 73에서 설명하는 예외 번역 관용구를 사용해 API 문서에 기재된 예외로 번역해줘야 한다.

주의할 점

  • 이번 아이템을 읽고 “매개변수에 제약을 두는 게 좋다”고 해석해서는 안 된다.
  • 메서드는 최대한 범용적으로 설계해야 하고, 메서드가 건네받은 값으로 무언가 제대로 된 일을 할 수 있다면 매개변수 제약을 적을수록 좋다.
  • 하지만 구현하려는 개념 자체가 특정 제약을 내재한 경우도 드물지 않게 존재한다.

핵심 정리

  • 메서드나 생성자를 작성할 때면 그 매개변수들에 어떤 제약이 있을지 생각하자.
  • 그 제약들을 문서화하고 메서드 코드 시작 부분에서 명시적으로 검사하자.
  • 이러한 습관을 들인다면, 그 노력은 유효성 검사가 실제 오류를 처음 걸러낼 때 충분히 보상받을 것이다.
This post is licensed under younghwani by the author.

[Effective Java] Item48. 스트림 병렬화는 주의해서 적용하라!

[Effective Java] Item50. 적시에 방어적 복사본을 만들라!

Comments powered by Disqus.