예외는 오직 예외 상황에만 사용할 것

예외를 제어 흐름용으로 사용하지 말자

• 예외는 오직 예외 상황에서만 사용해야 한다.

• 일반적인 제어 흐름용으로 사용해서는 안됨

    ◦  코드를 헷갈리게 한다(코드가 직관적이지 못하다).

    ◦  성능 저하의 원인이 될 수 있다.

    ◦  엉뚱한 곳에서 발생한 버그를 숨겨버릴 수 있다.

 

예외를 제어 흐름용으로 사용한 예시

try {
	int i = 0;
	while(true) { mountainList[i++].climb(); }
} catch(ArrayIndexOutOfBoundsException e) {...}
for(Mountain m : mountainList) { m.climb(); }

• 두 코드 모두 mountainList 배열의 원소를 순회하는 목적으로 쓰여졌다.

• 예외를 제어흐름용으로 사용하니 코드가 전혀 직관적이지 못하다.

• 이 작자는 코드를 왜 이렇게 짰을까?

    ◦  JVM 은 배열에 접근할 때 마다 경계를 넘지 않는지 검사한다.

    ◦  for 문은 배열의 경계에 도달하면 반복문을 종료한다.

    ◦  for 문 안에서 배열에 접근하고 있으니, 배열의 경계에 대한 검사를 2번 하고 있다고 생각할 수 있다.

    ◦  중복 검사를 피하면 성능을 향상시킬 수 있지 않을까? → 예외를 사용에 제어 흐름을 구현하면 되겠다!

 

    ◦  이 추론이 잘못된 이유

          -  예외는 예외 상황에 쓸 용도로 설계된 것이다. JVM 개발자가 최적화에 신경쓰지 않았을 가능성이 크다.

          -  코드를 try-catch 블록 안에 넣으면 JVM 이 적용할 수 있는 최적화가 제한된다.

          -  배열을 순회하는 for 문은 앞서 걱정한 중복 검사를 수행하지 않는다. JVM 이 알아서 최적화해주기 때문.

          -  결과적으로 성능이 개선되지 않는다! (실제로 코드를 돌려보면 예외를 사용한 쪽이 2배 이상 느리다)

 

    ◦  뿐만 아니라 반복문 안에 버그가 숨어 있다면 흐름 제어에 쓰인 예외가 이 버그를 숨겨 디버깅을 훨씬 어렵게 할 수 있다.

          -  반복문의 몸체 내부에서 다른 배열을 사용하는 경우, 해당 배열에서 ArrayIndexOutOfBound 예외가 발생할 수 있다.

          -  표준 관용구

              -  예외처리 하지 않고 스택 추적 정보를 남긴 다음 해당 스레드를 종료시킨다.

          -  예외를 사용한 반복문

              -  ArrayIndexOutOfBound 예외 발생 = 배열 순회 종료 라고 간주하고 있음

              -  즉 실제 버그 상황에서 발생한 예외를 반복문 종료 상황으로 오해하고 넘어간다.

 

코드를 짤 때는 꼼수를 쓰지 말자

• 표준적이고 쉽게 이해되는 관용구를 사용하라

• 성능 개선을 목적으로 과하게 머리를 쓴 기법은 자제하라.

    ◦  꼼수로 성능이 개선되더라도 자바 플랫폼은 꾸준히 업데이트 되기 때문에 상대적인 성능 우위는 오래가지 않는다.

    ◦  반면 꼼수에 숨겨진 미묘한 버그와 어려워진 유지보수 문제는 계속된다.


상태 검사 메소드 / 옵셔널 / 특정 값

상태 의존적 메소드와 상태 검사 메소드

• 지금까지 살펴본 원칙은 API 설계 시에도 유의해야하는 부분이다.

• 특히 특정 상태에서만 호출할 수 있는 상태 의존적 메소드를 제공하는 클래스의 경우, 상태 검사 메소드를 함께 제공해 사용자가

    예외 처리를 제어 흐름에 사용하지 않도록 해줘야 한다.

    ◦  상태 의존적 메소드 예시 : Iterator 의 next

          -  Iterator 에 다음 원소가 있어야 next 메소드 호출이 가능하므로

    ◦  상태 검사 메소드 예시 : Iterator 의 hasNext

          -  Iterator 에 다음 원소가 있는지 여부를 hasNext 메소드를 통해 검사할 수 있다.

