HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🍗
[New] 조규현팀
/
🏪
TS Store
/
🧶
슬기롭게 예외를 처리하는 방법
🧶

슬기롭게 예외를 처리하는 방법

Person
완료율%
상태
완료
나의 블로그
https://velog.io/@gudnr1451/%EC%98%AC%EB%B0%94%EB%A5%B4%EA%B2%8C-%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0
Think Sharing (TS)
🧨
Exception

예외란 무엇인가?

  • 프로그램 실행 과정에서 비 정상적인 상황을 "예외"라고 한다.
    • 예를들어 존재하지 않는 파일을 여는 시도를 한다거나 설정되지 않은 객체의 메소드를 호출하려고 하는 경우에 예외가 발생한다.

예외처리

  • 프로그램 실핼중에 발생하는 예외를 잘 처리해주는 것을 "예외처리(Exception Handling)"이라고 한다.
  • 예외 상황에서 벗어나도록 코드를 작성하던가 시스템을 망가트리지 않는 상황에서 자연스럽게 프로그램이 종료되도록 처리해줘야 한다.

예외를 처리하는 과정

  • 아래의 코드는 ArrayIndexOutOfBoundsException 예외를 발생시키게 된다.
public class ExceptionTest { public static void main(String[] args) { int[] nums = {1, 2}; printArrayItem(nums); } public static void printArrayItem(int[] array) { System.out.println("array[0] = " + array[0]); System.out.println("array[1] = " + array[1]); System.out.println("array[2] = " + array[2]); } }
  • 예외를 처리하려면 try-catch 구문을 이용해 처리할 수 있다.
public class ExceptionTest { public static void main(String[] args) { int[] nums = {1, 2}; try { printArrayItem(nums); } catch (ArrayIndexOutOfBoundsException e) { } System.out.println("프로그램 종료"); } public static void printArrayItem(int[] array) { System.out.println("array[0] = " + array[0]); System.out.println("array[1] = " + array[1]); System.out.println("array[2] = " + array[2]); } }

Java Exception best practice

try-catch 구문을 잘 쓰면 코드가 깔끔해 지지만 대충 쓴다면 오히려 안쓰는 것보다 못한 코드를 만들어 낼 수 있다. 예외처릴 잘 해야한다.

1. 리소스를 정리하자.

  • try-catch 블록에서 리소스에 접근하는 경우가 있다.
    • ex) jdbc 드라이버를 통한 데이터베이스 접근
    • 파일을 열어서 내용을 읽거나 쓰는 경우
