Olá, leitores!
Há alguns meses, tive a oportunidade de assumir a correção das avaliações técnicas dos candidatos para Delphi na DB1 Group. 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:
1 2 3 4 |
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:
1 |
result := ValorTempo > 0; |
2) Negando a negação
Seja cauteloso no código para evitar condições que neguem negações:
1 |
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:
1 |
if VerificarClienteEstaAtivo then |
3) Concatenação de strings
É comum encontramos códigos como esse:
1 2 |
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:
1 2 |
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:
1 2 3 |
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:
1 2 3 |
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:
1 |
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
:
1 |
for i := 0 to Pred(Lista.Count) do |
Lembrando, claro, que existe o For-In desde a versão 2005 do Delphi:
1 |
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:
1 2 3 4 5 6 7 8 9 10 |
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:
1 2 3 4 5 6 7 8 9 10 |
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:
1 2 3 4 |
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:
1 2 3 4 5 6 7 8 9 |
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
?
1 2 |
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 DocWiki.
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:
1 2 3 4 5 6 7 8 9 |
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:
1 2 3 4 5 6 7 8 9 |
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
):
1 |
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
:
1 |
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?
1 2 3 4 5 |
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:
1 |
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!
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.
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!
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!
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!
Muito bacana sua contribuição. Continue postando mais e mais, não vou perder nenhuma dica. rsrrs.
Obrigado, Ricardo!
Provavelmente postarei mais dessas dicas. Continue acompanhando!
Abração.
Dicas interesssantes que posem ser aplicadas em diversas linguagens. Parabéns, Batera!
Oooopa, grande Jorge! Quanto tempo, meu amigo!
Obrigado pelo comentário. Abração!
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.
Olá, José, como vai?
Infelizmente não tenho experiência com Unigui, então não adicionei nada (por enquanto) sobre este framework 🙁
De qualquer forma, recomendo que você acompanhe o canal do YouTube do Afonso Foletto. Lá tem bastante coisa sobre Unigui:
https://www.youtube.com/channel/UCmrW5Eq2YSzMJxZJlrBhzbw
Grande abraço!
Excelente Professor!
Parabéns!!!
Obrigado, Diego! 😀
Muito com, me ajudou bastante. obrigado!
Que bom, Kleiton!
Eu que agradeço o comentário. Abraço!
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.
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!
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.
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, oFieldByName
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:Abraço!
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
Olá, Daniel!
Gostei bastante da frase que você citou! 😀
Obrigado pelo comentário! Abraço!
Parabéns pelo post, no entanto eu assisti recentemente a um vídeo seu feito na Conference Delphi, e lá você apresentou como abrir um Notepad através do Delphi criando menu. Ao lado eu vi que havia dois menus referentes há dois plugins: o GExperts e o Cnpack, no qual você mencionou no finalzinho do post. Será que você poderia ensinar como usar pois ao instalá-lo no Delphi em vez de ajudar me deixou maluquinho. tive que desinstalar por não saber como utilizá-los. Por isso estou fazendo esse pedido, no mais, muito obrigado pela iniciativa de compartilhar conhecimento.
Olá, João, boa noite.
Tanto o CnPack quanto o GExperts possuem uma documentação bem detalhada de cada funcionalidade que oferecem.
Para o GExperts, acesse este link. Para o CnPack, basta acessar o menu CnPack > About > Help Topics para exibir a janela da documentação, ou então, leia este artigo sobre este plugin.
Além disso, muito se aprende ao utilizá-los diariamente. Eu, por exemplo, testei opção por opção de cada um desses plugins para identificar o que poderia ajudar na minha produtividade.
Abraço!
Referente ao Filtered do ClientDataSet eu realmente defino “False” como mencionou no exemplo, gostei de saber que não tem essa necessidade.
Gostei também de conhecer o AppendRecord, porém dependendo do número de registros a leitura ao meu ver fica inviável assim como no MySQL quando uso VALUES, a identificação de pra qual campo esta indo determinado valor.
Acaba sendo mais fácil a leitura dessa definição quando usado nos exemplos:
do que
Assim como no MySQL é mais fácil…
do que
…pois sei que o campo nome esta sendo preenchido com ‘JOÃO’, algo que pelo Índice é menos legível.
No ClienteDataSet também tem a possibilidade de usar:
Só reforçando que penso assim quando o número de registros é bem superior aos passados nos exemplos.
Mas claro, essa é só minha humilde opinião 🙂
Olá, Denis, tudo bem?
Peço desculpas pela demora para responder o seu comentário.
Denis, a sua opinião é muito bem-vinda, e concordo com você sobre a legibilidade do código, principalmente quando o projeto é compartilhado entre uma equipe de desenvolvedores.
A ideia do AppendRecord é justamente para DataSets que possuem poucos campos. Eu tenho o costume de utilizá-lo, por exemplo, em Mocks para escrever testes unitários.
Já em códigos mais complexos, envolvendo DataSets com vários campos, o uso do
FieldByName
ou de Fields persistentes realmente é mais viável. Outra alternativa é utilizar um framework ORM para que o uso dos campos de um DataSet fique mais expressivo.Obrigado pelo comentário! Abraço!
Artigo simples, bem explicado e muito útil.
Parabéns, André, por mais um artigo de qualidade.
Muito obrigado, Aleandro! 🙂
Em breve pretendo publicar uma “continuação” desse artigo.
Abraço!
Excelente! Dicas simples e importantíssimas!
Muito obrigado, Leonardo!
Grande abraço!
Muito bom André !!!!!!!!!!!!!!!!
Obrigado, Sergio!!!
Abração!