• Nenhum resultado encontrado

Os Sete Hábitos das Exceções Altamente Eficazes

N/A
N/A
Protected

Academic year: 2021

Share "Os Sete Hábitos das Exceções Altamente Eficazes"

Copied!
7
0
0

Texto

(1)

Mundo OO

Os Sete Hábitos das Exceções

Altamente Eficazes

Eduardo Guerra

(guerraem@gmail.com):

é desenvolvedor de frameworks, participando de projetos open-source como SwingBean, Esfinge Framework, ClassMock e JColtrane. Atualmente cursa doutorado no ITA, onde também já concluiu graduação em Engenharia da Computação e mestrado. Possui as certificações SCJA, SCJP, SCWCD, SCBCD (1.3 e 5.0), SCJWSD, SCMAD e SCEA e experiência como arquiteto de software nas plataformas Java SE, Java EE e Java ME. Atua também como professor na graduação do ITA e nos cursos de pós-graduação ITA/Stefanini.

O

c o l u n a

A modelagem no tratamento de erros de uma aplicação é uma atividade que muitas vezes é deixada de lado na criação de uma arquitetura. Isso pode trazer diversos problemas que vão desde bugs difíceis de serem encontrados até o vazamento de informações que podem comprometer a segurança. Este artigo traz uma série de boas práticas que ajudam a evitar os erros mais comuns de serem cometidos e auxilia na criação de um bom modelo de exceções.

Aprenda os bons hábitos na modelagem de erros de uma aplicação

mecanismo de processamento de erros é um importante fator que deve ser levado em consideração no momento de se modelar uma arquitetura. Infelizmente, em muitas aplicações, por questões de prazo ou inexperiência dos desenvolvedores, essa parte é deixada de lado. Isso pode levar a resultados catastróficos! O código pode ficar confuso e alguns erros mascarados, tornando-os difíceis, para não dizer impossíveis, de serem rastreados. Em alguns casos, uma má modelagem pode ocasionar em vazamento de informações do sistema que podem comprometer a segurança.

O objetivo deste artigo é apresentar sete hábitos que devem ser seguidos ao se lidar com exceções dentro de uma aplicação. Inicialmente será apresentada uma pequena revisão sobre o funcio-namento das exceções na linguagem Java. Em seguida, serão apresentadas sete boas práticas a serem seguidas pelos desenvolvedores para que o modelo de exceções seja altamente eficaz! Durante a apresentação das práticas, serão mostrados os principais erros que são cometidos ao se lidar com exceções.

Entendendo exceções

Diversas coisas podem dar errado no momento de execução de uma aplicação, e assumir isso no mo-mento da modelagem de um sistema não é ser pessimista, e sim realista. Os erros podem ser devido a questões que estão fora do nosso controle. Por exemplo, qualquer acesso que for feito através de uma rede deve estar preparado para situações em que a rede esteja fora do ar, assim como acessos a um banco de dados devem assumir que a conexão pode não ser criada com sucesso. Esses são fatores normalmente externos à aplicação que está sendo construída.

Existem também erros que ocorrem devido às entradas dos usuários. Por exemplo, o cadastro de um usuário que já existe na base de dados provavelmente irá violar alguma chave única na tabela onde ela é armazenada. Tentativas de entrar em funcionalidades as quais o usuário não deve executar irão ser bloqueadas por um componente de controle de acesso. É importante lembrar que os usuários do siste-ma também estão fora do nosso controle e os erros também podem acontecer através de suas ações. A grande questão é como modelar dentro da aplicação uma forma simples e elegante, de forma a indicar para quem chamou um determinado método que um erro ocorreu na sua execução. Antes da existência

(2)

29 29 29

Funcionamento das exceções

retorno de valores específicos que deviam ser interpretados pela aplicação. Diversas funções acabavam implementando algum tipo de retorno com significado para indicar a existência ou não de erro. Por exemplo, imagine uma função que lê um número de um arquivo e coloca em uma variável. Uma forma de indicar se ocorreu algum erro seria retornar -1 para quando ocorresse um erro e 0 caso contrário.

Esse tipo de estratégia muitas vezes não atende às necessidades da aplica-ção. Vejam algumas desvantagens desse tipo de abordagem:

