[Java] 예외처리

김세림·2024년 4월 26일

Java

목록 보기
18/24
post-thumbnail

예외처리


오류랑 예외?

주니어 개발자들은 구현을 중요하게 생각해서 예외처리에 대하여 심각하게 생각하지않는다고 한다.
그런데 이때부터 습관을 잡아놓지않으면 나중에가서도 처리를 하지않게되니 예외처리에 대하여 생각해보자!!

오류

오류는 회복이 불가능한 문제이다.
시스템레벨에서 환경적인 이유로 발생하며, 이미 문제가 벌어졌다면 프로그램은 종료됐을 것이다..

예외

예외는 회복이 가능한 문제이다.
어떤 예외가 발생할지 인지하고 대응하면 된다!
여기서 우리는 예외처리에 대하여 배울 것이다.

예외처리

예외의 종류

  1. 코드실행 관점에서의 예외
  • 컴파일 에러(예외)
    • 컴파일러는 .java파일을 .class파일로 컴파일해주는데 이때 발생하는 에러를 말한다.
    • 90% 이상은 문법오류이므로 이부분을 고치면 해결되는 덜 critical한 문제이다.
  • 런타임 에러(예외)
    • run(구동되는) + time(시간), 즉, 프로그램이 실행중일때 맞닥뜨리게 되는 우리가 주로 다룰 예외이다.
  1. 예외처리 관점에서의 예외 종류
  • 확인된 예외(Checked Exception)
    • 컴파일 시점에 확인하는 예외이며, 반드시 처리해줘야한다.
    • 그렇다고 컴파일 에러는 아니고 우리가 문제를 인지해서 이에 대한 예외를 정의해둔 예외라고 생각하면 된다.
      (Checked Exception에 대한 예외처리 하지않을시 컴파일 에러가 발생)
  • 미확인된 예외(Unchecked Exception)
    • 런타임 시점에 확인되는 예외이다.
    • 예외처리가 반드시 필요하지는 않다.

try~catch, finally

우리는 예외처리를 어떻게 정의하고, 이 예외가 발생할 수 있음을 알리고, 이 예외를 어떻게 핸들링하는지를 순차적으로 확인해봐야한다.
알리는 방법은 예외가 발생할 수 있는 로직에 플래그를 달아놓는다고 생각하면 된다.

1. 정의

Exception 클래스를 상속받은 클래스를 사용하여 직접 예외를 정의할 수 있다.

class OurBadException extends Exception {
	public OurBadException() {
		super("위험한 행동을 하면 예외처리를 꼭 해야합니다!");
	}
}

2. 알리기

throw, throws 예약어를 통해 이 메서드가 위험하다는 것을 알려야한다.

class OurClass {
    private final Boolean just = true;
		
		// 신규 문법 throws!
    public void thisMethodIsDangerous() throws OurBadException { 
    //메소들의 끝에 throws를 한 다음 해당 exception클래스를 써주어야한다.
        if (just) {
						// 신규 문법 throw!
            throw new OurBadException();
            //throw는 객체와 함께 써야한다.
        }
    }
}
throws
throw
메서드 이름 뒤에 붙어 이 메서드가 어떠한 예외사항을 던질 수 있는지 알려주는 예약어
메서드 안에서, 실제로 예외 객체를 던질 때 사용하는 예약어
여러 종류의 예외사항을 적을 수 있다.
실제로 던지는 예외 객체 하나와 같이 써야한다.
일반 메서드의 return 키워드처럼 throw 아래의 구문들은 실행되지 않고, throw문과 함께 메서드가 종료된다.

3. 핸들링

  1. 위험 감지하기

  2. 위험을 감지했다면 try~catch(finally)키워드를 이용하여 예외처리해주기

try~catch, finally

말 뜻대로
try : 시도하다
catch : 잡다(붙잡다)
finally : 마침내
라는 뜻을 가졌으며
try시도를 해보다가~ 예외처리가 나면 catch잡고!
시도를 해서 제대로 연산되었든 예외가 나서 예외처리가 되었든
finally 마침내! 마지막에 반드시 연산하는 것을 쓰면 된다.

try{
}catch(Exception e){ //Exception은 모든 예외이며,
//일부예외만 받고 싶으면 해당 예외 클래스명을 넣어주면 된다.
}finally{
}

형태로 만들어진다.
또, try하나에 여러 catch를 사용할 수 있다.

