Java 예외 처리 - 네트워크 예제 리팩토링 1
앞서 예외 처리 기초에서 네트워크 예제를 작성하였다.
NetworkClient 클래스에서 반환하는 오류 문구를 이용해 NetworkService 클래스로 if문으로 예외 처리를 시도를 하였다. 그로 인한 유지보수성와 가독성이 떨어지는 코드가 만들어졌다.
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;
}
}
}
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");
}
}
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
!!!네트워크 오류 발생함!!! 오류 코드: 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
다시 한번 발생한 문제점을 되짚어보자.
- 정상 흐름과 예외 흐름이 섞여 있어 코드의 가독성이 저하되었다.
- 예외 흐름이 많아 코드 분량을 대부분 차지하여 복잡한 코드가 완성되었다.
이러한 문제를 예외 처리를 본격적으로 도입하여 해결해보도록 한다.
예외 처리 - NetworkClient
NetworkClientException 객체를 생성하여 예외 코드로 만들어주도록 한다.
NetworkClientException.class
public class NetworkClientException extends Exception {
private String errorCode;
public NetworkClientException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
// 에러 코드 조회하기
public String getErrorCode() {
return errorCode;
}
}
- 예외도 객체로 필요한 멤버 필드와 메소드를 작성할 수 있다.
- errorCode 멤버 필드
- 이전의 오류 코드(errorCode)로 반환 값 용도로 사용하였다.
- 어떤 종류의 오류인지 구분하기 위함으로 코드를 보관한다.
- message 멤버 필드
- 오류 메시지(message)에는 어떠한 오류가 발생하였는지 개발자가 알 수 있도록 설명을 담는다.
- 오류 메시지는 상위 클래스의 Throwable 기본으로 제공하고 있다.
NetworkClient.class
public class NetworkClient {
private final String address;
public boolean connectError;
public boolean sendError;
public NetworkClient(String address) {
this.address = address;
}
// void 반환으로 변경 후 예외 클래스 사용
public void connect() throws NetworkClientException {
// exception error
if (connectError) {
throw new NetworkClientException("connectError", "Connect Open Failed");
}
// Connect Success
System.out.println(address + " Connect Open Success");
return "success";
}
// void 반환으로 변경 후 예외 클래스 사용
public void send(String message) throws NetworkClientException {
if (sendError) {
throw new NetworkClientException("sendError", "Send Failed");
}
// 전송 성공
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;
}
}
}
- 기존 코드와 비슷하다. 변경된 것은 예외 흐름에서 오류 코드 반환이 아닌 예외를 던지도록 하였다.
- 반환 값을 사용하지 않는다. 메소드 반환은 void 처리
- 이전에는 반환 값으로 성공, 실패 여부를 확인 했으나, 예외 처리를 사용하면 메소드가 정상 종료하였다면 성공이며 예외를 던진다면 실패로 확인할 수 있다.
- 오류 발생에 오류코드와 오류 메시지를 담을 수 있도록 예외 객체 NetworkClientException 클래스를 작성하였다. 예외 객체는 throw 던지도록 한다.
예외 처리 - NetworkService
먼저 Client 사용한 반환 값으로 예외 처리한 부분을 제거하고 간단한 로직으로 구성한다.
NetworkService.class - sendMessage(String data)
public void sendMessage(String data) throws NetworkClientException{
String address = "http://kiioio.com";
NetworkClient client = new NetworkClient(address);
client.initError(data); // 오류 코드로 반환.
client.connect();
client.send(data);
client.disconnect();
}
리팩토링 이전으로 sendMessage 구성하고, throws 키워드로 상속하여 컴파일 오류를 발생하지 않도록 한다.
throws 키워드로 인해 예외가 발생한다면 sendMessage 호출한 스택에게 예외로 던질 것이다.
예외 처리 - Main
import java.util.Scanner;
public class Main {
public static void main(String[] args) throws NetworkClientException {
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: error1
Exception in thread "main" NetworkClientException: Connect Open Failed
at NetworkClient.connect(NetworkClient.java:13)
at NetworkService.sendMessage(NetworkService.java:8)
at Main.main(Main.java:15)
Send Message data: error2
http://kiioio.com Connect Open Success
Exception in thread "main" NetworkClientException: Send Failed
at NetworkClient.send(NetworkClient.java:22)
at NetworkService.sendMessage(NetworkService.java:9)
at Main.main(Main.java:15)
여기서도 예외가 발생하면 throws 으로 던지도록 한다.
error1 오류 시뮬레이션을 동작시키면 main 예외를 던지는 결과로 인해 비정상 종료를 하였다.
- error1 오류 시뮬레이션으로 Connect Open Failed 발생시킴
- 모든 곳의 발생한 예외를 처리하는 로직이 없으므로 main() 밖으로 던지게 되었다.
- 예외 메시지와 예외를 추적하는 스택 트레이스 출력하고 프로그램 종료
- error2 오류 시뮬레이션으로 Connect Open Success 성공하였으나 메시지 전송에 실패함.
리팩토링 남은 개선사항
예외 처리는 했으나 문제가 해결되지 않는 부분이 있다.
- 예외 처리가 없으므로 복구되지 않았다. 예외 발생시 반드시 종료된다.
- 사용 후 반드시 disconnect() 호출하여 연결을 해제해주어야 한다.