[TIL] 20250113 예외

Drumj·2025년 1월 13일
0

2025 TIL

목록 보기
11/11

예외

예외도 객체다. 최상위 객체인 Object를 상속하고 있는걸 볼 수 있다. 그리고 Throable 을 상속 받은 이후에 ExceptionError로 나뉘는데 우리가 봐야할 것은 Exception 부분이다.

  • Throwable : 최상위 예외
  • Error : 메모리 부족이나 심각한 시스템 오류와 같이 애플리케이션에서 복구가 불가능한 시스템 예외. 개발자는 이 예외를 잡으려고 해서는 안된다.
  • Exception : 체크 예외
    • 애플리케이션 로직에서 사용할 수 있는 실질적인 최상위 예외
    • Exception과 그 하위 예외는 모두 컴파일러가 체크하는 체크 예외이다. 단 RuntimeException은 예외로 한다.
  • RuntimeException : 언체크 예외, 런타임 예외
    • 컴파일러가 체크하지 않는 언체크 예외
    • RuntimeException과 그 자식 예외는 모두 언체크 예외이다.

예외는 대강 이렇게 정리할 수 있다. 체크 예외와 언체크 예외에 대해서 물어보는 질문이 많은데 아주 간단하게는 컴파일러가 체크해줘서 우리가 따로 예외 처리를 명확하게 하고 넘어가야하는지 컴파일러가 체크하지 않아서 실행 시점에 예외가 터지는걸 확인할 수 있는지에 대해 설명하면 될 것 같다. 중요한 포인트는 컴파일러가 체크해주는지 안해주는지 이다.


체크 예외

체크 예외는 잡아서 처리하거나 밖으로 던지도록 선언해야한다. 그렇지 않으면 컴파일 오류가 발생.

커스텀한 예외를 만들때 Exception을 상속받으면 체크 예외가 된다.

public class MyCheckedException extands Exception {
	public MyCheckedException(String message) {
    	super(message);
    }
}

아주 간단하게 message만 가지는 클래스를 만들때는 위와 같이 만들면 된다. 이렇게 하면 이 예외가 터지는 곳에서 예외에 대한 처리를 명시적으로 하지 않으면 컴파일 오류가 발생한다. 코드를 작성하는 시점에서 어떤 예외가 터질지 알 수 있어서 아주 좋은 예외인 것처럼 보인다.

try-catchthrows 를 사용해서 예외를 잡아서 처리하거나 밖으로 던지면 된다.


//Client class
public void connect() throws ConnectException {
	if (connectError) {
    	throw new ConnectException(address, address + " 서버 연결 실패");
	}
	System.out.println(address + " 서버 연결 성공");
}

//Service class
public void sendMessage(String data) {
	String address = "http://example.com";
    NetworkClientV3 client = new NetworkClientV3(address);

    client.initError(data);

    try {
    	client.connect();
        client.send(data);
	} catch (ConnectException e) {
    	System.out.println("[연결 오류] 주소: " + e.getAddress() + ", 메시지: " + e.getMessage());
    } catch (NetworkClientExceptionV3 e) {
    	System.out.println("[네트워크 오류] 메시지: " + e.getMessage());
	} catch (Exception e) {
    	System.out.println("[알 수 없는 오류] 메시지: " + e.getMessage());
	} finally {
    	client.disconnect();
	}
}

CheckedException 인 경우에는 이렇게 사용하려는 메서드에 throws 를 붙여서 어떤 예외가 발생할지 선언해야하고 메서드를 사용하는 Service 클래스에서도 try-catch를 사용해서 예외를 잡거나 다시 throws를 해서 밖으로 던져야 한다.


언체크 예외

언체크 예외는 컴파일러가 예외를 체크하지 않는다. 그래서 우리가 코드를 작성할 때 이 메서드가 예외를 던지고 있는지 확인하기 힘든 경우가 있다.

기본적으로 체크 예외와 동일하지만 throws를 선언하지 않고 생략할 수 있다.

public class MyUncheckedException extends RuntimeException {
    public MyUncheckedException(String message) {
        super(message);
    }
}

이렇게 RuntimeException을 상속받으면 언체크 예외로 만들 수 있다.

//Client class
public void connect(){
	if (connectError) {
    	throw new ConnectExceptionV2(address, address + " 서버 연결 실패");
	}
		System.out.println(address + " 서버 연결 성공");
}


//Service class
public void sendMessage(String data) {
	String address = "http://example.com";
    NetworkClientV4 client = new NetworkClientV4(address);

    client.initError(data);
    
	try {
    	client.connect();
        client.send(data);
	} finally {
    	client.disconnect();
	}
}

코드를 보면 catch 블럭이 없는것을 볼 수 있다. 여기서는 예외가 발생해도 disconnect()를 호출하기 위해 finally만 있는것을 볼 수 있다.

이렇게 하면 실행 시 예외가 발생했을때 자동으로 예외를 던져준다. 자동으로 예외를 던지니까 좋은거 아니야? 할 수 있는데 컴파일러가 예외를 체크해 주지 않아서 코드를 작성할 때는 예외가 터지는지 알 수 없고 돌려보고 나서야 예외가 터지는 걸 알 수 있기 때문에 예외 처리하는 시간이 조금은 더 들 수도 있다.


장단점

체크 예외의 경우에는 코드를 작성하면서 바로 예외가 터지는지 확인 할 수 있어서 우리가 예외 처리를 바로 작성할 수 있다. 밖으로 던지든 잡아서 처리하든 우리가 바로바로 확인해서 조치를 취할 수 있다는 장점이 있으나 이게 바로 단점이 되기도 한다.

체크 예외를 마구마구 쓸 경우 해당 예외에 대한 처리를 다 해줘야 한다는 것이다. 그렇다고 귀찮아서 catch(Exception e) 만 사용하면 체크 예외를 사용하는 의미가 없어지게 된다.

우리가 코드를 작성하면서 어떤 예외가 발생하는지 미리 파악하고 알맞은 조치를 취하고 싶은데 그냥 Exception 으로 뭉떵 잡아버리면 중요한 체크 예외도 다 던져버리기 때문에 체크 예외를 체크할 수 없게 된다. 컴퍼일러 입장에서는 throws Exception 을 해도 문법에 맞다고 생각하기 때문에 그냥 통과시켜버리는 것..!

언체크 예외는 코드를 작성하는 시점에 예외가 터지는지 알지 못하지만 그렇기 때문에 우리가 여러 예외에 대해서 일일이 체크하고 넘어갈 필요가 없다는 장점이 있다. 일단 코드를 쭈우욱 작성하고 나서 공통으로 예외를 처리할 수 있는 코드를 만들어서 그곳에서 예외를 관리하면 된다.

그리고 언체크 예외는 내가 어찌할 수 없는 예외들이 더 많기 때문에 일단 예외가 터지면 왜 예외가 터졌는지에 대한 로그만 남기고 넘어가는게 좋다.


참조

  • 김영한의 실전 자바 - 중급 1편

0개의 댓글