for (Iterator<Foo> i = collection.iterator(); i.hasNext(); ) { Foo foo = i.next(); }

• 상태 검사 메소드 없이 상태 의존 메소드만 있었다면, 사용자는 아래와 같이 코드를 작성할 수 밖에 없을 것이다.

try {
	Iterator<Foo> i = collection.iterator();
	while(true) { Foo foo = i.next(); }
} catch (NoSuchElementException e) {...}

 

 

상태 검사 메소드 / 옵셔널 / 특정 값

• 상태 검사 메소드 대신 올바르지 않은 상태일 때 빈 옵셔널을 반환하거나, null 같이 사전에 정의해둔 특정 값을 반환하는 방법도 있다.

• 아래는 이 세가지 방식 중 하나를 선택하는 것에 대해 책에서 소개하는 지침이다.

    ◦  옵셔널 혹은 특정 값을 반환하는 방식

          -  외부 동기화 없이 여러 스레드가 동시에 접근할 수 있는 경우

          -  외부 요인으로 상태가 변할 수 있는 경우

              → 상태 검사 메소드와 상태 의존적 메소드를 호출하는 사이에 객체의 상태가 변할 수 있기 때문

          -  성능이 중요한 상황, 상태 검사 메서드가 상태 의존적 메서드의 작업 일부를 중복 수행하는 경우

    ◦  상태 검사 메서드를 사용하는 방식

          -  위의 상황을 제외한 다른 모든 경우

              → 가독성이 더 좋다 + 상태 검사 메서드 호출이 누락된 경우 상태 의존적 메소드가 이를 잡아주기 때문에 버그를 잡기 쉽다.

          -  단, 특정 값은 검사하지 않고 지나쳐도 발견하기 어렵다는 단점이 있다.

객체는 클래스가 아닌 인터페이스로 참조하라

클래스가 아닌 인터페이스로 참조하는 예시

• 적합한 인터페이스가 있다면 객체를 클래스 타입이 아닌 인터페이스 타입으로 선언하는 것이 좋다.

    ◦ 이렇게 되면 구체적인 클래스 타입은 객체를 생성할 때 생성자에서만 사용하면 된다

    ◦ 아래는 Set 인터페이스를 구현한 LinkedHashSet 변수를 선언하는 코드이다.

LinkedHashSet implements Set

코드의 유연성을 높이는 설계

• 기존에 사용하던 구현 클래스 타입을 다른 것으로 바꿔야 하는 상황이 종종 있다.

    ◦ 더 성능이 좋은 클래스 타입을 사용하고 싶은 경우

    ◦ 더 다양한 신기능을 사용할 수 있는 클래스 타입이 있는 경우

         -  HashMap 보다 EnumMap 이 속도도 더 빠르고 순회 순서도 키의 순서와 같아 순서 예측이 가능하다.

         -  EnumMap 은 키가 열거 타입인 경우에만 사용할 수 있다. 이 때 LinkedHashMap 을 사용하면 키 타입과 상관없이

            사용할 수 있으면서 순회 순서를 예측할 수 있다.

• 인터페이스 타입으로 객체를 참조했다면 이런 변동사항에 훨씬 유연한 대처가 가능하다.

    ◦ 구현 클래스를 교체하고 싶다면 다른 클래스의 생성자를 호출해주기만 하면 되기 때문

 

• Example

    ◦ LinkedHashSet 대신 HashSet 를 사용하려고 코드를 수정하는 경우

    ◦ Set 인터페이스를 사용한 경우 우변의 생성자 외 부분은 수정이 필요 없다

    ◦ LinkedHashSet 클래스를 사용한 경우 좌, 우변 모두 수정이 필요하다.

 

    ◦ 그러나 누군가는 이렇게 생각할 수 있다

         -  굳이 인터페이스 써야 하나? 그냥 클래스 쓰고 나중에 수정할 때 좌, 우변 다 바꿔주면 되는거 아님?

         -  그러나 이 방법은 자칫하면 컴파일 에러가 발생할 수 있다.

         -  처음에 객체를 인터페이스로 만들었다면, 메소드를 설계할 때 파라미터로 인터페이스 객체를 받도록 유도할 수 있다.

         -  처음에 객체를 클래스 만들었다면, 메소드를 설계할 때 인터페이스가 아닌 클래스 객체를 파라미터로 받게 해버리는 상황이

            발생할 수 있다.

         -  인터페이스를 사용하면 이후 구체 클래스가 바뀌어도 컴파일 문제가 없으나 클래스를 사용하면 구체 클래스가

            바뀔 때 컴파일 문제가 생긴다. (즉 하나가 바뀔 때 수정해야 하는 부분이 더 많아진다 : 유연성 낮음)

         -  즉 애초에 변수를 인터페이스 타입으로 선언해 이 문제를 방지하자.

 

