
수많은 에러들에 대한 예외를 잡아줘야 한다.
내가 만든 코드가 내 생각대로 작동한다는 생각은 안하는게 좋다.
아무리 잘 만든다고 해도 예상치 못한 결과가 나올 수도 있고, 악질(?)사용자들이 정상적이지 않은 값을 입력하거나 원하지 않았던 경로로 서비스를 사용한다거나의 경우가 발생할 수 있다.
이러한 여러 문제를 코드상으로 해결하는 방법에는 크게 3가지가 있다.
바로 try-catch, throw, throws이다.
try{
//예외가 발생할 일이 없는 로직 -1
**//예외가 발생할 수도 있는 로직**
//예외가 발생할 일이 없는 로직 -2
}catch(예외 클래스 e){
**//예외 처리**
}
예외 터지면 2번은 실행을 안 하게 된다. ⇒ 그래서 예외 터지면 영원히 안 쓰니까 finally이라는 게 나오게 되었는데 생각해보면 그냥 try-catch밖에 구현하면 되지 않냐는 생각을 할 수 있다. 그럼에도 불구하고 finally가 나온 이유를 생각해보면 명시적으로 한 메소드의 내용이라는 것을 암시하기 위해 사용한 것이 아니냐는 추측을 해볼 수 있다.
나도 실제로는 크게 생각안하고 finally를 쓰기 보다는 try-catch밖에 만들 것 같기는 하다.
try-catch 에서 catch는 if-else와 같이 여러 개 쓸 수 있다. 작은 에러 잡는 거 부터 catch로 해서 조금씩 늘려서 에러를 확실히 잡아서 예외 처리를 확실히 하는 것이 좋을 것이다. ( 제일 위단에 가장 작은 catch )

throw는 다음과 같이 try-catch와 함께 사용하여 try문에서 에러가 발생할 경우 에러를 던져 해당 메소드 내에서 오류를 처리하는 로직을 만들 수 있도록 처리하는 방법이다. 다음으로 설명할 throws와 비슷하면서도 다른 점은 어디서 에러를 처리하냐의 차이로 볼 수 있다.
throws는 앞선 throw와 같이 에러를 날리긴 할 건데 메소드 내에서가 아니라 해당 메소드를 호출한 곳으로 이동하여서 그 쪽에서 해결하도록 처리시키는 것이다. 나는 책임 전가 느낌으로 이해하였고 실제로도 비슷한 의미로 사용되는 것 같았다.
throw나 throws와 같이 예외 처리를 던져줄 때에 Exception class를 이용하는 것을 볼 수 있다.
여기서 Exception class는 다시 2가지로 나누어 볼 수 있다.

다음 그림에서 보듯이 exception에는 check exceptions와 uncheck exceptions가 존재하게 된다.
check Exceptions은 컴파일러가 잡아주는 에러, Uncheck Exceptions는 RuntimeException의 하위 클래스가 아니면서 Exception 클래스의 하위 클래스들을 의미한다.
check excecptions에 해당하는 에러들은 throws를 안써서 에러를 안 잡아주면 컴파일 에러가 나와서 프로그램이 죽는 치명적인 상황을 초래하게 되므로 throw를 함께 사용하게 된다. 예시로는 그림에서 알 수 있듯이 IO exception등이 있다.
반대로 uncheck exceptions의 경우에는 구동되기 전에는 체크하지 않는 예외 케이스로 (런타임 시점 에러) RuntimeException의 하위 클래스들을 의미합니다. 이것은 체크 예외와는 달리 에러 처리를 강제하지 않는다는 특징이 있고 말 그대로 실행 중에(runtime) 발생할 수 있는 예외를 의미한다.
여러 exception들의 예시
ClassCastException 수행할 수 없는 타입 변환이 진행될 경우
ArrayIndexOutOfBoundsException 배열에 잘못된 인덱스를 사용하여 접근할 경우
NullPointerException null 객체의 인스턴스 메소드를 호출하는 등의 경우
ArithmeticException 산술 연산에서 정수를 0으로 나누는 등 연산을 수행할 수 없는 경우
NumberFormatException 정수가 아닌 문자열을 정수로 변환할 때 예외 발생
EmptyStackException 스택이 비어있는데 요소를 제거하려고 할 때 발생 (자료구조 스택? 메모리 구조 스택?) IOException 입출력 작업 중에 발생하는 예외
ArithmeticException 0으로 나누는 경우와 같이 계산 조건이 잘못됐을 때 발생하는 예외 클래스
IllegalArgumentException 메소드의 전달 인자값이 잘못된 경우
IndexOutOfBoundException Index 값이 범위를 넘어갈 경우 발생
InputMismatchException 의도한 입력이 아닐 경우 발생하는 런타임 예외 예를 들어, 정수를 입력해야하는데 문자를 입력할 경우 발생한다.
NoClassDefFoundException 컴파일 시점에 존재했던 클래스가 런타임에 존재하지 않으면 발생하는 에러
FileNotFoundException 파일에 접근하려고 하는데 파일을 찾지 못했을 때 발생하는 에러
NoSuchElementException 존재하지 않는 값을 가져오려고 할 때
이렇듯 수많은 예외 클래스를 만들어서 이를 구체화 하면 좋은 이유는 에러가 어디서 발생했는지, 그리고 어떤 에러가 발생했는지를 찾는데에 용이한 점을 가지고 있다. 즉, 사용자가 에러를 고칠 수 있는 데에 도움을 줄 수 있다.
그럼 여기서 한가지 의문을 갖게 될 수 있다.
throw와 throws등으로 예외를 계속 전달 전달 해주게 되면 그래서 어디서 예외 처리를 하실 것인가??
나는 개인적인 생각으로는 레포지는 단순히 db랑 로직을 하는 곳일 뿐. 아무런 다른 게 들어가면 안되기 때문에 throws를 서비스에서 판정해 컨트롤러에서 처리하고 ResponseEntity를 통해 status를 반환해주는 게 용이하다고 생각했었다.
이는 내일 다른 동료들과 함께 이야기를 통해 좀 더 정확한? 명확한 생각을 정해 볼 생각이다.
예외를 잘 잡아보자. 잡을 수 있다면