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