t PSFUPSOPEFWBMPSFTFTQFDÓmDPTFNDBTPEFFSSPNVJUBTWF[FTÏJHOPSB-do e desconheciPSFUPSOPEFWBMPSFTFTQFDÓmDPTFNDBTPEFFSSPNVJUBTWF[FTÏJHOPSB-do por quem está invonca aquele procedimento; t BWFSJmDBÎÍPEPSFUPSOPEFVNFSSPUPSOBPDØEJHPDPOGVTPFQPVDP

claro;

t DBTPPNÏUPEPPVBGVOÎÍPKÈSFUPSOFNVNWBMPS NVJUBTWF[FTÏJOWJ-ável retornar um valor com significado de erro. Imagine, por exemplo, uma função que retorne um valor booleano;

t PWBMPSSFUPSOBEPOBNBJPSJBEBTWF[FTOÍPUSB[EFUBMIFTTPCSFRVBM erro ocorreu, onde o erro ocorreu e circunstâncias nas quais ocorreu. Felizmente, a linguagem Java possui um mecanismo elegante para o trata-mento de erros: as exceções. Com elas é possível enviar os erros ocorridos durante a execução de um método para quem os invocou, independen-temente do tipo do retorno. Existe também a diretiva “try{...} catch(...){...} finally{...}” que provê uma forma dessas exceções serem tratadas. Na próxima seção, será detalhado como funciona o modelo de exceções da linguagem Java.

A figura 1 apresenta um diagrama explicativo com a hierarquia das exce-ções na linguagem Java. A classe Throwable representa a raiz da hierar-quia de exceções, sendo que todo tipo de objeto que pode ser “jogado” de um método deve de alguma forma ser um subtipo dessa classe. Como subclasse imediata de Throwable, temos a classe Error. As sub-classes de Error representam erros que normalmente são lançados pela máquina virtual, pelo class loader ou outro tipo de código de suporte, ou seja, erros não previstos pelo programador. Normalmente não existem erros específicos da aplicação desse tipo e, por serem erros irrecupe-ráveis, raramente se deve também tentar tratar problemas desse tipo. Exemplos de subclasses de Error são OutOfMemoryError, que é lançada quando a máquina virtual falha na tentativa de uma alocação de

me-chamada de métodos da máquina virtual.

Outra subclasse de Throwable é a classe Exception, que representa situações excepcionais que podem ocorrer durante a execução de um programa. Como subclasse de Exception, destaca-se uma classe especial chamada RuntimeException, cujas subclasses possuem um tratamento diferenciado de outras hierarquias que se derivam diretamente de Ex-ception.

As subclasses de RuntimeException normalmente representam erros da aplicação que não são recuperáveis e por esse motivo o desenvolvedor não é obrigado a tratá-los em um código onde existe a possibilidade de-les serem lançados. Por esse motivo, elas são conhecidas como exceções não-checadas. Exemplos de RuntimeExceptions são: ArithmeticExcep-tion, quando ocorre uma situação excepcional em algum cálculo aritmé-tico, como divisão por zero; ArrayIndexOutOfBoundsException, quando tenta-se acessar um índice de um array que está fora dos seus limites; e o famoso NullPointerException, que indica a tentativa de se invocar um método ou acessar um atributo em uma referência nula.

'JHVSB)JFSBSRVJBEFFYDFÎÜFTOBMJOHVBHFN+BWB Erros recuperáveis

lançados pela aplicação.

Erros não recuperáveis lançados pela aplicação.

Erros não recuperá-veis lançados pela máquina virtual, class

loader e código de suporte. Throwable Error Exception RuntimeException

(3)

.VOEP00t0T4FUF)ÈCJUPTEBT&YDFÎÜFT"MUBNFOUF&mDB[FT

publicclass ServicoUsuario {

public String procuraTrechoComum(String login, String senha){ for(int i=0; i<login.length()-4; i++){

String sub = login.substring(i,i+3); if(senha.contains(sub)){ return sub; } } returnnull; }

publicvoid cadastra(String login, String senha) throws

SenhaFracaException{

String trecho = procuraTrechoComum(login, senha); if(trecho != null){

thrownew SenhaFracaException(trecho); }

//cadastra usuário em base de dados }

publicstaticvoid main(String[] args) { String login = args[0];

String senha = args[1];

ServicoUsuario serv = new ServicoUsuario();

try {

serv.cadastra(login,senha);

System.out.println(“Cadastro com sucesso”); } catch (SenhaFracaException e) {

System.out.println(“Tente uma senha mais forte.”); String sugestao = senha.replace(e.getTrecho(),”@#2”); System.out.println(“Sugestão de nova senha:”+sugestao); } catch(Exception e){

//Trata outros erros } finally{

System.out.println(“Obrigado por usar o nosso cadastro!”); }

} }

Listagem 1. Exemplo de uso do mecanismo de exceções. Outras subclasses de Exception que não tenham RuntimeException

em sua hierarquia representam erros que são lançados e que obrigato-riamente devem ser tratados pela aplicação. Por esse motivo, elas são chamadas de exceções checadas. Uma das opções ao se executar um trecho de código onde pode ser lançada uma exceção em um método é passar o problema para quem o invocou. Isso pode ser feito propagando a exceção e declarando na assinatura do método que ele lança aquele tipo de exceção através da cláusula throws. Como consequência, quem chamar o método deverá também propagá-la ou tratar a exceção. Para uma exceção poder ser tratada, o trecho de código onde existe a possibilidade dela ser lançada deve ser envolvido em um bloco try. Este bloco deve ser sucedido de pelo menos um bloco catch ou finally. Os blocos catch são sempre seguidos de um parâmetro do tipo da exceção que ele trata. Por exemplo, um bloco do tipo catch(SecurityException e) { … } será executado caso seja lançada uma exceção do tipo SecurityEx-ception ou qualquer subclasse da mesma. Diversos blocos catch podem ser criados para tratar diferentes tipos de exceção que podem ocorrer em um mesmo bloco try. Pode haver blocos catch para exceções da mesma hierarquia, porém as mais específicas devem vir primeiro e apenas o primeiro bloco, cuja exceção se encaixar, será executado.

Caso a exceção ocorra, o fluxo de execução é imediatamente transferido para o bloco catch correspondente. Quaisquer comandos do bloco try que venham depois do erro ocorrer não serão executados. Dessa forma, se houver comandos que precisem ser executados em qualquer situação, eles devem ser colocados dentro do bloco finally. A “regra de ouro” é que o bloco finally será SEMPRE executado, tanto se o bloco try for executado normalmente até o final quanto se ele for terminado de forma abrupta, com a ocorrência de uma exceção ou com o retorno de um método. Isso mesmo! Quando o método possuir um comando return em um bloco try, o comando finally mesmo assim será executado. O finally com frequência é utilizado para tarefas de limpeza de recurso, como o fechamento de conexões.

Uma exceção não precisa necessariamente ser lançada por métodos e classes de APIs e a aplicação também pode conter erros que façam sen-tido dentro do seu domínio. Apesar de tudo, uma exceção é uma classe como qualquer outra e pode possuir atributos para guardar informações e comportamentos implementados em métodos. O comando throw é utilizado para lançar uma instância de qualquer classe que herda de Throwable.

A Listagem 1 apresenta o código da classe ServicoUsuario que exempli-fica o uso de exceções. O método cadastra() é um método que cadastra um usuário em uma base de dados, mas que chama o método procu-raTrechoComum() para verificar a força da senha. Esse método verifica se existem três caracteres consecutivos do login que são utilizados na senha. O método cadastra() lança a exceção SenhaFracaException caso o método procuraTrechoComum() retorne um valor diferente de nulo. A exceção SenhaFracaException estende diretamente a classe Exception e por esse motivo o método cadastra() precisa declarar que existe a pos-sibilidade dessa exceção ser lançada. No método main(), onde esse mé-todo é invocado, a exceção é tratada através de um bloco try. O primeiro bloco catch trata a ocorrência de SenhaFracaException, informando ao usuário que a senha é fraca e sugerindo uma nova senha. É importante observar que esse trecho do código utiliza uma informação da classe da exceção, que é o trecho do login que foi encontrado na senha, para

sugerir uma mudança na senha (bem trivial nesse caso, mas que poderia ser algo mais sofisticado).

O outro bloco catch irá tratar quaisquer outras exceções que vierem a ocorrer, inclusive as que herdarem de RuntimeException. Uma observa-ção interessante é que quando uma SenhaFracaException for lançada e o fluxo for redirecionado para o primeiro catch, o segundo não será exe-cutado, apesar dessa exceção também ser uma subclasse de Exception. O bloco finally, por outro lado, será sempre executado, tanto no caso de ocorrer exceções quanto no caso de nenhuma ocorrer.

Quando qualquer classe de exceção é criada, a máquina virtual cria o seu stack trace e armazena dentro dela. Isso é uma informação extremamente valiosa na tentativa de detecção de um erro. O stack trace de uma exceção, que

(4)

normal-31 31 31

publicintsoma(int[] array){ int i = 0; int soma = 0; try { while(true){ soma += array[i]; i++; } } catch (ArrayIndexOutOfBoundsException e) { } return soma; }

publicclass Exemplo {

//Um pesadelo para ser tratado no cliente...

publicvoidmetodo1() throws MinhaException, OutraException, AlgumaOutraException, MaisOutraException {

... }

//Tudo que existe de errado pode acontecer aqui! publicvoidmetodo2() throws Exception{ ...

} }

Listagem 2. Uso indevido do mecanismo de exceções.

Listagem 3. Más práticas na declaração de exceções. mente é impresso no console ou armazenado em um arquivo de log, contém

a pilha de chamada de métodos do ponto de ocorrência do erro. Nessa pilha, estão todos os métodos que foram chamados, com as devidas informações de classe e linha de código (quando essa informação está disponível no byteco-de), desde o método main() ou o início de uma Thread até o ponto onde o erro ocorreu.

)ÈCJUPo6TFFYDFÎÜFTQBSBFSSPT

)ÈCJUPo%FDMBSFFSSPTFN

uma granularidade adequada

Como ficou claro desde o início do artigo, o uso de exceções deve ser feito para tratamento de erros. Infelizmente alguns desenvolvedores subvertem esse uso e utilizam exceções como uma forma de passar informações entre métodos ou para o controle do fluxo de execução.

A Listagem 2, por exemplo, apresenta um uso indevido do mecanismo de exceções. O array que é recebido como parâmetro pelo método soma, é per-corrido dentro de um loop infinito. No momento em que houver uma tentativa de acesso a um índice inválido do mesmo, a exceção ArrayIndexOutOfBoun-dsException será lançada. Isso irá fazer com que o fluxo de execução saia do loop while(true){ … } e seja redirecionado para o bloco catch correspondente.

O uso de exceções para redirecionar a saída de loops ou fazer desvios no fluxo de execução é uma má prática. A primeira desvantagem é que o código fica menos legível e de difícil entendimento. Além disso, esse tipo de construção

Como foi visto na parte introdutória sobre exceções, nos métodos onde é possível que uma exceção seja lançada, com exceção das RuntimeExceptions, é necessário declarar isso na assinatura dos métodos. Da mesma forma que os parâmetros e o retorno de um método devem ser modelados, as exceções que ele lança também são fatores a serem pensados e modelados. Exemplos de problemas que podem acontecer estão representados na Listagem 3. O método1() declara um número muito grande de erros, mostrando que o que pode dar errado naquele método não foi algo planejado. Isso dificulta o cliente do método no tratamento dos erros, obrigando-o a criar diversos blocos catch. Em um sistema bem modelado, os métodos são razoavelmente coesos, o que faz com que os tipos de erro que possam ocorrer façam sentido dentro de um determinado domínio.

Imagine por exemplo que em um método de autenticação as seguintes exceções possam ocorrer: LoginInvalidoException, para o caso do login não existir; SenhaInvalidaException, para o caso da senha daquele login estar er-rada; e TentativasEsgotadasException, no caso de haver três tentativas falhas na última meia hora para aquele login. Apesar de serem erros diferentes e que podem carregar informações diferentes, todos se referem a erros de au-tenticação. Sem esquecer que exceções são classes como quaisquer outras, é possível criar uma superclasse para todos esses erros chamada Autenticaca-oException. O método poderia declarar que lança apenas a AutenticacaoEx-ception e, caso o cliente queira fazer um tratamento diferenciado para uma das subclasses, basta que um bloco catch seja criado antes do tratamento da superclasse.

O objetivo desta seção foi dar uma visão geral sobre o uso de exceções. Espera-se que esse pequeno resumo sirva para os leitores poderem recordar os conceitos sobre o assunto e acompanharem o restante do artigo. As próximas seções irão apresentar sete importantes hábitos para lidar com exceções de uma forma eficiente em uma aplicação.

Voltando ao exemplo da Listagem 3, o método2() apresenta o outro extremo quando o método declara que lança a classe Exception. Isso é uma forma de dizer ao cliente daquele método que todo tipo de erro pode acontecer, o que na grande maioria das vezes não é verdade. Esse tipo de prática normalmen-te faz com que o tratamento dos erros que realmennormalmen-te podem aconnormalmen-tecer não seja adequado.

pode evitar que a máquina virtual realize otimizações no bytecode em tempo de execução resultando em um pior desempenho. Dessa forma, as exceções devem ser utilizadas para aquilo que foram criadas: tratamento de erros!

É importante que uma hierarquia das exceções que podem ocorrer dentro de uma aplicação seja bem modelada, de forma a ser representativa em termos da diversidade de erros e possuir abstrações que permitam uma representação de categorias de erros. Dessa forma, as exceções de método podem ser escolhidas de acordo com sua posição na arquitetura e de uma forma que representem os erros que podem ocorrer em uma granularidade adequada.

(5)

)ÈCJUPo.BOUFOIBP4UBDL5SBDF

Essa prática é uma dica simples, mas que pode evitar terríveis dores de cabeça com erros difíceis de serem encontrados. Como foi dito nas primeiras seções deste artigo, quando uma classe descendente de Throwable é criada, dentro dela é armazenado o stack trace, que possui todos os métodos da pilha de exe-cução desde o método main() ou o início da thread até onde aquele método foi invocado.

O problema acontece quando uma exceção é criada e lançada pela aplicação por causa de outra. Imagine, por exemplo, que uma classe DAO que acessa o banco de dados utilizando JDBC recebe uma SQLException. Essa classe então cria um erro com maior nível de abstração e lança para o método que a invo-cou. Em muitos casos, essa nova exceção não leva as informações necessárias para que ao imprimir seu stack trace em um arquivo de log ou no console, seja possível detectar exatamente qual foi o erro que motivou a criação daquela exceção.

A figura 2 ilustra a propagação correta e incorreta do stack trace da exceção que foi a causa real do erro. A classe Exception possui um construtor que recebe como parâmetro outra exceção. Quando esse parâmetro é utilizado, a

Figura 2. Propagando o stack trace em exceções.

impressão do stack trace é seguida por “caused by” e a mensagem e o stack trace da exceção causadora, e assim por diante. Quando o parâmetro não é utilizado, o stack trace impresso é somente o da exceção criada, tornando difícil a identificação de qual é o erro que realmente aconteceu.

)ÈCJUPo6UJMJ[FFYDFÎÜFTFYJTUFOUFT

Esse hábito também pode ser chamado de “Não reinvente a roda”. A API padrão da linguagem Java provê diversas exceções de uso geral que podem e devem ser utilizadas pela aplicação. A vantagem de fazer isso é que a grande parte dos desenvolvedores já estão acostumados e sabem qual o significado que elas possuem. Abaixo seguem alguns exemplos de exceções da API padrão e exemplos de situações em que poderiam ser utilizadas:

t *MMFHBM"SHVNFOU&YDFQUJPO essa exceção pode ser utilizada em métodos que precisam validar parâmetros quando um valor in-válido for recebido. Por exemplo, um método que espera receber como um valor inteiro um número de 0 a 100 representando um percentual, lançaria essa exceção caso recebesse um valor fora des-ses limites;

t *MMFHBM4UBUF&YDFQUJPO essa exceção pode ser utilizada em classes onde a chamada de alguns métodos é inválida dependendo de seu estado interno. Uma classe que representa uma aeronave em uma simulação lançaria esse erro caso o método pousar() fosse chama-do antes chama-do métochama-do decolar();

t *OEFY0VU0G#PVOET&YDFQUJPOeste erro representa uma situação

na qual foi recebido um índice que está fora do esperado. Normal-mente pode ser utilizado quando o acesso a uma lista ou a um array é encapsulado por um objeto;

t $PODVSSFOU.PEJmDBUJPO&YDFQUJPO esse erro é utilizado quando uma classe realiza uma tarefa que exige que seu estado se mante-nha estático e concorrentemente tenta-se fazer uma modificação no mesmo. Como exemplo, imagine um Singleton que faça cache de uma lista, essa exceção poderia ser lançada quando a lista de objetos fosse percorrida e outra thread tentasse modificá-la de forma concorrente;

t 6OTVQQPSUFE0QFSBUJPO&YDFQUJPO muito utilizada quando uma determinada operação declarada em uma interface ou superclas-se não é implementada pelas suas subclassuperclas-ses.

As exceções citadas servem apenas para ilustrar como as classes exis-tentes na API padrão podem ser utilizadas dentro de outros contextos. A grande lição que se pode tirar desse hábito é que antes de criar diversas classes de exceções, deve-se verificar se já não existe alguma que se encaixe.

(6)

33 33 Muitos desenvolvedores assumem que as únicas informações que devem

estar disponíveis em uma exceção são o stack trace e a mensagem de erro, com o único objetivo de serem impressas no console. Como já foi dito algumas vezes neste artigo, as exceções são classes como quaisquer outras e podem possuir atributos e métodos. Dessa forma, é importante que essas classes de erro possuam informações a respeito das circunstâncias que o erro aconteceu. Na Listagem 1, apresentada no início do artigo, a exceção SenhaFracaException possui um atributo para armazenar a substring que foi encontrada em comum entre o login e a senha. Essa informação, no tratamento do erro, é levada em consideração para identificar qual trecho da senha precisaria ser substituído. Esse tipo de informação auxilia o cliente a fazer um tratamento adequado daquele tipo de erro.

Vamos imaginar a existência de uma exceção chamada AutenticacaoException que representa uma autenticação falha. Informações óbvias sobre esse tipo de erro seriam qual o login e a senha enviados que não conseguiram se auten-ticar. Muitas vezes os desenvolvedores não acrescentam essa informação na

exceção, pois assumem que quem irá tratar o erro está ciente do contexto de chamada do método que lançou a exceção. Isso nem sempre é verdade! Neste caso, o erro poderia ser enviado, por exemplo, para um componente que faz um controle da quantidade de falhas de autenticação por login nos últimos 30 minutos.

Outras informações em relação aos erros em si também são bem valiosas! Uma exceção devido a uma falha de conexão que trouxesse a informação de quan-tas falhas consecutivas já ocorreu, pode ser extremamente útil para o cliente decidir se deve ser feita mais uma tentativa. Trazer esse tipo de informação muitas vezes torna o código do cliente mais simples, encapsulando o controle dela dentro da classe chamada.

Não devemos esquecer das próprias mensagens de erro, que devem ser o mais claras e explicativas possível. Uma alternativa é sobrescrever o método getMessage() na exceção para retornar uma String criada com base em seus atributos. Uma mensagem bem descritiva poupa muitas dores de cabeça ao tentar identificar o que pode ter causado uma determinada exceção.

Ignorar erros é um dos deslizes mais comum em relação a exceções. Esse tipo de coisa ocorre muito em sistemas grandes que já estão funcionan-do em produção. Começa como aquela solução temporária para fazer o código funcionar rapidamente, porém, como a maioria das soluções temporárias acaba se tornando definitiva, isso fica no código. Uma das consequências é que diversos erros que irão acontecer na aplicação irão passar despercebidos, muitas vezes mascarando motivos pelos quais certas funcionalidades não funcionam corretamente.

A Listagem 4 apresenta algumas das formas de ignorar os erros. O exemplo mais clássico é deixar o bloco catch vazio. Dessa forma, o erro que ocorre é completamente ignorado e o programa continua como se nada tivesse acontecido. Isso faz com que muitas vezes a execução siga em frente assumindo que uma tarefa incompleta foi executada. Isso ocasionaria em exceções para acesso a bancos de dados algo como uma tela de inserção em que as informações são enviadas, o sistema diz ter as inserido com sucesso, porém, ao tentar recuperá-las, elas não estão na base de dados. O que aconteceu?

Outro exemplo dessa má prática em métodos que possuem retorno é retornar null ao pegar uma exceção. Além de todos os problemas decorrentes de ignorar uma exceção, este caso ainda pode causar um NullPointerException em quem está invocando aquela função. Por mais que o método que está chamando esteja preparado para receber null em caso de erro, isso seria voltar àquela mesma situação antiga em que os métodos precisavam retornar valores especiais em caso de erro. Alguns desenvolvedores acreditam que apenas logar o erro já é o suficiente para o tratamento de uma exceção. Isso é um grande erro e também uma forma de ignorar que eles aconteceram. A única diferença é que eles ficariam registrados, mas a execução do programa continuaria normalmente podendo causar os mesmos problemas descritos acima.

Com a ocorrência de um erro dentro de uma aplicação, diversas medidas devem ser tomadas, como realizar o rollback em transações, registrar o erro para auditoria, tomar medidas para que um processamento pos-terior não seja executado e até mesmo enviar mensagens ao usuário avisando-o do problema ocorrido. Ignorar ou tentar mascarar esses erros dificulta a manutenção da aplicação e pode ter consequências ainda maiores com a execução do resto do programa. O quadro “Tratamento de Erros no SwingBean” traz uma experiência do autor em relação a esse tipo de problema.

//Não faz nada!

catch (Exception e) { } //Retornar null catch (Exception e) { returnnull; }

//Loga o erro e continua

catch (Exception e) { LOG.error(“Blah”, e); }

Listagem 4. Formas de ignorar erros.

)ÈCJUPo/ÍPJHOPSFFSSPT

(7)

Referências

t +BWB &YDFQUJPO "OUJ1BUUFSOT IUUQXXXSPDLTUBSQSPHSBNNFSPSHQPTU jun/09/java-exception-antipatterns/

t $8&4"/4 501  .PTU %BOHFSPVT 1SPHSBNNJOH &SSPST IUUQXXXTBOTPSH top25errors/

t 5IF.PUIFSPG&YDFQUJPO)BOEMJOH"OUJ1BUUFSOTIUUQEFWMJDJPVTCMPHTCJMMZ@ND-DBõFSUZBSDIJWFFYDFQUJPOIBOEMJOHBOUJQBUUFSOBTQY