• 주의사항

    ◦ 기존에 사용하던 구현 클래스 타입을 다른 것으로 바꿀 때 유의사항

    ◦ 원래의 클래스가 인터페이스 레벌에서는 없는 특별한 기능을 제공했고, 주변 코드가 이 기능에 의존하여 동작하고 있었다면

      새로운 클래스도 동일한 기능을 제공해야 코드가 깨지지 않는다.

         -  LinkedHashSet 는 반복자의 순회 순서를 보장한다. 이를 염두에 두고 코드를 작성한 상황에서 LinkedHashSet

            HashSet 으로 대체해버리면 연산 오류가 발생할 수 있다.


참조할 만한 인터페이스가 없는 경우

값 클래스 (Value Class)

• 한번 값이 할당 된 이후에 변경되지 않음을 보장해야 하는 경우

    ◦ 예) String, BigInteger 등의 불변 객체

• 값 클래스가 여러가지로 구현될 수 있음을 염두에 두고 설계하는 일이 거의 없다.

• final 인 경우가 많고 상응하는 인터페이스가 별도로 존재하는 경우가 드물다.

• 값 클래스는 매개변수 / 변수 / 필드 / 반환 타입으로 사용해도 무방하다.

 

클래스 기반으로 작성된 프레임워크가 제공하는 객체

• OutputStreamjava.io 패키지의 여러 클래스가 이 부류에 속한다.

 

인터페이스에는 없는 특별한 메소드를 제공하는 클래스

• PriorityQueue 클래스는 Queue 인터페이스에는 없는 comparator 메서드를 제공한다.

• 클래스 타입을 직접 사용하는 경우, 이런 특별 메서드의 사용은 꼭 필요한게 아니라면 최소화하는 것이 좋다.

    ◦ 코드의 유연성을 많이 떨어트리기 때문


정리

객체를 표현할 적절한 인터페이스가 있는지 먼저 찾아보자. 인터페이스로 객체를 참조하면 더 유연한 코드를 작성할 수 있기 때문이다.

만약 없다면, 필요한 기능을 제공해주는 범위 내에서 가장 상위 클래스의 타입을 사용하자.

 

표준 라이브러리를 사용하면 좋은점

선대 개발자들의 노고를 누리자

• 표준 라이브러리를 사용하면 그 코드를 작성한 전문가의 지식과, 앞서 라이브러리를 사용한 다른 개발자들의 경험을 활용할 수 있다.


쓸데없는데 시간을 허비하지 말자

• 핵심적인 일과 크게 관련없는 문제를 해결하느라 시간을 허비하지 않아도 된다.

• 자잘한 기능 개발이 아닌, 어플리케이션 핵심 기능 개발에 집중할 수 있다.


자동으로 개선되는 성능

 따로 노력하지 않아도 성능이 지속해서 개선된다.

 자바 플랫폼 라이브러리의 많은 부분이 수년에 걸쳐 다시 작성되며, 때로는 성능이 많이 개선되기도 한다.


기능 무한 증식

 라이브러리에 부족한 부분이 있다면 개발자 커뮤니티에서 이야기가 나오고, 논의된 후 다음 릴리스에 해당 기능이 추가되곤 한다.


알아보기 쉬운 코드 작성 가능

• 작성한 코드가 많은 사람에게 낯익은 코드가 된다.

    ◦  읽기 쉽고, 유지보수하기 좋고, 재사용하기 쉬운 코드


그럼에도 많은 개발자들이 기능을 직접 구현하는 이유

• 아마도 라이브러리에 그런 기능이 있는지 모르기 때문일 것이다.

• 메이저 릴리즈마다 주목할만한 수많은 기능이 라이브러리에 추가된다.

• 메이저 릴리즈마다 제공되는 릴리즈 노트에서 새로운 기능에 대한 설명을 참고하자.

 

JDK Release Notes

We’re sorry. We could not find a match for your search. We suggest you try the following to help find what you’re looking for: Check the spelling of your keyword search. Use synonyms for the keyword you typed, for example, try "application" instead of

