Java 예외 처리 - 네트워크 예제 리팩토링 4

이전 작성 글 "예외 처리 - 네트워크 리팩토리 3" 이어서 살펴보자.

이전의 작성 글에서는 NetworkService 에서 네트워크 로직이 있으며 외부 자원을 연결 시 반드시 disconnect() 호출하여 외부 자원을 끊는 코드가 필요했다.

NetworkService.class 로직을 살펴보자.

NetworkService.class

public class NetworkService {

    public void sendMessage(String data) throws NetworkClientException {
        String address = "http://kiioio.com";
        NetworkClient client = new NetworkClient(address);
        client.initError(data); // 오류 코드로 반환.

        try {
            client.connect();
            client.send(data);
            client.disconnect();
        } catch (NetworkClientException e) {
            System.out.println("[Error] Code: " + e.getErrorCode() + ", Message: " + e.getMessage());
        }
    }

    private static boolean isError(String connectResult) {
        return !connectResult.equals("success");
    }
}
  • try ~ catch 문 예외 처리가 되어있음
  • connect(), send(data) 예외 상황 발생 시 disconnect() 호출하지 못하는 문제가 남아 있음

남은 문제를 해결을 위해 client.disconnect(); (try ~ catch) 문에서 아래에 선언을 하였다.

try {
    client.connect();
    client.send(data);
} catch (NetworkClientException e) {
    System.out.println("[Error] Code: " + e.getErrorCode() + ", Message: " + e.getMessage());
}
client.disconnect();

그렇지만 여기서 아직 치명적인 문제가 남아있다.


문제점 찾아내기

상단의 코드에서 예외 처리 발생 시 disconnect() 호출하는 모습을 볼 수 있었다.

Send Message data: error1
[Error] Code: connectError, Message: Connect Open Failed
http://kiioio.com Connect Closed Success

Send Message data: error2
http://kiioio.com Connect Open Success
[Error] Code: sendError, Message: Send Failed
http://kiioio.com Connect Closed Success

Connect Closed Success 문구로 끊어졌다. 언뜻 보기에는 훌륭하게 잘 동작하는 코드이다.

만약 협업으로 NetworkService 관리하는 개발자가 한명 더 생겼을 때를 가정한다.
예외 처리를 위한 Exception 객체 생성을 하여 동작하고 있다. 새로운 개발자가 만약 RuntimeException 객체로 Client 구현 코드로 새로 만들었다면 어떻게 되는지 살펴보자.

NetworkClient.class

public class NetworkClient {
    private final String address;
    public boolean connectError;
    public boolean sendError;

    public NetworkClient(String address) {
        this.address = address;
    }

    public void connect() throws NetworkClientException {
        // exception error
        if (connectError) {
            throw new NetworkClientException("connectError", "Connect Open Failed");
        }

        // Connect Success
        System.out.println(address + " Connect Open Success");
    }

    public void send(String message) throws NetworkClientException {
        if (sendError) {
            throw new NetworkClientException("sendError", "Send Failed");
        }
        // 전송 성공
        System.out.println(address + " Send Message data: " + message);
    }

    public void disconnect() {
        System.out.println(address + " Connect Closed Success");
    }

    public void initError(String data) {
        if (data.contains("error1")) {
            connectError = true;
        }

        if (data.contains("error2")) {
            sendError = true;
        }
    }
}

이 코드에서 새로운 개발자가 send() 메소드를 RuntimeException 객체로 사용한다면?

send(...) 메소드 수정

public void send(String message) throws NetworkClientException {
    if (sendError) {
        // throw new NetworkClientException("sendError", "Send Failed");
        throw new RuntimeException("Send Failed");
    }
    // 전송 성공
    System.out.println(address + " Send Message data: " + message);
}

이제 예외처리로 인한 호출을 시도해보자.

Main 출력 결과

Send Message data: error2
http://kiioio.com Connect Open Success
Exception in thread "main" java.lang.RuntimeException: Send Failed
at NetworkClient.send(NetworkClient.java:23)
at NetworkService.sendMessage(NetworkService.java:10)
at Main.main(Main.java:15)

"Connect Closed Success" 문구로 외부 자원 연결이 끊어졌다는 문구가 표시되지 않고 예외 처리가 되었다!

이 코드를 계속 사용하면 결국 원인을 찾을 수 없고 네트워크 과부하로 큰 장애로 이어진다.

이 문제 해결 방법은 try ~ catch 문에서 finally 키워드를 사용하는 방법이 있다.


finally 추가하여 개선하기

try ~ catch문의 finally 키워드를 붙일 수 있는데, 이 것은 어떤 경우의 예외라도 마지막으로 호출시켜주는 기능을 제공한다.

NetworkService 클래스에서 finally 추가한 send 메소드는 다음과 같다.

NetworkService.class

public class NetworkService {

    public void sendMessage(String data) throws NetworkClientException {
        String address = "http://kiioio.com";
        NetworkClient client = new NetworkClient(address);
        client.initError(data); // 오류 코드로 반환.

        try {
            client.connect();
            client.send(data);
        } catch (NetworkClientException e) {
            System.out.println("[Error] Code: " + e.getErrorCode() + ", Message: " + e.getMessage());
        } finally {
            client.disconnect();
        }

    }

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

이제 Main 엔트리 포인트를 실행하면 올바르게 표시 되는 것을 살필 수 있다.

Send Message data: error1
[Error] Code: connectError, Message: Connect Open Failed
http://kiioio.com Connect Closed Success

Send Message data: error2
http://kiioio.com Connect Open Success
http://kiioio.com Connect Closed Success
Exception in thread "main" java.lang.RuntimeException: Send Failed
at NetworkClient.send(NetworkClient.java:23)
at NetworkService.sendMessage(NetworkService.java:10)
at Main.main(Main.java:15)

개선사항 체크

  • 사용 후 반드시 disconnect() 호출하여 연결을 해제해주어야 한다. -> 해결
  • 예외 처리 했으나 정상, 예외 흐름이 두 개가 생겨 가독성이 저하되었다 -> 해결