Técnico

Lidando com exceções

 

Recentemente reli o livro Código Limpo – Robert Cecil Martin, e um assunto me chamou atenção: – Boas práticas com exceções! 

Não sei você leitor, mas quando comecei a programar há alguns anos atrás, tive dificuldade com exceções. Não no âmbito de funcionamento, mas sim em saber quais as melhores práticas, assunto o qual abordarei logo abaixo com base no livro citado acima:

O que é interessante sobre exceções é que elas definem um escopo dentro de sua aplicação. Quando executamos uma instrução no try, assumimos que pode ser cancelada a qualquer momento e, então, devemos continuar no catch de forma que estabilize a aplicação após um comportamento inesperado.

Ok. Até ai tudo numa boa. Mas o que usar? Exceções verificadas (Exception) ou não verificadas (RuntimeException)?

Exceções verificadas violam o princípio do Aberto-Fechado. Como assim?

Considere uma hierarquia de N chamadas e digamos que um método de nível mais baixo fosse refatorado para lançar uma exceção verificada, a assinatura do método deverá adicionar uma instrução throws. Ai que está, cada método chamador do nosso método refatorado deverá ser alterado para capturar a nova exceção ou anexar uma nova instrução trows. Neste ponto fica claro a quebra de encapsulamento, pois funções no caminho de um lançamento (throw) devem enxergar os detalhes daquela implementação de nível mais baixo. Podemos ver que uma simples alteração é propagada por todo o sistema e o resultado é uma cascata de alterações.

CTA-ebook-transformação-digital

Exceções verificadas podem ser úteis se você está desenvolvendo uma biblioteca crítica, mas em geral os custos da depência superam as vantagens.

Forneça exceções com contexto

Suas exceções devem fornece o contexto, ou seja, a identificação do erro. Crie mensagens de erro informativa e passe junto à exceção.

Há algumas formas de identificar erros: Pela origem – vieram desse ou daquele componente? – ou  pelo Tipo  – são falhas de dispositivos, de redes, ou erro de programação? –

Defina as classes de exceções conforme as necessidades do chamador. Ex:

ConnectionPort port = new ConnectionPort();
try {
  port.open();
} catch (DeviceResponseException e) {
  reportPortError(e);
  logger.log("Device response exception", e);
} catch (ATM1212UnlockedException e) {
  reportPortError(e);
  logger.log("Unlock exception", e);
} catch (GMXError e) {
  reportPortError(e);
  logger.log("Device response exception", e);
} finally {
  ...
}

O que fazemos é padrão, registramos o erro e nos certificamos que podemos prosseguir. Há muita duplicação de código e a tarefa é a mesma independente da exceção, logo podemos simplificar nosso código consideravelmente:

LocalPort port = new LocalPort(8081);
		
try {
  port.open();
} catch (PortDeviceFailure e) {
  reportError(e)
  logger.log(e.getMessage(), e);
} finally {
  ...
}

Pegamos a API e encapsulamos em uma classe LocalPort que é um wrapper (“empacotador”), que usamos para capturar e traduzir as exceções lançadas por ConnectionPort:

public class LocalPort {
  private ConnectionPort innerPort;
	
  public LocalPort(int portNumber) {
    this.innerPort = new ConnectionPort(portNumber);
  }
	
  public void open() {
    try {
      port.open();
    } catch (DeviceResponseException e) {
      new PortDeviceFalure(e);
    } catch (ATM1212UnlockedException e) {
      new PortDeviceFalure(e);
    } catch (GMXError e) {
      new PortDeviceFalure(e);
    } finally {
       ...
    }
  }
}

Se seguirmos as dicas passadas acima, acabaremos com um código bem dividido entre tratamento de erro e lógica de negócio, o que ajuda a tornar o algoritimo limpo. Empacote suas APIs, lance suas próprias exceções e defina um controlador capaz de lidar com qualquer processamento abortado. Na grande maioria, esta é uma ótima abordagem, mas há situações nas quais talvez você não queira lidar com as exeções.

E este será assunto para um próximo post relacionado a exceções.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Compartilhe isso: