[Delphi] Breves dicas de Clean Code

[Delphi] Breves dicas de Clean Code

Olá, leitores!
Há alguns meses, tive a oportunidade de assumir a correção das avaliações técnicas dos candidatos para Delphi na DB1 Global Software. O pessoal manda muito bem nos projetos!
No entanto, eventualmente encontro algumas codificações que, apesar de simples, podem ser melhoradas para resultar em um código mais clean. Confira!

 

Para muitos de vocês, as dicas abaixo serão meramente óbvias. Mesmo assim, faço questão de publicá-las aqui para fomentar cada vez mais as práticas de Clean Code. A intenção é colaborar, aos poucos, para a eliminação daqueles códigos que nos fazem torcer o nariz. 🙂

 

1) Resultados de condições booleanas
Um erro que às vezes encontro nas codificações envolve condições booleanas para atribuir resultados:

if ValorTempo > 0 then
  result := True
else
  result := False;

Este código pode ser substituído por apenas uma linha, facilitando até mesmo a interpretação da regra:

result := ValorTempo > 0;

 

2) Negando a negação
Seja cauteloso no código para evitar condições que neguem negações:

if not VerificarClienteNaoEstaAtivo then

Ao ler este código, pensamos: “Espere aí… se a função avalia se o cliente não está ativo, mas tem um not… então a condição verifica se ele está ativo?!”. Sim! E se este é o caso, vale muito a pena reescrevê-lo:

if VerificarClienteEstaAtivo then

 

3) Concatenação de strings
É comum encontramos códigos como esse:

Texto := 'Há ' + IntToStr(DataSet.RecordCount) + 
  'registros encontrados com o termo ' + QuotedStr(Edit1.Text) + '.';

Observe que há 4 operadores de adição (+) utilizados para concatenar o texto, dificultando a visualização do texto como um todo. Na verdade, essa dificuldade é exponencial. Quanto mais operadores existirem, mais difícil será a visualização.
Para evitar o uso demasiado deste operador, basta recorrer à função Format nativa do Delphi, que permite declarar um texto com parâmetros e preenchê-los em um array:

Texto := Format('Há %d registros encontrados com o termo %s.',
  [DataSet.RecordCount, QuotedStr(Edit1.Text]);

 

4) Incrementar/Decrementar valores
Podemos incrementar ou decrementar uma variável dessa forma:

Contador := Contador + 1;
...
Contador := Contador - 1;

Contudo, considere a utilização das funções Inc e Dec, presentes no Delphi desde suas primeiras versões:

Inc(Contador);
...
Dec(Contador);

 

Aproveitando o tópico, muitos desenvolvedores têm o hábito de decrementar a segunda expressão de uma instrução For, por exemplo, para acessar os itens de uma lista:

for i := 0 to Lista.Count - 1 do

Para evitar o “- 1”, que transmite uma sensação de número mágico, podemos utilizar o Pred:

for i := 0 to Pred(Lista.Count) do

Lembrando, claro, que existe o For-In desde a versão 2005 do Delphi:

for Item in Lista do

 

5) Condições if aninhadas
Algumas vezes tendemos a trabalhar sempre com resultados verdadeiros em condições if. Veja este exemplo:

if DocumentoEstaPreenchido then
begin
  if ConfirmarEnvioDocumento then
  begin
    if ValidarConfiguracoesEmail then
    begin
      EnviarEmailComDocumento;
    end;
  end;
end;

Essas condições aninhadas exigem a construção de uma “sequência lógica” em nossa memória para que possamos acompanhar o fluxo de execução. Entretanto, em certo ponto, naturalmente nos perdemos em meio à tantas condições, que se agravam ainda mais quando há fluxos alternativos (else).
Para “limpar” este código, podemos empregar condições de guarda com instruções de saída do método:

if not DocumentoEstaPreenchido then
  Exit;
 
if not ConfirmarEnvioDocumento then
  Exit;
 
if not ValidarConfiguracoesEmail then
  Exit;
 
