'김영한의 실전 자바 - 중급 1편' 강의를 들으면서 복습할만한 내용을 정리하였다.

먼저 이 글을 읽어 자바 예외에 대한 이해를 하자.

9. 예외 처리

9.1 실무 예외 처리 방안

처리할 수 없는 예외

  • 예를 들어서 상대 네트워크 서버에 문제가 발생해서 통신이 불가능하거나, 데이터베이스 서버에 문제가 발생해서 접속이 안되면, 애플리케이션에서 연결 오류, 데이터베이스 접속 실패와 같은 예외가 발생한다.

  • 이렇게 시스템 오류 때문에 발생한 예외들은 대부분 예외를 잡아도 해결할 수 있는 것이 거의 없다. 예외를 잡아서 다시 호출을 시도해도 같은 오류가 반복될 뿐이다.

  • 이런 경우 고객에게는 "현재 시스템에 문제가 있습니다."라는 오류 메시지를 보여주고, 만약 웹이라면 오류 페이지를 보여주면 된다. 그리고 내부 개발자가 문제 상황을 빠르게 인지할 수 있도록, 오류에 대한 로그를 남겨두어야 한다.

체크 예외의 부담

체크 예외는 개발자가 실수로 놓칠 수 있는 예외들을 컴파일러가 체크해주기 때문에 오래전부터 많이 사용되었다. 그런데 처리할 수 없는 예외가 많아지고, 또 프로그램이 점점 복잡해지면서 체크 예외를 사용하는 것이 점점 더 부담스러워졌다.

9.2 런타임 예외 사용

체크 예외의 이런 문제점 때문에 최근 라이브러리들은 대부분 런타임 예외를 기본으로 제공한다. 런타임 예외도 필요하면 잡을 수 있기 때문에 필요한 경우에는 잡아서 처리하고, 그렇지 않으면 자연스럽게 던지도록 둔다. 그리고 처리할 수 없는 예외는 예외를 공통으로 처리하는 부분을 만들어서 해결하면 된다.

9.3 공통 예외 처리

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 이라는 표준 오류에 결과를 출력한다.
    • IDE에서는 System.err 로 출력하면 출력 결과를 빨간색으로 보여준다.
    • 일반적으로 이 방법을 사용한다.

System.out, System.err 둘 다 결국 콘솔에 출력되지만, 서로 다른 흐름을 통해서 출력된다. 따라서 둘을 함께 사용하면 출력 순서를 보장하지 않는다. 출력 순서가 꼬여서 보일 수 있다.

실무에서는 System.out 이나 System.err 을 통해 콘솔에 무언가를 출력하기 보다는, 주로 Slf4J, logback 같은 별도의 로그 라이브러리를 사용해서 콘솔과 특정 파일에 함께 결과를 출력한다. 그런데 e.printStackTrace() 를 직접 호출하면 결과가 콘솔에만 출력된다. 이렇게 되면 서버에서 로그를 확인하기 어렵다. 서버에서는 파일로 로그를 확인해야 한다. 따라서 콘솔에 바로 결과를 출력하는 e.printStackTrace() 는 잘 사용하지 않는다. 대신에 로그 라이브러리를 통해서 예외 스택 트레이스를 출력한다.

9.4 try-with-resources

애플리케이션에서 외부 자원을 사용하는 경우 반드시 외부 자원을 해제해야 한다. 따라서 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();
    }
    
  • implementsAutoCloseable 인터페이스를 구현한다고 명시한다.
  • 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 with resources 구문은 try 괄호 안에 사용할 자원을 명시한다.
  • 이 자원은 try 블럭이 끝나면 자동으로 AutoCloseable.close() 를 호출해서 자원을 해제한다.
  • try 구문 안에서 예외가 발생하여 catch 구문을 실행하게 될 때 catch 구문보다 AutoCloseable.close() 를 먼저 호출하여 finally 를 사용했을 때보다 더 빠르게 자원을 해제한다.

Try with resources 장점

  • 리소스 누수 방지 : 모든 리소스가 제대로 닫히도록 보장한다. 실수로 finally 블록을 적지 않거나, finally 블럭 안에서 자원 해제 코드를 누락하는 문제들을 예방할 수 있다.

  • 코드 간결성 및 가독성 향상 : 명시적인 close() 호출이 필요 없어 코드가 더 간결하고 읽기 쉬워진다.

  • 스코프 범위 한점 : 예를 들어 리소스로 사용되는 client 변수의 스코프가 try 블럭 안으로 한정된다. 따라서 코드 유지보수가 더 쉬워진다.

  • 조금 더 빠른 자원 해제 : 기존에는 try -> catch -> finallycatch 이후에 자원을 반납했다. Try with resources 구문은 try 블럭이 끝나면 즉시 close() 를 호출한다.

profile
가오리의 개발 이야기

0개의 댓글