t &õFDUJWF+BWB OE&EJUJPO +PTIVB#MPDI

Considerações finais

Agradecimento

Este artigo fez uma pequena revisão sobre o mecanismo de exceções e apresentou diversos bons hábitos que os desenvolvedores devem pos-suir ao modelar e implementar um mecanismo de tratamento de erros em uma aplicação. Junto com os bons hábitos, foram apresentadas as más práticas que são comuns de serem vistas em aplicações em produ-ção.

É importante ressaltar a relevância de um mecanismo de erros em uma arquitetura. Muitos desenvolvedores se preocupam com o “caminho feliz”, em que todo processo da aplicação ocorre da forma prevista, e dei-xando de lado o tratamento de situações excepcionais. Lembre-se que ignorar essas práticas pode ter consequências muito ruins como erros misteriosos difíceis de serem encontrados ou vazamento de informações que podem comprometer a segurança da aplicação. ”Você vai dar chan-ce?”

Este artigo foi baseado numa aula para um curso de Tópicos Avançados em Orientação a Objetos que preparei em conjunto com meu amigo Alessandro Oliveira. Gostaria de deixar um agradecimento registrado a ele, pois o conteúdo deste artigo de certa forma teve a sua contribuição.

)ÈCJUPo4FQBSFOÓWFJTEFBCTUSBÎÍP

