[교재 EffectiveJava] 아이템 49. 매개변수가 유효한지 검사하라

반응형
728x90
반응형

메서드 매개변수 검사

메서드와 생성자 대부분은 입력 매개변수의 값에 제약이 있다. 예를 들어, 음수이면 안되고, 객체 참조는 null이 아니여야한다는 식이다. 이런 제약은 반드시 문서화해야 하며, 메서드 몸체가 시작되기 전에 검사해야한다. 이는 "오류는 가능한 한 빨리 잡아야 한다"는 일반 원칙의 한 사례다. 오류를 발생한 즉시 잡지 못하면 해당 오류를 감지하기 어려워지고, 감지하더라도 오류의 발생 지점을 찾기 어려워진다.

 

메서드 몸체가 실행되기 전에 매개변수를 확인한다면 잘못된 값이 넘어왔을때 즉각적이고 깔끔한 방식으로 예외를 던질 수 있다. 매개변수 검사를 제대로 하지 못하면 몇가지 문제가 생길 수 있다.

1) 메서드가 수행되는 중간에 모호한 예외를 던지며 실패할 수 있다.
2) 메서드가 잘못된 결과를 반환할 수 있다.
3) 실패 원자성을 어기는 결과를 낳을 수 있다. 이는 매개변수 검사에 실패하여 발생하는 오류를 말한다.

 

 

매개변수 제약 문서화

public, protect 메서드는 매개변수 값이 잘못됬을때 던지는 예외를 문서화 해야한다. @throws 자바독 태그를 사용하면 된다. 매개변수의 제약을 문서화한다면 그 제약을 어겼을때 발생하는 예외도 함께 기술해야한다. 이런 간단한 방법으로 API 사용자가 제약을 지킬 가능성을 크게 높일 수 있다.

 

BigInteger.java
/**
* 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 이 발생하는데, 이는 위 메서드 설명에 없다. 이 에러는 해당 클래스의 모든 public 메서드에 해당하므로 클래스 수준 주석으로 보고, 클래스 수준에서 기술한다.

 

 

자바의 null 검사 기능 사용

자바 7에 추가된 java.util.Objects.requireNonNull 메서드는 유연하고 사용하기 편하다. 이를 사용하면 null 검사를 수동으로 하지 않아도 된다. 원하는 예외 메시지도 지정할 수 있고, 입력을 그대로 반환하므로 값을 사용하는 동시에 null 검사를 수행할 수 있다.

this.strategy = Objects.requireNonNull(strategu, "전략");

 

수행결과
Exception in thread "main" java.lang.NullPointerException: 전략
	at java.base/java.util.Objects.requireNonNull(Objects.java:246)
	at com.java.effective.item49.Main.main(Main.java:10)

 

Objects.requireNonNull()
public static <T> T requireNonNull(T obj, String message) {
    if (obj == null)
        throw new NullPointerException(message);
    return obj;
}

 

 

단언문 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;
    
    ... // 계산 수행
}

여기서의 핵심은 이 단언문들은 자신이 단언한 조건이 무조건 참이라고 선언한다. 단언문은 일반적인 유효성 검사와 다르다. 디버깅 모드로 사용된다.

1) 실패하면 AssertionError를 던진다.
2) 런타임에 아무런 효과도, 아무런 성능 저하도 없다. (실제 배포 환경에서는 무시된다.)

 

 

주의할점

메서드가 직접 사용하지 않으나 나중에 쓰기 위해 저장하는 매개변수는 특히 더 신경 써서 개발해야한다. 이는 생성자가 특수한 사례다. 생성자 매개변수의 유효성 검사는 클래스 불변식을 어기는 객체가 만들어지지 않게 하는데 꼭 필요하다. 

 

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

 

Collection.sort(List) : 객체 리스트를 정렬하는 메서드

리스트안의 객체들은 모두 상호 비교될 수 있어야하며, 정렬 과정에서 이 비교가 발생한다. 상호 비교될 수 없는 타입의 객체가 들어있다면 그 객체와 비교할때 ClassCastException 에러가 발생한다.  따라서 비교하기 앞서 리스트의 모든 객체가 상호 비교될 수 있는지 검사해야봐야 별다른 실익이 없다. 하지만 암묵적 유효성 검사에 너무 의존했다가는 실패 원자성을 해칠 수 있으니 주의하자.

 

 

결론

"매개변수에 제약을 두는게 좋다"라고 해석하는게 아니고, 메서드는 최대한 범용적으로 설계하여 메서드가 건네 받은 값으로 무언가 제대로 된 일을 할 수 있다면 매개변수 제약은 적을 수록 좋다라는 뜻으로 이해하자.

 

 

반응형

Designed by JB FACTORY