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 |