[Java] 김영한의 실전 자바 - 중급편 섹션10 예외처리2 - 실습
강의 실습이 길어, 이번에는 코드예시들보단 흐름을 따라가며 핵심위주로 요약해 정리했다ㅎ :)
○ 예외 처리 도입
처음 만들었던 프로그램에 자바 예외 처리를 도입해본다.예외를 통해 정상 흐름과 예외 흐름을 분리하여 가독성을 높여보자.
반환 값을 통해 예외를 처리하는 방식이 아닌, 예외객체에 오류 코드와 메시지를 포함하여 처리한다.
○ 예외 복구 try-catch
Main()밖으로 예외가 던져지면 스택 트레이스 출력후 프로그램이 종료된다. 예외를 잡아 예외 흐름을 정상 흐름으로 복구하기위해,
try-catch 블록을 사용하여 예외를 처리하고, 오류 메시지를 출력한다.
public class MainV2 { // 기존코드
public static void main(String[] args) throws NetworkClientExceptionV2 {
NetworkServiceV2_1 networkService = new NetworkServiceV2_1();
try - catch를 사용해 예외를 잡아 정상흐름으로 복구할 수 있다.
// NetworkServiceV2_2
...
try{
client.connect();
}catch (NetworkClientExceptionV2 e){
System.out.println("[오류] 코드: " + e.getErrorCode() + ", 메시지: " + e.getMessage());
return; // 예외 잡아 처리후 정상흐름 복귀
}
client.disconnect();
○ 리소스 반환 finally
하지만 위 코드는 에러 발생시,그리고 예상치못한 예외발생시 자원이 해제되지않는다.
사용 후에는 반드시 disconnect() 를 호출해서 연결을 해제해야 한다. 따라서 finally 블록을 사용하여 리소스를 반환한다.
finally 블록은 예외가 발생해도 무조건 호출된다. (그 후 예외가 밖으로 던져진다.)
public class NetworkServiceV2_5 {
public void sendMessage(String data) {
...
try {
client.connect();
client.send(data);
} catch (NetworkClientExceptionV2 e) {
System.out.println("[오류] 코드: " + e.getErrorCode() + ", 메시지: " + e.getMessage());
} finally {
client.disconnect();
}
}
}
다음과 같이 catch 없이 try ~ finally 만 사용할 수도 있다.
try {
client.connect();
client.send(data);
} finally {
client.disconnect();
}
🟢 자바예외처리 try~catch~finally 구조 장점
- 정상 흐름과 예외 흐름을 분리해서, 코드를 읽기 쉽게 만든다.
- 사용한 자원을 항상 반환할 수 있도록 보장해준다.
○ try-with-resources
자바7부터, 인터페이스를 구현하면 Try with resources를 사용할 때 try 가 끝나는 시점에 close() 가 자동으로 호출된다.
🟢try-with-resources
package java.lang;
public interface AutoCloseable {
void close() throws Exception;
}
try (Resource resource = new Resource()) {
// 리소스를 사용하는 코드
} //try가 끝난후 close() 자동으로 호출
🟢 사용 장점
- 리소스 누수 방지: 모든 리소스가 제대로 닫히도록 보장한다.
- 코드 간결성 및 가독성 향상: 명시적인 close() 호출이 필요 없어 코드가 더 간결하고 읽기 쉬워진다.
- 스코프 범위 한정: 리소스로 사용되는 client 변수의 스코프가 try 블럭 안으로 한정된다. 코드 유지보수가 더 쉬워진다.
- 조금 더 빠른 자원 해제: 기존에는 try catch finally로 catch 이후에 자원을 반납했다. Try with resources 구분은 try 블럭이 끝나면 즉시 close() 를 호출한다.
🟢 사용예시
public class NetworkClientV5 implements AutoCloseable{
public void connect() {
if (connectError) {
throw new ConnectExceptionV4(address, address + " 서버 연결 실패");
}
System.out.println(address + " 서버 연결 성공");
}
public void send(String data) {
if (sendError) {
throw new SendExceptionV4(data, address + " 서버에 데이터 전송 실패: " + data);
}
System.out.println(address + " 서버에 데이터 전송: " + data);
}
...
@Override
public void close() {
System.out.println("NetworkClientV5.close");
disconnect();
}
}
public class NetworkServiceV5 {
public void sendMessage(String data) {
String address = "https://example.com";
try (NetworkClientV5 client = new NetworkClientV5(address)) {
client.initError(data);
client.connect();
client.send(data);
} catch (Exception e) {
System.out.println("[예외 확인]: " + e.getMessage());
throw e;
}
}
}
○ 예외 계층화
예외를 계층화해서 다양하게 만들면 더욱 세밀하게 예외를 처리할 수 있다.
아래처럼 예외를 계층화하여 다양한 예외를 세밀하게 처리할 수 있다.
try { //NetworkServiceV3_1
client.connect();
client.send(data);
} catch (ConnectExceptionV3 e) {
System.out.println("[연결 오류] 주소: " + e.getAddress() + ", 메시지: " + e.getMessage());
} catch (SendExceptionV3 e) {
System.out.println("[전송 오류] 전송 데이터: " + e.getSendData() + ", 메시지: " + e.getMessage());
} finally {
client.disconnect();
}
또한 상위 예외를 잡아 하위 예외도 함께 처리할 수 있다.
ConnectExceptionV3는 메시지를 명확히 남기고, 나머지 예외들은 에러메시지를 단순히 출력한다고 하자.
try {
client.connect();
client.send(data);
} catch (ConnectExceptionV3 e) {
System.out.println("[연결 오류] 주소: " + e.getAddress() + ", 메시지: " + e.getMessage());
} catch (NetworkClientExceptionV3 e) {
System.out.println("[네트워크 오류] 메시지: " + e.getMessage());
} catch (Exception e) {
System.out.println("[알 수 없는 오류] 메시지: " + e.getMessage());
} finally {
client.disconnect();
}
아래처럼 여러 예외를 한번에 잡을 수도있다.
try {
client.connect();
client.send(data);
} catch (ConnectExceptionV3 | SendExceptionV3 e) {
○ 실무 예외 처리 방안
1. 처리할 수 없는 예외
시스템 오류로 인해 발생하는 예외(예: 네트워크 서버 문제, 데이터베이스 접속 문제)는 애플리케이션에서 복구할 수 없는 경우가 많으므로, 고객에게 "현재 시스템에 문제가 있습니다"라는 오류 메시지 or 오류 페이지를 제공하여 안내해야 한다.
개발자는 빠르게 문제를 인지하고 해결할 수 있도록 오류 로그를 남겨야 한다.
2. 체크 예외의 부담
- 처리할 수 없는 예외: 예외를 잡아도 복구할 수 없는 예외가 더 많다.
- 체크 예외의 부담: 처리할 수 없는 예외는 밖으로 던져야 한다. 체크 예외이므로 throws 에 던질 대상을 일일이 명시해야 한다.
😥모든 체크 예외를 잡아서 처리하는 경우
try {
} catch (NetworkException) {...}
catch (DatabaseException) {...}
catch (XxxException) {...}
😥 모든 체크 예외를 던지는 경우
class Service {
void sendMessage(String data) throws NetworkException, DatabaseException, ... {
...
}
}
😥 또한 throws Exception으로 모든 체크 예외를 던져버릴수있다.( 좋지않은 방법이다 OTL )
class Facade {
void send() throws Exception
}
class Service {
void sendMessage(String data) throws Exception
}
😉따라서 모든 예외를 하나하나 처리하기보다는 공통으로 처리하는 부분을 만들어 해결하는 것이 효율적이다.
😉 예외를 던지는 코드에서 throws를 사용하지 않고, 필요할 때만 예외를 잡아서 처리한다.
언체크 예외를 사용하면, 언체크 예외가 늘어도 본인이 필요한 예외만 잡으면 되고, throws 를 늘리지 않아도 된다.
class Service {
void sendMessage(String data) {// 언체크예외는 throws를 선언하지않아도된다.
...
try { //일부 언체크 예외를 잡아서 처리할 수도 있다.
} catch (XxxException) {...}
}
}
🟢 예외 처리 공통 방법 요약
- 모든 예외 처리: Exception으로 모든 예외를 잡아서 exceptionHandler(e) 메서드로 처리.
- 추가 처리: instanceof로 예외 타입을 확인해 필요 시 추가 처리.
/*
* ConnectExceptionV4 , SendExceptionV4 를 잡아도 해당 오류들을 복구할 수 없다.
* 따라서 예외를 밖으로 던진다.
*/
public class NetworkServiceV4 {
public void sendMessage(String data) {
String address = "https://example.com";
NetworkClientV4 client = new NetworkClientV4(address);
client.initError(data);
try {
client.connect();
client.send(data);
} finally {
client.disconnect();
}
}
}
public class MainV4 {
public static void main(String[] args) {
NetworkServiceV4 networkService = new NetworkServiceV4();
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("전송할 문자: ");
String input = scanner.nextLine();
if (input.equals("exit")) {
break;
}
try {
networkService.sendMessage(input);
} catch (Exception e) { // 모든 예외를 잡아서 처리
exceptionHandler(e);
}
System.out.println();
}
System.out.println("프로그램을 정상 종료합니다.");
}
//공통 예외 처리
private static void exceptionHandler(Exception e) {
//공통 처리
System.out.println("사용자 메시지: 죄송합니다. 알 수 없는 문제가 발생했습니다.");
System.out.println("==개발자용 디버깅 메시지==");
e.printStackTrace(System.out); // 스택 트레이스 출력
//e.printStackTrace(); // System.err에 스택 트레이스 출력
//필요하면 예외 별로 별도의 추가 처리 가능
if (e instanceof SendExceptionV4 sendEx) {
System.out.println("[전송 오류] 전송 데이터: " + sendEx.getSendData());
}
}
}
🟢 강의 핵심 정리
- 체크 예외: 초기 자바 설계에서 많이 사용됨.
- 문제점: 복구 불가능한 예외가 많아지고 라이브러리 사용 증가로 처리해야 하는 예외가 많아짐.
- 극단적 해결책: throws Exception 사용은 모든 예외를 던지지만 어떤 예외를 잡는지 알 수 없음.
- 현대적 접근: 최근 라이브러리들은 런타임 예외 사용.
- 장점: 필요할 때만 예외를 잡고, 그렇지 않으면 자연스럽게 던짐.
- 공통 처리: 처리할 수 없는 예외는 공통 처리 부분에서 해결.
'Java' 카테고리의 다른 글
[Java] 김영한의 실전 자바 - 중급편2 섹션10 컬렉션 프레임워크 - 순회, 정렬, 전체 정리 (0) | 2024.07.08 |
---|---|
[Java] 김영한의 실전 자바 - 중급편2 섹션1 제네릭 - Generic1,Generic2 (0) | 2024.06.07 |
[Java] 김영한의 실전 자바 - 중급편 섹션9 예외처리1 - 이론 (0) | 2024.05.22 |
[Java] 김영한의 실전 자바 - 중급편 섹션8 중첩 클래스,내부 클래스2 (0) | 2024.05.20 |
[Java] 김영한의 실전 자바 - 중급편 섹션5 열거형 - ENUM (0) | 2024.05.09 |