C# - Os erros mais comuns cometidos pelos iniciantes - IV


Hoje vamos continuar a ver alguns dos 'erros' mais comuns cometidos pelos iniciantes na linguagem C# que fazem com que o código não 'cheire bem'.

Continuando a terceira parte do artigo  vou mostrar como podemos escrever um código mais refinado e mais aderente às boas práticas.

Vamos iniciar recodando o conceito do que significa dizer que o código não 'cheira bem'...

O termo Code Smell foi popularizado por Kent Beck e é um característica na programação do código usado que indica que existe uma má construção no seu projeto. Não é um bug, pois o código aparenta estar tecnicamente correto, e não impede o programa de funcionar.

Na verdade o code smell indica que o projeto de software não foi bem feito e que vai apresentar uma alta taxa de manutenção com riscos de apresentar bugs catastróficos no futuro.

Para mais detalhes veja o meu artigo : .NET Princípios de programação e padrões de projeto

Esse artigo apresenta alguns 'cheiros de código' comuns que os iniciantes (e não iniciantes) costumam usar.

Recursos usados:

1 - Usando o literal booleano de forma redundante

Este é um erro comum, principalmente entre os iniciantes.

Devemos evitar a utilização do literal booleano de forma redundante.

Para entender o problema veja o código a seguir:

Escrever no código 'true' e 'false' não é necessário, pois, a variável flag é do tipo boolean e o método IsBoolMetodo vai retornar um boolean.

Podemos aumentar a legebilidade do código escrevendo assim:

Além disso temos vários padrões de expressões booleanas que podem ser reescritas e serem simplificadas.

Dessa forma, expressões booleanas envolvendo comparações com literais booleanos, condicionais ternários com um literal booleano como um dos resultados, negações duplas ou comparações negadas podem ser alteradas para expressões equivalentes e mais simples.

A seguir temos uma tabela onde A e B são expressões do tipo boolean e podemos comparar a simplificação possível para cada caso:

Expressão   Expressão simplificada
 A || false    A
 A ? true : false    A
 A ? true : B    A || B
 A ? false : true    !A
 A ? false : B    !A && B
 A ? B : true    !A || B
 A ? B : false    A && B
 A == true    A
 A == false    !A
 A && true    A
 A != true    !A
 A != false    A
 !!A    A

A seguir temos um exemplo de código onde as propriedades boolean Pequeno, Medio e Grande são usadas de forma que não cheira bem:

        class ExemploCheirandoMal
        {
            int Tamanho { get; set; }
            bool Pequeno => !(Tamanho > 4);
            bool Medio => Pequeno == false && Tamanho <= 8;
            bool Grande => Pequeno == false ? Medio != true : false;
        }

Agora o mesmo código corrigido:

        class ExemploCheirandoMal
        {
            int Tamanho { get; set; }
            bool Pequeno => Tamanho <= 4;
            bool Medio => !Pequeno && Tamanho <= 8;
            bool Grande => !Pequeno && !Medio;
        }

Evitando a redundância do literal booleano evitamos a complexidade no código e aumentamos a sua legibilidade.

2 - Lançando exceções explicitamente

Quando lançamos uma exceção, devemos fazê-lo simplesmente chamando throw; e não throw ex; pois isso reinicia a variável de exceção.

Reiniciar uma variável de exceção perderá o rastreamento de pilha na exceção original e a substituirá pelo rastreamento de pilha da instrução throw.

Isso vai tornar a depuração da causa raiz da exceção mais difícil de rastrear, por exemplo, se o rastreamento de pilha for gravado em um arquivo de log.

No exemplo a seguir temos um tratamento de exceções que define o status como UnexpectedException se uma exceção for lançada. No entanto, estamos lançando ex, que vai descartar o rastreamento de pilha original que contém a origem do erro.

        static void Main(string[] args)
        {
            try
            {
                Run();
                status = Status.Success;
            }
            catch (Exception ex)
            {
                status = Status.UnexpectedException;
                throw ex;    // CHEIRANDO MAL
            }
        }

Corrigimos o problema fazendo assim:

        static void Main(string[] args)
        {
            try
            {
                Run();
                status = Status.Success;
            }
            catch (Exception ex)
            {
                status = Status.UnexpectedException;
                throw;    // OK
            }
        }

Outro detalhe importante e não relançar exceções no bloco finally.

Neste bloco você pode limpar a exceção, liberar os objetos ou registrar a exceção no arquivo de log ou no banco de dados para rastrear a exceção.

  try
  {
      // Codigo
  }
  finally
  {
     throw new Exception("Erro no bloco finally");
  }
   try
   {
        // Codigo  
   }
   finally
   {
       //Libera objetos e escreve no log de registro
   }

3 - Campo marcado com 'static readonly' deve ser 'const'

O valor para um campo const é calculado em tempo de compilação, enquanto que para um campo estático somente leitura é calculado em tempo de execução.

Como o valor calculado no tempo de execução para const melhora o desempenho, use const em vez de campos estáticos somente leitura.

Assim evite o código abaixo:

  public class Teste
  {
        static readonly int x = 5;                         // cheirando mal
        static readonly bool flag = true;               // cheirando mal
        static readonly string nome = "Macoratti";  // cheirando mal
   }

Prefira usar o código a seguir:

   public class Teste
    {
        const int x = 5;
        const bool flag = true;
        const string nome = "Macoratti";
    }

E com isso estamos conversados.

"Mas eu confio na tua benignidade; na tua salvação se alegrará o meu coração.
Cantarei ao Senhor, porquanto me tem feito muito bem."
Salmos 13:5,6

Referências:


José Carlos Macoratti