public static void main(String[] args) { try { File file = new File("./test.txt"); byte[] bytes = new byte[512]; FileInputStream fileInputStream = new FileInputStream(file); fileInputStream.read(bytes, 0, 512); fileInputStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
  • 이 코드에서 파일을 열고 512 바이트 버퍼를 생성하고 파일로부터 512 바이트만큼 읽은 다음 스트림을 닫는 코드이다. 정상적으로 진행된다면 문제없이 잘 종료가 될 것이다.
  • 만약에 fileInputStream.read(bytes, 0, 512); 에서 IOException이 발생한다면 에러 내용을 출력하고 진행을 하지만 스트림은 여전히 열려있다. 즉 리소스 정리가 완벽하게 안된 것이다.
  • try-catch 구문에서 리소스를 연다면 finally 블록에서 리소스를 정리하거나 try-with-resource 구문을 이용해야 한다.
public static void main(String[] args) { File file = new File("./test.txt"); try (FileInputStream fileInputStream = new FileInputStream(file);){ byte[] bytes = new byte[512]; fileInputStream.read(bytes, 0, 512); fileInputStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }

2. 더 자세한 예외를 사용하자

  • 메소드를 정의할 때 발생할 수 있는 예외를 명시하게 되어있다. 메소드에서 발생할 수 있는 예외는 최대한 자세한 예외를 명시하는 것이 좋다.
public void exceptableMethod() throws Exception; public void exceptableMethod() throws NumberFormatException;
  • 가장 간단한 방법은 Exception 클래스로 다 던져버리는 것이다. 하지만 이렇게 되면 이 메소드를 사용하는 쪽에서 Exception을 받아 처리하는 코드가 복잡해진다.
  • 하지만 Exception으로 명시한 경우 발생한 예외가 매우 다양하기 때문에 모든 경우에 대해서 처리를 해주어야 한다.
따라서 더 상세한 예외를 사용하도록 하자.

3. 발생할 수 있는 예외를 기술하라

  • 메소드가 예외를 발생시킬 수 있는 경우, 어떤 경우에 어떤 예외가 발생할 수 있는지 javadoc을 통해 명시해야 한다.
  • 최대한 자세한 예외를 사용하더라도 어떤 경우에 예외가 발생할 수 있는지 자세히 글로 설명해줘야 메소드를 호출하는 쪽에서 적절한 대응이 가능하다.
/** * 이 메소드는 ~일때 사용하는 메소드입니다. * * @param input 입력 값 * @throws MyCustomException ~일때 ~한 경우에 이 예외가 발생합니다. */ public void method1(String input) throws MyCustomException;

4. 메시지를 자세하게 적는다.

  • 사용자 정의 예외를 만들 때, 예외가 발생한 상황에 대해 메시지를 적을 수 있다.
  • 이 때, 어떤 상황에서 예외가 발생했는지 1~2문장 정도로 간결하게 잘 적어야 한다. 이 메시지 또한 너무 길면 좋지 않다. 예외의 이름에서 어떤 상황인지 알 수 있을 때에는 너무 많은 정보를 메시지에 적지 않아도 된다.
  • 예를들어 IOException 같은 경우는 왜 실패했는지 부가적인 정보가 필요하지만 NumberFormatException 예외의 경우는 숫자 포맷이 아닌 값을 변환하려고 했구나 라고 추측이 가능하다.
  • 이러한 경우에는 어떤 값을 변환시키려고 했는지에 대한 정보만 알려주면 된다.
try { new Long("xyz"); } catch (NumberFormatException e) { System.out.println(e); }

5. catch 순서

  • try-catch 블럭에서 여러 예외가 발생할 수 있는 경우엔 좀 더 상세한 예외부터 처리해야 한다.
    • 예외가 상속관계에 있을 경우에 앞쪽 catch 절에서 더 넓은 범위의 예외를 먼저 처리해버리면, 뒤 쪽 catch 절이 쓸모 없게 되어버리게 된다.
try { method(); } catch (Exception e) { log.error(e); } catch (IllegalArgumentException e) { // 실행 안 됨 log.error(e); }

6. Throwable은 catch하지 마라

  • Throwable은 모든 예외 클래스와 에러의 슈퍼 클래스이다. Throwalbe 역시 catch 절에서 잡아 처리할 수 있지만 하지 않는것을 권장한다.
  • Throwable을 catch 절에서 잡아버리면 예외뿐만 아니라 에러도 잡아서 처리해버린다.
  • 예외와 다르게 에러의 경우 JVM이 심각한 문제라고 판단한 경우 발생한다.
  • 사용자가 잡아서 예외처리를 해버리면 JVM에서 예상치 못한 동작을 해버릴 수 있다.
  • 예를들어 OutOfMemoryError, StackOverflowError 같은건 사용자가 잡아서 처리하지 않는게 좋다. 따라서 Throwable은 예외처리하지 않는 것을 추천한다.

7. Exception을 씹지 말자

  • 빠른 속도로 코드를 작성하다보면 다음과 같은 임시방편 코드를 만들어 내는 경우가 있다.
try{ method() } catch(Exception e) { // 에외는 발생할 수 없음 }
  • 이렇게 메소드에서 발생하는 예외를 잡고 아무것도 안한 상태에서 넘어가는 즉 씹는 행위를 코드를 작성하는 경우가 있다.
  • 메소드에서 절대 예외가 발생할 수 없는 경우라고 해도 메소드 시그니처를 바꾸지 않으면 나중에 예외가 발생하도록 패치가 될 수도 있다.
  • 이렇게 될 경우 위의 코드는 문제가 될 수 있다.
  • 예외가 발생하는 않는다면 시그니처에서 예외를 제거하는게 옳다. 그럴 수 없다면 로그라도 찍어서 상황을 모니터링 할 수 있게 해야한다.

8. 로그 찍고 다시 던지지 마라

  • 예외를 처리할 때 습관적으로 로그를 남기고 다시 상위 메소드로 발생한 예외를 던지는 경우가 있다.
try { new Integer("kim"); } catch(NumberFormatException e) { log.error(e); throw e; }
  • 위의 방법으로 작성한다면 로그가 너무 자주찍혀 가독성을 훼손하게 된다.
  • 다시 상위로 Throw 할 경우 로그를 찍지 말아야 한다. 그 상황에 대한 컨텍스트를 남기고 싶으면 차라리 예외를 래핑해서 새로운 클래스로 만들고 컨텍스트에 대한 정보를 담아 상위로 던지기 바란다.

9. 예외를 래핑할 경우 Cause 예외를 담아서 던져라

  • 8에 나온 것처럼 컨텍스트를 추가하고 상위로 던지고 싶을 경우에 예외를 래핑해서 사용할 수 있다.
  • 이 때 반드시 원래 발생했던 예외를 생성자에 넘겨줘야 스텍정보와 메시지, 컨텍스트 정보 등이 상위로 전달된다.
  • 이러한 정보가 사라지면 디버깅이 힘들어진다.
try{ method(); } catch(NumberFormatException e) { throw new MyException("New Message", e); }
  • 위의 방법으로 잘 처리해야 깔끔하고 보기좋게 처리가 가능하다.