[Java] 예외처리

박세진·2021년 1월 17일
0
post-thumbnail
  • 글을 쓰게 된 계기🤔
그동안 매번 Java코드를 작성하면서도 throws 를 왜 하는지, try~catch블록을 왜 사용하는지 제대로 알지 못하고
그냥 오류가 나니까 이렇게 해야지! 하는 생각으로만 했던 것 같다.
이번 글을 쓰면서 예외처리의 개념에 대해 다시 정리하고, 공부하는 시간을 갖고자 한다.

1. 에러(Error) vs 예외(Exception)

Java에서는 실행 시 발생할 수 있는 오류를 에러예외의 두가지로 구분한다.

  • 에러 : 컴퓨터 하드웨어의 오동작/고장으로 인해 응용프로그램 실행 오류가 발생하는 것. 개발자가 미리 예측하여 처리할 수 없는 것.
  • 예외 : 사용자의 잘못된 조작/개발자의 잘못된 코딩으로 인해 발생. 개발자가 미리 예측하여 처리 가능하다.

2. 예외 클래스

위 그림에서 보이듯, 최상위 클래스 Object의 자식클래스가 Throwable이고, 모든 예외클래스는 Throwable 클래스를 상속받는다.

Throwable을 상속받는 클래스는 Error와 Exception이 있다.

Error : 시스템 레벨 수준의 심각한 에러. 
Exception : 개발자가 로직을 추가하여 처리할 수 있음.

Exception의 자식 클래스중 RuntimeException을 주목해야 한다.
RuntimeException은 CheckedException과 UncheckedException을 구분하는 기준이다.

CheckedException과 UncheckedException

CheckedExceptionUncheckedException
처리여부반드시 처리해야 한다명시적인 예외처리 하지 않아도 된다.
확인시점컴파일 단계실행 단계
트랜잭션 처리Roll-back 하지 않음Roll-back 함
대표 예외Exception을 상속받는 하위 클래스 중 RuntimeException을 제외한 모든 예외 (IOException,SQLException등)RuntimeException 하위 예외 (NullPointerException, IndexOutOfBoundException 등)

CheckedException 과 UnchekedException의 가장 명확한 구분 기준은
꼭 처리를 해야하느냐 이다.

  • CheckedException 발생 가능한 메소드 : 로직을 try~catch로 감싸거나, throw로 던져서 처리해야 함.
  • UncheckedException : 명시적인 예외처리 하지 않아도 됨. (피할 수 있지만 개발자가 부주의해서 발생하는 경우가 대부분, 미리 예측하지 못했던 상황에서 발생하는게 아니므로 굳이 로직으로 처리 할 필요 없음)

또한 예외를 확인하는 시점에서도 구분할 수 있다.

  • Checked는 컴파일 단계에서 명확하게 Exception 체크가 가능한 것
  • Unchecked는 컴파일 단계에서 확인할 수 없는 것이다. 실행 과정 중 발견된다 하여 RuntimeException이라고 한다.

마지막 구분 기준은 예외 발생시 트랜잭션의 roll-back여부이다.

  • Checked : 예외 발생시 트랜잭션을 roll-back하지 않고 예외를 던져준다.
  • Unchekced : 예외 발생시 트랜잭션을 roll-back한다.

3. 예외처리 코드

예외처리란? 🤔
프로그램 실행 시 발생할 수 있는 예외의 발생에 대비한 코드를 작성하는 것.

프로그램에서 예외가 발생했을 때, 프로그램의 갑작스러운 종료를 막고 정상 실행을 유지할 수 있도록 처리하는 코드를 예외 처리 코드 라고 한다. 이때 RuntimeException은 컴파일러가 체크해 주지 않기 때문에, 예외처리 코드 작성시 개발자의 경험을 바탕으로 작성해야 한다.

(참고) 자주 발생되는 RuntimeException

