Adventure Time - Lady Rainicorn [아이템 49] 매개변수가 유효한지 검사하라
본문 바로가기
🤓 스터디/이펙티브 자바

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

by 강켄트 2023. 4. 13.

아이템 주제처럼 

메서드나 생성자 작성 시 매개변수 유효검사를 왜 해야 하는지 

이해하는 것이 요번 아이템의 핵심인 것 같다.

 

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

... 이런 제약은 반드시 문서화해야 하며메서드 몸체가 시작되기 전에 검사해야 한다."

 

매개변수 검사를 제대로 하지 못하면 생기는 문제 

▶️ 메서드가 수행되는 중간에 모호한 예외를 던질 수 있다.

▶️ 메서드가 잘 수행되었지만 잘못된 결과를 반환할 수 있다.

▶️ 메서드는 문제없이 수행됐지만,

    어떤 객체를 이상한 상태로 만들어 놓아서 미래를 알 수 없는 시점에 메서드와 관련 없는 오류를 낼 수 있다.

    (실패 원자성을 어기는 결과)

 

public과 protected 메서드는 매개변수 값이 잘못됐을 때 던지는 예외를 문서화해야 한다. (@throws 자바독 태그 사용)

보통 던지는 예외는 IllegalArgumentException, IndexOutOfBoundException, NullPointerException이다.

매개변수의 제약을 문서화한다면 그 제약을 어겼을 때 발생하는 예외도 함께 기술해야 한다. 

 

예시)

public class Sample {

    /**
     * 항상 음이 아닌 BigInteger를 반환한다는 점에서 remainder 메서드와 다르다.
     * 
     * @param m 계수(양수여아함)
     * @return 현재 값 mod m
     * @throws ArithmeticException m이 0보다 작거나 같으면 발생
     */
    public BigInteger mod(BigInteger m){
        if(m.signum()<=0){
            throw new ArithmeticException("m(계수)은 양수여야함 " +m);
        }
    }
}

 

m이 null이면 m.signum()이 호출될 때 NullPointException을 던지지만,

메서드 설명에 없는 이유는 개별 메서드가 아닌 BigInteger 클래스 수준에서 기술했기 때문이다.

클래스 수준 주석은 그 클래스의 모든 public 메서드에 적용되기 때문에

각 메서드에 일일이 기술하는 것보다 훨씬 깔끔한 방법이다.

 

 

자바의 null 검사 기능

@Nullable 같은 어노테이션을 사용해서 특정 매개변수가 null이 될 수 있음을 알려줄 수 있지만, 표준방법이 아니라고 한다.

 

👉 자바 7에 추가된 java.uti.Objects.requireNonNull 메서드

이걸 사용하면 null 검사를 수동으로 하지 않아도 되고, 원하는 예외 메시지도 지정할 수 있다.

또한 입력을 그대로 반환하므로 값을 사용하는 동시에 null 검사를 수행할 수 있다.

 

반환값은 그냥 무시하고 필요한 곳 어디서든 순수한 null 검사 목적으로 사용 가능하다.

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

 

👉 자바 9에 추가된 Objects에 범위 검사 기능 checkFromIndexSize, checkFromToIndex, checkIndex 메서드 

예외 메시지를 지정할 수 없고, 리스트와 배열 전용으로 설계됐다.

또한 닫힌 범위는 다루지 못하는 제약들이 있기 때문에 null 검사 메서드만큼 유연하지 않다.

 

public이 아닌 메서드의 매개변수 유효성 검증 방법

👉 assert(단언문)

 

public이 아닌 메서드 즉, 공개되지 않은 메서드라면 메서드 호출 상황을 통제할 수 있고

유효한 값만이 메서드에 넘겨지리라는 걸 보증할 수 있다. 

 

재귀 정렬용 private 도우미 함수

public class Sample2 {
    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️⃣ 런타임에 아무런 성능 저하도 없다.

 

 

메서드 몸체 실행 전에 매개변수 유효성을 검사하라의 예외

  • 유효성 검사 비용이 지나치게 높거나 실용적이지 않을 때
  • 계산 과정에서 암묵적 검사가 수행될 때 

💡"암묵적 유효성 검사에 너무 의존했다가는 실패 원자성을 해칠 수 있으니 주의"

 

실패 원자성에 대한 블로그 글을 첨부해 보았다.

https://donghyeon.dev/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C%EC%9E%90%EB%B0%94/2021/09/28/%EA%B0%80%EB%8A%A5%ED%95%9C-%ED%95%9C-%EC%8B%A4%ED%8C%A8-%EC%9B%90%EC%9E%90%EC%A0%81%EC%9C%BC%EB%A1%9C-%EB%A7%8C%EB%93%A4%EC%9E%90/

 

가능한 한 실패 원자적으로 만들자

실패 원자적(failure-atomic)

donghyeon.dev

 

정리 

"메서드나 생성자를 작성할 때 그 매개변수들에 어떤 제약이 있는지 생각해야 한다.

그 제약들을 문서화하고 메서드 코드 시작 부분에서 명시적으로 검사해야 한다."

 

댓글