[JAVA] 예외 처리1 -이론

2025. 7. 10. 20:15·자바

오늘은 자바에서 예외 처리가 필요한 이유를 적절한 예제와 함께 설명할 것이다.

 

자바에서 예외 처리는 선택이 아니라 필수다.
하지만 처음 프로그램을 짜기 시작하면, 대부분은 오류가 발생할 것이라 생각하지 않고 "일단 동작하는 코드"를 작성하는 데 집중한다.
이번 글은 네트워크 메시지 전송 프로그램을 V0 → V1.1 → V1.2 → V1.3 순으로 단계적으로 개선하면서, 예외 처리 없이 직접 오류를 다루는 것이 얼마나 불편하고 위험한지를 체험하며 정리한 기록이다.

 

먼저 한 예제에 대한 프로그램 구성도이다.

 

V0: 예외가 없는 구현

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

        client.connect();
        client.send(data);
        client.disconnect();
    }
}
// NetworkClient0.java
public class NetworkClient0 {
    private final String address;

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

    public String connect(){
        System.out.println(address + " 서버 연결 성공");
        return "success";
    }

    public String send(String data){
        System.out.println(address + " 서버의 데이터 전송 " + data);
        return "success";
    }

    public void disconnect(){
        System.out.println(address + " 서버 연결 해제");
    }
}
// MainV0.java
public class MainV0 {
    public static void main(String[] args) {
        NetworkService0 networkService = new NetworkService0();
        Scanner sc = new Scanner(System.in);
        while(true){
            System.out.print("전송할 문자: ");
            String input = sc.nextLine();
            if(input.equals("exit")) break;

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

 

결과 화면:

전송할 문자: hello
http://example.com 서버 연결 성공
http://example.com 서버의 데이터 전송 hello
http://example.com 서버 연결 헤제

 

문제점

이 구조의 가장 큰 문제는 실패 상황을 전혀 고려하지 않는다는 것이다.

  • 네트워크 연결에 실패하면 어떻게 되는가?
  • 데이터 전송이 실패하면 사용자에게 알려야 하는가?
  • 실패한 상태에서 disconnect()를 호출하는 게 의미 있는가?

모든 작업을 무조건 성공한다고 가정하고 있기 때문에, 실제 운영 환경에서는 전혀 쓸 수 없는 코드다.
문제가 발생해도 감지가 안 되고, 감지가 안 되면 대응도 못 한다.
즉, 문제를 무시한 채 다음 단계로 넘어가 버리는 구조인 것이다.

 

 

V1.1. 오류를 시뮬레이션해보자 – 하지만 대응은 안 함

package exception.ex1;

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

    public NetworkClientV1(String address) {
        this.address = address;
    }
    public String connect(){
        if(connectError){
            System.out.println(address+" 서버 연결 실패");
            return "connectError";
        }
        System.out.println(address+" 서버 연결 성공");
        return "success";
    }
    public String send(String data){
        if(sendError){
            System.out.println(address+" 서버에 데이터 전송 실패");
            return "sendError";
        }
        //전송 성공
        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;
        }

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

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

결과화면:

전송할 문자: hello
 http://example.com 서버 연결 성공
http://example.com 서버에 데이터 전송: hello
 http://example.com 서버 연결 해제
전송할 문자: error1
 http://example.com 서버 연결 실패
http://example.com 서버에 데이터 전송: error1
 http://example.com 서버 연결 해제
전송할 문자: error2
 http://example.com 서버 연결 성공
http://example.com 서버에 데이터 전송 실패: error2
 http://example.com 서버 연결 해제
전송할 문자: exit
프로그램을 정상 종료합니다.

 

문제점

이번에는 "error1"이 들어가면 연결 실패, "error2"가 들어가면 전송 실패가 일어나도록 만들었다.
즉, 오류는 발생하지만 여전히 아무런 조치도 하지 않는다.

이 구조의 문제는 다음과 같다:

  • connect()가 실패해도 send()가 호출된다. 이미 연결도 안 된 상태에서 데이터를 보내려는 시도 자체가 잘못이다.
  • send()가 실패해도 disconnect()가 호출된다. 의미는 없지만, 코드상 흐름은 계속 진행된다.
  • 실패한 줄도 모르고 정상처럼 작동하는 구조이기 때문에, 문제를 추적하기 어렵다.

이처럼 실패를 감지했지만 무시하는 구조는, 실패하지 않은 것보다 더 위험할 수 있다.

 

 

V1.2 - 오류를 직접 감지하고 분기 처리

이제부터는 오류가 발생했는지를 직접 확인하고, 문제가 있을 경우 그에 맞게 대응하는 코드를 작성해본다.
즉, "성공" 여부를 비교해서 분기 처리하는 방식이다.

package exception.ex1;

public class NetworkService1_2 {
    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);
            return;
        }

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

        client.disconnect();
    }

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

 

결과화면:

전송할 문자: hello
 http://example.com 서버 연결 성공
http://example.com 서버에 데이터 전송: hello
 http://example.com 서버 연결 해제
 
전송할 문자: error1
 http://example.com 서버 연결 실패
[네트워크 오류 발생] 오류 코드: connectError

전송할 문자: error2
 http://example.com 서버 연결 성공
http://example.com 서버에 데이터 전송 실패: error2
 [네트워크 오류 발생] 오류 코드: sendError
 
전송할 문자: exit
프로그램을 정상 종료합니다.

 

 

문제를 해결한 점

이제 코드가 실패 여부를 직접 체크하고 있다.

