본문 바로가기
Java

Java 중급 1 - 예외처리(3)

by KongJiHoon 2025. 5. 5.

1. 예외처리 (1) 코드 리펙토링

  • 정상 흐름과 예외 흐름이 섞여 있기 때문에 코드를 한눈에 이해하기 어렵다.
  • 심지어 예외 흐름이 더 많은 코드 분량을 차지. 실무에서는 예외 처리가 훨씬 복잡하다

 

(1) 예외 클래스 정의

public class NetworkClientExceptionV2 extends Exception {
    private String errorCode;

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

    public String getErrorCode() {
        return errorCode;
    }
}
  • 오류 코드
    • 이전에는 오류 코드(errorCode)를 반환 값으로 리턴해서 구분
    • 이 로직에는 어떤 종류의 오류가 발생했는지 구분하기 위해 예외 안에 오류 코드를 보관
  • 오류 메세지
    • 오류 메시지에는 어떤 오류가 발생했는지 개발자가 보고 이해할 수 있는 설명이 담긴다
  • ✅ 예외 클래스를 직접 만들면 단순 문자열 비교보다 훨씬 안전하게 로직 제어 가능.

2. NetworkServiceV2_1 ~ V2_5

 

(1) V2_1 : 예외를 던지고 main에서 처리

public void sendMessage(String message) throws NetworkClientExceptionV2 {

    String address = "http://example.com";
    NetworkClientV2 client = new NetworkClientV2(address);


    client.initError(message);

    client.connect();
    client.send(message);

    client.disconnect();

}

 

  • 예외를 직접 던지기만 하고 처리하지 않음.
  • 메인에서 try-catch로 처리하게 위임하는 방식.
  • 작은 프로그램에선 괜찮지만, 계층이 많아질수록 문제 발생.

 

(2) V2_2 : 단계별 try-catch 처리

public void sendMessage(String message)  {

    String address = "http://example.com";
    NetworkClientV2 client = new NetworkClientV2(address);


    client.initError(message);


    try {
        client.connect();

    } catch (NetworkClientExceptionV2 e) {
        System.out.println("[오류] 코드 : " + e.getErrorCode() + ", 메시지 : " + e.getMessage() );
        return;
    }

    try {
        client.send(message);
    } catch (NetworkClientExceptionV2 e) {
        System.out.println("[오류] 코드 : " + e.getErrorCode() + ", 메시지 : " + e.getMessage() );
        return;
    }


    client.disconnect();

}

 

 

  • 각 단계별로 처리하면 에러 위치 추적은 쉬움, 하지만 코드가 장황해짐.
  • 연결 실패 시에도 disconnect 호출 안됨 → 자원 회수 누락 위험.
  • 또한 아직 정상 흐름과 예외 흐름의 분리가 완벽하지 않다.

 

(3) V2_3: 통합 try-catch

public void sendMessage(String message)  {

    String address = "http://example.com";
    NetworkClientV2 client = new NetworkClientV2(address);


    client.initError(message);


    try {
        client.connect();
        client.send(message);
        client.disconnect();

    } catch (NetworkClientExceptionV2 e) {
        System.out.println("[오류] 코드 : " + e.getErrorCode() + ", 메시지 : " + e.getMessage() );

    }




}

 

  • 코드 간결하지만, 중간 실패 시 disconnect 호출 안 될 수 있음.
  • 정상흐름과 예외흐름이 분리되었지만 예외가 잡혔을 경우 disconnect()가 호출이 안될 수 있다.

 

(4) V2_4: 예외 발생 여부와 관계없이 disconnect 호출

package practice.exception.ex2;

public class NetworkServiceV2_4 {

    public void sendMessage(String message)  {

        String address = "http://example.com";
        NetworkClientV2 client = new NetworkClientV2(address);


        client.initError(message);


        try {
            client.connect();
            client.send(message);

        } catch (NetworkClientExceptionV2 e) {
            System.out.println("[오류] 코드 : " + e.getErrorCode() + ", 메시지 : " + e.getMessage() );

        }

        client.disconnect();



    }

}
  • 좀 더 안정적이지만, 예외 발생 시 정상 흐름처럼 disconnect 보이기 때문에 코드 읽기 어려움.
  • 또한 예를 들어 RuntimeException과 같이 예상치 못한 예외가 발생하였을 때 자원회수가 안되는 상황이 발생할 수 있다.