EnviarEmailComDocumento;

 

6) Chamadas repetidas com longos namespaces
Já encontrei códigos parecidos com este:

Self.Controller.Servico.Regras.Funcoes.AtualizarPagamentos;
 
if Self.Controller.Servico.Regras.Funcoes.ExistemPendencias then
  Self.Controller.Servico.Regras.Funcoes.NotificarPendencias;

Para a leitura, é muito mais cômodo atribuir parte dos namespaces à uma variável e utilizá-la nas instruções seguintes:

var
  Funcoes: TFuncoes;
begin
  Funcoes := Self.Controller.Servico.Regras.Funcoes;
 
  Funcoes.AtualizarPagamentos;
 
  if Funcoes.ExistemPendencias then
    Funcoes.NotificarPendencias;

No entanto, deixo uma observação: se as chamadas aos métodos exige acesso a toda essa sequência de namespaces, talvez seja uma boa hora de rever a arquitetura!

 

7) Tipos de dados inadequados
No Delphi, assim como em outras linguagens, cada tipo de dado usa uma porção de espaço na memória. Por exemplo, o tipo integer ocupa 4 bytes devido à sua dimensão, que pode atingir um valor de até pouco mais de 2 bilhões. Pensando assim, você acha que faria sentido criar uma variável que se refere a um dia do mês como integer?

var
  Dia: integer;

Bom, sabemos que o valor máximo para o dia do mês é 31. Se o tipo integer passa de 2 bilhões, estamos alocando um espaço que jamais será utilizado. Podemos, então, substituir o integer pelo byte.
Há quem diga que os computadores atuais possuem alta capacidade de memória e que detalhes como o tipo ideal de dado podem ser ignorados. Concordo. Porém, lembre-se que a sua aplicação declara, preenche e acessa variáveis a todo momento, incontáveis vezes por dia. Acredito que, quanto menos memória uma aplicação exige durante a sua execução, melhor será o aproveitamento de recursos do sistema operacional para outras tarefas, certo? 🙂
Para mais informações sobre o limite de valores de cada tipo, acesse este link do Delphi Basics.

 

8) Atribuição de uma nova condição no filtro do DataSet
Alguns desenvolvedores assumem que é necessário desabilitar a propriedade Filtered do DataSet para atribuir um novo valor à propriedade Filter, como no código abaixo:

DataSet.Filtered := False;
DataSet.Filter := 'Selecionado = ' + QuotedStr('S');
DataSet.Filtered := True;
 
...
 
DataSet.Filtered := False;
DataSet.Filter := 'Total > 0';
DataSet.Filtered := True;

Podem ficar tranquilos, pessoal. A propriedade Filtered, uma vez habilitada, aplica as novas definições de filtro automaticamente, portanto, não é necessário desabilitá-lo para cada nova atribuição:

DataSet.Filtered := True;
 
DataSet.Filter := 'Selecionado = ' + QuotedStr('S');
 
...
 
// define um novo filtro, no qual entrará em vigor imediatamente,
// pois "Filtered" já está habilitada
DataSet.Filter := 'Total > 0';

 

9) Estado de inserção ou edição
É comum a necessidade de identificar se um DataSet está em estado de inserção (após um Append/Insert) ou em estado de edição (após um Edit):

if (DataSet.State = dsInsert) or (DataSet.State = dsEdit) then

Existe uma instrução mais simples para essa verificação, que consiste na comparação apenas com dsEditModes:

if DataSet.State in dsEditModes then

Explicando: dsEditModes equivale a dsInsert, dsEdit e dsSetKey.

 

10) Inserção de registros em um DataSet
Considere um DataSet com 3 campos. Para inserir registros, você provavelmente escreveria o código abaixo, certo?

DataSet.Append;
Dataset.FieldByName('Campo1').AsInteger := 10;
Dataset.FieldByName('Campo2').AsString := 'Débito';
Dataset.FieldByName('Campo3').AsFloat := 150.00;
DataSet.Post;