  • 연결에 실패하면 send()를 호출하지 않고 바로 종료된다.
  • 전송에 실패하면 disconnect() 전 단계에서 오류 메시지를 출력하고 중단된다.

이전 단계에 비해 개선된 점은 분명하다.
이제 오류가 발생했을 때 무시하지 않고, 최소한 사용자에게 "문제가 발생했음"을 알리고 흐름을 끊는다.

 

하지만 새로 생긴 문제

  • "success"라는 문자열을 하드코딩하여 비교하고 있다. 오타가 나면 오류를 잡지 못할 수도 있다.
  • 조건문이 하나 둘씩 늘어나면서, 로직이 길어지고 지저분해진다.
  • 본래 목적이었던 "연결 → 전송 → 끊기"라는 흐름이 조건문에 가려져 가독성이 떨어진다.
  • 사용 후에는 반드시 disconnect()를 호출해서 연결을 헤재해야하지만, 이 메서드가 실행이 안된다.

또한, 실패 원인별로 메시지를 출력하는 것은 좋지만, 오류에 대한 구체적인 정보를 구조적으로 처리하지 못하고 단순 문자열만 사용하고 있다.
이 방식은 기능이 늘어나고 오류 종류가 많아질수록 유지보수가 어려워진다.

 

 

V1.3 - 조건문 정리, 흐름 분리

이번 단계에서는 if-else 구조를 사용하여 흐름을 더 명확하게 나눈다.

package exception.ex1;

public class NetworkService1_3 {
    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 result) {
        return !result.equals("success");
    }
}

 

개선된 점

  • connect()가 실패했을 경우엔 send()를 시도조차 하지 않는다.
  • disconnect()는 항상 호출된다.
  • 흐름을 if-else로 나눠서, 연결에 성공한 경우에만 전송을 하게 되어 논리적으로 맞는 흐름이 되었다.

여전히 남아있는 문제

  • 여전히 "success"라는 문자열 비교가 반복되고 있다. "connectError", "sendError" 같은 값도 전부 문자열이므로 실수 가능성이 크다.
  • 조건문이 많아지면서 코드의 중첩 구조가 깊어지고, 가독성이 떨어진다.
  • 핵심 로직과 예외 처리 로직이 서로 얽혀 있다. → 연결, 전송이라는 주 흐름이 명확하게 보이지 않는다.

이 상태에서 기능이 더 추가되거나, 전송 후 응답 확인 로직 등이 들어오면 조건문만으로 로직을 유지하는 것이 사실상 불가능해진다.

 

 

결론: 코드가 무너지기 시작했다

 