(5) V2_5: finally 블록을 이용한 자원 회수

package practice.exception.ex2;

public class NetworkServiceV2_5 {

    public void sendMessage(String message)  {

        String address = "http://example.com";
        NetworkClientV2 client = new NetworkClientV2(address);


        client.initError(message);


        try {
            client.connect();
            client.send(message);

        } catch (NetworkClientExceptionV2 e) {
            System.out.println("[오류] 코드 : " + e.getErrorCode() + ", 메시지 : " + e.getMessage() );

        } finally {
            client.disconnect(); // 예외 발생 여부와 무관하게 실행

        }




    }

}
  • 가장 안전하고 직관적인 방식.
  • finally는 자원 정리에 가장 적합한 위치.

💡 실무에서도 DB 연결, 파일 입출력, 네트워크 통신 시 반드시 finally 또는 try-with-resources를 활용해 자원 정리해야 함.


MainV2

package practice.exception.ex2;





import java.util.Scanner;

public class MainV2 {


    public static void main(String[] args) throws NetworkClientExceptionV2 {
//        NetworkServiceV2_1 networkService = new NetworkServiceV2_1();
//          NetworkServiceV2_2 networkService = new NetworkServiceV2_2();
//        NetworkServiceV2_3 networkService = new NetworkServiceV2_3();
//        NetworkServiceV2_4 networkService = new NetworkServiceV2_4();
        NetworkServiceV2_5 networkService = new NetworkServiceV2_5();
        Scanner scanner = new Scanner(System.in);

        while (true) {
            System.out.print("전송할 문자: ");
            String input = scanner.nextLine();

            if (input.equals("exit")) {
                break;
            }
            networkService.sendMessage(input);
            System.out.println();
        }

        System.out.println("프로그램을 정상 종료합니다.");

    }
}

 

NetworkClientV2

package practice.exception.ex2;

public class NetworkClientV2 {

    private final String address;

    public boolean connectError;
    public boolean sendError;



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

    public String connect() throws NetworkClientExceptionV2{
        if (connectError) {
            throw new NetworkClientExceptionV2("connectError", address + " 서버 연결 실패");
        }

        // 연결 성공
        System.out.println(address + " 서버 연결 성공");
        return "success";
    }

    public String send(String data) throws NetworkClientExceptionV2{

        if (sendError) {
            throw new NetworkClientExceptionV2("sendError", address + " 서버 연결 실패");
            // 중간에 다른 예외가 발생했다고 가정
            // -> 예외를 잡지 못해 리소스 반환 불가 문제 발생?
//            throw new RuntimeException("ex");
        }

        // 전송 성공
        System.out.println(address + " 서버에 데이터 전송: " + data);
        return "success";
    }

    public void disconnect() {
        System.out.println(address + " 서버 연결 해제");
    }


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

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

 

 

정리
개념 설명
사용자 정의 예외 Exception(체크예외) 또는 RuntimeException(언체크 예외) 상속하여 직접 생성
예외 전파 예외를 던지면 호출한 쪽에서 처리해야 함
try-catch 블록 예외 발생 시 해당 블록으로 흐름 이동
finally 블록 예외 발생 여부와 관계없이 실행되는 자원 정리 구간
예외 메시지 + 코드 분리 메시지는 사용자용, 코드는 로직 판단용 (로깅, 응답 구분)

 

 

출처: 김영한 자바 중급 1편

'Java' 카테고리의 다른 글

자바 중급 2편 - 제네릭(1)  (0) 2025.05.11
Java 중급 1 - 예외처리(4)  (0) 2025.05.10
Java 중급 1 - 예외처리(2)  (0) 2025.05.04
Java 중급 1 - 예외처리(1)  (0) 2025.05.02
Java 중급 1 - 중첩클래스, 내부 클래스(4)  (0) 2025.04.21