Clean Code (Código limpo) – Manipulação de erros

As coisas podem dar errado e, quando acontecem, nós, como programadores, somos responsáveis por garantir que nosso código faça o que precisa ser feito.

O tratamento de erros é importante, mas se obscurece a lógica, está errado.

Use exceções em vez de códigos de retorno

No passado distante, havia muitas linguagens de programação que não tinham exceções. Nessas linguagens, as técnicas de tratamento e relatório de erros eram limitadas. Você poderia definir uma flag de erro ou retornar um código de erro que o chamador pode verificar.

Hoje, as linguagens têm uma maneira limpa de lidar com erros chamados de exceções. É sempre melhor lançar uma exceção ao encontrar um erro, do que fazer como antigamente. Quando se usa exceções, o código de chamada é mais limpo e sua lógica não é obscurecida pelo tratamento dos erros.

Escreva primeiro sua declaração de try-catch-finally

Quando você executa o código na parte try de uma instrução try-catch-finally, está declarando que a execução pode abortar a qualquer ponto e, então, retomar no catch.

De certa forma, os blocos try são como transações. Sua captura tem que deixar seu programa em um estado consistente, não importa o que aconteça na tentativa de execução. Por esse motivo, é uma boa prática começar com uma instrução try-catch-finally quando estiver escrevendo um código que pode lançar exceções. Isso ajuda a definir o que o usuário desse código deve esperar, não importa o que esteja errado com o código que é executado.

Por exemplo, se estiver fazendo um código que acessa um arquivo e faz a leitura, inicialmente seu bloco try-catch já pode esperar uma exceção FileNotFoundException quando o arquivo não for encontrado. Assim que o tratamento do catch for realizado, então devemos seguir o fluxo de sucesso da aplicação, montando a lógica dentro do try.

Forneça contexto com exceções

Cada exceção lançada deve fornecer contexto suficiente para determinar a origem e a localização de um erro.

Crie mensagens de erro informativas e transmita-as com suas exceções. Mencione a operação que falhou e o tipo de falha. Se você estiver fazendo login em seu aplicativo, passe informações suficientes para poder registrar o erro em sua captura.

Definir classes de exceção baseadas das necessidades de um chamador

Existem muitas maneiras de classificar os erros. Podemos classificá-los por sua fonte: eles vieram de um componente ou de outro? Ou o tipo deles: são falhas de dispositivos, falhas de rede ou erros de programação? No entanto, quando definimos classes de exceção em um aplicativo, nossa preocupação mais importante deve ser como elas são capturadas.

Para isso, os Wrappers podem ser muito úteis. Na verdade, agrupar APIs de terceiros é uma prática recomendada. Ao preparar uma API de terceiros para utilização em seu código, você pode minimizar as suas dependências: você pode escolher mover para uma biblioteca diferente no futuro sem muitas penalidades. Wrappers também torna mais fácil simular chamadas de terceiros quando você está testando seu próprio código.

Vamos analisar o codigo a seguir

            try
            {
                var llclass = new LLClass();
                llclass.OpenConection();
            }
            catch (FormatException e)
            {
                ExceptionHandlerMethod(e);
            }
            catch (OverflowException e)
            {
                ExceptionHandlerMethod(e);
            }
            catch (ArgumentException e)
            {
                ExceptionHandlerMethod(e);
            }

Agora vamos criar um Wrappers e ver como este mesmo codigo ficaria usando os conceitos de clean code.

A chamada fica mais simples e facil de entender.

            try
            {
                var llclass = new MyConnection();
                llclass.Open();
            }
            catch (MyCustomException e)
            {
                ExceptionHandlerMethod(e);
            }

Todas as exceções estão encapsuladas nessa nova classe que lida com todas e retorna uma conhecida para a aplicação.

    class MyConnection
    {
        private LLClass llclass;
        public MyConnection()
        {
            llclass = new LLClass();
        }
        public void Open()
        {
            try
            {
                var llclass = new LLClass();
                llclass.OpenConection();
            }
            catch (FormatException e)
            {
                throw new MyCustomException(e);
            }
            catch (OverflowException e)
            {
                throw new MyCustomException(e);
            }
            catch (ArgumentException e)
            {
                throw new MyCustomException(e);
            }
        }
    }

    public class MyCustomException : Exception
    {
        private ArgumentException e;

        public MyCustomException(ArgumentException e)
        {
            this.e = e;
        }
    }

Defina o fluxo normal

Às vezes, você pode não querer abortar quando ocorrer uma exceção. Digamos que seu aplicativo, em caso de exceção, tenha que realizar alguma ação. Por exemplo, em caso de sucesso, adiciona um valor a um total e, no caso de uma exceção, chama outra função e remove-a do total.

Podemos resolver isso criando uma classe ou configurar um objeto para que ele trate este comportamento de uma maneira especial. Quando você faz isso, o código não precisa lidar com um comportamento excepcional. Esse comportamento é encapsulado no objeto de caso especial. Isso é chamado de Special Case Patter [Fowler], uma subclasse que fornece comportamento especial para casos particulares.

Não Retorne null

Quando retornamos null, estamos essencialmente criando trabalho para nós mesmos e infligindo problemas aos nossos chamadores. Basta uma verificação nula ausente para termos uma aplicação agindo fora de controle.

É fácil dizer que podemos adicionar validação de nulos em nossas aplicações, mas o problema é que ele vai ter tem muitas dessas validações. Se você ficar tentado a retornar null de um método, considere lançar uma exceção ou retornar um objeto Special Case Patter em vez disso. Se você estiver chamando um método de retorno nulo de uma API de terceiros, considere encapsular esse método com um método que emita uma exceção ou retorna um objeto tipo Special Case Patter.

Não Passe null

Retornar null de métodos é ruim, mas passar null para métodos é pior. A menos que você esteja trabalhando com uma API que espera que você passe null, você deve evitar em seu código sempre que possível.

Na maioria das linguagens de programação, não há uma boa maneira de lidar com um null que é passado acidentalmente por um chamador. Por ser esse o caso, a abordagem racional é proibir a passagem de null por padrão. Ao fazer isso, você pode codificar sabendo que um null em uma lista de argumentos é uma indicação de um problema e acabar com muito menos erros por descuido e esquecimento.

Conclusão

O código limpo é legível, mas também deve ser robusto. Esses não são objetivos conflitantes. Podemos escrever um código limpo e robusto se virmos o tratamento de erros como uma preocupação separada, algo que pode ser visualizado independentemente de nossa lógica principal. Na medida em que somos capazes de fazer isso, podemos raciocinar sobre isso de forma independente e podemos fazer grandes avanços na sustentabilidade do nosso código.

Qualquer dúvida ou dicas, entre em contato: leandrolt@gmail.com

Referência
– Clean Code: A Handbook of Agile Software Craftsmanship (English Edition) – Robert C. Martin – Capítulo 7
– https://martinfowler.com/eaaCatalog/specialCase.html

Leave a Reply

Your email address will not be published. Required fields are marked *