
'김영한의 실전 자바 - 중급 1편' 강의를 들으면서 복습할만한 내용을 정리하였다.
처리할 수 없는 예외
예를 들어서 상대 네트워크 서버에 문제가 발생해서 통신이 불가능하거나, 데이터베이스 서버에 문제가 발생해서 접속이 안되면, 애플리케이션에서 연결 오류, 데이터베이스 접속 실패와 같은 예외가 발생한다.
이렇게 시스템 오류 때문에 발생한 예외들은 대부분 예외를 잡아도 해결할 수 있는 것이 거의 없다. 예외를 잡아서 다시 호출을 시도해도 같은 오류가 반복될 뿐이다.
이런 경우 고객에게는 "현재 시스템에 문제가 있습니다."라는 오류 메시지를 보여주고, 만약 웹이라면 오류 페이지를 보여주면 된다. 그리고 내부 개발자가 문제 상황을 빠르게 인지할 수 있도록, 오류에 대한 로그를 남겨두어야 한다.
체크 예외의 부담
체크 예외는 개발자가 실수로 놓칠 수 있는 예외들을 컴파일러가 체크해주기 때문에 오래전부터 많이 사용되었다. 그런데 처리할 수 없는 예외가 많아지고, 또 프로그램이 점점 복잡해지면서 체크 예외를 사용하는 것이 점점 더 부담스러워졌다.
체크 예외의 이런 문제점 때문에 최근 라이브러리들은 대부분 런타임 예외를 기본으로 제공한다. 런타임 예외도 필요하면 잡을 수 있기 때문에 필요한 경우에는 잡아서 처리하고, 그렇지 않으면 자연스럽게 던지도록 둔다. 그리고 처리할 수 없는 예외는 예외를 공통으로 처리하는 부분을 만들어서 해결하면 된다.
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();
// 필요하면 예외 별로 별도의 추가 처리 기능
if (e instanceof SendExceptionV4 sendEx) {
System.out.println("[전송 오류] 전송 데이터: " + sendEx.getSendData());
}
}
}
networkService.sendMessage(input) 부분에서 생기는 모든 예외(Exception)를 잡아서 exceptionHandler 라는 공통 예외 처리 메서드로 처리한다.exceptionHandler()
instanceof 와 같이 예외 객체의 타입을 확인해서 별도의 추가 처리를 할 수 있다.e.printStackTrace()
e.printStackTrace(System.out) 을 사용해서 표준 출력으로 보냈다.e.printStackTrace() 를 사용하면 System.err 이라는 표준 오류에 결과를 출력한다.System.err 로 출력하면 출력 결과를 빨간색으로 보여준다.
System.out, System.err둘 다 결국 콘솔에 출력되지만, 서로 다른 흐름을 통해서 출력된다. 따라서 둘을 함께 사용하면 출력 순서를 보장하지 않는다. 출력 순서가 꼬여서 보일 수 있다.
실무에서는
System.out이나System.err을 통해 콘솔에 무언가를 출력하기 보다는, 주로 Slf4J, logback 같은 별도의 로그 라이브러리를 사용해서 콘솔과 특정 파일에 함께 결과를 출력한다. 그런데e.printStackTrace()를 직접 호출하면 결과가 콘솔에만 출력된다. 이렇게 되면 서버에서 로그를 확인하기 어렵다. 서버에서는 파일로 로그를 확인해야 한다. 따라서 콘솔에 바로 결과를 출력하는e.printStackTrace()는 잘 사용하지 않는다. 대신에 로그 라이브러리를 통해서 예외 스택 트레이스를 출력한다.
애플리케이션에서 외부 자원을 사용하는 경우 반드시 외부 자원을 해제해야 한다. 따라서 finally 구문을 반드시 사용해야 한다.
try {
//정상 흐름
} catch {
//예외 흐름
} finally {
//반드시 호출해야 하는 마무리 흐름
}
try 에서 외부 자원을 사용하고, try 가 끝나면 외부 자원을 반납하는 패턴이 반복되면서 자바에서는 Try with resources 라는 편의 기능을 자바 7에서 도입했다. 이름 그대로 try 에서 자원을 함께 사용한다는 뜻이다. 여기서 자원은 try 가 끝나면 반드시 종료해서 반납해야 하는 외부 자원을 뜻한다.
AutoCloseable 인터페이스
public interface AutoCloseable {
void close() throws Exception;
}
이 기능을 사용하려면 AutoCloseable 인터페이스를 구현해야 한다. 이 인터페이스를 구현하면 Try with resources를 사용할 떄 try 가 끝나는 시점에 close() 가 자동으로 호출된다.
try (Resource resource = new Resource()) {
// 리소스를 사용하는 코드
}
AutoCloseable 구현 예제
public class NetworkClientV5 implements AutoCloseable {
...
public void disconnect() {
System.out.println(address + " 서버 연결 해제");
}
@Override
public void close() {
System.out.println("NetworkClientV5.close");
disconnect();
}
implements 로 AutoCloseable 인터페이스를 구현한다고 명시한다.close() 메서드를 구현한다. 여기서는 disconnect() 메서드가 실행되도록 하였다.사용 예제
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 괄호 안에 사용할 자원을 명시한다.try 블럭이 끝나면 자동으로 AutoCloseable.close() 를 호출해서 자원을 해제한다.try 구문 안에서 예외가 발생하여 catch 구문을 실행하게 될 때 catch 구문보다 AutoCloseable.close() 를 먼저 호출하여 finally 를 사용했을 때보다 더 빠르게 자원을 해제한다.Try with resources 장점
리소스 누수 방지 : 모든 리소스가 제대로 닫히도록 보장한다. 실수로 finally 블록을 적지 않거나, finally 블럭 안에서 자원 해제 코드를 누락하는 문제들을 예방할 수 있다.
코드 간결성 및 가독성 향상 : 명시적인 close() 호출이 필요 없어 코드가 더 간결하고 읽기 쉬워진다.
스코프 범위 한점 : 예를 들어 리소스로 사용되는 client 변수의 스코프가 try 블럭 안으로 한정된다. 따라서 코드 유지보수가 더 쉬워진다.
조금 더 빠른 자원 해제 : 기존에는 try -> catch -> finally 로 catch 이후에 자원을 반납했다. Try with resources 구문은 try 블럭이 끝나면 즉시 close() 를 호출한다.