Tratamento de erros no SwingBean

O encapsulamento é um importante conceito da orientação a objetos, no qual o cliente de uma classe deve se preocupar apenas com a interface da mesma e não com o seu comportamento interno. Em uma arquitetura que utilize o conceito de camadas, as camadas mais baixas se preocupam com questões como o acesso a recursos e as camadas mais altas encapsu-lam a chamada às camadas mais baixas para formar as regras de negócio. Da mesma forma que quem acessa as camadas mais altas da aplicação não deve ser preocupar com as funcionalidades das camadas mais bai-xas, também não deve haver preocupação com os erros que ocorrem em camadas mais baixas. Por exemplo, uma classe controller de uma aplicação web não deve se preocupar com um erro devido à ocorrência de uma chave duplicada no banco de dados, ou seja, não deve receber uma SQLException.

Da mesma forma que as funcionalidades são encapsuladas, os erros tam-bém devem ser. Isso não quer dizer que um erro que ocorre devido a uma falha no acesso a base de dados não deva causar um erro na camada web. A questão é que o erro deve chegar com o nível de abstração correto para aquela camada. A figura 3 ilustra o encapsulamento de um erro ao passar por diversas camadas.

Quando a arquitetura for projetada, devem ser definidos os erros que podem ser lançados por cada camada. Um erro de mais baixo nível deve ser encapsulado por uma exceção de mais alto nível para poder ser pro-pagado. Não se deve esquecer da propagação também do stack trace conforme mencionado no Hábito 3 deste artigo.

