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

앞서 예외 처리 기초에서 네트워크 예제를 작성하였다.

NetworkClient 클래스에서 반환하는 오류 문구를 이용해 NetworkService 클래스로 if문으로 예외 처리를 시도를 하였다. 그로 인한 유지보수성와 가독성이 떨어지는 코드가 만들어졌다.

NetworkClient.class

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

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

    public String connect() {
        // exception error
        if (connectError) {
            System.out.println(address + " Connect Open Failed");
            return "connectError";
        }

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

    public String send(String message) {
        if (sendError) {
            System.out.println(address + " Send Message Failed data: " + message);
            return "sendError";
        }
        // 전송 성공
        System.out.println(address + " Send Message data: " + message);
        return "success";
    }

    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;
        }
    }
}

NetworkService.class

public class NetworkService {

    public void sendMessage(String data) {
        String address = "http://kiioio.com";
        NetworkClient client = new NetworkClient(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");
    }
}

Main.class

public class Main {
    public static void main(String[] args) {

        NetworkService networkService = new NetworkService();

        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.print("Send Message data: ");
            String input = scanner.nextLine();
            if (input.equals("exit")) {
                break;
            }
            networkService.sendMessage(input);
            System.out.println();
        }
        System.out.println("네트워크 프로그램 종료");
    }
}

Send Message data: error1
http://kiioio.com Connect Open Failed
!!!네트워크 오류 발생함!!! 오류 코드: connectError
http://kiioio.com Connect Closed Success

Send Message data: error2
http://kiioio.com Connect Open Success
http://kiioio.com Send Message Failed data: error2
!!!네트워크 오류 발생함!!! 오류 코드: sendError
http://kiioio.com Connect Closed Success

다시 한번 발생한 문제점을 되짚어보자.

  • 정상 흐름과 예외 흐름이 섞여 있어 코드의 가독성이 저하되었다.
  • 예외 흐름이 많아 코드 분량을 대부분 차지하여 복잡한 코드가 완성되었다.

이러한 문제를 예외 처리를 본격적으로 도입하여 해결해보도록 한다.


예외 처리 - NetworkClient

NetworkClientException 객체를 생성하여 예외 코드로 만들어주도록 한다.

NetworkClientException.class

public class NetworkClientException extends Exception {
    private String errorCode;

    public NetworkClientException(String errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    // 에러 코드 조회하기
    public String getErrorCode() {
        return errorCode;
    }
}
  • 예외도 객체로 필요한 멤버 필드와 메소드를 작성할 수 있다.
  • errorCode 멤버 필드
    • 이전의 오류 코드(errorCode)로 반환 값 용도로 사용하였다.
    • 어떤 종류의 오류인지 구분하기 위함으로 코드를 보관한다.
  • message 멤버 필드
    • 오류 메시지(message)에는 어떠한 오류가 발생하였는지 개발자가 알 수 있도록 설명을 담는다.
    • 오류 메시지는 상위 클래스의 Throwable 기본으로 제공하고 있다.

NetworkClient.class

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

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

    // void 반환으로 변경 후 예외 클래스 사용
    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");
        return "success";
    }
    
    // void 반환으로 변경 후 예외 클래스 사용
    public void send(String message) throws NetworkClientException {
        if (sendError) {
            throw new NetworkClientException("sendError", "Send Failed");
        }
        // 전송 성공
        System.out.println(address + " Send Message data: " + message);
        return "success";
    }

    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;
        }
    }
}
  • 기존 코드와 비슷하다. 변경된 것은 예외 흐름에서 오류 코드 반환이 아닌 예외를 던지도록 하였다.
  • 반환 값을 사용하지 않는다. 메소드 반환은 void 처리
  • 이전에는 반환 값으로 성공, 실패 여부를 확인 했으나, 예외 처리를 사용하면 메소드가 정상 종료하였다면 성공이며 예외를 던진다면 실패로 확인할 수 있다.
  • 오류 발생에 오류코드와 오류 메시지를 담을 수 있도록 예외 객체 NetworkClientException 클래스를 작성하였다. 예외 객체는 throw 던지도록 한다.

예외 처리 - NetworkService

먼저 Client 사용한 반환 값으로 예외 처리한 부분을 제거하고 간단한 로직으로 구성한다.

NetworkService.class - sendMessage(String data)

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

    client.connect();
    client.send(data);
    client.disconnect();
}

리팩토링 이전으로 sendMessage 구성하고, throws 키워드로 상속하여 컴파일 오류를 발생하지 않도록 한다.
throws 키워드로 인해 예외가 발생한다면 sendMessage 호출한 스택에게 예외로 던질 것이다.


예외 처리 - Main

import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws NetworkClientException {

        NetworkService networkService = new NetworkService();

        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.print("Send Message data: ");
            String input = scanner.nextLine();
            if (input.equals("exit")) {
                break;
            }
            networkService.sendMessage(input);
            System.out.println();
        }
        System.out.println("네트워크 프로그램 종료");
    }
}

Send Message data: hello
http://kiioio.com Connect Open Success
http://kiioio.com Send Message data: hello
http://kiioio.com Connect Closed Success

Send Message data: error1
Exception in thread "main" NetworkClientException: Connect Open Failed
at NetworkClient.connect(NetworkClient.java:13)
at NetworkService.sendMessage(NetworkService.java:8)
at Main.main(Main.java:15)

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

여기서도 예외가 발생하면 throws 으로 던지도록 한다.
error1 오류 시뮬레이션을 동작시키면 main 예외를 던지는 결과로 인해 비정상 종료를 하였다.

  • error1 오류 시뮬레이션으로 Connect Open Failed 발생시킴
  • 모든 곳의 발생한 예외를 처리하는 로직이 없으므로 main() 밖으로 던지게 되었다.
    • 예외 메시지와 예외를 추적하는 스택 트레이스 출력하고 프로그램 종료
  • error2 오류 시뮬레이션으로 Connect Open Success 성공하였으나 메시지 전송에 실패함.

리팩토링 남은 개선사항

예외 처리는 했으나 문제가 해결되지 않는 부분이 있다.

  • 예외 처리가 없으므로 복구되지 않았다. 예외 발생시 반드시 종료된다.
  • 사용 후 반드시 disconnect() 호출하여 연결을 해제해주어야 한다.