[Effective Java 3/E] ITEM 49. 매개변수가 유효한지 검사하라

    Effective Java 표지. Source : https://blog.insightbook.co.kr/

    *알림 : *
    Effective Java 3판은 Java 9까지 도입된 언어적 기능을 중심으로 서술되어 있습니다. 10버젼 이후의 Java 개발을 하시는 분들은 우회적인 접근법 대신 Java 언어 내 새로 도입된 기능이 더 간결하고 좋을 수 있습니다.

    해당 포스팅은 SSAFY 내 책읽기 스터디의 활동을 통해 작성된 포스팅입니다.
    https://github.com/kjsu0209/JavaBook
    https://medium.com/javabook

     

    JavaBook – Medium

    Documentation space of our book study.

    medium.com

     

    kjsu0209/JavaBook

    책읽기 스터디. Contribute to kjsu0209/JavaBook development by creating an account on GitHub.

    github.com

    매개변수 검사

    메서드와 생성자의 매개변수의 값은 특정 조건을 만족한다는 가정 하에 코드를 작성할 것이다. 인덱스 값이 음수가 되지 않아야 하는게 대표적인 예다. 제약 조건은 코드 body를 작성하기 전에 문서화 해야 하며, 반드시 매개변수를 받자마자 검사하여야 한다.

    만약 검사를 하지 않는다면 메서드가 수행되는 중간 모호한 예외를 던지며 실패할 수 있다. 혹은 예외를 던지지 않고 잘못 수행되어 예상하지 못한 결과를 던질 경우에는 해당 메서드 바깥으로 오류가 전파될 수 있다.

    public과 protected 메서드는 매개변수 값이 잘못됐을 때 던지는 예외를 문서화 해야한다.

    • IllegalArgumentException
    • IndexOutOfBoundsException
    • NullPointerException

    중 하나를 던지는 것을 고려할 수 있다. 어떤 매개변수에서 어떤 제역을 어겨서 어떤 예외가 발생하는 지 기술하면 좋다.

    예제 1 : BigInteger mod() 메서드의 제약조건

    public BigInteger mod(BigInteger m) {
            if (m.signum() <= 0)
                    throw new ArithmeticException("Modulus <= 0: " + m);
            ... // Do the computation
    }

    해당 메서드의 docs에는 예외에 대한 설명이 없다. BigInteger 클래스 수준에 기술했기 때문에 메서드에 기술하는 것 보다 훨씬 깔끔한 방법이 될 수 있다.

    예제 2 : Objects.requireNonNull(strategy, "전략");

    this.strategy = Objects.requireNonNull(strategy, "strategy");

    자바에서는 null 검사를 돕는 requireNonNull 메서드를 제공한다.

    예제 3 : 공개 API가 아닌 메서드를 위한 assert

    // Private helper function for a recursive sort
    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;
        //...
    }

    공개되지 않은 메서드는 패키지 제작자가 메서드의 매개변수를 넘기는 상황을 통제할 수 있다. 예외를 던지는 대신 assert를 이용하자.

    예외를 던지는 것과의 차이점은

    1. 실패 시 AssertionError를 던진다.
    2. 런타임에 아무런 성능 저하가 없다.
    3. 다만 -ea 혹은 --enableassertions 플래그를 달고 실행시에는 영향을 준다.

    나중을 위해 저장하는 매개변수는 더 엄격하게 검사해야 한다.

    정적 팩터리 메서드 패턴을 생각해보자.

    예제 4 : ITEM 20에서 다뤘던 정적 팩터리 메서드

    
    static List<Integer> intArrayAsList(int[] a) {
            Objects.requireNonNull(a);
            return new AbstractList<>() {
                    @Override public Integer get(int i) {
                            return a[i]; // Autoboxing (Item 6)
                    }
                    @Override public Integer set(int i, Integer val) {
                            int oldVal = a[i];
                            a[i] = val; // Auto-unboxing
                            return oldVal; // Autoboxing
                    }
                    @Override public int size() {
                            return a.length;
                    }
            };
    }

    입력받은 int 배열을 List 처럼 다룰 수 있는 view를 제공하는 정적 팩터리 메서드였다. 만약 Objects.requireNonNull(a); 검사를 수행하지 않았다면, null을 받아도 문제없이 수행하겠지만 나중에 접근할 때가 되어서야 NullPointerException을 던진다. 이 view는 어디서 왔는지 알 길도 없어서 디버그가 골치아파진다.

    생성자도 나중을 위해 저장하는 매개변수의 특별한 예시 중 하나이다. 생성자 매개변수의 유효성 검사는 클래스 불변식을 망치는 객체가 만들어지지 않게 하는데 중요하다.

    메서드 매개변수 유효성 예외

    만약 매개변수 유효성 검사비용이 높은 경우, 실용적이지 않은 경우, 계산과정에서 암묵적으로 검사가 수행될 때다.

    Collections.sort(List)를 생각했을 때, 리스트 안 객체는 Comparable을 구현하여야 한다. 그러나

    1. 리스트를 전체 순회하며 검사해야 하는데, 리스트가 매우 크다면 배보다 배꼽이 더 큰 경우가 생긴다.
    2. 비교가 불가능한 객체가 있다면 ClassCastException을 던질 것이기 때문에 비교과정에서 자연스레 유효성이 검사될 것이다.

    댓글