Skip to content

Latest commit

 

History

History
140 lines (91 loc) · 6.78 KB

49_매개변수가_유효한지_검사하라_박경철.md

File metadata and controls

140 lines (91 loc) · 6.78 KB

item 49. 매개변수가 유효한지 검사하라

메서드와 생성자 대부분은 입력값이 특정 조건을 만족하기를 바란다.

private List<String> names;

// ...

public String getNameOf(int index) {
	return names.get(index);
}

위와 같이 이름을 관리하고 있는 클래스가 있고 관리하고 있는 이름의 인덱스값을 통해 값을 조회하는 getNameOf가 있다고 가정한다. 이때 index가 음수이거나 names의 크기보다 크다면 에러가 발생한다.

이런 제약은 반드시 문서화해야하며 메서드 바디가 실행되기 전에 검증을 해야한다.

오류는 가능한 한 빨리 잡아야한다.

오류를 발생한 지점에서 바로 잡지 못하면 해당 오류를 감지하기 더더욱 어려워지고 오류의 발생 지점을 찾기 힘들어진다.

따라서 메서드 바디가 실행되기 전에 매개변수를 확인한다면 잘못된 값이 넘어왔을때 즉각적이고 깔끔한 방식으로 예외를 던질 수 있다.

매개변수 검증이 이뤄지지 않을때

매개변수 검증이 이뤄지지 않는다면 메서드가 실행되는 중간에 모호한 예외를 던지며 실패할 수 있다. 또는 메서드가 제대로 수행되더라도 예상치 못한 결과를 도출할 수 있다.

특히 후자의 경우가 문제가 되는데 전자는 예외를 던지며 에러로그를 남기게 되지만, 후자는 실제 데이터의 정합성이 깨진것을 확인했을때 인지가 가능하기 때문이다.

이렇게 매개변수 검증에 실패하면 실패 원자성을 어기는 결과가 나타난다.

실패 원자성 item 76

호출된 메서드가 실패하더라도 해당 객체는 호출 전의 상태를 유지해야한다.

public과 protected 메서드는 던지는 예외를 문서화해야한다.

publicprotected는 외부 접근도가 높은 제어자이므로 이들 메서드에는 @throws Javadoc 태그를 통해 문서화해야한다.

/**
 * 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));
}

위는 BigInteger#mod메서드로 매개변수 m이 0 이하일때 ArithmeticException이 발생하는 것을 @throws Javadoc 태그로 명시하고 있다.

참고로 모든 메서드에 적용되는 문서화를 하고 싶다면 클래스 수준에서 Javadoc을 작성하면된다.

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

assert

Baeldung 참고: https://www.baeldung.com/java-assert

방어적 프로그래밍 참고: http://statkclee.github.io/xwmooc-sc/novice/python/05-defensive.html

public, protected 메서드 이외에는 assert 단언문을 통해서 매개변수 유효성을 검증할 수 있다.

private String get(int index) {
	assert index >= 0;
	return names.get(index);
}

assert 단언문은 자신이 선언한 조건이 무조건 참이어야 다음 로직을 수행한다. 그렇지 않으면 AssertionException을 던진다.

assert는 Java 실행시 -ea 또는 --enableassertions 옵션을 주지 않으면 런타임에 아무련 영향을 주지 않으며 성능 저하도 없다.

보통 개발 중에 테스팅하는 목적으로 사용된다.

매개변수와 생성자

메서드에서 직접 사용하지는 않지만 나중에 사용하기 위해서 필드로 저장하는 경우는 더더욱 철저히 검사가 필요하다. 이때 검사가 제대로 이뤄지지 않으면 null이 필드에 할당되거나 컬렉션이라면 컬렉션 안에 null이 들어갈 수 있다. 그리고 이 null을 사용함으로써 NullPointerException이 발생할 수 있다. 이렇게 NullPointerException이 발생한다면 디버깅이 매우 힘들다.

생성자는 '나중에 쓰려고 저장하는 매개변수의 유효성을 검사하라' 원칙의 특수사례이다. 생성자 매개변수의 유효성 검사는 클래스 불변식을 어기는 객체가 만들어지지 않게 하는데 필수이다.

그외

  • 유효성 검사 비용이 지나치게 높거나 실용적이지 않다면 예외적으로 유효성 검사에 대해 고려해볼 수 있다.

    대신 Exception에 대한 대응 방안을 반드시 두어야 할 것

  • 암묵적으로 유효성 검사를 하는 경우

    Collections#sort처럼 컬렉션에 들어있는 객체의 타입이 다르면 ClassCastException이 발생한다. 로직이 수행되면서 유효성에 어긋나는 경우 Exception을 던지므로 실행 전에 유효성 검사를 수행할 필요는 없다.

    다만, 암묵적 유효성 검사에 너무 의존하면 실패 원자성을 해칠 수 있다.

java.util.Objects.requireNonNull

Effective Java에서는 Java 7에 추가된 java.util.Objects.requireNonNull를 유연하고 사용하기 편한 메서드로 소개하고 있다.

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

java.util.Objects.requireNonNull의 구현은 위와 같다. 다만, java.util.Objects.requireNonNull도 결국에는 매개변수 obj가 null이라면 그대로 NullPointerException을 일으킨다.

솔직히 java.util.Objects.requireNonNull의 장점은 'NullPointerException이 발생할 위치를 앞당긴다' 정도 밖에 안보인다.

장점이 될 수도 있고 안될수도 있고....

java.util.Objects.requireNonNull를 활용한다면 다음과 같이 null일때 default값을 반환할 수 있도록 만들어준다면 그나마 사용이 편리할 거 같다.

public static <T> T requireNonNullElse(T obj, T defaultObj) {
    try {
        Objects.requireNonNull(obj)
        return obj;
    } catch(NullPointerException e) {
        log.error("{} is null", obj);
        return defaultObj;
    }
}

참고로 Java 9에는 [requireNonNullElse](https://docs.oracle.com/javase/9/docs/api/java/util/Objects.html#requireNonNullElse-T-T-)를 지원하여 Java API에서도 지원한다.

Java 9에는 requireNonNullElse 이외에도 checkFromIndexSize, checkFromToIndex, checkIndex 등 다양한 검사 기능이 추가되었다.