- NullPointerException : 객체가 없는 상태에서 객체 사용하려 할 때
- ArrayIndexOutOfBoundsException : 배열에서 인덱스 범위를 초과해서 사용할 때
- NumberFormatException : 숫자로 변환할 수 없는 문자를 변환하려고 할 때
- ClassCastException : 상위-하위 클래스 / 인터페이스-구현 클래스 간의 관계가 아닌데 억지로 타입변환을 시도할 때
  1. try-catch-finally 블록 이용
  2. throws를 이용한 예외 떠넘기기

    메소드 내부에서 예외가 발생할 수 있는 코드 작성시, 메소드를 호출한 곳으로 예외를 떠넘기는 것.
    이 때 throws키워드가 붙어있는 메소드는 반드시 try블록내에서 호출 되어야 한다. 그리고 catch블록에서 떠넘겨 받은 예외를 처리해야 한다.

예외를 떠넘기는 이유?🤔
1. 메서드 선언부에 선언된 throws문을 통해 해당 API를 사용했을 때 어떤 예외가 발생할 수 있는지를 예측할 수 있다.
2. 현재 메서드 내에서 처리할 필요가 없다고 판단했을 경우.

예를 들어 내가 methodB()라는 메서드를 개발중에 있고, 현재 이 메서드에서 NullPointerException이 발생할 수 있다는 것을 인지했다고 하자.
이때 내가 직접 try-catch문을 사용하여 원하는 대로 처리해 줄 수도 있지만, 내가 만든 코드를 사용하는 다른 팀원들이 해당 예외가 발생했을 때의 처리를 각각 원하는 대로 구현하도록 해줌과 동시에 메서드 선언부를 통해 NullPointerException이 발생할 수 있다는 것을 알려주는 것이다.
단순히 예외 처리를 하기 귀찮아서 사용하는 것은 절대금지 라는 것을 알아두자.

public void method1(){
	try{
    		method2();
    }  catch{
    		System.out.println("클래스가 존재하지 않습니다.");
    }
}

public void method2() throws ClassNotFoundException{
	Class clazz = Class.forName("java.lang.String2");
}
  • method2()는 throws 키워드가 붙어있으므로, try블록에서 호출되었음을 볼 수 있다. 그리고 catch블록에서 떠넘겨 받은 예외를 처리한다.
  • 또는 method1()에서도 try-catch블록으로 예외를 처리하지 않고 throws키워드를 이용해서 다시 예외를 떠넘길 수 있다
public void method1() throws ClassNotFoundException{
	method2();
    }

throws 키워드가 붙어있는 생성자/메소드를 사용하고 싶다면,
1. try-catch 블록으로 예외 처리하기
2. throws를 다시 사용해서 메소드를 호출한 곳으로 예외 떠넘기기
둘 중 하나 사용해야 한다.

4. 사용자 정의 예외

자바 표준 API에서 제공하는 예외 클래스 만으로는 다양한 종류의 예외를 표현하기 어렵다.
ex) 은행 업무 처리 프로그램 : 잔고보다 더 많은 출금 요청이 들어온 경우 오류가 되며, 프로그램은 잔고 부족 예외를 발생 시킬 필요가 있다
하지만 이러한 예외는 자바 표준 API에는 존재하지 않는다. 이와 같이 애플리케이션 서비스와 관련된 예외를 Application Exception이라고 한다.
사용자가 직접 정의해서 만들어야 하므로 사용자 정의 예외 라고도 한다

  • CheckedException으로 선언할 경우 Exception 상속
  • UncheckedException으로 선언할 경우 RuntimeException 상속
public class XXXException extends [Exception | RuntimeException]{
	public XXXException(){}  
    	public XXXException(String message){super(message);}
}
  • 예외 클래스의 이름은 XXXException으로 끝나도록.
  • 대부분 생성자 선언만을 포함한다

    한개는 매개변수가 없는 기본 생성자
    다른 한개는 예외발생 원인을 전달하기 위해 String타입의 매개변수를 갖는 생성자 -> 상위 클래스의 생성자를 호출하여 예외메시지를 넘겨준다.
    🤔 why?
    catch{} 블록의 예외처리 코드에서 이용하기 위해서!

5. 예외 발생시키기

예외를 왜 발생시킬까?🤔

-> 정보를 주기 위해서!

name.equals("park se jin"); // NullPointerException이 발생 되어야 함.

