Back-End

21 jun, 2016

Código limpo e a arte do tratamento de exceção – Parte 02

Publicidade

Na primeira parte, vimos como manipular exceções é uma coisa boa e como nunca resgatar uma exceção, entre outros assuntos. Agora, veremos que nem toda exceção precisa ser tratada, além de outros tópicos.

***

Não é necessário lidar com todas as exceções

Ao desenvolver uma gem ou uma biblioteca, muitos desenvolvedores tentarão encapsular a funcionalidade e não permitirem qualquer exceção de propagar para fora da biblioteca. Mas, às vezes, não é óbvio como lidar com uma exceção até que o aplicativo específico seja executado.

Vamos pegar ActiveRecord como um exemplo da solução ideal. A biblioteca fornece aos desenvolvedores duas abordagens para completude. O método save lida com exceções, sem propagá-las, simplesmente retornando false, enquanto save! gera uma exceção quando ele falha. Isso dá aos desenvolvedores a opção de lidar com casos de erro específicos de forma diferente, ou simplesmente lidar com qualquer falha de uma forma geral.

Mas e se você não tem tempo ou recursos para fornecer essa implementação completa? Nesse caso, se houver alguma incerteza, é melhor expor a exceção, e liberá-la para a vida selvagem.

exceção-4

Aqui está o porquê: estamos trabalhando com a movimentação dos requisitos quase o tempo todo, e tomar a decisão de que uma exceção que será sempre tratada de forma específica pode realmente prejudicar a nossa implementação, danificando a extensibility e a maintainability, e potencialmente acrescentando enorme dívida técnica, especialmente ao desenvolver bibliotecas.

Tomemos o exemplo anterior de uma busca de preços de ações consumindo uma API. Escolhemos lidar com a resposta incompleta e mal formatada no local, e escolhemos repetir o mesmo pedido novamente até termos uma resposta válida. Mais tarde, os requisitos podem mudar, de modo que temos de olhar novamente para os dados históricos de estoque salvos, em vez de repetir o pedido.

Nesse ponto, vamos ser forçados a mudar a própria biblioteca, atualizando como essa exceção é tratada, porque os projetos dependentes não vão lidar com essa exceção (como eles poderiam? Ela nunca foi exposta a eles antes). Também teremos que informar os proprietários dos projetos que dependem da nossa biblioteca. Isso pode se tornar um pesadelo se houver muitos desses projetos, uma vez que são susceptíveis de terem sido construídos sobre o pressuposto de que esse erro será tratado de uma maneira específica.

Agora, podemos ver aonde estamos indo com a gestão de dependências. A perspectiva não é boa. Essa situação acontece com bastante frequência e, mais frequentemente do que não, degrada a utilidade, a extensibilidade e a flexibilidade da biblioteca.

Então aqui está a conclusão: se não está claro como uma exceção deve ser tratada, deixe-a propagar graciosamente. Há muitos casos em que existe um lugar claro para lidar com a exceção internamente, mas há muitos outros casos em que a exposição da exceção é melhor. Portanto, antes de optar por lidar com a exceção, apenas pense mais uma vez. Uma boa regra de ouro é somente insistir em lidar com exceções quando você está interagindo diretamente com o usuário final.

Siga a convenção

A implementação do Ruby e, mais ainda, Rails, segue algumas convenções de nomenclatura, como distinguir entre method_names e method_names! com um “bang”. Em Ruby, o bang indica que o método irá alterar o objeto que o invocou; em Rails, isso significa que o método irá gerar uma exceção se não conseguir executar o comportamento esperado. Tente respeitar a mesma convenção, especialmente se você estiver para abrir o código da sua biblioteca.

Se fôssemos escrever um novo method! com um bang em uma aplicação Rails, deveríamos levar essas convenções em conta. Não há nada nos obrigando a gerar uma exceção quando esse método falhar, mas, por se desviar da convenção, esse método pode enganar programadores a acreditar que será dada a oportunidade de lidar eles mesmos com as exceções, quando, na verdade, eles não irão.

Outra convenção Ruby, atribuída a Jim Weirich, é usar fail para indicar falha de método, e só usar raise se você estiver levantando novamente a exceção.

Um aparte, porque eu uso exceções para indicar falhas, eu quase sempre uso a palavra-chave “fail” em vez de “raise” em Ruby. Fail e raise são sinônimos para que não haja diferença, exceto que “fail” comunica mais claramente que o método falhou. A única vez que eu uso “raise” é quando eu estou pegando uma exceção e elevando-a novamente, porque aqui eu não estou falhando, mas de forma explícita e propositadamente levantando uma exceção. Essa é uma questão estilística que eu sigo, mas eu duvido que muitas outras pessoas o façam.

Muitas outras comunidades de linguagens adotaram convenções como essas em torno de como as exceções são tratadas, e ignorar essas convenções vai prejudicar a legibilidade e a facilidade de manutenção do nosso código.

Logger.log(everything)

Esta prática não só se aplica a exceções, é claro, mas se há uma coisa que deve ser sempre conectada é uma exceção.

Logging é extremamente importante (importante o suficiente para Ruby enviar um logger com a sua versão padrão). É o diário de nossas aplicações e, ainda mais importante do que manter um registro de como nossas aplicações tem sucesso, é registrar como e quando eles falham.

Não há falta de bibliotecas de registro ou serviços baseados em log e padrões de projeto. É fundamental manter o controle de nossas exceções para que possamos analisar o que aconteceu e investigar se algo não parece certo. Mensagens de log adequadas podem apontar os desenvolvedores diretamente para a causa de um problema, poupando um tempo imensurável.

A confiança no código limpo

exceção-5

Manipulação de exceção limpa irá enviar seu código de qualidade para a lua!

Exceções são uma parte fundamental de qualquer linguagem de programação. Elas são especiais e extremamente poderosas, e devemos alavancar seu poder de elevar a qualidade do nosso código em vez de nos esgotar lutando contra elas.

Neste artigo, nós mergulhamos em algumas boas práticas para a estruturação de nossas árvores de exceção e como ela pode ser benéfica para facilitar a leitura e a qualidade para estruturá-las logicamente. Nós visitamos diferentes abordagens para o tratamento de exceções, em um lugar ou em vários níveis.

Vimos que é ruim o “catch ‘em all”, e que está ok deixá-las flutuando ao redor e borbulhando.

Vimos onde lidar com exceções de forma DRY, e aprendemos que não somos obrigados a lidar com elas quando ou onde elas surgirem pela primeira vez.

Discutimos quando exatamente é uma boa ideia lidar com elas, quando é uma má ideia, e por que, em caso de dúvida, é uma boa ideia deixá-las se propagarem.

Finalmente, discutimos outros pontos que podem ajudar a maximizar a utilidade das exceções, como seguir convenções e registrar tudo.

Com essas orientações básicas, podemos nos sentir muito mais confortáveis e confiantes ao lidar com casos de erro em nosso código, e tornar as nossas exceções verdadeiramente excepcionais!

Um agradecimento especial ao Avdi Grimm e à sua palestra impressionante Exceptional Ruby, que ajudou muito na elaboração deste artigo.

***

Ahmed Razzak faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: https://www.toptal.com/qa/clean-code-and-the-art-of-exception-handling