As exceções são usadas na linguagem Java para assinalar que algo não correu como esperado.
São classes que herdam de java.lang.Exception e
cujos objetos podem ser atirados (throw) e
apanhados (caught).
As exceções que herdam de java.lang.RuntimeException (RTE) são chamadas unchecked exceptions. Neste caso, o compilador não obriga o programador a declarar se apanha ou se atira. Por este motivo, qualquer linha de código pode atirar uma exceção destas. A mais conhecida é a NullPointerException (NPE).
As exceções que herdam de java.lang.Exception são chamadas checked exceptions, no sentido, em que a sua utilização é verificada pelo compilador. Nestes casos é preciso explicitar se se apanha a exceção (catch) ou se se lança (throws). Normalmente, se não se vai tentar recuperar a exceção, pode simplesmente dizer que se atira. É preferível atirar do que fazer um falso tratamento de exceção.
Podemos ter as seguintes abordagens em relação às exceções:
A abordagem 'apanhar-ignorar' é claramente errada porque perde informação e tornam muito mais difícil diagnosticar e resolver problemas.
Os outputs seguintes foram produzidos usando o servidor java-sockets-server e o cliente java-sockets-client e ilustram diversas situações.
As linhas mais importantes dos outputs são as começadas por
"Caused by:".
É aí que se podem encontrar as mensagens das exceções.
Dado que uma exceção pode ter outra exceção aninhada
(cause)
pode ser necessário consultar várias linhas para perceber o que causou a exceção de topo.
As linhas começadas por "at" indicam o contexto de execução. Observando estas linhas é possível ver o conteúdo da pilha de execução do programa, que diz que parte do código estava a chamar que outra parte.
Na situação abaixo, o servidor tenta criar o socket com um porto fora do intervalo [0;65535].
java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:293) at java.lang.Thread.run(Thread.java:745) Caused by: java.lang.IllegalArgumentException: Port value out of range: 65536 at java.net.ServerSocket.<init>(ServerSocket.java:232) at java.net.ServerSocket.<init>(ServerSocket.java:128) at example.SocketServer.main(SocketServer.java:24) ... 6 more
Na situação abaixo, o cliente tenta ligar ao servidor por meio dum porto incorreto e não consegue.
java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:293) at java.lang.Thread.run(Thread.java:745) Caused by: java.net.ConnectException: Connection refused: connect at java.net.DualStackPlainSocketImpl.connect0(Native Method) at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:345) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) at java.net.Socket.connect(Socket.java:589) at java.net.Socket.connect(Socket.java:538) at java.net.Socket.<init>(Socket.java:434) at java.net.Socket.<init>(Socket.java:211) at example.SocketClient.main(SocketClient.java:32) ... 6 more
// ANTI-padrão 'apanhar-ignorar!' // Ignorar a exceção sem dizer nada a ninguém. // Evitar! try { doSomething(); } catch(Exception e) { } ... // ANTI-padrão 'apanhar-imprimir-ignorar!' // Imprimir o stack trace, não resolve nada. // O programa vai "rebentar" mais à frente, onde será mais difícil perceber porquê. // Evitar também! try { doSomething(); } catch(Exception e) { e.printStackTrace(); }
Ignorar a exceção torna muito mais difícil detetar e corrigir erros no código!
Vamos então ilustrar alguns bons exemplos:
// padrão 'deixar-passar' // Se a exceção não vai ser tratada, mais vale lançá-la (throws) public static void main(String[] args) throws Exception { doSomething(); } ... // padrão 'apanhar-imprimir-atirar' // Registar onde foi apanhada a exceção, mas voltar a atirá-la para que seja tratada depois try { doSomething(); } catch(MyException e) { System.err.println("Caught exception when doing something: " + e); System.err.println("Rethrowing"): throw e; } // padrão 'apanhar-embrulhar-atirar' // Apanhar exceção da camada inferior // Envolver com mais contexto (novo tipo, mensagem de erro melhor) // Atirar try { doSomething(); } catch(MyLowerLevelException e) { System.err.println("Caught exception when doing something: " + e); System.err.println("Wrapping and throwing, adding meaningful message") throw new MyHigherLevelException("Failed to do something.", e); }
Em resumo, um bom tratamento de exceções é muito importante em Sistemas Distribuídos.
As melhores estratégias a seguir são:
'deixar-passar', 'apanhar-*-atirar' e 'apanhar-recuperar' quando possível.
© Docentes de Sistemas Distribuídos,
Dep. Eng. Informática,
Técnico Lisboa