만약 Java에 예외라는 개념 자체가 없는 상태에서, 문자열 비교 연산을 한다고 하자. 이때 name에 객체의 참조값이 할당되지 않아 null을 참조하게 되었다.
만약 내가 어떤 대기업의 쇼핑몰 서버를 개발했을 때, 회원 이름을 비교하다가 위와 같은 상황이 발생하였다면 서버는 비 정상적으로 종료되었을 것이지만 해당 서버를 개발한 나는 어떠한 정보나 로그도 찾을 수 없었을 것이다.

위와 같이 JVM에서는 표준 API에 정의한 여러가지 예외를 가지고 개발자에게 예외적인 상황의 정보를 제공한다.
또한 애플리케이션을 개발하는데 있어서, 우리는 팀을 이루어 개발을 하게 된다. 개발자는 자신의 코드보다 남의 코드를 보는 일이 훨씬 많으며, 내가 짠 코드보다 남이 짠 코드를 사용하는 일이 훨씬 많다. 때문에 내가 소스를 짤 때 상황에 맞는 예외를 발생시키는 코드를 작성함으로써 팀원이나 혹은 내가 만든 API나 라이브러리를 사용하는 이에게 정보를 제공해 주고, 이를 통해 더욱 견고한 프로그램을 개발할 수 있도록 해야한다.

본론으로 돌아와서.

사용자 정의 예외 또는 자바 표준 예외를 코드에서 발생시키는 법을 알아보자.

	throw new XXXException();
    	throw new XXXException("메시지");

예외를 발생시킬 때에는 throw키워드를 사용한다. 이것은 예외를 강제로 던지는 것으로, 이를 위해서는 던질 예외의 객체가 필요하다.
위와 같이 new연산자로 예외 객체를 만들어 던질 수 있다.
기본 생성자/ 예외 메시지를 갖는 생성자 중 어느 것을 이용해도 된다.
만약 catch블록에서 예외 메시지가 필요하다면 예외 메시지를 갖는 생성자를 이용해야 한다.

public void method() throws XXXException(){
	throw new XXXException("메시지");
}

위의 코드처럼 대부분은 자신을 호출한 곳에서 예외를 처리하도록 throws키워드로 예외를 떠넘긴다.

6. 예외 정보 얻기

try 블록에서 예외 발생 : 예외 객체는 catch블록의 매개변수에서 참조하게 되므로 매개변수를 이용하면 예외 객체의 정보를 알 수 있다
모든 예외 객체는 Exception 클래스를 상속하기 때문에, Exception이 가지고 있는 메소드 들을 모든 예외 객체에서 호출할 수 있다.
그 중 가장 많이 사용되는 것은 getMessage()printStackTrace() 이다.

getMessage()

throw new XXXException("예외 메시지");

위 처럼 예외를 발생시킬 때 String 타입의 메시지를 갖는 생성자를 이용하였다면, 메세지는 자동적으로 예외 객체 내부에 저장된다.
또한 예외 메시지의 내용에는 왜 예외가 발생했는지에 대한 간단한 설명이 포함된다.
이와 같은 예외 메시지는 다음과 같이 catch 블록에서 getMessage()의 리턴값으로 얻을 수 있다.

} catch(Exception e){
	String message = e.getMessage();
}

printStackTrace()

메서드의 이름에서도 알 수 있듯이, 예외 발생 코드를 추적해서 모두 콘솔에 출력한다.
어떤 예외가 어디에서 발생했는지 상세하게 출력해준다 -> 프로그램 테스트 하면서 오류 찾을 때 활용된다.

try{
// 예외 객체 생성
} catch(예외클래스 e){
// 예외가 가지고 있는 Message 얻기
	String message = e.getMessage(); 
    
// 예외의 발생 경로를 추적
	e.printStackTrace();
} 

마치면서

그동안 throw로 왜 에러를 던지는지, printStackTrace()를 왜 사용하는지 등 예외처리에 대해 잘 알지 못하고 사용했던 나를 반성하는 계기가 되었다. 앞으로도 Java에 대해 더 공부하면서 몰랐던 사실은 꼼꼼히 공부하고, 알았던 것도 제대로 알기 위해 열심히 공부할 것이다.

profile
계속해서 기록하는 개발자. 조금씩 성장하기!

0개의 댓글