이전에 시간에 했던 예제를 사용해 예외처리를 도입해보자
package exception.ex2;
public class NetworkClientExceptionV2 extends Exception {
private String errorCode;
public NetworkClientExceptionV2(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public String getErrorCode() {
return errorCode;
}
}
이전에는 error code를 반환값으로 리턴해서, 로그를 확인할 수 있었는데, 이번엔 어떤 종류의 오류가 발생했는지
구분하기 위해 예외 안에 오류 코드를 보관한다.
message는 어떤 오류가 발생했는지 보고 이해할 수 있게 설명을 담아두었다. (오류 메세지는 Throwable에서 기본 제공하는 메서드다)
package exception.ex2;
public class NetworkClientV2 {
private final String address;
public boolean connectError;
public boolean sendError;
public NetworkClientV2(String address) {
this.address = address;
}
public void connect() throws NetworkClientExceptionV2 {
if (connectError) {
throw new NetworkClientExceptionV2("connectError", address + "서버 연결 실패");
}
System.out.println(address + "서버 연결 성공");
}
public void send(String data) throws NetworkClientExceptionV2 {
if (sendError) {
throw new NetworkClientExceptionV2("sendError", address + "서버에 데이터 전송 실패");
}
System.out.println(address + "서버에 데이터 전송: " + data);
}
public void disconnect() {
System.out.println(address + "서버 연결 해제");
}
public void initError(String data) {
if (data.contains("error1")) {
connectError = true;
}
if (data.contains("error2")) {
sendError = true;
}
}
}
예전 코드와 다르게 반환값을 void로 주었다. 왜냐면 exception이 터지면 바로 호출자에게 넘겨지고 메서드가 종료되기 때문이다.
여기서는 체크 예외이기 때문에 throws 키워드를 사용했다.
오류가 발생하면 예외 객체를 만들고, 거기에 오류 코드와 메세지를 담는 코드이다.
지금까지는 예외처리는 도입을 했지만, 아직 예외가 복구되지 않는다. 따라서 예외가 발생하면 프로그램이 종료된다.
그리고 사용후에 반드시 disconnect() 메서드를 호출해 연결을 해제해야한다.
예외 복구
package exception.ex2;
public class NetworkServiceV2_2 {
public void sendMessage(String data) {
String address = "http://example.com";
NetworkClientV2 client = new NetworkClientV2(address);
client.initError(data); // 추가
try {
client.connect();
} catch (NetworkClientExceptionV2 e) {
System.out.println("[오류] 코드: " + e.getErrorCode() + ", 메세지: " + e.getMessage());
return;
}
try {
client.send(data);
} catch (NetworkClientExceptionV2 e) {
System.out.println("[오류] 코드: " + e.getErrorCode() + ", 메세지: " + e.getMessage());
return;
}
client.disconnect();
}
}
try-catch를 사용해 예외를 복구했다. 이제 예외가 발생해도 프로그램이 정상 흐름대로 작동한다.
그리고 오류 코드와 오류 메세지를 출력한다. return을 사용해서 sendMessage() 메서드를 성공적으로 빠져나올 수 있다.
하지만 이 코드도 정상 흐름과 예외 흐름이 섞여있어 코드를 읽기 힘들다. 그리고 disconnect() 메서드가 실행되지 않는다.
package exception.ex2;
public class NetworkServiceV2_3 {
public void sendMessage(String data) {
String address = "http://example.com";
NetworkClientV2 client = new NetworkClientV2(address);
client.initError(data); // 추가
try {
client.connect();
client.send(data);
client.disconnect();
} catch (NetworkClientExceptionV2 e) {
System.out.println("[오류] 코드: " + e.getErrorCode() + ", 메세지: " + e.getMessage());
}
}
}
try에 정상 흐름을 담고, catch에 예외 흐름을 담아 명확하게 분리할 수 있다. 하지만 마찬가지로 disconnect() 메서드는 동작하지 않는다.
리소스 반환
package exception.ex2;
public class NetworkServiceV2_4 {
public void sendMessage(String data) {
String address = "http://example.com";
NetworkClientV2 client = new NetworkClientV2(address);
client.initError(data); // 추가
try {
client.connect();
client.send(data);
} catch (NetworkClientExceptionV2 e) {
System.out.println("[오류] 코드: " + e.getErrorCode() + ", 메세지: " + e.getMessage());
}
client.disconnect();
}
}
disconnect() 메서드가 결국엔 실행 되었다. 하지만 이 코드는 문제가 있는데, catch로 잡은 NetworkClientExceptionV2의
예외가 아닌 다른 예상치 못한 에러가 발생한다면..??
public void send(String data) throws NetworkClientExceptionV2 {
if (sendError) {
// throw new NetworkClientExceptionV2("sendError", address + "서버에 데이터 전송 실패");
throw new RuntimeException("runtime exception");
}
System.out.println(address + "서버에 데이터 전송: " + data);
}
또 연결 성공 후 프로그램이 종료되어서 정상적으로 연결 해제를 하지 못한 상황이 발생한다.
위 처럼 disconnect() 를 호출해서 연결 해제를 보장하는것은 쉽지 않다. 왜냐면 모든 예외 상황을 예측하는것은 매우 어렵기 때문이다.
finally
자바는 어떠한 경우라도 반드시 호출되는 finally 기능을 제공한다.
package exception.ex2;
public class NetworkServiceV2_5 {
public void sendMessage(String data) {
String address = "http://example.com";
NetworkClientV2 client = new NetworkClientV2(address);
client.initError(data); // 추가
try {
client.connect();
client.send(data);
} catch (NetworkClientExceptionV2 e) {
System.out.println("[오류] 코드: " + e.getErrorCode() + ", 메세지: " + e.getMessage());
} finally {
client.disconnect();
}
}
}
try ~ catch ~ finally 구조는 정상 흐름, 예외 흐름, 마무리 흐름을 제공한다.
try를 시작하기만 하면 finally는 어떠한 경우라도 반드시 호출된다.
catch에서 잡지 못했지만 disconnect()가 호출된 것을 확인할 수 있다. catch보다 finally가 먼저 호출되고 그 다음에 예외를 던진다.
위 내용으로 알 수 있는점
- 정상 흐름과 예외 흐름을 분리해서 코드를 읽기 쉽게 만들 수 있다.
- finally 기능으로 리소스를 항상 반환할 수 있도록 보장할 수 있다.
'Java' 카테고리의 다른 글
제네릭 (1) (1) | 2024.12.12 |
---|---|
예외 처리 (3) (0) | 2024.12.11 |
예외 처리 (1) (0) | 2024.12.10 |
중첩 클래스, 내부 클래스 (Local, Anonymous) (0) | 2024.11.19 |
중첩 클래스, 내부 클래스 (Static, Inner) (0) | 2024.11.15 |