반응형

자바(Java)에서 Exception을 잘 다루면 여러 상황에 대한 대응력과 유연성을 높일 수 있지만, 예외처리에 대한 일관된 기준이나 잘 정리된 설계가 없으면 어떻게 처리(handling)할 지 막막해지기도 한다. 그래도 코딩하는 과정에서, 내가 만든 특정한 클래스를 참조해서 쓰는 다른 (작성자가 나일 수도 있고, 다른 개발자일 수도 있는) 클래스에게 이 예외상황만큼은 반드시 명시적으로 인지하고 특별하게 처리하도록 하고 싶으면 메쏘드 제목에서 throws 구문으로 예외를 caller에게 전달해야 한다.


그런데 특정한 예외를 caller에게 전달하려고 throws로 예외를 명시했는데도 caller 쪽에서 그 예외를 catch하지 못하는 경우가 있는데, 알고 보니 throw 하는 예외가 상속 관계에 의해서 부모 예외 타입으로 다른 곳에서 먼저 catch되는 바람에 caller 쪽에서 처리하지 못했던 것이었다.



구체적인 상황은 다음과 같다:


UDP Datagram Socket을 이용한 메세지 송수신 역할을 담당하는 클래스가 있는데, 여기에 타임아웃(timeout) 기능을 추가해서 특정한 메세지 전송에 대해서 응답을 받기 위해서 UDP Socket을 열되, 일정 시간 동안만 기다리도록 만들 필요가 생겼다. 찾아보니 Socket 클래스의 setSoTimeout 메쏘드를 시간값 파라미터와 함께 호출하면 해당 시간이 지난 뒤에 자동으로 SocketTimeoutException을 발생시킨다는 사실을 확인했다. 


그래서 아래와 같이 sendMessageAndGetReply(String ip, int port, String msg, int timeout) 메쏘드를 정의하고 throws SocketTimeoutException을 추가로 적어 주었는데, 그럼에도 불구하고 caller 쪽에서는 SocketTimeoutException을 catch할 수가 없었다.



public static String sendMessageAndGetReply(String ip, int port, String msg, int timeout) throws SocketTimeoutException {

String reply = null;

byte[] sendData = new byte[1024];

byte[] receiveData = new byte[1024];

try {

DatagramSocket clientSocket = new DatagramSocket();

sendData = msg.getBytes();

DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, InetAddress.getByName(ip), port);

clientSocket.send(sendPacket);

DatagramPacket recvPacket = new DatagramPacket(receiveData, receiveData.length);

clientSocket.setSoTimeout(timeout); // 추가로 타임아웃을 명시적으로 설정한 부분

clientSocket.receive(recvPacket);

reply = new String(recvPacket.getData());

clientSocket.close();

} catch (UnknownHostException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

return reply;

}



그런데 신기하게도 실행 화면에서는 SocketTimeoutException이 발생했다고 출력은 되는데, 정작 caller에서의 catch 구문은 실행되지 않았다.

코드의 다른 곳에서 해당 예외를 이미 처리했다는 의미인데, 알고 보니 sendMessageAndGetReply 메쏘드에서 catch (IOException e) 부분이 원인이었다. SocketTimeoutException은 java.io.InterruptedIOException을 extend한 자식 클래스이고, InterruptedIOException은 또한 IOException을 extend하고 있기 때문에, sendMessageAndGetReply가 해당 예외를 이미 처리해 버렸기 때문에, 제아무리 메쏘드 이름 옆에 throws로 명시해 두어도 throw가 되지 않았던 것이었다.


이것을 해결하기 위해서 결국 코드를 아래와 같이 고쳐야 했다. [2]


public static String sendMessageAndGetReply(String ip, int port, String msg, int timeout) throws SocketTimeoutException {

String reply = null;

byte[] sendData = new byte[1024];

byte[] receiveData = new byte[1024];

try {

DatagramSocket clientSocket = new DatagramSocket();

sendData = msg.getBytes();

DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, InetAddress.getByName(ip), port);

clientSocket.send(sendPacket);

DatagramPacket recvPacket = new DatagramPacket(receiveData, receiveData.length);

clientSocket.setSoTimeout(timeout); // 추가로 타임아웃을 명시적으로 설정한 부분

clientSocket.receive(recvPacket);

reply = new String(recvPacket.getData());

clientSocket.close();

} catch (UnknownHostException e) {

e.printStackTrace();

} catch (IOException e) {

if(e instanceof SocketTimeoutException){

// 자식 클래스 예외를 특별하게 처리

throw new SocketTimeoutException();

}

e.printStackTrace();

}

return reply;

}


이렇게 했더니 콜 스택이 짧아지긴 했지만, 그래도 caller 쪽으로 Socket TimeoutException만큼은 확실하게 넘어가는 것을 확인할 수 있었다.


결론적으로, 당연한 얘기지만 예외를 처리할 때, throw하려는 예외가 혹시 해당 위치에 이미 존재하는 다른 catch 구문에 의해서 같이 처리되지는 않는지 상속 관계를 바탕으로 꼼꼼하게 확인할 필요가 있다.




<참고자료>

[1] http://stackoverflow.com/questions/27797451/i-cant-catch-sockettimeoutexception

[2] http://stackoverflow.com/questions/20532855/why-cant-i-handle-exception-e-with-try-catch-clause



반응형
블로그 이미지

Bryan_

,