Deixar com que exceções de baixo nível se propaguem para a interface gráfica pode gerar um tremendo problema de segurança ao revelar informações de infraestrutura a possíveis atacantes. Uma mensagem de erro do banco de dados pode revelar qual banco é utilizado e até mesmo sua versão. Este tipo de erro sob o nome “Error Message Information -FBLwDPNQÜFBMJTUBi$8&4"/4501.PTU%BOHFSPVT1SPHSBNNJOH Errors”, onde são listados os 25 erros de programação mais perigosos para a segurança de uma aplicação.

Figura 3. Exemplo do nível de abstração de exceções.

Quando comecei a desenvolver o SwingBean, a ideia era que os formu-lários fossem gerados mesmo se houvesse erros de configuração nos arquivos XML. Foi criado todo um mecanismo para contornar os erros e criar o formulário a qualquer custo. Havia pensado em fazer algo “error friendly” que funcionasse mesmo com erros.

Quando o SwingBean começou a ser utilizado, eu logo percebi que essa decisão não havia sido muito boa. Alguns formulários começaram a não

recuperar e popular certos campos sem que o desenvolvedor soubesse o motivo. Os erros eram mascarados pelo framework, que fazia o que podia para gerar os componentes gráficos, porém a aplicação não fun-cionava direito.

