메서드와 매개변수

• 메서드의 입력 매개변수 값에는 제약 조건이 있을 수 있다.

    ◦  ex) 인덱스 값은 음수이면 안된다.

    ◦  ex) 객체 참조는 null 이 아니어야 한다.

• 매개변수가 이러한 제약 조건을 위배하지 않는지는 메서드 body가 시작되기 전에 검사해주는 것이 좋다.

    ◦  오류는 가능한 한 빨리 잡아야 하기 때문

    ◦  오류를 발생한 즉시 잡지 못하면(문제가 생긴채로 어딘가에 저장되어 버리면) 해당 오류를 감지하기 어려워지고,

         감지하더라도 오류의 발생 지점을 찾기 어려워진다.


매개변수 검사를 하지 않으면 생길 수 있는 문제

• 메서드가 수행되는 중간에 모호한 예외를 던지며 실패할 수 있다.

• 메서드는 잘 수행되지만 잘못된 결과를 반환할 수 있다.

• 메서드는 잘 수행되지만 어떠한 객체의 상태를 변화시켜서 미래의 알 수 없는 시점에 이 메서드와 관련 없는 오류가 발생할 수 있다.


매개변수 관련 예외를 문서화 하는 방법

• 메서드의 매개변수 값이 잘못됐을 때 발생되는 예외를 문서화하면 개발자가 매개변수 관련 오류를 발생시킬 위험을 줄일 수 있다.

    ◦  @throws 자바독 태그를 사용할 수 있다.

    ◦  매개변수의 제약을 문서화 할 때는 제약을 어겼을 시 발생하는 예외도 함께 기술해주는 것이 좋다.

    ◦  일반적으로 발생하기 쉬운 예외

          -  IllegalArgumentException, IndexOutOfBoundsException, NullPointerException

/**
* (현재 값 mod m) 값을 반환한다. 
* 이 메서드는 항상 음이 아닌 BigInteger를 반환한다는 점에서 remainder 메서드와 다르다.
*
* @param m (계수 : 양수여야 한다)
* @return mod m (현재 값)
* @throws ArithmeticException (m 이 0 이하이면 발생한다)
*/

public BigInteger mod(BigInteger m) {
    if (m.signum() < 0) // m이 양수면 1, 0이면 0, 음수면 -1 반환
        throw new ArithmeticException("계수(m)는 양수여야 합니다. " + m);
    ...
}

• m == null 이면 m.signum 호출시 NullPointerException 예외가 발생한다.

    ◦  위 사항이 메서드 설명에서 언급되지 않는 이유

          -  이 설명은 메서드가 아닌, BigInteger 클래스 수준에 기술되어 있기 때문이다.

          -  모든 메서드에 일일이 주석을 작성해두는 것 보다, 클래스 단계에 주석을 한 번만 작성해두는 것이 훨씬 간편한 경우가 많다.


매개변수 검사에 사용할 수 있는 유용한 메소드

java.util.Objects.requireNonNull

this.value = Objects.requireNonNull(value, "예외메시지");

• java.util.Objects.requireNonNull 메서드를 활용하면 편리하게 null 검사를 수행할 수 있다.

    ◦  자바 7 에서 추가된 메서드이다.

    ◦  원하는 예외 메시지를 지정할 수도 있고, 입력을 그대로 반환하므로 값을 사용하는 동시에(언제든지) null 검사를 수행할 수 있다.

          -  반환되는 값을 사용하지 않고 오로지 null 검사만을 위해 사용해도 무방하다.

 

checkFromIndexSize , checkFromToIndex , checkIndex

public static int checkFromIndexSize(int fromIndex, int size, int length) {
  return Preconditions.checkFromIndexSize(fromIndex, size, length, null);
}

public static int checkFromToIndex(int fromIndex, int toIndex, int length) {
  return Preconditions.checkFromToIndex(fromIndex, toIndex, length, null);
}

public static int checkIndex(int index, int length) {
  return Preconditions.checkIndex(index, length, null);
}

• 자바 9 에서 추가된 메서드

• 리스트와 배열 전용으로 설계된 메소드이다.

• requireNonNull 과 달리 예외메시지를 지정할 수는 없다.

• 이상/이하(닫힌범위)는 다루지 못한다.

 

단언문(assert)

• public 이 아닌 메서드라면 단언문을 사용해 매개변수의 유효성을 검증할 수 있다.

    ◦  public 이 아닌 메서드의 경우, 직접 메서드가 호출되는 상황을 통제할 수 있다고 보는 것

    ◦  오직 유효한 값 만이 메서드에 넘겨지리라는 것을 보증할 수 있다.

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

• 단언문은 자신이 단언한 조건이 무조건 참이라고 선언한다.

• 단언문과 일반적인 유효성 검사의 차이

    ◦  단언문은 실패하면 AssertionError 를 던진다.

    ◦  Runtime 효과/성능 저하를 전혀 발생시키지 않는다.


나중에 쓰려고 저장하는 매개변수의 유효성을 검사하라

• 메서드가 직접 사용하지는 않지만, 이후 다른 연산에서 사용하기 위해 저장되는 매개변수는 특히 더 신경써서 검사하는것이 좋다.

    ◦  생성자가 이 원칙의 한 예시이다.

    ◦  생성자 매개변수의 유효성 검사는 클래스 불변식을 어기는 객체의 생성을 방지하기 위해 반드시 필요


메서드 매개변수 유효성 검사 규칙의 예외

• 다음과 같은 경우에는 메서드 body 실행 전 매개변수의 유효성을 검사하지 않아도 된다.

    ◦  유효성 검사 비용이 지나치게 높거나 실용적이지 않은 경우

    ◦  계산 과정에서 암묵적으로 검사가 수행되는 경우

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

          -  리스트 안의 객체들은 모두 상호 비교될수 있어야 하며, 정렬 과정에서 이 비교가 이루어진다.

          -  만약 상호 비교할 수 없는 타입의 객체가 들어 있다면 그 객체와 비교할 때 ClassCastException 이 발생한다.

    ◦  때로는 계산 과정에서 필요한 유효성 검사가 이루어지지만, 계산에 실패했을 때 의도한것과 다른 예외가 발생할 수도 있다.

          -  잘못된 매개변수 값을 사용해서 발생된 예외와, 문서에서 던지기로 작성된 예외가 다를 수 있다.

          -  이 경우 예외번역(exception translate) 관용구를 사용해 문서에 작성된 예외로 번역해줘야 함

try {
    ... // 저수준 추상화를 이용한다.
} catch (LowerLevelException e) { // 추상화 수준에 맞게 번역한다.
    throw new HigherLevelException(...);
}

아이템을 마치며

• 이번 아이템을 '매개변수에 제약을 두는 것이 좋다' 라고 해석해서는 안 된다.

• 메서드는 최대한 범용적으로 설계되어야 하며, 매개변수 제약은 적을수록 좋다.