You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
예외는 예외 상황에서 쓸 의도로 설계되었다. 정상적인 제어 흐름에서 사용해서는 안되며, 이를 프로그래머에게 강요하는 API를 만들어서도 안된다. 표준적이고 쉽게 이해되는 관용구를 사용하고, 성능 개선을 목적으로 과하게 머리를 쓴 기법은 자제하라.
정상적인 제어 흐름 vs 예외 상황
프로그램에서의 정상 상황과 예외 상황은 정의하기 나름이다.
예를 들어 배열에 접근하는 코드라면, 유효한 범위만큼 탐색하는 것은 정상적인 상황이지만 배열의 범위를 벗어난 인덱스로 접근하는 것은 예외적인 상황이다. IndexOutOfBoundsException과 같은 예외를 사용한다.
다른 예시로 특정 글에 달린 댓글을 조회하는 API라면, DB에 등록된 글의 PK를 FK로 가지는 모든 댓글을 가져오는 것은 정상적인 제어 흐름이지만, 사용자가 존재하지 않는 글을 요청하는 것은 예외적인 상황이다. NotFoundException과 같은 커스텀 예외를 사용한다.
정상적인 제어 흐름에서 예외를 사용한 예시
다음은 배열 원소를 순회하는 코드이다. 다만 아주 끔찍한 방식으로 하고 있다.
원소를 순차 탐색하다가 배열의 끝에 도달하여 유효하지 않은 인덱스로 접근을 시도하면 ArrayIndexOutOfBoundsException가 발생할 것이다. 이 예외가 터지면 탐색을 종료한다.
배열 원소를 순회하는 프로그램에서 유효한 범위 탐색을 마치는 것은 정상적인 제어 흐름이다. 보통 이런 상황에서는 예외를 쓰지 말고 관용적인 for문을 사용하는 것이 좋다.
for (Mountainm : range) m.climb();
너무 자명해보이는 사실인데, 왜 관용적인 for문 대신 while문을 사용한 것일까? 어설프게 성능을 높이려고 했기 때문이다. JVM은 배열에 접근할 때마다 경계를 넘지 않는지 검사하는데, 일반적인 for문도 경계를 검사한다. 따라서 같은 일이 중복되지 않도록 while문과 예외 조합을 사용한 것이다. 하지만 이러한 시도는 실제로 성능을 높이는데 기여하지 않는다. 예외는 경계 검사보다 느리다.(당연함. 예외는 예외 상황에서 사용될것이라고 가정하고 설계되었으므로 최적화 x) 코드를 try catch 안에 넣으면 JVM이 적용할 수 있는 최적화가 제한된다. 그리고 배열을 순회하는 표준 관용구는 앞서 걱정한 중복 검사를 수행하지 않는다. JVM이 알아서 최적화한다.
배열 원소 순회에서 예외를 사용한 코드는 그렇지 않은 코드에 비해 다음과 같은 단점을 가진다.
가독성이 떨어진다.
성능이 느리다.
버그를 숨겨 디버깅을 어렵게 만든다.
만약 반복문에서 호출한 climb()가 range가 아닌 다른 배열에 접근하다가 유효하지 않은 인덱스로 접근하여 ArrayIndexOutOfBoundsException가 발생했다고 가정하자. while문 코드는 해당 예외가 range 배열 탐색이 끝나서 발생한 것으로 오인하여 예외를 잡고 정상적인 제어 흐름으로 처리할 것이다. 반면 for문을 사용했다면 해당 예외를 잡지 않고 스레드를 즉각 종료시킬 것이다.
API의 예외 사용 예시 - 상태 검사 메소드
잘 설계된 API라면 클라이언트가 정상적인 제어 흐름에서 예외를 사용할 일이 없게 해야한다. 만약 클래스가 특정한 상태에서만 정상적으로 동작하는 상태 의존적 메소드를 제공한다면, 이 상태의 검사 메소드도 함께 제공해야한다.
예를 들어 Iterator 인터페이스의 next와 hasNext는 각각 상태 의존 메소드, 상태 검사 메소드에 속한다. next는 다음에 가져올 객체가 있는 상태일 때 정상적으로 동작한다. 따라서 hasNext로 유효한지 먼저 검사를 하고 유효하다면 next를 사용한다. 만약 상태 검사를 안하고 hasNext에 접근했다가 유효하지 않은 곳을 탐색한다면 NoSuchElementException 예외가 발생한다.
어쨌든 덕분에 다음과 같은 관용구를 작성할 수 있다.
for (Iterator<Foo> i = collection.iterator(); i.hasNext();) {
Foofoo = i.next();
...
}
만약 상태 검사 메소드가 없었다면 while로 탐색하다가 NoSuchElementException 예외를 잡는 방식으로 컬렉션을 순회해야할 것이다.
try {
Iterator<Foo> i = collection.iterator();
while (true) {
Foofoo = i.next();
...
}
} catch (NoSuchElementException) {}
상태 검사 메소드의 대안
상태 검사 메소드를 호출하고, 그 결과 true일 때 상태 의존 메소드를 사용하는 것이 적절한 클라이언트의 사용 흐름이다. 이렇게 사용하지 않으면 예외가 발생할 것이다.
상태 검사 메소드 외에도 올바른 상태가 아닐 때 빈 Optional이나 null 등을 반환하는 방법도 있다. 다음 지침을 따른다.
여러 스레드가 동시에 접근하거나 상태가 변할 수 있다면 옵셔널이나 특정 값을 사용한다. 상태검사 메소드와 상태의존 메소드를 호출하는 사이에 객체의 상태가 변할 위험이 있다.
성능이 중요한 상황에서 상태검사 메소드가 상태의존 메소드의 일부 기능을 중복 수행한다면 옵셔널이나 특정 값을 선택한다.
다른 모든 경우에는 상태 검사 메서드 방식이 더 낫다. 가독성이 더 좋고, 잘못 사용했을 때 예외를 던지므로 버그를 찾기 쉽다. 특정 값을 사용하는 방식은 예외를 던지지 않으므로 검사하지 않고 지나치면 발견이 어렵다.
The text was updated successfully, but these errors were encountered:
정상적인 제어 흐름 vs 예외 상황
프로그램에서의 정상 상황과 예외 상황은 정의하기 나름이다.
예를 들어 배열에 접근하는 코드라면, 유효한 범위만큼 탐색하는 것은 정상적인 상황이지만 배열의 범위를 벗어난 인덱스로 접근하는 것은 예외적인 상황이다. IndexOutOfBoundsException과 같은 예외를 사용한다.
다른 예시로 특정 글에 달린 댓글을 조회하는 API라면, DB에 등록된 글의 PK를 FK로 가지는 모든 댓글을 가져오는 것은 정상적인 제어 흐름이지만, 사용자가 존재하지 않는 글을 요청하는 것은 예외적인 상황이다. NotFoundException과 같은 커스텀 예외를 사용한다.
정상적인 제어 흐름에서 예외를 사용한 예시
다음은 배열 원소를 순회하는 코드이다. 다만 아주 끔찍한 방식으로 하고 있다.
원소를 순차 탐색하다가 배열의 끝에 도달하여 유효하지 않은 인덱스로 접근을 시도하면 ArrayIndexOutOfBoundsException가 발생할 것이다. 이 예외가 터지면 탐색을 종료한다.
배열 원소를 순회하는 프로그램에서 유효한 범위 탐색을 마치는 것은 정상적인 제어 흐름이다. 보통 이런 상황에서는 예외를 쓰지 말고 관용적인 for문을 사용하는 것이 좋다.
너무 자명해보이는 사실인데, 왜 관용적인 for문 대신 while문을 사용한 것일까? 어설프게 성능을 높이려고 했기 때문이다. JVM은 배열에 접근할 때마다 경계를 넘지 않는지 검사하는데, 일반적인 for문도 경계를 검사한다. 따라서 같은 일이 중복되지 않도록 while문과 예외 조합을 사용한 것이다. 하지만 이러한 시도는 실제로 성능을 높이는데 기여하지 않는다. 예외는 경계 검사보다 느리다.(당연함. 예외는 예외 상황에서 사용될것이라고 가정하고 설계되었으므로 최적화 x) 코드를 try catch 안에 넣으면 JVM이 적용할 수 있는 최적화가 제한된다. 그리고 배열을 순회하는 표준 관용구는 앞서 걱정한 중복 검사를 수행하지 않는다. JVM이 알아서 최적화한다.
배열 원소 순회에서 예외를 사용한 코드는 그렇지 않은 코드에 비해 다음과 같은 단점을 가진다.
만약 반복문에서 호출한 climb()가 range가 아닌 다른 배열에 접근하다가 유효하지 않은 인덱스로 접근하여 ArrayIndexOutOfBoundsException가 발생했다고 가정하자. while문 코드는 해당 예외가 range 배열 탐색이 끝나서 발생한 것으로 오인하여 예외를 잡고 정상적인 제어 흐름으로 처리할 것이다. 반면 for문을 사용했다면 해당 예외를 잡지 않고 스레드를 즉각 종료시킬 것이다.
API의 예외 사용 예시 - 상태 검사 메소드
잘 설계된 API라면 클라이언트가 정상적인 제어 흐름에서 예외를 사용할 일이 없게 해야한다. 만약 클래스가 특정한 상태에서만 정상적으로 동작하는 상태 의존적 메소드를 제공한다면, 이 상태의 검사 메소드도 함께 제공해야한다.
예를 들어 Iterator 인터페이스의 next와 hasNext는 각각 상태 의존 메소드, 상태 검사 메소드에 속한다. next는 다음에 가져올 객체가 있는 상태일 때 정상적으로 동작한다. 따라서 hasNext로 유효한지 먼저 검사를 하고 유효하다면 next를 사용한다. 만약 상태 검사를 안하고 hasNext에 접근했다가 유효하지 않은 곳을 탐색한다면 NoSuchElementException 예외가 발생한다.
어쨌든 덕분에 다음과 같은 관용구를 작성할 수 있다.
만약 상태 검사 메소드가 없었다면 while로 탐색하다가 NoSuchElementException 예외를 잡는 방식으로 컬렉션을 순회해야할 것이다.
상태 검사 메소드의 대안
상태 검사 메소드를 호출하고, 그 결과 true일 때 상태 의존 메소드를 사용하는 것이 적절한 클라이언트의 사용 흐름이다. 이렇게 사용하지 않으면 예외가 발생할 것이다.
상태 검사 메소드 외에도 올바른 상태가 아닐 때 빈 Optional이나 null 등을 반환하는 방법도 있다. 다음 지침을 따른다.
The text was updated successfully, but these errors were encountered: