본문 바로가기

반환 코드의 흐름 붕괴

연결과 전송 실패를 문자열 반환 값으로 다루면서 생기는 누락과 자원 해제 버그를 관찰하고 정상 값과 실패 신호의 경계를 다시 설계합니다.

외부 시스템과 통신하는 코드는 성공 경로만 읽어서는 완성되지 않습니다.

연결이 실패하면 전송을 시작하지 않아야 하고, 연결 뒤 전송이 실패하더라도 확보한 자원은 닫아야 합니다.

반환 코드만 사용하는 첫 구현에서 이 두 규칙이 어떻게 호출자의 기억에 의존하는지부터 확인합니다.


실패 코드와 흐름 붕괴

다음 클라이언트는 테스트 입력에 따라 실패 문자열을 돌려줍니다.

서비스는 반환값을 받기만 하고 검사하지 않아 연결되지 않은 상태에서 전송까지 진행합니다.

컴파일러는 String이 성공인지 오류인지 알지 못합니다.

lab/IgnoredReturnCodeBug.java
public final class IgnoredReturnCodeBug {
    public static void main(String[] args) {
        NetworkClient client = new NetworkClient(true, false);
        String connectCode = client.connect();
        String sendCode = client.send("create post request");
        client.disconnect();

        System.out.println("connect=" + connectCode);
        System.out.println("send=" + sendCode);
    }

    private static final class NetworkClient {
        private final boolean connectError;
        private final boolean sendError;

        NetworkClient(boolean connectError, boolean sendError) {
            this.connectError = connectError;
            this.sendError = sendError;
        }

        String connect() {
            if (connectError) return "CONNECT_ERROR";
            System.out.println("connected");
            return "SUCCESS";
        }

        String send(String data) {
            if (sendError) return "SEND_ERROR";
            System.out.println("sent=" + data);
            return "SUCCESS";
        }

        void disconnect() { System.out.println("disconnected"); }
    }
}
관찰 결과
sent=create post request
disconnected
connect=CONNECT_ERROR
send=SUCCESS

연결 실패와 전송 성공이 한 결과에 함께 나타납니다.

문제는 실패 문자열의 철자가 아니라 호출 순서를 보호하는 제어 흐름이 없다는 점입니다.

반환 타입이 String이면 "SUCCESS", "success", 빈 문자열도 모두 같은 타입이라 호출자가 매번 해석 규칙을 알아야 합니다.


즉시 분기와 후속 동작 차단

첫 개선은 연결과 전송 뒤에 결과를 확인하는 것입니다.

연결이 실패하면 send를 건너뛰고, 전송이 실패하면 오류를 보고합니다.

그러나 중간 return으로 빠져나가면 마지막 disconnect도 건너뛸 수 있으므로 종료 동작은 분기 바깥에 둡니다.

src/ReturnCodeNetworkFlow.java
public final class ReturnCodeNetworkFlow {
    public static void main(String[] args) {
        sendMessage("error2");
    }

    private static void sendMessage(String data) {
        NetworkClient client = new NetworkClient();
        client.initError(data);

        String connectResult = client.connect();
        if (isSuccess(connectResult)) {
            String sendResult = client.send(data);
            if (!isSuccess(sendResult)) {
                System.out.println("network-error=" + sendResult);
            }
        } else {
            System.out.println("network-error=" + connectResult);
        }

        client.disconnect();
    }

    private static boolean isSuccess(String code) {
        return "SUCCESS".equals(code);
    }

    private static final class NetworkClient {
        private boolean connectError;
        private boolean sendError;

        void initError(String data) {
            connectError = data.contains("error1");
            sendError = data.contains("error2");
        }

        String connect() {
            if (connectError) return "CONNECT_ERROR";
            System.out.println("connected");
            return "SUCCESS";
        }

        String send(String data) {
            if (sendError) return "SEND_ERROR";
            System.out.println("sent=" + data);
            return "SUCCESS";
        }

        void disconnect() { System.out.println("disconnected"); }
    }
}
connected
network-error=SEND_ERROR
disconnected

이 버전은 정해진 두 실패에는 올바르게 동작합니다.

연결 실패와 전송 실패가 동시에 설정된 입력도 호출 순서로 판단할 수 있습니다. sendMessage의 입력만 "error1 error2"로 바꾼 별도 실행을 생각해 보세요. initError는 두 플래그를 모두 참으로 만들지만 connect가 먼저 실패하므로 send에는 도달하지 않습니다. 출력은 network-error=CONNECT_ERROR 다음 disconnected이며 connected나 sent는 출력하지 않습니다. sendError가 있다는 사실과 send를 실제로 호출했다는 사실은 다릅니다.

핵심 개선은 정상 흐름에서 실패 조건을 분리하고 연결 성공 여부에 따라 전송을 제한한 것입니다.

반환값을 받는 것과 다음 호출을 막는 것은 다르다

검사 생략과 error2의 조건부 send를 각 독립 원문으로 추적합니다. connect 반환→send 도달 여부→disconnect의 차이를 순서대로 읽되 입력이 같다고 합치지 않아야 합니다.

반환값을 받는 것과 다음 호출을 막는 것은 다르다

두 원문은 입력이 다르다. 반환값을 검사하지 않는 첫 main은 연결 실패 뒤에도 send를 실행하고, error2 입력의 분기 원문은 연결 성공일 때만 send를 실행한다.

반환 코드 · 후속 호출 조건

반환값을 받는 것과 다음 호출을 막는 것은 다르다

검사 없는 원문은 (true, false), 분기 원문은 "error2" 입력입니다. 다른 입력의 호출과 결과를 비교합니다.

반환값 검사와 실제 후속 호출 두 영역은 입력이 다른 별도 원문입니다. 검사 생략의 연결 실패 뒤 send 성공과 error2 분기 예제의 연결 성공 뒤 send 실패를 구분합니다. 실선은 해당 입력의 실제 호출·출력 순서이며, 서로 다른 영역의 결과가 같은 입력에 대한 전후 개선이라고 주장하지 않습니다. 원문 A · 검사 생략 connect 실패 CONNECT_ERROR 반환 connected 출력 없음 send 그대로 호출 SUCCESS 반환 sent=create post request disconnect 호출 disconnected 출력 main의 결과 출력 connect=CONNECT_ERROR send=SUCCESS 원문 B · error2 입력 connect 성공 SUCCESS 반환 connected 출력 성공 조건이 참 isSuccess가 true 이 입력은 send로 진행 send 실패 보고 SEND_ERROR 반환 network-error=SEND_ERROR disconnect 호출 disconnected 출력 영역마다 입력이 다른 원문 · 실선은 실제 후속 호출·출력 · 예상 밖 예외의 정리 보장 없음
반환값 검사와 실제 후속 호출 두 영역은 입력이 다른 별도 원문입니다. 검사 생략의 연결 실패 뒤 send 성공과 error2 분기 예제의 연결 성공 뒤 send 실패를 구분합니다. 실선은 해당 입력의 실제 호출·출력 순서이며, 서로 다른 영역의 결과가 같은 입력에 대한 전후 개선이라고 주장하지 않습니다. 원문 A · 검사 생략 connect 실패 CONNECT_ERROR 반환 connected 출력 없음 send 그대로 호출 SUCCESS 반환 sent=create post request disconnect 호출 disconnected 출력 main의 결과 출력 connect=CONNECT_ERROR send=SUCCESS 원문 B · error2 입력 connect 성공 SUCCESS 반환 connected 출력 성공 조건이 참 isSuccess가 true 이 입력은 send로 진행 send 실패 보고 SEND_ERROR 반환 network-error=SEND_ERROR disconnect 호출 disconnected 출력 영역마다 입력이 다른 원문 실선은 실제 후속 호출·출력 예상 밖 예외의 정리 보장 없음

반환값을 검사하지 않는 원문

connect
반환 CONNECT_ERROR
connected는 출력하지 않습니다.
send
연결 결과와 무관하게 호출합니다.
반환 SUCCESS
출력 sent=create post request
disconnect 이후
출력 disconnected
그 뒤 main이 두 반환값을 출력합니다.
connect=CONNECT_ERROR
send=SUCCESS

결과에 따라 분기하는 원문

connect
반환 SUCCESS
출력 connected
send
isSuccess(connectResult)가 참이어서 호출합니다.
반환 SEND_ERROR
출력 network-error=SEND_ERROR
disconnect 이후
오류 보고 뒤 disconnected를 출력합니다.
추가 결과 출력은 없습니다.

첫 원문의 send=SUCCESS는 테스트 클라이언트의 반환값이지 실제 연결 성공의 증거가 아닙니다. 분기 원문도 finally를 쓰지 않으므로 예상 밖 예외까지 종료 호출을 보장하지 않습니다.

이 예의 조건

  • A와 B는 입력이 다른 독립 원문이다.
관계도의 보충 설명
원 도표의 설명과 주석
구분내용
전체 읽기 정보반환값을 받는 것과 다음 호출을 막는 것은 다르다 반환값을 받는 것과 다음 호출을 막는 것은 다르다 두 원문은 입력이 다르다. 반환값을 검사하지 않는 첫 main은 연결 실패 뒤에도 send를 실행하고, error2 입력의 분기 원문은 연결 성공일 때만 send를 실행한다. 반환 코드 · 후속 호출 조건 반환값을 받는 것과 다음 호출을 막는 것은 다르다 검사 없는 원문은 (true, false), 분기 원문은 "error2" 입력입니다. 다른 입력의 호출과 결과를 비교합니다. 반환값을 검사하지 않는 원문 connect 반환 CONNECT_ERROR connected 는 출력하지 않습니다. send 연결 결과와 무관하게 호출합니다. 반환 SUCCESS 출력 sent=create post request disconnect 이후 출력 disconnected 그 뒤 main이 두 반환값을 출력합니다. connect=CONNECT_ERROR send=SUCCESS 결과에 따라 분기하는 원문 connect 반환 SUCCESS 출력 connected send isSuccess(connectResult) 가 참이어서 호출합니다. 반환 SEND_ERROR 출력 network-error=SEND_ERROR disconnect 이후 오류 보고 뒤 disconnected 를 출력합니다. 추가 결과 출력은 없습니다. 첫 원문의 send=SUCCESS 는 테스트 클라이언트의 반환값이지 실제 연결 성공의 증거가 아닙니다. 분기 원문도 finally 를 쓰지 않으므로 예상 밖 예외까지 종료 호출을 보장하지 않습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
connect 호출

A는 실패하고 B error2는 성공합니다.

  • A: CONNECT_ERROR
  • B: SUCCESS / connected
  • 비교의 입력이 다르므로 같은 요청 개선 전후로 묶지 않는다.
send 도달

A는 검사 없이 호출하고 B는 isSuccess를 통과해 호출합니다.

  • A send: SUCCESS / sent=create post request
  • B send: SEND_ERROR / network-error
  • 후속 호출 조건과 실제 전송 결과는 별개다.
disconnect 후 결과

두 프로그램이 각 정리와 관찰을 마칩니다.

  • A: disconnected; connect=CONNECT_ERROR,send=SUCCESS
  • B: disconnected
  • 정상 반환 코드 경로만 보여 주며 예상 밖 예외의 정리 보장을 추가하지 않는다.

반환값을 받는 것과 다음 호출을 막는 것은 다르다

검사 생략과 error2의 조건부 send를 각 독립 원문으로 추적합니다. connect 반환→send 도달 여부→disconnect의 차이를 순서대로 읽되 입력이 같다고 합치지 않아야 합니다.

반환값을 받는 것과 다음 호출을 막는 것은 다르다

두 원문은 입력이 다르다. 반환값을 검사하지 않는 첫 main은 연결 실패 뒤에도 send를 실행하고, error2 입력의 분기 원문은 연결 성공일 때만 send를 실행한다.

반환 코드 · 후속 호출 조건

반환값을 받는 것과 다음 호출을 막는 것은 다르다

검사 없는 원문은 (true, false), 분기 원문은 "error2" 입력입니다. 다른 입력의 호출과 결과를 비교합니다.

반환값 검사와 실제 후속 호출 두 영역은 입력이 다른 별도 원문입니다. 검사 생략의 연결 실패 뒤 send 성공과 error2 분기 예제의 연결 성공 뒤 send 실패를 구분합니다. 실선은 해당 입력의 실제 호출·출력 순서이며, 서로 다른 영역의 결과가 같은 입력에 대한 전후 개선이라고 주장하지 않습니다. 원문 A · 검사 생략 connect 실패 CONNECT_ERROR 반환 connected 출력 없음 send 그대로 호출 SUCCESS 반환 sent=create post request disconnect 호출 disconnected 출력 main의 결과 출력 connect=CONNECT_ERROR send=SUCCESS 원문 B · error2 입력 connect 성공 SUCCESS 반환 connected 출력 성공 조건이 참 isSuccess가 true 이 입력은 send로 진행 send 실패 보고 SEND_ERROR 반환 network-error=SEND_ERROR disconnect 호출 disconnected 출력 영역마다 입력이 다른 원문 · 실선은 실제 후속 호출·출력 · 예상 밖 예외의 정리 보장 없음
반환값 검사와 실제 후속 호출 두 영역은 입력이 다른 별도 원문입니다. 검사 생략의 연결 실패 뒤 send 성공과 error2 분기 예제의 연결 성공 뒤 send 실패를 구분합니다. 실선은 해당 입력의 실제 호출·출력 순서이며, 서로 다른 영역의 결과가 같은 입력에 대한 전후 개선이라고 주장하지 않습니다. 원문 A · 검사 생략 connect 실패 CONNECT_ERROR 반환 connected 출력 없음 send 그대로 호출 SUCCESS 반환 sent=create post request disconnect 호출 disconnected 출력 main의 결과 출력 connect=CONNECT_ERROR send=SUCCESS 원문 B · error2 입력 connect 성공 SUCCESS 반환 connected 출력 성공 조건이 참 isSuccess가 true 이 입력은 send로 진행 send 실패 보고 SEND_ERROR 반환 network-error=SEND_ERROR disconnect 호출 disconnected 출력 영역마다 입력이 다른 원문 실선은 실제 후속 호출·출력 예상 밖 예외의 정리 보장 없음

반환값을 검사하지 않는 원문

connect
반환 CONNECT_ERROR
connected는 출력하지 않습니다.
send
연결 결과와 무관하게 호출합니다.
반환 SUCCESS
출력 sent=create post request
disconnect 이후
출력 disconnected
그 뒤 main이 두 반환값을 출력합니다.
connect=CONNECT_ERROR
send=SUCCESS

결과에 따라 분기하는 원문

connect
반환 SUCCESS
출력 connected
send
isSuccess(connectResult)가 참이어서 호출합니다.
반환 SEND_ERROR
출력 network-error=SEND_ERROR
disconnect 이후
오류 보고 뒤 disconnected를 출력합니다.
추가 결과 출력은 없습니다.

첫 원문의 send=SUCCESS는 테스트 클라이언트의 반환값이지 실제 연결 성공의 증거가 아닙니다. 분기 원문도 finally를 쓰지 않으므로 예상 밖 예외까지 종료 호출을 보장하지 않습니다.

이 예의 조건

  • A와 B는 입력이 다른 독립 원문이다.

관계도의 보충 설명

원 도표의 설명과 주석
구분내용
전체 읽기 정보반환값을 받는 것과 다음 호출을 막는 것은 다르다 반환값을 받는 것과 다음 호출을 막는 것은 다르다 두 원문은 입력이 다르다. 반환값을 검사하지 않는 첫 main은 연결 실패 뒤에도 send를 실행하고, error2 입력의 분기 원문은 연결 성공일 때만 send를 실행한다. 반환 코드 · 후속 호출 조건 반환값을 받는 것과 다음 호출을 막는 것은 다르다 검사 없는 원문은 (true, false), 분기 원문은 "error2" 입력입니다. 다른 입력의 호출과 결과를 비교합니다. 반환값을 검사하지 않는 원문 connect 반환 CONNECT_ERROR connected 는 출력하지 않습니다. send 연결 결과와 무관하게 호출합니다. 반환 SUCCESS 출력 sent=create post request disconnect 이후 출력 disconnected 그 뒤 main이 두 반환값을 출력합니다. connect=CONNECT_ERROR send=SUCCESS 결과에 따라 분기하는 원문 connect 반환 SUCCESS 출력 connected send isSuccess(connectResult) 가 참이어서 호출합니다. 반환 SEND_ERROR 출력 network-error=SEND_ERROR disconnect 이후 오류 보고 뒤 disconnected 를 출력합니다. 추가 결과 출력은 없습니다. 첫 원문의 send=SUCCESS 는 테스트 클라이언트의 반환값이지 실제 연결 성공의 증거가 아닙니다. 분기 원문도 finally 를 쓰지 않으므로 예상 밖 예외까지 종료 호출을 보장하지 않습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
connect 호출

A는 실패하고 B error2는 성공합니다.

  • A: CONNECT_ERROR
  • B: SUCCESS / connected
  • 비교의 입력이 다르므로 같은 요청 개선 전후로 묶지 않는다.
send 도달

A는 검사 없이 호출하고 B는 isSuccess를 통과해 호출합니다.

  • A send: SUCCESS / sent=create post request
  • B send: SEND_ERROR / network-error
  • 후속 호출 조건과 실제 전송 결과는 별개다.
disconnect 후 결과

두 프로그램이 각 정리와 관찰을 마칩니다.

  • A: disconnected; connect=CONNECT_ERROR,send=SUCCESS
  • B: disconnected
  • 정상 반환 코드 경로만 보여 주며 예상 밖 예외의 정리 보장을 추가하지 않는다.

새 반환 코드의 업무 의미는 별도로 해석해야 합니다. 특히 send()가 예상하지 못한 런타임 예외를 던지면, 이 코드에는 finally가 없어 마지막 종료 호출이 보장되지 않습니다.


닫힌 결과 타입

예외를 배우기 전에도 문자열 코드는 enum 또는 결과 객체로 개선할 수 있습니다.

호출자는 가능한 상태를 자동 완성으로 확인하고, switch 식으로 모든 상태를 다뤘는지 컴파일 시점에 확인할 수 있습니다.

src/TypedNetworkResult.java
public final class TypedNetworkResult {
    public static void main(String[] args) {
        NetworkClient client = new NetworkClient(false, true);
        NetworkResult connect = client.connect();
        NetworkResult send = connect == NetworkResult.SUCCESS
                ? client.send("typed result")
                : NetworkResult.SKIPPED;

        System.out.println(render(connect));
        System.out.println(render(send));
        client.disconnect();
    }

    private static String render(NetworkResult result) {
        return switch (result) {
            case SUCCESS -> "success";
            case CONNECT_ERROR -> "cannot connect";
            case SEND_ERROR -> "cannot send";
            case SKIPPED -> "not attempted";
        };
    }

    private enum NetworkResult { SUCCESS, CONNECT_ERROR, SEND_ERROR, SKIPPED }

    private record NetworkClient(boolean connectError, boolean sendError) {
        NetworkResult connect() {
            return connectError ? NetworkResult.CONNECT_ERROR : NetworkResult.SUCCESS;
        }

        NetworkResult send(String data) {
            return sendError ? NetworkResult.SEND_ERROR : NetworkResult.SUCCESS;
        }

        void disconnect() { System.out.println("closed"); }
    }
}
success
cannot send
closed

결과 타입은 실패가 흔하고 호출자가 두 결과를 모두 업무적으로 사용해야 할 때 좋은 선택입니다.

예를 들어 검색 결과 없음, 유효성 검사 실패, 결제 거절처럼 예상 가능한 분기는 값으로 모델링할 수 있습니다.

반면 연결 라이브러리 깊은 곳에서 정상 값을 만들 수 없고 호출 스택을 중단해야 한다면 예외가 더 자연스럽습니다.


실패 신호의 선택 기준

반환 값과 예외는 “오류냐 아니냐”만으로 나누지 않습니다.

호출자가 즉시 대안을 선택할 수 있고 실패 빈도가 정상 사용의 일부라면 명시 결과 타입이 읽기 쉽습니다.

호출자가 현재 계층에서 복구할 수 없고 여러 단계를 건너 경계 처리기로 전달해야 한다면 예외가 중복 분기를 줄입니다.

  • 정상적으로 비어 있을 수 있는 조회: Optional이나 sealed 결과 타입
  • 사용자 입력 검증: 오류 목록을 포함한 결과 값
  • 파일·네트워크·DB 접근 실패: 원인과 문맥을 담은 예외
  • 프로그래밍 계약 위반: IllegalArgumentException 같은 언체크 예외
  • 반드시 실행할 정리: 반환 방식과 무관하게 구조적으로 보장

예외가 자동으로 자원을 닫아 주는 것은 아닙니다.

다음 문서들에서 try/catch/finally와 try-with-resources가 각각 어떤 흐름을 보장하는지 분리해서 다룹니다.


연습 문제

OperationResult enum을 사용해 connect가 실패하면 send를 실행하지 않고, 세 가지 입력 ok, connect, send 모두 마지막에 close가 출력되도록 완성하세요.

아직 try/finally는 사용하지 않습니다.

OperationResult의 값은 SUCCESS, CONNECT_ERROR, SEND_ERROR, SKIPPED입니다. scenario는 null이 아닌 문자열이며 connect 입력만 연결 실패, send 입력만 전송 실패로 정합니다. 연결에 성공한 경우에만 send를 호출하고, 그 전에는 전송 결과가 SKIPPED입니다.

출력은 먼저 close=시나리오, 다음 시나리오=연결결과/전송결과입니다. 즉 자원 종료 호출 뒤에 관찰용 결과 줄을 출력합니다. 이 연습의 Client 메서드는 예외를 던지지 않고 정해진 enum 값을 반환한다고 가정합니다.

독립 입력 "other"도 실행한다면 send가 호출되는지와 두 출력 줄을 먼저 예상하세요.

해설 보기
src/ReturnCodeFlowExercise.java
public final class ReturnCodeFlowExercise {
    public static void main(String[] args) {
        run("ok");
        run("connect");
        run("send");
    }

    private static void run(String scenario) {
        Client client = new Client(scenario);
        OperationResult connected = client.connect();
        OperationResult sent = OperationResult.SKIPPED;
        if (connected == OperationResult.SUCCESS) {
            sent = client.send();
        }
        client.close();
        System.out.println(scenario + "=" + connected + "/" + sent);
    }

    private enum OperationResult { SUCCESS, CONNECT_ERROR, SEND_ERROR, SKIPPED }

    private record Client(String scenario) {
        OperationResult connect() {
            return scenario.equals("connect")
                    ? OperationResult.CONNECT_ERROR
                    : OperationResult.SUCCESS;
        }

        OperationResult send() {
            return scenario.equals("send")
                    ? OperationResult.SEND_ERROR
                    : OperationResult.SUCCESS;
        }

        void close() { System.out.println("close=" + scenario); }
    }
}
close=ok
ok=SUCCESS/SUCCESS
close=connect
connect=CONNECT_ERROR/SKIPPED
close=send
send=SUCCESS/SEND_ERROR

SKIPPED는 실패와 “호출하지 않음”을 구분합니다.

other는 connect나 send와 같지 않으므로 두 호출이 모두 SUCCESS입니다. 출력은 close=other 다음 other=SUCCESS/SUCCESS입니다. 문자열 이름을 별도의 성공 판정 없이 그대로 오류 상태로 취급하지 않습니다.

연결 실패의 SKIPPED는 전송 실패와 다르다

연결 결과 검사 뒤 send 실행 또는 SKIPPED 유지, close 출력 뒤 결과 출력의 실제 순서입니다. 세 입력 영역을 고정하고 현재 호출/미도달을 단계로 강조하기 좋습니다.

연결 실패의 SKIPPED는 전송 실패와 다르다

ReturnCodeFlowExercise의 ok, connect, send 세 입력은 연결 반환값에 따라 send 호출 여부가 달라진다. 세 원문 경로는 close를 먼저 출력한 뒤 결과를 출력한다.

반환 결과 · 세 입력

연결 실패의 SKIPPED는 전송 실패와 다르다

연결 성공일 때만 send를 호출합니다. 연결 실패의 SKIPPED는 전송을 시도한 SEND_ERROR와 다른 상태입니다.

호출 여부와 반환 결과의 분기 세 알려진 입력의 connect 결과를 검사합니다. SUCCESS일 때만 send가 실행되어 ok는 SUCCESS, send는 SEND_ERROR를 받습니다. connect 입력은 send를 호출하지 않아 SKIPPED를 유지합니다. 두 분기 뒤 close 출력과 결과 출력으로 합류합니다. 예상 밖 예외를 포함한 finally 보장의 그림은 아닙니다. 세 입력 중 하나 ok / connect / send connect 결과 connect만 CONNECT_ERROR 나머지는 SUCCESS 연결 성공? 반환값을 검사 send 호출 ok → SUCCESS send → SEND_ERROR send 호출 안 함 SKIPPED 그대로 connect 입력의 경로 close와 결과 출력 close 출력 먼저 결과 출력은 그 뒤 참 거짓 실선은 검사와 후속 호출 · 두 분기 뒤 close로 합류 · SKIPPED는 호출 결과가 아님
호출 여부와 반환 결과의 분기 세 알려진 입력의 connect 결과를 검사합니다. SUCCESS일 때만 send가 실행되어 ok는 SUCCESS, send는 SEND_ERROR를 받습니다. connect 입력은 send를 호출하지 않아 SKIPPED를 유지합니다. 두 분기 뒤 close 출력과 결과 출력으로 합류합니다. 예상 밖 예외를 포함한 finally 보장의 그림은 아닙니다. 세 입력 중 하나 ok / connect / send connect 결과 connect만 CONNECT_ERROR 나머지는 SUCCESS 연결 성공? 반환값을 검사 send 호출 ok → SUCCESS send → SEND_ERROR send 호출 안 함 SKIPPED 그대로 connect 입력의 경로 close와 결과 출력 close 출력 먼저 결과 출력은 그 뒤 참 거짓 실선은 검사와 후속 호출 두 분기 뒤 close로 합류 SKIPPED는 호출 결과가 아님
세 입력의 실제 호출·반환값과 원문 출력 순서
입력connect 반환send 호출·반환원문 출력 순서
okSUCCESSsend 호출: SUCCESS
close=ok
ok=SUCCESS/SUCCESS
connectCONNECT_ERRORsend 호출 안 함: SKIPPED 유지
close=connect
connect=CONNECT_ERROR/SKIPPED
sendSUCCESSsend 호출: SEND_ERROR
close=send
send=SUCCESS/SEND_ERROR

ok

실제 호출과 반환값
connect: SUCCESS
send 호출: SUCCESS
원문 출력 순서
close=ok
ok=SUCCESS/SUCCESS

connect

실제 호출과 반환값
connect: CONNECT_ERROR
send 호출 안 함: SKIPPED 유지
원문 출력 순서
close=connect
connect=CONNECT_ERROR/SKIPPED

send

실제 호출과 반환값
connect: SUCCESS
send 호출: SEND_ERROR
원문 출력 순서
close=send
send=SUCCESS/SEND_ERROR

SKIPPED는 전송을 시도하지 않았다는 값입니다. SEND_ERROR는 전송 메서드를 호출해 받은 실패 값입니다. 표의 close 도달은 이 세 반환 코드 경로의 결과이며 예상 밖 예외의 정리까지 보장하지 않습니다.

이 예의 조건

  • 알려진 ok/connect/send 입력만 추적하며 예상 밖 예외의 finally 보장이 아니다.
관계도의 보충 설명
원 도표의 설명과 주석
구분내용
전체 읽기 정보연결 실패의 SKIPPED는 전송 실패와 다르다 연결 실패의 SKIPPED는 전송 실패와 다르다 ReturnCodeFlowExercise의 ok, connect, send 세 입력은 연결 반환값에 따라 send 호출 여부가 달라진다. 세 원문 경로는 close를 먼저 출력한 뒤 결과를 출력한다. 반환 결과 · 세 입력 연결 실패의 SKIPPED는 전송 실패와 다르다 연결 성공일 때만 send를 호출합니다. 연결 실패의 SKIPPED는 전송을 시도한 SEND_ERROR와 다른 상태입니다. 세 입력의 실제 호출·반환값과 원문 출력 순서 입력 connect 반환 send 호출·반환 원문 출력 순서 ok SUCCESS send 호출: SUCCESS close=ok ok=SUCCESS/SUCCESS connect CONNECT_ERROR send 호출 안 함: SKIPPED 유지 close=connect connect=CONNECT_ERROR/SKIPPED send SUCCESS send 호출: SEND_ERROR close=send send=SUCCESS/SEND_ERROR ok 실제 호출과 반환값 connect: SUCCESS send 호출: SUCCESS 원문 출력 순서 close=ok ok=SUCCESS/SUCCESS connect 실제 호출과 반환값 connect: CONNECT_ERROR send 호출 안 함: SKIPPED 유지 원문 출력 순서 close=connect connect=CONNECT_ERROR/SKIPPED send 실제 호출과 반환값 connect: SUCCESS send 호출: SEND_ERROR 원문 출력 순서 close=send send=SUCCESS/SEND_ERROR SKIPPED 는 전송을 시도하지 않았다는 값입니다. SEND_ERROR 는 전송 메서드를 호출해 받은 실패 값입니다. 표의 close 도달은 이 세 반환 코드 경로의 결과이며 예상 밖 예외의 정리까지 보장하지 않습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
연결 결과

세 입력이 connect 결과를 받습니다.

  • connect 입력: CONNECT_ERROR
  • ok/send 입력: SUCCESS
  • send 최초 상태: SKIPPED
  • 반환값을 검사하여 후속 호출 허가를 정한다.
전송 허가

성공 경로만 send를 실행합니다.

  • ok: send SUCCESS
  • send: send SEND_ERROR
  • connect: send 미호출 / SKIPPED
  • SKIPPED는 호출 결과가 아니라 실행되지 않은 상태다.
정리와 관찰

분기 후 close를 출력한 다음 결과를 출력합니다.

  • 출력 순서: close → 결과
  • 세 결과: SUCCESS / CONNECT_ERROR·SKIPPED / SEND_ERROR
  • 후속 관찰 전에 알려진 경로의 정리 호출이 합류한다.

연결 실패의 SKIPPED는 전송 실패와 다르다

연결 결과 검사 뒤 send 실행 또는 SKIPPED 유지, close 출력 뒤 결과 출력의 실제 순서입니다. 세 입력 영역을 고정하고 현재 호출/미도달을 단계로 강조하기 좋습니다.

연결 실패의 SKIPPED는 전송 실패와 다르다

ReturnCodeFlowExercise의 ok, connect, send 세 입력은 연결 반환값에 따라 send 호출 여부가 달라진다. 세 원문 경로는 close를 먼저 출력한 뒤 결과를 출력한다.

반환 결과 · 세 입력

연결 실패의 SKIPPED는 전송 실패와 다르다

연결 성공일 때만 send를 호출합니다. 연결 실패의 SKIPPED는 전송을 시도한 SEND_ERROR와 다른 상태입니다.

호출 여부와 반환 결과의 분기 세 알려진 입력의 connect 결과를 검사합니다. SUCCESS일 때만 send가 실행되어 ok는 SUCCESS, send는 SEND_ERROR를 받습니다. connect 입력은 send를 호출하지 않아 SKIPPED를 유지합니다. 두 분기 뒤 close 출력과 결과 출력으로 합류합니다. 예상 밖 예외를 포함한 finally 보장의 그림은 아닙니다. 세 입력 중 하나 ok / connect / send connect 결과 connect만 CONNECT_ERROR 나머지는 SUCCESS 연결 성공? 반환값을 검사 send 호출 ok → SUCCESS send → SEND_ERROR send 호출 안 함 SKIPPED 그대로 connect 입력의 경로 close와 결과 출력 close 출력 먼저 결과 출력은 그 뒤 참 거짓 실선은 검사와 후속 호출 · 두 분기 뒤 close로 합류 · SKIPPED는 호출 결과가 아님
호출 여부와 반환 결과의 분기 세 알려진 입력의 connect 결과를 검사합니다. SUCCESS일 때만 send가 실행되어 ok는 SUCCESS, send는 SEND_ERROR를 받습니다. connect 입력은 send를 호출하지 않아 SKIPPED를 유지합니다. 두 분기 뒤 close 출력과 결과 출력으로 합류합니다. 예상 밖 예외를 포함한 finally 보장의 그림은 아닙니다. 세 입력 중 하나 ok / connect / send connect 결과 connect만 CONNECT_ERROR 나머지는 SUCCESS 연결 성공? 반환값을 검사 send 호출 ok → SUCCESS send → SEND_ERROR send 호출 안 함 SKIPPED 그대로 connect 입력의 경로 close와 결과 출력 close 출력 먼저 결과 출력은 그 뒤 참 거짓 실선은 검사와 후속 호출 두 분기 뒤 close로 합류 SKIPPED는 호출 결과가 아님
세 입력의 실제 호출·반환값과 원문 출력 순서
입력connect 반환send 호출·반환원문 출력 순서
okSUCCESSsend 호출: SUCCESS
close=ok
ok=SUCCESS/SUCCESS
connectCONNECT_ERRORsend 호출 안 함: SKIPPED 유지
close=connect
connect=CONNECT_ERROR/SKIPPED
sendSUCCESSsend 호출: SEND_ERROR
close=send
send=SUCCESS/SEND_ERROR

ok

실제 호출과 반환값
connect: SUCCESS
send 호출: SUCCESS
원문 출력 순서
close=ok
ok=SUCCESS/SUCCESS

connect

실제 호출과 반환값
connect: CONNECT_ERROR
send 호출 안 함: SKIPPED 유지
원문 출력 순서
close=connect
connect=CONNECT_ERROR/SKIPPED

send

실제 호출과 반환값
connect: SUCCESS
send 호출: SEND_ERROR
원문 출력 순서
close=send
send=SUCCESS/SEND_ERROR

SKIPPED는 전송을 시도하지 않았다는 값입니다. SEND_ERROR는 전송 메서드를 호출해 받은 실패 값입니다. 표의 close 도달은 이 세 반환 코드 경로의 결과이며 예상 밖 예외의 정리까지 보장하지 않습니다.

이 예의 조건

  • 알려진 ok/connect/send 입력만 추적하며 예상 밖 예외의 finally 보장이 아니다.

관계도의 보충 설명

원 도표의 설명과 주석
구분내용
전체 읽기 정보연결 실패의 SKIPPED는 전송 실패와 다르다 연결 실패의 SKIPPED는 전송 실패와 다르다 ReturnCodeFlowExercise의 ok, connect, send 세 입력은 연결 반환값에 따라 send 호출 여부가 달라진다. 세 원문 경로는 close를 먼저 출력한 뒤 결과를 출력한다. 반환 결과 · 세 입력 연결 실패의 SKIPPED는 전송 실패와 다르다 연결 성공일 때만 send를 호출합니다. 연결 실패의 SKIPPED는 전송을 시도한 SEND_ERROR와 다른 상태입니다. 세 입력의 실제 호출·반환값과 원문 출력 순서 입력 connect 반환 send 호출·반환 원문 출력 순서 ok SUCCESS send 호출: SUCCESS close=ok ok=SUCCESS/SUCCESS connect CONNECT_ERROR send 호출 안 함: SKIPPED 유지 close=connect connect=CONNECT_ERROR/SKIPPED send SUCCESS send 호출: SEND_ERROR close=send send=SUCCESS/SEND_ERROR ok 실제 호출과 반환값 connect: SUCCESS send 호출: SUCCESS 원문 출력 순서 close=ok ok=SUCCESS/SUCCESS connect 실제 호출과 반환값 connect: CONNECT_ERROR send 호출 안 함: SKIPPED 유지 원문 출력 순서 close=connect connect=CONNECT_ERROR/SKIPPED send 실제 호출과 반환값 connect: SUCCESS send 호출: SEND_ERROR 원문 출력 순서 close=send send=SUCCESS/SEND_ERROR SKIPPED 는 전송을 시도하지 않았다는 값입니다. SEND_ERROR 는 전송 메서드를 호출해 받은 실패 값입니다. 표의 close 도달은 이 세 반환 코드 경로의 결과이며 예상 밖 예외의 정리까지 보장하지 않습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
연결 결과

세 입력이 connect 결과를 받습니다.

  • connect 입력: CONNECT_ERROR
  • ok/send 입력: SUCCESS
  • send 최초 상태: SKIPPED
  • 반환값을 검사하여 후속 호출 허가를 정한다.
전송 허가

성공 경로만 send를 실행합니다.

  • ok: send SUCCESS
  • send: send SEND_ERROR
  • connect: send 미호출 / SKIPPED
  • SKIPPED는 호출 결과가 아니라 실행되지 않은 상태다.
정리와 관찰

분기 후 close를 출력한 다음 결과를 출력합니다.

  • 출력 순서: close → 결과
  • 세 결과: SUCCESS / CONNECT_ERROR·SKIPPED / SEND_ERROR
  • 후속 관찰 전에 알려진 경로의 정리 호출이 합류한다.

단순 boolean 두 개보다 상태 의미가 분명하지만, 예상하지 못한 예외에도 close를 보장하려면 finally 또는 AutoCloseable이 필요합니다.