처음엔 간단한 흐름이었다.
하지만 예외 없이, 오직 문자열로 오류를 비교하고 처리하다 보니 점점 코드가 다음과 같이 변질되었다:

  • 핵심 로직보다 오류 처리 코드가 더 눈에 띈다
  • 조건문이 늘어나고 중첩 구조로 인해 가독성이 떨어진다
  • 실수하기 쉬운 하드코딩된 문자열 비교

무엇보다도 중요한 것은,
예외를 잡기 위해 작성한 코드가 오히려 핵심 로직을 가리고 있다는 점이다.

원래 이 프로그램의 목적은 단순하다.
"연결하고, 메시지를 보내고, 연결을 끊는 것"이다.
그런데 예외 상황을 처리하려다 보니, 실제로 하고 싶은 작업은 뒤로 밀리고, 모든 줄이 에러 방지용 조건문으로 채워진다.

결국 이런 코드가 되어간다:

  • 기능 하나 구현하는 데, 에러 체크 if만 2~3줄
  • 추가 기능이 생길수록 조건문은 늘어나고, 중첩이 반복된다
  • 에러 메시지를 관리하기 위해 별도의 규칙이 필요해진다

즉, 예외를 예외처럼 처리하지 않으면 코드 전체가 예외 처리 로직에 휘둘리게 되는 것이다.
이 문제는 프로그램이 커질수록, 그리고 예외 상황이 복잡해질수록 더 심각해진다.

 

 

마무리하며:

결국, 항상 이렇게 코딩할 수는 없다.
기능 하나 구현할 때마다 오류 상황을 직접 체크하고, 조건문을 분기하며, 일일이 문자열을 비교하는 방식은
작고 단순한 프로그램에서는 그럭저럭 통할 수 있어도, 규모가 커지거나 예외 상황이 복잡해지면 코드의 유지보수성과 안정성은 급격히 무너진다.

그래서 자바는 예외(Exception)라는 개념을 언어 차원에서 제공한다.
예외 처리는 단순히 오류를 처리하는 문법이 아니라, 복잡한 흐름 속에서도 핵심 로직을 깔끔하게 유지할 수 있게 해주는 구조적인 해법이다.

이제 다음 장에서는 실제로 throw를 통해 예외를 발생시키고, try-catch를 사용해 예외를 처리하며,
커스텀 예외 클래스를 설계하는 방식까지 소개할 것이다.
이 흐름을 통해 예외 처리의 필요성과 그 효과를 명확히 체감할 수 있을 것이다.

 

감사합니다.

 

'자바' 카테고리의 다른 글

[JAVA] 예외 처리3 -실습  (1) 2025.07.11
[JAVA] 예외 처리 2 -이론  (5) 2025.07.10
[JAVA] 중첩 클래스. 내부 클래스3  (2) 2025.07.09
[JAVA] 중첩 클래스, 내부 클래스 2  (2) 2025.07.09
[JAVA] 중첩 클래스, 내부 클래스 1  (2) 2025.07.07
'자바' 카테고리의 다른 글
  • [JAVA] 예외 처리3 -실습
  • [JAVA] 예외 처리 2 -이론
  • [JAVA] 중첩 클래스. 내부 클래스3
  • [JAVA] 중첩 클래스, 내부 클래스 2
0kingki_
0kingki_
자바 + 스프링 웹 개발
  • 0kingki_
    0kingki_
    0kingki_
  • 전체
    오늘
    어제
    • 분류 전체보기 (134)
      • 코딩 테스트 (54)
      • 자바 (21)
      • 스프링 (27)
      • 타임리프 (16)
      • 스프링 데이터 JPA (8)
      • 최적화 (2)
      • QueryDSL (4)
      • AWS (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    코딩 테스트
    예외처리
    BFS
    재귀
    스프링 데이터 JPA
    불변객체
    SpringDataJpa
    fetch join
    spring
    컬렉션
    타임리프
    예외 처리
    SOLID
    스프링 컨테이너
    Java
    자바
    JPA
    쿼리
    QueryDSL
    mvc
    LocalDateTime
    백준
    다형성
    코딩테스트
    쿼리dsl
    객체지향
    thymeleaf
    스프링
    dfs
    최적화
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
0kingki_
[JAVA] 예외 처리1 -이론
상단으로

티스토리툴바