Java 예외 처리 - 기초

실제 사용자의 입력을 받고 입력한 문자를 외부 서버로 발송되는 프로그램이 있을 때, 이 프로그램에서 몇 가지 예외 처리가 필요하다.

프로그램 구성도

Key Input(data)
    |
+---+---+
| Main  |
+---+---+
    |      <- sendMessage(data)
+---+------------+
| NetworkService |
+---+------------+
    |      <- connect(); send(data); disconnect()
+---+-----------+
| NetworkClient |
+---+-----------+
    |
+---+-------------+
| External Server |
+-----------------+

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

네트워크 클래스 설명

  • NetworkClient
    • 어느 외부서버와 연결할지 정하고, 데이터를 전송하고, 연결을 종료한다.
  • NetworkService
    • NetworkClient와 의존된 객체로 데이터 전송을 담당. NetworkClient 사용에는 연결, 전송, 연결 종료의 복잡한 흐름을 제어. 이러한 부분을 NetworkService가 담당한다.
  • Main
    • 처음 시작하는 엔트리 포인트. 전송할 사용자 입력을 기다리고 있다.

네트워크의 흐름은 외부 서버 연결해서 전송하고 연결을 종료한다.


네트워크 기초적인 사용기

자바 네트워크는 다음 결과와 같이 간단한 슈드코드로 구현해보도록 한다.

네트워크 자바 코드 예시

String address = "http://kiioio.com"
NetworkClient client = new NetworkClient(address);
client.connect();
client.send(data);
client.disconnect();

코드 설명

  • connect()
    • 네트워크를 호출하고 서버와 연결
  • send(data)
    • 메시지 데이터를 서버로 전송한다.
  • discount()
    • 연결을 종료한다.

네트워크 사용시 주의할 점

  • connect()
    • 실패한 경우 send() 호출되어선 안된다.
  • disconnect()
    • 연결이 성공하였다면 반드시 disconnect() 사용하여 연결을 종료되어야 한다.
    • connect(), send(...) 호출 오류가 발생하여도 disconnect() 로 정상 종료가 필요하다.

네트워크 예제 클래스명

  • NetworkClient
  • NetworkService
  • Main

NetworkClient.class

public class NetworkClient {
    private final String address;

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

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

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

    public void disconnect() {
        System.out.println(address + "Connect Closed Success");
    }
}
  • String address 멤버 필드
    • 서버 주소
  • connect() 멤버 메소드
    • 외부 서버 연결
  • send(String data) 멤버 메소드
    • 외부 서버로 데이터 전송
  • disconnect() 멤버 메소드
    • 외부 서버와 연결 종료

NetworkClient 클래스는 구현 로직이 포함되어 있는 것을 볼 수 있다.

Network.Service.class

public class NetworkService {

    public void sendMessage(String data) {
        String address = "http://kiioio.com";
        NetworkClient client = new NetworkClient(address);
        client.connect();
        client.send(data);
        client.disconnect();
    }
}
  • NetworkService 클래스
    • NetworkClient 구현을 호출하여 원하는 기능을 동작하도록 서비스한다.
    • Service 적힌 부분이 있다면 호출부 코드이다.
  • sendMessage(String data) 멤버 메소드
    • NetworkClient 객체 생성 후 외부 서버 주소를 전달한다.
    • NetworkClient 사용에 필요한 connect(), send(data), disconnect() 순서로 호출한다.

Main.class

import java.util.Scanner;

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: hello
http://kiioio.com Connect Open Success
http://kiioio.com Send Message data: hello
http://kiioio.com Connect Closed Success

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

Send Message data: exit
네트워크 프로그램 종료

네트워크에서는 예외 처리하여 개선 할 부분이 많다.



네트워크 코드 오류 상황 만들기

예외 상황을 작성 전에 오류 상황을 만들어보고 왜 예외 처리가 필요한지 살펴보자.

실제 네트워크를 개발 시 다양한 문제들이 야기할 수 있다.

  • 외부 연결과 실패 (네트워크 지연 오류 등등)
  • 데이터 전송 문제 (프로토콜 불일치 등등)

이러한 오류 상황 대처에 먼저 가상으로 시뮬레이션 한다.

