[김영한의 실전 자바 중급편1] - 예외처리 이론

jkky98·2024년 5월 27일
0

Java

목록 보기
21/51

예외 처리가 필요한 이유

package exception.ex1;

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

        client.initError(data);


        String connectResult = client.connect();
        // 결과가 성공이 아니다 -> 오류다!
        if (isError(connectResult)) {
            System.out.println("[네트워크 오류 발생] 오류 코드: " + connectResult);
        } else {
            String sendResult = client.send(data);
            if (isError(sendResult)) {
                System.out.println("[네트워크 오류 발생] 오류 코드: " + sendResult);
            }
        }

        client.disconnect();
    }
    private static boolean isError(String connectResult) {
        return !connectResult.equals("success");
    }
}

connect()send()는 네트워크 연결(connect)과 메시지 보내기(send)라는 기능을 담당한다. 연결이 우선되고, 메시지 전송은 연결이 되어 있어야 시도할 수 있다.

연결(connect) 성공과 메시지(send) 성공이 있을 수 있지만, 연결 실패나 메시지 실패도 존재할 수 있다. 따라서 모두 정상적으로 처리되기 위해서는 연결 성공 → 메시지 성공이어야 하며, 에러 상황에는 두 가지가 있다:
1. 연결 실패 시 메시지를 보낼 수 없다.
2. 연결 성공 후 메시지 전송 실패가 있을 수 있다.

위 코드는 이러한 관점을 잘 구현한 듯 보이지만, 정상 흐름과 예외 흐름이 섞여 있어 코드가 지저분한 느낌이 든다. 이러한 상황을 보다 효율적으로 처리할 수 있는 방법은 자바 예외 처리를 사용하는 것이다.

// 정상흐름
client.connect();
client.send(data);
client.disconnect();

만약 위 코드처럼 코딩하고 어느 코드에서든 예외가 발생한다면, 예를 들어 client.connect();에서 예외가 발생한다면, 뒤의 두 코드는 실행되지 않아야 한다. 이때 자바 예외 처리를 활용하면, 예외를 반환값으로 받아 if문으로 구별하는 것보다 코드 가독성을 크게 개선할 수 있다. 예외 처리를 통해 예외 상황을 명확하게 구분하고, 정상 흐름과 오류 흐름을 분리할 수 있어 코드가 더 깔끔하고 이해하기 쉬워진다.

자바 예외 계층

자바의 예외또한 객체이다. Throwable은 최상위 예외이며 Exception과 Error는 이를 상속받는다. Error는 처리할 생각을 하면 안된다. 예외는 체크예외와 언체크예외로 구성된다.

예외 기본규칙

1. 예외는 잡아서 처리하거나 밖으로 던진다.
2. 예외를 잡거나 던질 때 지정한 예외뿐만 아니라 그 예외의 자식들도 함께 처리할 수 있다.(다형성)

체크 예외

// Exception을 상속받은 예외는 체크 예외가 된다.
public class MyCheckedException extends Exception{
    public MyCheckedException(String message) {
        super(message);
    }
}

public class Client {
    // throws -> 예외를 밖으로 던짐 ( 해당 객체가 들어올 때 )
    public void call() throws MyCheckedException {
        // 문제 상황 발생
        throw new MyCheckedException("ex");

    }
}

MyCheckedException은 커스텀 예외 객체로, 예외 객체가 되기 위해 Exception 클래스를 상속받는다. super(message)Exception 클래스에 예외 메시지를 전달할 수 있다.

Client.call()은 예외 객체를 강제로 생성한다. 예외는 기본 규칙에 따라 처리하거나 밖으로 던져야 한다. 예외를 밖으로 던진다는 것은 Client.call()을 호출한 곳으로 예외를 전달하는 것이다.

체크 예외의 경우, throws를 사용하여 MyCheckedException을 던질 것임을 명시해야 한다. 또한, throw는 예외를 실제로 던지는 동작을 의미한다. 즉, Client.call() 메서드의 로직은 항상 throw new MyCheckedException()처럼 새로운 예외 메시지를 담은 예외 객체를 밖으로 던진다.

이때, throw는 예외를 던지는 행위를, throws는 예외를 던질 의도를 명시하는 "문" 역할을 한다고 생각하면 이해하기 쉽다.

언체크 예외

반면, 언체크 예외throws를 작성할 필요가 없다. 언체크 예외는 체크 예외와 비슷하지만, 잡을 수 있고, 던질 수 있지만, 던질 때 throws 문 없이 바로 던져진다.

즉, 체크 예외는 반드시 잡아서 처리하거나 throws 키워드를 사용하여 예외를 던지는 것을 명시해야 하지만, 언체크 예외throws 없이 던질 수 있다. 이 차이가 체크 예외언체크 예외의 유일한 차이점이다.

만약 특정 메서드에서 예외가 발생했을 때, 이 예외가 체크 예외라면 throws가 명시되지 않으면 컴파일러가 이를 검사하여 개발자에게 알려준다. 그러나 언체크 예외throws 유무와 관계없이 잡지 않으면 밖으로 던져지기 때문에, 개발자가 예외를 놓칠 수 있다.

catch

public void callCatch() {
	try {
    	client.call();
    } catch ( MyCheckedException e ){
    	sout('예외 처리 로직')
    }
    sout("정상 흐름")
}

예외를 잡는 catch는 try와 함께 사용가능하다. call()이 예외를 줄 수 있지만 catch로 완벽히 잡는 로직이 존재한다면 throws 작성은 필요 없어진다.

  • 결론부터 말하자면 실무에서는 체크예외보다는 언체크예외를 많이 사용한다.
profile
자바집사의 거북이 수련법

0개의 댓글