예외 클래스 구조 이해하기

Throwable Class

모든 객체의 최상위 클래스인 Object 클래스에서 시작한다.
문제상황을 뜻하는 Throwable 클래스가 Object 클래스를 상속한다.
또, Throwable 클래스의 자식으로는 Error와 Exception클래스가 있다.
또한, Error클래스와 Exception클래스는 IOError 클래스, Runtime Exception 클래스오 같이 구분하여 처리된다.


여기서 RuntimeException은 이전에 말한 사전에 예방할수 없는 실행과정 중에 발생하는 Unchecked exception이고, 이외의 runtimeException을 상속하지않은 예외들은 반대로 checked exception으로 구현되어있다고 생각하면 된다.

이외에도 많은 구현체들이 있고 대부분은 상황에 맞게 에러들을 결정하면 되지만, 만약 원하는 에러가 없다면 특정 에러를 더 구체화하여 내가 직접 정의하고 구현하면된다!

Chained Exception

실제로 예외를 처리하는 방법이다.

연결된 예외

예외는 다른 예외를 유발할 수 있다.
예외 A가 예외 B를 발생시킬수 있다는 것이다. 이랬을 때 A는 B의 원인 예외이다.
이렇게 원인예외를 새로운 예외에 등록한 후 다시 새로운 예외를 발생시키는걸 예외 연결이라고 한다.

원인 예외를 다루기 위한 메소드

  • initCause()
    연결하는 메소드
  • getCause()
    반환하는 메소드
// 연결된 예외 
public class main {

    public static void main(String[] args) {
        try {
            // 예외 생성
            NumberFormatException ex = new NumberFormatException("가짜 예외이유");

            // 원인 예외 설정(지정한 예외를 원인 예외로 등록)
            ex.initCause(new NullPointerException("진짜 예외이유"));

            // 예외를 직접 던집니다.
            throw ex;
        } catch (NumberFormatException ex) {
            // 예외 로그 출력
            ex.printStackTrace();
            // 예외 원인 조회 후 출력
            ex.getCause().printStackTrace();
            //원인에 대한 tracking이 편해진다.
        }

        // checked exception 을 감싸서 unchecked exception 안에 넣습니다.
        throw new RuntimeException(new Exception("이것이 진짜 예외 이유 입니다."));
    }
}

// 출력
Caused by: java.lang.NullPointerException: 진짜 예외이유

위 코드처럼 마지막에 throw new RuntimeException(new Exception("이것이 진짜 예외 이유 입니다."));와 같이 예외를 연결하면 하나의 큰 분류의 예외로 묶여져서 wrapping되어 코드를 try catch로 묶지않아도 사용할 수 있게 된다. 따라서 코드가 줄어들 수 있다.

실제 예외처리 방법

  1. 예외 복구
public String getDataFromAnotherServer(String dataPath) {
		try {
				return anotherServerClient.getData(dataPath).toString();
		} catch (GetDataException e) {
				return defaultData;
		}
}

이처럼 try catch로 예외처리를 해서 정상상태로 복구하는 방법이다.
최소한의 대응만 가능한 경우가 많아 잘 사용하지는 않는다.
2. 예외 처리 회피하기

public void someMethod() throws Exception { ... }

public void someIrresponsibleMethod() throws Exception {
		this.someMethod();
}

exception으로 예외처리를 하는 someIrresponsibleMethod 메소드가 있고 그안에 Exception으로 예외처리하는 someMethod를 호출한다.
이렇게 처리하면, someMethod에서 발생한 에러가 someIrresponsibleMethod의 throws를 통해 그대로 다시 흘러나가게 된다.
위 코드는 예시이지만 이렇게 관심사를 분리해 한 레이어에서 처리하기 위햐 에러를 회피해서 그대로 흘러보내는 것이다.
3. 예외 전환하기

public void someMethod() throws IOException { ... }

public void someResponsibleMethod() throws MoreSpecificException {
		try {
			this.someMethod();
		} catch (IOException e) {
			throw new MoreSpecificException(e.getMessage());
		}
}

회피하는 방식과 비슷하지만 좀 더 적절한 Exception을 지정해서 던져주는 것이다.
예외처리에 더 신경쓰고싶거나 runtimeException처럼 일괄 처리하기 편한 예외로 바꿔서 던지고 싶은 경우 사용한다.

0개의 댓글