오류상황 조건

  • 사용자 입력으로 활용하여 다음 단어로 에러상황을 만든다.
    • 사용자가 error1 문자 입력 시 연결 실패한다. (connectError)
    • 사용자가 error2 문자 입력 시 데이터 전송 실패한다. (sendError)

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;
        }
    }
}
  • 신규 멤버 필드 connectError, sendError 작성
    • connectError 값이 참인 경우 실패하는 코드이다.
    • sendError 참인 경우 실패하는 코드이다.
  • 신규 멤버 메소드 initError(String data)
    • 이 메소드로 사용자 입력으로 error1, error2 오류 코드로 시뮬레이션한다.

NetworkService.class

public class NetworkService {

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

호출부에서도 initError 시뮬레이션 호출이 당연 필요하다.

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
http://kiioio.com Send Message data: error1
http://kiioio.com Connect Closed Success

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

Open Failed와 Message Failed data 문자열 호출하는 것을 정상적으로 확인된다.
즉, 시뮬레이션이 정상적으로 오류코드로 만들어주고 있다.

오류 코드는 정상적으로 동작하나, 오류 발생하더라도 서버와 연결이 마치 정상적으로 연결되어 전송하는 것처럼 보인다.

마찬가지로 전송에 실패해도 서버로 전달하는 모습을 볼 수 있다.

오류 대처로 예외 상황이 필요한 상황이다.



반환 값으로 예외 처리해보기

앞서 작성한 코드에서 발생한 문제들이 있다.

  • 연결 실패시 전송되지 않아야할 데이터가 전송됨
  • 오류 발생 시 로그를 저장이 필요함

우리가 작성한 NetworkClient 오류 코드를 문자열로 반환한 값이 있다. 이 값으로 예외 상황을 해결해보자.

NetworkClient.class, Main.java

앞 서 작성한 코드를 가져온다.

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

        String sendResult = client.send(data);
        if (isError(sendResult)) {
            System.out.println("!!!네트워크 오류 발생함!!! 오류 코드: " + sendResult);
            return;
        }

        client.disconnect();
    }

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

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
http://kiioio.comConnect Open Failed
!!!네트워크 오류 발생함!!! 오류 코드: connectError

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

Send Message data: exit
네트워크 프로그램 종료

오류 상황 조건을 살펴보고 요구상황에 대한 대처가 되었는지 살펴보자.

네트워크 사용시 주의할 점

  • connect() => 해결
    • 실패한 경우 send() 호출되어선 안된다.
  • disconnect() => 미흡
    • 연결이 성공하였다면 반드시 disconnect() 사용하여 연결을 종료되어야 한다.
    • connect(), send(...) 호출 오류가 발생하여도 disconnect() 로 정상 종료가 필요하다.

connect() 실패한 경우 send() 호출이 안되는 부분이 해결 완료하였다.
그리고 사용 후 반드시 disconnect() 호출해야하는 문제는 해결이 되지 않았다.
사용자 입력으로 error2 메시지 실패 시뮬레이션에 살펴보아도 연결 종료되지 않는다.
이를 방치할 경우 네트워크는 계속 점유하면서 네트워크가 모두 끊어질 수 있다.

💡
자바는 GC로 VM 메모리 인스턴스를 자동으로 해제할 수 있다.
하지만 외부 연결에 대한 자원은 자동으로 해제되지 않는다.
자원을 사용한 후 반드시 반납해주어야 한다.

disconnect() 호출하기

기존의 NetworkClient, Main 클래스는 그대로 두고 NetworkService 코드만 수정해보도록 한다.

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

Send Message data: error1
http://kiioio.comConnect 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

이제 네트워크 주의에 대한 요구사항을 충족하였다.

네트워크 사용시 주의할 점

  • connect() => 해결
  • disconnect() => ​해결

그러나 NetworkClient 가 주는 반환 값으로 예외 처리하는 NetworkService 정상흐름과 비정상 흐름이 분리되어 있지 않으며 구분과 이해가 어렵다.
예외 흐름을 처리하는 부분이 더 많은 것을 볼 수 있다.

예외 처리 전 초기 NetworkService 코드

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

예외 처리 후 NetworkService 코드

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();
  • 정상 흐름과 예외 흐름 문제로 가독성이 뒤쳐졌다.
  • 예외 흐름이 더 많아 복잡해 유지보수 관리에 어려워보인다.

이와 같이 NetworkClient 반환 값을 사용한 예외 상황을 컨트롤 하는 것은 어렵다.

다음 시간에는 반환 값으로 정상 흐름과 비정상 흐름을 구분하지 않고,
예외 처리 메커니즘을 사용 해 예외 처리를 만들어보자