www.oracle.com

 

표준 라이브러리 사용 예시

난수 생성 메소드

많은 개발자들이 메소드를 이렇게 작성하곤 한다.

• java.util.Random 을 이용하여 작성된 메소드 : 0 이상 n 미만의 수를 랜덤 반환한다.

•문제점

   ◦  n 이 그리 크지 않은 2의 제곱수라면 얼마 지나지 않아 같은 수열이 반복된다.

   ◦  n 이 2의 제곱수가 아니라면 몇몇 숫자가 평균적으로 더 자주 반환된다.

n 의 값이 크면 이 현상은 더 두드러진다.

   ◦  지정한 범위 바깥의 수가 종종 반환될 수 있다.

         -  rnd.nextInt( ) 의 반환값이 -2147483648 일 때

         -  자바의 int 타입은 32 bit 이므로 -2147483648 ~ 2147483647 의 값만 나타낼 수 있기 때문에
             rnd.nextInt() 이 -2147483648 를 반환하는 경우 절댓값 처리가 되지 않는다.

         -  Math.abs(-2147483648) = -2147483648

해결 방법

   ◦  java.util.Random 을 이용해 메소드를 작성하는 대신 Random.nextInt(int) 를 사용하면 된다.

         -  몇몇 숫자가 더 자주 반환되는 문제 해결

         -  범위 바깥의 수가 반환될 일도 없음

Java 7 부터는 성능이 훨씬 개선된 TreadLocalRandom 을 사용하는 것이 좋다.

Random.nextInt(int) vs TreadLocalRandom 성능 비교

포크-조인 풀이나 병렬 스트림에서는 SplittableRandom 을 사용하는 것이 좋다.


입력한 URL에서 내용을 가져오는 어플리케이션

자바 9에서 InputStream 에 추가된 transferTo 메서드를 사용하면 쉽게 구현할 수 있다.

Command Line 으로 입력 받기 까다로워 임시 String url 을 선언해 사용했다.

위 코드는 transferTo 를 이용해 이 기능을 완벽히 구현한 코드이다.

 

알아두면 좋은 라이브러리

java.lang

• Object 클래스

• String 클래스

• System 클래스

• Wrapper(래퍼) 클래스


java.util

• Arrays 클래스

• java.util.concurrent (동시성 라이브러리)

   ◦  멀티스레드 프로그래밍 작업을 단순화해주는 편의 기능 탑재

   ◦  자신만의 멀티스레딩 코드를 직접 구현할 수 있도록 도와주는 요소 탑재

   ◦  예) Semaphore 클래스, locks 패키지 등


java.io

• File 클래스

• BufferedReader / Writer 클래스

• Serializable 인터페이스


컬렉션 프레임워크

• List / Set / Map 클래스


스트림 라이브러리

• 기존 Java 에서 컬렉션 데이터를 처리할때는 for, for-each 루프문을 사용했음

   ◦  복잡한 처리 or 컬렉션의 크기가 커지면 성능 저하를 일으킴

• Stream : Java8에서 추가된 기능

   ◦  컬렉션 데이터를 선언형으로 쉽게 처리할 수 있음

   ◦  복잡한 루프문을 사용하지 않아도 되며, 루프문을 중첩해서 사용할 필요도 X

   ◦  병렬 처리를 별도의 멀티스레드 구현 없이 쉽게 사용 가능


Guava 라이브러리

• 구글이 작성한 자바 오픈 소스 라이브러리

•  Guava 사용 예시

Guava 를 이용해 리스트의 가장 마지막 원소 구하기

정리

어떤 기능이 필요할 때, 이미 구현된 라이브러리가 있는지 먼저 찾아보고 있으면 그걸 쓰자.

만약 찾아봤는데 없으면 구현해라. 즉, 무작정 구현부터 하진 말자. (그래야 너도 나도 좋다)

 

||  null 대신 빈 배열이나 빈 컬렉션을 반환하라.
||  null 을 반환한다고 해서 성능이 좋아지는 것도 아니고,
||  오히려 작성해야 하는 오류 처리 코드만 늘어나기 때문이다.

메서드에서 null 이 반환되면 생기는 일