Na versão seguinte, todos os erros de configuração eram lançados como exceções com mensagens bem explicativas e que ajudavam o desenvol-vedor a encontrar facilmente os erros. Essa experiência me ensinou uma lição valiosa: nunca mascarar erros dentro de qualquer componente, deixando para quem o invoca essa decisão.

.VOEP00t0T4FUF)ÈCJUPTEBT&YDFÎÜFT"MUBNFOUF&mDB[FT

SQLException

"Existe a chave da tabela X na tabela Y"

ApplicationException

"Não foi possível submeter sua ordem" DAOException

"Não foi possível inserir no BD" JDBC

DAO Serviço

Referências

Documentos relacionados

62 daquele instrumento internacional”, verifica-se que não restam dúvidas quanto à vinculação do Estado Brasileiro à jurisdição da Corte Interamericana, no que diz respeito a

Em 2000, os dois programas foram fundidos e deram origem ao Instituto de Relações Internacionais (IREL/UnB), com crescente predomínio das abordagens historiográficas

Nessa situação temos claramente a relação de tecnovívio apresentado por Dubatti (2012) operando, visto que nessa experiência ambos os atores tra- çam um diálogo que não se dá

Este estudo apresenta como tema central a análise sobre os processos de inclusão social de jovens e adultos com deficiência, alunos da APAE , assim, percorrendo

Em 1981 esse grupo de enfermeiros elaborou um documento com atribuições do enfermeiro, oficializado pela direção da Secretaria (Porto Alegre, 1981) que definia aos chefes de

O PROGRAMA DE PÓS-GRADUAÇÃO EM ANÁLISE DE BACIAS E FAIXAS MÓVEIS, DA UNIVERSIDADE DO ESTADO DO RIO DE JANEIRO –UERJ, torna público o presente Edital, com normas,

No entanto, maiores lucros com publicidade e um crescimento no uso da plataforma em smartphones e tablets não serão suficientes para o mercado se a maior rede social do mundo

Finally,  we  can  conclude  several  findings  from  our  research.  First,  productivity  is  the  most  important  determinant  for  internationalization  that