클린 코드 예외 처리 부분을 보다가 이런 문구를 보게 되었습니다. 정상 흐름을 정의해라. 처음에는 이 말이 무슨 뜻인지 잘 몰랐습니다. 곰곰히 생각해 보니, 이펙티브 자바에서도 비슷한 이야기가 있었는데요. 예외는 예외 상황에서만 사용하라는 것이였습니다.

 


 아래 예제는 너무나도 유명한 예제일 겁니다.

 

 

 먼저, 길이가 5인 ArrayList를 생성합니다. 그리고 0부터 4까지 넣었습니다. 다음에, 9 ~ 12번째 줄에서, 리스트에 있는 모든 원소를 순회하게 됩니다. 그리고 foo 메서드를 실행합니다. for loop를 도는데, OutOfBound 에러가 뜨는 경우에, 에러 로그를 남기고 종료합니다.

 

 

 foo 메서드를 봅시다. 이 메서드는 0 이상 6 미만의 정수를 뽑습니다. 0, 1, 2, 3, 4, 5를 뽑는다는 이야기입니다. 이를 k라 합시다. 21번째 줄에, lt의 k번째 원소를 가져오는데요. Random 하게 뽑은 수가 5인 경우, 바운드 에러가 뜰 겁니다. 이펙티브 자바에서 언급하는 메서드의 "버그"를 의미합니다.

 

 이제, 프로그램을 실행시켜 보겠습니다.

 

 

 stack trace를 보니, Main.java의 10번째 줄에서 예외가 발생했다고 떴습니다.

 

 

 그런데, 어떤 경우에는 11번째 줄에서 예외가 걸려버리게 됩니다. 문제는, 이런 예외 상황을 모니터링 하거나 로그를 남기는 경우, 예외 상황이 아님에도 예외라고 오판을 해서 문제가 생길 수 있습니다. 극단적인 예로 10번째 줄에서 catch된 예외가 3000만개쯤 로그 파일에 쌓였고, 11번째 줄에서 catch된 예외가 1개가 쌓였다면?

 

 모니터링을 하는 입장에서는 3000만개쯤 쌓인, 정상적인 흐름에서 발생한 예외 때문에, 진짜 문제였던 1개가 쌓인 예외를 발견하기 쉽지 않을 겁니다.

 

 


 위 프로그램에서 어떤 경우에 예외가 발생하는지 그림을 그려 보겠습니다.

 

 

 프로그램의 순서도는 위와 같습니다. li의 크기는 5이고, i가 계속 증가할 겁니다. i가 5가 되는 순간 li.get(i)를 부르게 되면, 인덱스 범위를 초과했다는 예외가 떨어질 겁니다.

 

 

 그리고, foo에서는 어떤가요? 여기에서는 [0, 5] 사이의 정수를 랜덤하게 뽑아요. 이 정수값을 x라 했을 때, x번째 값을 얻어오게 되는데요. 여기에서도 사실 Bound exception이 떨어집니다. 그래서 예외가 발생할 수 있는 부분은 2개입니다.

 

 

 이 둘 중에 하나라도 문제가 발생하면 13번째 줄의 catch를 탈 거라는 예상을 할 수 있습니다. 노란색 부분은 list를 순회하다가 더 이상 순회할 리스트가 없을 때 예외를 떨구는데요. 사실, 처음부터 끝까지 순회를 하고, 더 이상 원소가 없으면 빠져나오는 것은 정상적인 흐름일 겁니다. 그런데, while loop가 계속 돌면서 i가 끝도 없이 증가합니다. 그렇기 때문에, 10번째 줄에 대해서도 예외가 발생할 수 있습니다. 로그에는 노란색 부분에서 예외가 터졌다는 정보 또한 기록을 하게 됩니다.

 

 그러면, 모니터링 하는 입장에서는 어떤가요? 노란색 부분이 정상적인 흐름이 아니네? 오해를 할 수도 있습니다. 혹은, 노란색 부분 때문에 발생한 예외가 너무 많다 보니, 주황색 부분 때문에 발생한 예외를 지나칠 수도 있을 겁니다. 이는, 순회가 끝났다는 것을 왜 억지로 인덱스 범위가 초과하는 예외가 발생하는지로 체크했기 때문입니다. 


 처음부터 끝까지 모두 탐색해서 더 이상 탐색할 원소가 없어서 빠져나오는 것은 정상적인 흐름입니다. 그런데, foo 메서드 안에서 랜덤한 숫자를 뽑아서 k번째 원소를 뽑았는데, k가 재수없게도 5인 경우는? 이 경우는 정상적인 흐름은 아닐 겁니다.  고로, 아래와 같이 바꿔 보겠습니다.

 

 

 저는 첫 원소부터 차례대로 보고 있는데요. 상태를 어디서 검사하나요? i < li.size() 에서 검사합니다. 이 i의 값이 리스트의 크기보다 커지거나 같아지면 빠져나오는 것은 정상 흐름일 겁니다. 그러면 남은 것은 foo 메서드 내부입니다. 0부터 5까지를 뽑아서 랜덤하게 접근하는 메서드인데, 하필 이 값이 리스트의 크기보다 같거나 컸습니다. 이 경우에는 정상 흐름이 아닐 겁니다.

 

 이 때 Bound 예외가 처리되게 하면 됩니다.

 

계속 i를 증가시키면서 10번째 줄의 lt.get(i)에도 예외가 발생했는데, 코드를 조금 바꾸었습니다. i가 lt.size()보다 항상 작을 때에만 10 ~ 11번째 줄이 수행되기 때문에, lt.get(i)에서 IndexOutOfBoundException이 떨어질 일이 없습니다. 대신 foo 메서드 내에서 떨어질 수는 있을 겁니다. 크기가 5인 리스트에서 lt.get(5) 를 수행할 확률이 조금이라도 있기 때문입니다.