private final List<Cheese> cheesesInStock = ... ;
public List<Cheese> getCheeses() {
    return cheesesInStock.isEmpty() ? null : new ArrayList<>(cheesesInStock);
}
List<Cheese> cheeses = shop.getCheeses();
if (cheeses != null && cheeses.contains(Cheese.STILTON))
    System.out.println("good");

• getCheeses 메소드를 호출하는 쪽에서는 NullPointerException 을 피하기 위해 반환된 값이 null 인지의 여부를 매번

    체크해줘야 한다.

    ◦  이렇게 오류가 발생하는 것을 방지하기 위해 추가적으로 작성해주는 코드를 방어코드 라고 부른다.

    ◦  위 상황을 보면 불필요한 작업이 2개나 이루어지고 있다.

          -  getCheeses 메소드 : 반환하려는 대상의 Empty 여부를 체크하고 True 인 경우 null 반환

          -  getCheeses 메소드를 호출하는 쪽 : 메소드에서 반환된 값이 null 인지의 여부를 체크해 조건 처리

 

null 대신 빈 배열/컬렉션을 반환하는 경우

public List<Cheese> getCheeses() {
    return new ArrayList<>(cheesesInStock);
}

• cheesesInStock 에 값이 들어 있으면 그 값으로 ArrayList 가 구성되어 반환되고, 비어있으면 Empty ArrayList 가 반환된다.

    ◦ cheesesInStock 이 Empty 인지 확인하는 절차를 생략할 수 있다.

    ◦  당연히 getCheeses 메소드를 호출하는 쪽에서도 null 이 반환되지 않으니 null 을 처리하는 과정을 생략할 수 있다.

 

null 대신 빈 배열/컬렉션을 반환하는 것이 더 좋은 이유

• 빈 배열/컨테이너를 굳이 만들어서 반환하는데에도 비용이 발생하기 때문에 null 을 반환하는 것이 낫다는 주장도 있다.

• 하지만 아래 두가지 이유 때문에 이는 틀린 주장이라고 할 수 있다.

1) null 대신 빈 배열/컬렉션을 반환하는 경우의 성능 차이는 아주 미미하다.

• 분석 결과, 이 정도의 할당으로 인해 발생하는 성능 차이는 대부분 신경 쓸 수준이 되지 못한다.

2) 빈 배열/컬렉션은 굳이 새로 할당하지 않고도 반환할 수 있다.

• 만에 하나 빈 배열/컬렉션을 반환하는 행위가 성능 차이의 주범이 된다고 해도, 매번 새로운 배열/컬렉션을 할당하여 반환하는 대신

    빈 불변 배열/컬렉션을 하나 만들어두고, 매번 이 똑같은 객체를 반환 시킴으로써 간단하게 해결할 수 있다.

public List<Cheese> getCheeses() {
    return cheeseInStock.isEmpty() ? Collections.emptyList() : new ArrayList<>(cheeseInStock);
}

||  Collections.emptyList : 빈 불변 리스트를 반환하는 메소드
||  Collections.emptySet : 빈 불변 집합(Set)을 반환하는 메소드
||  Collections.emptyMap : 빈 불변 맵(Map)을 반환하는 메소드

 

• 단 이 역시 최적화에 해당하니 꼭 필요할 때에만 사용하고,

  사용한 경우에는 수정 전과 후의 성능을 측정하여 실제로 성능이 개선 되었는지 여부를 꼭 확인하자.


public Cheese[] getCheeses() {
    return cheesesInStock.toArray(new Cheese[0]);
}
// 또는 이렇게 작성할수도 있다.
return cheesesInStock.toArray(new Cheese[cheesesInStock.size()]);

• 컬렉션 말고 배열을 사용하는 경우에는 위 코드처럼 구현할 수 있다.

    ◦ toArray 메소드에 파라미터로 넣어준 Cheese[0] 배열은 반환 타입을 지정해주는 역할

    ◦ 이 방식이 성능을 떨어뜨릴 것 같다면 이 역시 길이가 0인 배열(불변)을 미리 선언해두고 매번 그 배열을 사용하면 된다.

private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
public Cheese[] getCheeses() {
    return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
}

• 단, toArray 메소드에 넘기는 배열을 미리 할당해 두는 것이 오히려 성능 저하의 원인이 된다는 연구 결과도 있으므로,

    정말 필요한 상황이 아닌 단순히 성능을 개선할 목적이라면 이 방식은 지양하도록 하자.

메서드와 매개변수

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

    ◦  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(...);
}

아이템을 마치며

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

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