→ 예외를 안써도 되는 상황에서는 예외를 쓰지마라
Car[] cars = new Car[100];
try {
int index = 0;
while (true) {
cars[index++].advance();
}
} catch (ArrayIndexOutOfBoundsException e) {
}
- JVM이 배열에 접근할 때 경계를 검사하는데, 일반적인 반복문도 경계에 도달하면 종료한다.
- 일반적인 반복문을 사용하면 일을 두 번 하니까 한가지 일을 생략했다.
- 코드를 한눈에 파악하기 어렵다.
- 예외는 예외 상황에서 쓸 용도로 만들어졌기 때문에 최적화를 신경 쓴 코드로 판단하기 힘들다.
- 코드를 try-catch 안에 넣으면 JVM이 적용할 수 있는 최적화가 제한된다.
- 배열을 순회하는 표준 관용구는 중복검사를 하지 않는다. JVM이 최적화를 했다.
for (Cars car : cars) {
car.advance();
}
자동차 5000만개를 생성해서 전진시키는 코드를 작성했다.
-
코드 보기
public class ExceptionTest { @Test void test1() { Car[] cars = new Car[50_000_000]; for (int i = 0; i < 50_000_000; i++) { cars[i] = new Car(new Name("test")); } double before = System.currentTimeMillis(); for (Car car : cars) { car.advance(RandomUtil.getNumbersInRange(10)); } double after = System.currentTimeMillis(); System.out.println("반복문 속도 : " + (after - before) + "(ms)"); } @Test void test2() { Car[] cars = new Car[50_000_000]; for (int i = 0; i < 50_000_000; i++) { cars[i] = new Car(new Name("test")); } double before = System.currentTimeMillis(); try { int index = 0; while (true) { cars[index++].advance(RandomUtil.getNumbersInRange(10)); } } catch (ArrayIndexOutOfBoundsException e) { } double after = System.currentTimeMillis(); System.out.println("예외처리한 속도 : " + (after - before)+ "(ms)"); } }
책에서는 느리다고 하지만 생각보다 큰 차이는 없다.
반복문 안에 버그가 숨어있다면 흐름 제어에 쓰인 예외가 이 버그를 숨겨 디버깅을 어렵게 만든다.
반복문에서 호출한 메서드가 내부에서 관련 없는 배열을 사용하였다가 ArrayIndexOutOfBoundsException
을 일으켰다고 가정해보자.
표준 관용구
- 예외를 잡지 않고 스택에 정보를 남기고 스레드를 즉각 종료시킨다.
예외를 사용한 반복문
- 버그때문에 발생한 엉뚱한 예외를 정상적인 반복문 종료 상황으로 오해하고 넘어간다.
표준적인 관용구를 사용하고, 성능 개선을 목적으로 과하게 머리를 쓴 기법을 자제한다. 실제 성능이 좋아지더라도 자바가 발전하면서 최적화로 얻은 성능 우위가 오래가지 않을 수도 있다. 반면 버그의 폐해와 유지보수 문제는 계속 이어질 것이다.
특정 상태에서만 호출할 수 있는 상태 의존적
인 메서드를 제공한다면 상태 검사
메서드도 함께 제공해야 한다.
Iterator
인터페이스의 next
와 hasNext
가 각각 상태 의존적 메서드와 상태 검사 메서드에 해당된다. 이러한 메서드 덕분에 for 관용구를 사용할 수 있다. for-each도 내부적으로 hasNext를 사용한다.
List<Car> cars = new ArrayList<>();
for (Iterator<Car> i = cars.iterator(); i.hasNext();) {
...
}
List<Car> cars = new ArrayList<>();
try {
Iterator<Car> i = cars.iterator();
while (true) {
Car car = i.next();
}
} catch (NoSuchElementException e) {
}
null 또는 Optional과 같은 특수한 값을 반환하는 방법이다.
- 동기화 없이 여러 스레드가 동시에 접근할 수 있거나 외부 요인으로 상태가 변할 수 있다면 옵셔널이나 특정 값을 사용한다.
- 성능이 중요한 상황에서 상태 검사 메서드가 상태 의존적 메서드의 작업 일부를 중복 수행한다면 옵셔널이나 특정 값을 선택한다.
- 이외에는 상태 검사 메서드 방식이 더 낫다고 할 수 있다.