Labs SD > Ferramentas

Exceções

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.


Exceções impressas na consola

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

Código de exceções

O seguinte código mostra maus exemplos de tratamento de exceções.

    // 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