Olááá, leitor! Você se considera um profissional tecnicamente expressivo? Hoje trago uma discussão sobre a importância da clareza e objetividade no código-fonte, que se resumem no termo “expressividade”. Acompanhe as ideias, analogias e exemplos apresentandos neste artigo e verifique se você está sendo expressivo ao programar. Go, go, go!
Introdução
Há algum tempo, venho citando o termo “expressividade” em alguns artigos relacionados à programação. A intenção em mencionar esse termo é ressaltar o nível de entendimento que um código bem escrito pode revelar. Mas, em detalhes, o que seria essa tal de expressividade?
Suponha que você esteja escrevendo um e-mail para o seu chefe explicando um determinado problema no módulo que está desenvolvendo. Certamente, você tentará encontrar as melhores palavras e estruturar o texto de forma que o seu chefe não tenha dificuldades em entendê-lo, não é? Pois bem, assim como e-mails, recados, cartas, artigos, gostaríamos que outras pessoas nos compreendessem sem complicações, ou melhor, queremos nos expressar ao máximo.
Sendo assim… por que não podemos fazer o mesmo com o nosso código-fonte?
Expressividade no código
Eis que lhes introduzo o conceito de expressividade no código. Como bons desenvolvedores, devemos considerar cada unidade de código como uma página de um livro, não somente pela questão da regra decrescente, mas pelo propósito em propiciar uma boa leitura.
Se você trabalha em uma empresa de desenvolvimento, outros desenvolvedores eventualmente precisam ler o seu código para realizar manutenções ou interpretar a regra de negócio. Neste caso, é correto afirmar que eles são os seus “leitores”. Por outro lado, se você é um desenvolvedor autônomo, é bem provável que você mesmo irá retornar ao seu código com uma certa frequência, e, convenhamos: é gratificante quando abrimos o nosso próprio código e conseguimos entender, com facilidade, o que ele faz.
Exemplo 1: Princípio da Menor Surpresa
Bom, mas nada do que eu disse fará sentido se eu não apresentar alguns exemplos.
Considere a declaração do método abaixo na classe “Clientes” de um aplicativo:
1 |
procedure Consultar; |
Legal… consultar o que? Nome do cliente, contato, endereço, quantidade de compras?
Só a palavra “Consultar” não nos passa esclarecimento algum. Em primeira instância, teremos uma ideia subjetiva sobre a função deste método, ou seja, saberemos que ele faz algum tipo de consulta, mas não sabemos qual. Para entender o que o método faz, será necessário verificar a sua implementação, exigindo um pouco mais de esforço. O problema é que, se a implementação do método estiver tão ruim quanto a declaração, prepare-se para passar alguns minutos interpretando as linhas de código…
Esse equívoco de utilizar um verbo genérico como nome de método é um dos fatores que motivaram a elaboração do Princípio da Menor Surpresa (ou POLA – Principle of Least Astonishment). Este princípio visa evitar que um desenvolvedor se surpreenda com o funcionamento de um método ao utilizá-lo para um determinado comportamento, quando, na verdade, ele possui outro. Para isso, o POLA sugere a busca pela previsibilidade. Ao se deparar com a declaração ou chamada do método, o desenvolvedor já deve ter uma ideia concreta da sua ação.
Se declararmos métodos com nomes mais objetivos, mesmo que ligeiramente mais extensos, teremos uma segurança maior em utilizá-los, além de evitar o desperdício de tempo interpretando suas reais finalidades:
1 2 3 |
procedure ConsultarQuantidadeDeCompras; procedure AbrirTelaCadastroClientes; function VerificarSeClienteEstaCadastrado: boolean; |
Ao ler as declarações acima, torna-se mais fácil compreender o que cada método realiza, ou pelo menos deveria realizar. Observe como o código fica mais legível em uma condição IF:
1 2 3 4 |
if VerificarSeClienteEstaCadastrado then ConsultarQuantidadeDeCompras else AbrirTelaCadastroClientes; |
Perfeito! É só bater o olho no código para identificar o fluxo da regra de negócio!
O mais interessante é que, por exemplo, o desenvolvedor pode utilizar a função VerificarSeClienteEstaCadastrado
em qualquer ponto do software, evitando a duplicidade e concentrando a regra de negócio em apenas um local. Curiosamente, quando usamos um nome objetivo como esse, há uma tendência em memorizá-lo. Algumas vezes, já pensei: “Preciso verificar a regra de negócio X. Espere aí… acho que já existe uma função para isso!”.
Exemplo 2: Nome de métodos
Continuando, confira a declaração abaixo. O nível de expressividade está adequado, concorda?
1 |
procedure ConverterDocumentoPDF(const Doc: TDocumento); |
Bom, eu não concordo. Pela declaração, eu não tenho certeza se o método converte o documento para PDF ou se converte um documento que é um PDF para outro formato. Mais uma vez, teríamos que gastar mais alguns minutinhos para investigar a implementação. Se, por acaso, o método estivesse declarado em uma das formas abaixo, não nos depararíamos com esse tipo de dúvida.
1 2 3 |
procedure ConverterDocumentoParaPDF(const DocumentoComum: TDocumento); // ou procedure ConverterDocumentoPDFParaDOC(const DocumentoPDF: TDocumento); |
Atente-se que, além do próprio nome, a declaração dos parâmetros também é importante, principalmente quando se utiliza o recurso de Code Insight, no qual exibe a lista de parâmetros de um método sem a necessidade de acessar a declaração.
Exemplo 3: Assinatura dos parâmetros
Não somente os nomes dos métodos são importantes, mas também os parâmetros. Concordo que a clareza dos parâmetros pode parecer uma questão óbvia, porém, tenho quase certeza de que você já encontrou uma chamada de método parecida com essa:
1 |
GerarRelatorio(True, False, True, 1, nil, 'C', False); |
Embora o nome do método esteja relativamente compreensível, é praticamente impossível desvendar o seu comportamento. Claro, pelo nome, sabemos que um relatório será gerado, mas essa quantidade de parâmetros obscuros nos deixa inseguros de como será a saída real deste relatório. Um simples “chaveamento” em um destes parâmetros (por exemplo, alterar o primeiro parâmetro de True
para False
) pode modificar consideravelmente o resultado. A única solução é abrir a classe que contém o método e conferir a finalidade de cada parâmetro, que, por ventura, pode não ser agradável:
1 2 3 4 |
procedure GerarRelatorio(const GerarDuasVias: boolean; const ImprimirDiretoNaImpressora: boolean; const AtivarAgrupamento: boolean; const ColunaAgrupamento: integer; var ObjetoExportacao: TExportacao; const TipoPapel: string; const SalvarEmPDF: boolean); |
Na busca pela expressividade, a minha sugestão é utilizar tipos enumerados e constantes para aprimorar a legibilidade dessa chamada. Provavelmente, ainda teríamos que verificar a declaração do método, porém, a própria chamada já seria meramente auto-explicativa. No exemplo abaixo, foi utilizado o prefixo enum para tipos enumerados e letras maiúsculas para constantes. Observe como a chamada se torna mais explícita:
1 2 |
GerarRelatorio(enumGerarDuasVias, enumNaoImprimir, enumAtivarAgrupamento, AGRUPAMENTO_POR_DATA, nil, PAPEL_CONTINUO, enumNaoSalvar); |
Os tipos enumerados, por sua vez, poderiam ser declarados dessa forma:
1 2 3 4 5 |
type TenumNumeroDeVias = (enumGerarViaUnica, enumGerarDuasVias, enumGerarFrenteVerso); TenumAcaoImpressao = (enumNaoImprimir, enumImprimirAutomatico, enumExibirPrompt); TenumFormaAgrupamento = (enumNaoAtivarAgrupamento, enumAtivarAgrupamento); TenumSalvarRelatorio = (enumNaoSalvar, enumSalvarPDF, enumSalvarDOC, enumSalvarHTML); |
Diga-se de passagem: o código fica bem mais atraente!
Exemplo 4: Quantidade de parâmetros
Entretanto, tenho mais uma crítica a respeito do método acima. Mesmo utilizando tipos enumerados e constantes, a quantidade de parâmetros ainda continua a mesma, ferindo o nosso compromisso com a expressividade. Várias vezes já tive que contar o número de parâmetros para saber qual deles se referia à minha necessidade. Algo como: “Vamos ver… 1… 2… 3… 4… 5… sexto parâmetro! É esse que preciso alterar.”. Chato, não? Imagine se houver 20 parâmetros e você precisa alterar o 14º?
Usando uma analogia ao código acima, é como se alguém nos pedisse, apenas verbalmente, para fazer compras em 7 lugares diferentes na mesma viagem. Podemos nos confundir sobre os lugares e o que devemos comprar em cada um deles. Seria mais fácil se nos entregassem, por exemplo, um único papel com as 7 tarefas descritas.
Boa ideia! É isso que também podemos aplicar ao nosso caso: ao invés de passar 7 parâmetros, poderíamos criar uma classe que tenha todos estes valores e utilizá-la como um parâmetro único, conforme abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 |
type TParametrosRelatorio = class // como é apenas um exemplo, não vou detalhar os aspectos de visibilidade aqui public NumeroDeVias: TenumNumeroDeVias; AcaoImpressao: TenumAcaoImpressao; FormaAgrupamento: TenumFormaAgrupamento; ColunaAgrupamento: integer; ObjetoExportacao: TExportacao; TipoPapel: string; SalvarRelatorio: TenumSalvarRelatorio; end; |
Em seguida, basta alterar a definição do método GerarRelatorio
para que ele receba um objeto do tipo TParametrosRelatorio
como parâmetro. Na chamada do método, será preciso apenas preencher um objeto dessa classe com os mesmos valores que estávamos passando como parâmetros anteriormente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var objParametrosRelatorio: TParametrosRelatorio; begin objParametrosRelatorio := TParametrosRelatorio.Create; try objParametrosRelatorio.NumeroDeVias := enumGerarDuasVias; objParametrosRelatorio.AcaoImpressao := enumNaoImprimir; objParametrosRelatorio.FormaAgrupamento := enumAtivarAgrupamento; objParametrosRelatorio.ColunaAgrupamento := AGRUPAMENTO_POR_DATA; objParametrosRelatorio.ObjetoExportacao := nil; objParametrosRelatorio.TipoPapel := PAPEL_CONTINUO; objParametrosRelatorio.SalvarRelatorio := enumNaoSalvar; GerarRelatorio(objParametrosRelatorio); finally FreeAndNil(objParametrosRelatorio); end; end; |
Nossa, mas compensa mesmo trocar apenas aquela chamada por todas essas linhas de código?
Lembre-se de que o nosso objetivo aqui é aprimorar a expressividade do código-fonte, mesmo que isso implique no acréscimo de algumas linhas, desde que elas realmente sejam necessárias. Além disso, essa mesma classe de parâmetros poderá ser utilizada em outros locais do software, proporcionando uma padronização.
Por outro lado, vale ressaltar que o excesso de expressividade também pode ser ruim, tornando o código maçante. Eventualmente, para encontrar o equilíbrio adequado, é necessário “sacrificar” algumas diretrizes de expressividade. Neste caso, uma alternativa é fazer uso de comentários ou anotações no código-fonte, mas, novamente, só se forem realmente úteis.
Exemplo 5: Nome de variáveis
Se escrever nomes melhores para os nossos métodos é uma boa prática, por que não fazer o mesmo para as nossas variáveis? Assim como acontece com os métodos, nomes objetivos irão melhorar a leitura do nosso código, facilitando a interpretação. Considere as seguintes variáveis:
1 2 3 4 |
var Soma: real; Qtd: integer; Nome: string; |
Ao ler essas declarações, eu pensaria: “Soma do quê? Quantidade do quê? Nome de quem?”. E sabe qual seria a única forma de descobrir suas finalidades? Lendo os valores que são atribuídos à elas. Aí já é tarde. Isso demonstra a evidência de que essas variáveis não são nada expressivas. Imagine, então, se elas forem declaradas com palavras compostas:
1 2 3 4 |
var SomaDoFaturamento: real; QuantidadeDeVendas: integer; NomeDaTransportadora: string; |
Bem melhor, concorda? Pelo nome, já temos uma ideia do tipo de informação que cada variável irá receber. A propósito, o uso de preposições nos nomes (“do”, “de”, “da”) são opcionais. Se a variável QuantidadeDeVendas
fosse declarada somente como QuantidadeVendas
, a interpretação seria essencialmente a mesma. Eu apenas optei por inclui-las para que o código se assemelhe à leitura natural de um texto.
Agora, mais uma dica que aprendi na empresa em que trabalho, mas é opcional: procure adquirir o hábito de utilizar notação húngara e adicionar um prefixo no nome da variável para indicar o seu tipo. Confira:
1 2 3 4 5 |
var rSomaDoFaturamento: real; iQuantidadeDeVendas: integer; sNomeDaTransportadora: string; bClienteComPendencias: boolean; |
Dessa forma, em qualquer linha do código será possível identificar o tipo da variável, evitando a necessidade de mover a barra de rolagem para o início do método para verificá-lo. Olha só que interessante: só pelo fato de aprimorarmos o nome da variável, conseguimos deixar explícito tanto o tipo quanto o propósito.
No exemplo acima, utilizei a inicial de cada tipo (“r”, “i”, “s” e “b”) como prefixo, mas nada impede que você mesmo crie suas próprias convenções.
Exemplo 6: Condições booleanas
Cuidado com elas! Essas condições podem ser o motivo de uma falha de interpretação que pode resultar em um tempo longo (e chato) de depuração. Observe a condição abaixo:
1 2 3 4 |
if (iQtdeVendas > 0) and (Query.FieldByName('Cancelado').AsString <> 'N') and (not DataSet.FieldByName('Status').AsString = 'P') and (RadioGroup.ItemIndex = 1) then GerarRelatorio; |
Acho que nem preciso comentar, não é? 🙂
Seria algo como: “Se isso for verdade, e aquilo for diferente que N, e o status não for igual a P e opção 1 estiver marcada, então…” – observe que só a leitura da condição como um todo já traz um princípio de dificuldade. Bom, sem dizer que utilizei apenas 4 condições como exemplo. Imagine se fossem 6, 8?
A recomendação é transformar toda essa condição em uma função que retorne um valor booleano:
1 2 |
if ValidarGeracaoRelatorio then GerarRelatorio; |
A função declarada acima, por sua vez, também pode ser aprimorada:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function ValidarGeracaoRelatorio: boolean; var bVendaEncontrada: boolean; bVendaCancelada: boolean; bClientePendente: boolean; bConfirmaGeracao: boolean; begin bVendaEncontrada := ConsultarQtdeVendas > 0; bVendaCancelada := Query.FieldByName('Cancelado').AsString = 'S'; bClientePendente := DataSet.FieldByName('Status').AsString = 'P'; bConfirmaGeracao := RadioGroup.ItemIndex = 1; result := (bVendaEncontrada) and (not bVendaCancelada) and (not bClientePendente) and (bConfirmaGeracao); end; |
Sentiu-se incomodado com a quantidade de vezes que a palavra “and” foi utilizada? Eu também! Alternativamente, podemos empregar uma lógica diferente para o retorno dessa função, aplicando uma técnica conhecida como cláusula de guarda:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
begin ... // o retorno inicia-se como falso result := False; // se não há registros, o método é interrompido (retorno falso) if not bVendaEncontrada then Exit; // se a venda estiver cancelada, o método é interrompido (retorno falso) if bVendaCancelada then Exit; // se o cliente estiver pendente, o método é interrompido (retorno falso) if bClientePendente then Exit; // se o usuário não confirmou a geração, o método é interrompido (retorno falso) if not bConfirmaGeracao then Exit; // logo, o retorno somente será verdadeiro se todas as condições forem satisfeitas result := True; end; |
Exemplo 7: Tamanho dos métodos
A terceira e última recomendação é relacionada ao tamanho dos nossos métodos. Quanto menores eles forem, mais objetivos serão. Digo isso por dois motivos: primeiro, métodos grandes ferem o princípio de responsabilidade única (SRP), ou seja, tendem a realizar mais do que deveriam, assumindo mais de uma responsabilidade; segundo, a leitura fica comprometida, já que, dependendo dos loops e condições, teremos que mover a barra de rolagem várias vezes para interpretar o método ou acompanhar o fluxo de depuração.
Quando notar que o método está tomando uma dimensão extensa, considere imediatamente a possibilidade de refatorá-lo. Ou então, quando se deparar com um método grande, divida-o em vários pedaços, de modo que cada um deles fique pequeno.
Para finalizar, vale mencionar uma frase cômica que Uncle Bob, autor do livro Clean Code, escreveu sobre essa prática:
A primeira regra dos métodos é que eles devem ser pequenos. A segunda regra é que eles devem ser menores ainda.
Abraço, leitores!
Na próxima semana, bateremos um papo sobre avanço tecnológico.
Amigo, desculpe não estar falando a respeito do topico, mas preciso de uma ajuda, se possível. Estou tentando elaborar uma rotina para envio de SMS via Delphi 7 com componentes HTTP, IdIOHandler, IdIOHandlerSocket e IdSSLOpenSSL. Meu sistema é 64bits. Está me informando uma mensagem de “could not load ssl library”. Uso o SMS da comtele que usa HTTPs. Desculpe mais uma vez, mas preciso urgente desta ajuda. Se possível, responda no meu e-mail. Um forte abraço.
Olá, Emilson!
Vou enviar a versão das DLLs que utilizo nos meus projetos em Delphi 7 e algumas orientações para o seu e-mail, ok?
Abraço!
Salve André!
Rapaz, já faz um tempinho que eu não visitava seu blog… Tanto que ele se tornou um site e eu nem vi!
Gostei deste texto acerca da “expressividade”…
Gostei, por que, como a maioria de nós, eu também gosto de encontrar argumentos para embasar meus modos de fazer algo…
Acontece que, como não sou programador, apenas um curioso (mas já estou cuidando de fazer um curso técnico para começar), sempre que escrevo meus programinhas bestas, trato de colocar em cada função ou mesmo em cada variável um nome bem claro, para eu não me perder depois se precisar voltar e analisar o código. Além disso, eu gosto de usar comentários para me auxiliar..
Pois bem, voltando ao ponto que citei (gosto de encontrar argumentos para embasar meus modos de fazer algo), foi muito gratificante encontrar este post, pois agora vejo que o que eu achava que era um comportamento de aspirante a programador, e que deixava meus códigos bobos com aspecto de “super amador”, são na verdade ótimas ferramentas aconselhadas para uso profissional!!
Gostei demais de saber disso! Que ótimo que seu site existe!
Parabéns pela nova página!
Como sempre, continuarei acompanhando, mesmo que em intervalos nada regulares!
Abraço, e sucesso, SEMPRE!
Olá, Jadilson, quanto tempo!
Rapaz, muito obrigado pelo seu feedback! São comentários como o seu que me motivam a continuar o trabalho no blog!
Fico contente em saber que estou alcançando o meu objetivo de compartilhar o conhecimento!
Jadilson, embora você ainda não tenha feito um curso de programação, eu diria, sinceramente, que você já é um programador profissional só pelo fato de produzir um código expressivo. Isso já o coloca na frente de muitos, muitos programadores formados.
Assim como mencionei em um dos artigos, programar é uma arte. Nós, desenvolvedores, devemos assumir a nossa função de “artista” e produzir belas artes, ou melhor, códigos bem escritos. Você está nessa direção!
Boa sorte nos estudos e na carreira profissional, meu caro!
Obrigado novamente pelo incentivo! Abraço!
Caro André, vindo de um profissional de seu gabarito só posso ficar muito contente com o elogio! Muito obrigado pela força! Abraço!