O código não está incorreto, mas pode ser melhorado! Com um método (pouco conhecido) chamado AppendRecord, pode-se reduzir essas cinco linhas em apenas uma: 

DataSet.AppendRecord([10, 'Débito', 150.00]);

 

Essa última é boa, hein? 🙂

Fico por aqui, pessoal. Foi apenas um artigo breve e informativo.
Volto em breve com o primeiro artigo do SOLID. Abraço!


 

20 comentários

  1. Boa noite André,

    Excelentes dicas. Toda possível redução de código é de grande valia para toda a aplicação e para melhor entendimento para de quem for dar manutenção no código.

    Abração.

    1. Excelente comentário, Daniel!
      Devemos sempre pensar na colaboração. Em uma equipe de desenvolvimento, é comum que outro desenvolvedor, em algum momento, tenha que alterar o código que você escreveu. Portanto, quanto mais legível, melhor a compreensão. Todos saem ganhando.

      Abração!

  2. Grande André!

    Taí um assunto que me fascina: clean code. Simplificar um código é uma obra de arte e percebe-se que nesse campo vc é um artista. Uma boa prática que gosto de utilizar nos fontes que altero é eliminar o que é desnecessário, como variáveis declaradas e não utilizadas e as units que não são referenciadas (para isso recomendo o comando Uses Cleaner do CnPack). Parece meio óbvio mas em sistemas legados encontramos de tudo. 🙂

    Abraços!

    1. Sábias palavras, Cleo! Clean Code realmente é uma arte!
      Eu também adquiri esse hábito de deixar o código mais limpo a cada vez que é alterado. No contexto do Clean Code, essa ação é chamada de The Boy Scout Rule (Regra do Escoteiro). Pretendo elaborar um artigo sobre esse assunto em breve!

      Obs: o comando Uses Cleaner do CnPack é excelente! 😀

      Grande abraço!

  3. Muito bacana sua contribuição. Continue postando mais e mais, não vou perder nenhuma dica. rsrrs.

  4. André, não sei se esta na sua pauta, mas poderia escrever algo sobre Unigui? Se vale a pena? Quais os riscos? Entre outros. Obrigado.

  5. Excelentes dicas André, obrigado por compartilhar o conhecimento com a comunidade.
    Acho muito bacana estes seus artigos sobre clean code, por favor continue postando sobre isso, ajuda muito.
    Abraços.

    1. Muito obrigado, Lucas!
      Fiquei contente em receber o seu feedback!
      A segunda parte dessas dicas já está em elaboração. Em breve faço a publicação!
      Abraço!

  6. Muito bacana, sou programador Delphi desde 2003 e confesso que tenho alguns “vícios” feios as vezes.

    Lendo a matéria e vi a questão de inserção de dados em um DataSet e dependendo da situação prefiro usar o FieldByName pois tenho tabelas com 50 Fields e para um entendimento melhor de outros programadores do que está sendo passado para o DataSet, prefiro o FieldByName.

    1. Olá, Dejorgenes! Obrigado pelo comentário!
      O uso da função FieldByName realmente deixa o código mais legível, já que informamos o nome do campo como parâmetro. Porém, em versões mais antigas do Delphi, o FieldByName tem um custo de processamento. Internamente, essa função faz um loop nos Fields do DataSet para encontrar o campo desejado. No seu caso, que há 50 Fields, o custo pode ser ainda maior.
      Uma boa prática é criar variáveis do tipo TField e atribuir a referência dos campos. Veja um exemplo:

      var
        FieldDescricao: TField;
      begin
        FieldDescricao := DataSet.FieldByName('Descricao');
      
        while not DataSet.Eof do
        begin
          ...
          ShowMessage(oFieldDescricao.AsString);
        end;
      end;

      Abraço!

  7. Grande André. Admiro as pessoas que compartilham seus conhecimentos.
    “I think the teaching profession contributes more to the future of our society than any other single profession.” – John Wooden

Deixe uma resposta

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

Preencha o campo abaixo * Time limit is exhausted. Please reload CAPTCHA.