Java Mid 11 - Exception 2
in Programming Language on Java
예외처리를 더 세밀하게 하자
예외를 단순히 오류 코드로 분리하는 것이 아닌 다양하게 계층화 시켜서 예외를 처리할 수 있다.
NetworkClientExceptionV3:NetworkClient에서 발생하는 모든 예외는 이 예외의 자식이다.ConnectExceptionV3: 연결 실패시 발생하는 예외이다. 내부에 연결을 시도한address를 보관한다.SendExceptionV3: 전송 실패시 발생하는 예외이다. 내부에 전송을 시도한 데이터인sendData를 보관한다.
예외를 디테일하게 패키지를 만들어서 예외만 따로 관리한다.
package exception.ex3.exception;
public class NetworkClientExceptionV3 extends Exception {
public NetworkClientExceptionV3(String message) {
super(message);
}
}
package exception.ex3.exception;
public class ConnectExceptionV3 extends NetworkClientExceptionV3 {
private final String address;
public ConnectExceptionV3(String address, String message) {
super(message);
this.address = address;
}
public String getAddress() {
return address;
}
}
package exception.ex3.exception;
public class SendExceptionV3 extends NetworkClientExceptionV3 {
private final String sendData;
public SendExceptionV3(String sendData, String message) {
super(message);
this.sendData = sendData;
}
public String getSendData() {
return sendData;
}
}
필요에 맞는 예외 잡기
public class NetworkServiceV3_1 {
public void sendMessage(String data) {
String address = "http://example.com";
NetworkClientV3 client = new NetworkClientV3(address);
client.initError(data); //추가
try {
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();
}
}
}
- 예외 클래스를 각각의 예외 상황에 맞추어 만들면, 각 필요에 맞는 예외를 잡아서 처리할 수 있다.
- 예를 들면
e.getAddress(),e.getSendData()와 같이 각각의 예외 클래스가 가지는 고유의 기능을 활용 할 수 있다. catch (ConnectExceptionV3 e): 연결 예외를 잡고, 해당 예외가 제공하는 기능을 사용해서 정보를 출력 한다.catch (SendExceptionV3 e): 전송 예외를 잡고, 해당 예외가 제공하는 기능을 사용해서 정보를 출력한 다.
실무에서는 예외를 어떻게 잡을까?
처리할 수 없는 예외
예를 들어서 상대 네트워크 서버에 문제가 발생해서 통신이 불가능하거나, 데이터베이스 서버에 문제가 발생해서 접속이 안되면, 애플리케이션에서 연결 오류, 데이터베이스 접속 실패와 같은 예외가 발생한다. 이렇게 시스템 오류 때문에 발생한 예외들은 대부분 예외를 잡아도 해결할 수 있는 것이 거의 없다. 예외를 잡아서 다시 호출을 시도해도 같은 오류가 반복될 뿐이다. 이런 경우 고객에게는 “현재 시스템에 문제가 있습니다.”라는 오류 메시지를 보여주고, 만약 웹이라면 오류 페이지를 보여주면 된다. 그리고 내부 개발자가 문제 상황을 빠르게 인지할 수 있도록, 오류에 대한 로그를 남겨두어야 한다.
그럼 어떻게 보통 처리하는 것이 좋을까
실무에서는 System.out 이나 System.err 을 통해 콘솔에 무언가를 출력하기 보다는, 주로 Slf4J, logback 같은 별도의 로그 라이브러리를 사용해서 콘솔과 특정 파일에 함께 결과를 출력한다. 그런데 e.printStackTrace() 를 직접 호출하면 결과가 콘솔에만 출력된다. 이렇게 되면 서버에서 로그를 확인하기 어렵다. 서버에서는 파일로 로그를 확인해야 한다. 따라서 콘솔에 바로 결과를 출력하는 e.printStackTrace() 는 잘 사용하지 않는다. 대신에 로그 라이브러리를 통해서 예외 스택 트레이스를 출력 한다. 지금은 로그 라이브러리라는 것이 있다는 정도만 알아두자. 학습 단계에서는 e.printStackTrace() 를 적극 사용해도 괜찮다.
처음 자바를 설계할 당시에는 체크 예외가 더 나은 선택이라 생각했다. 그래서 자바가 기본으로 제공하는 기능들에는 체 크 예외가 많다. 그런데 시간이 흐르면서 복구 할 수 없는 예외가 너무 많아졌다. 특히 라이브러리를 점점 더 많이 사용 하면서 처리해야 하는 예외도 더 늘어났다. 라이브러리들이 제공하는 체크 예외를 처리할 수 없을 때마다 throws 에 예외를 덕지덕지 붙어야 했다. 그래서 개발자들은 throws Exception 이라는 극단적?인 방법도 자주 사용하게 되었 다. 물론 이 방법은 사용하면 안된다. 모든 예외를 던진다고 선언하는 것인데, 결과적으로 어떤 예외를 잡고 어떤 예외를 던지는지 알 수 없기 때문이다. 체크 예외를 사용한다면 잡을 건 잡고 던질 예외는 명확하게 던지도록 선언해야 한다. 체크 예외의 이런 문제점 때문에 최근 라이브러리들은 대부분 런타임 예외를 기본으로 제공한다. 가장 유명한 스프링이 나 JPA 같은 기술들도 대부분 언체크(런타임) 예외를 사용한다. 런타임 예외도 필요하면 잡을 수 있기 때문에 필요한 경우에는 잡아서 처리하고, 그렇지 않으면 자연스럽게 던지도록 둔 다. 그리고 처리할 수 없는 예외는 예외를 공통으로 처리하는 부분을 만들어서 해결하면 된다.
체크예외는 부담
체크 예외는 개발자가 실수로 놓칠 수 있는 예외들을 컴파일러가 체크해주기 때문에 오래전부터 많이 사용되었다. 그런 데 앞서 설명한 것 처럼 서비스가 점차 커지면서 처리할 수 없는 예외가 많아지고, 또 프로그램이 점점 복잡해지면서 체크 예외를 사용하는 것이 점점 더 부담스러워졌다.
- 모든 경우의 수를 생각하기 힘들기 때문이라고 생각한다.
Reference
인프런 김영한님의 자바강의 + 나의 뇌 + 나의 눈 + 나의 코