CDD e legibilidade do código: essa relação garante melhorias?

Neste artigo você vai ver:

A relação entre CDD e legibilidade de código é um assunto rico quando se trata da compreensão de linhas de código. Afinal, usar CDD garante melhorias de legibilidade ou não? É essa questão que buscamos responder neste conteúdo.

Lembrando que este blog post é o resumo do artigo científico publicado na trilha de pesquisa da conferência ACM/IEEE International Symposium on Empirical Software Engineering and Measurement (ESEM), em 2022. O texto completo do artigo, em inglês, está disponível aqui.

O que é esse tal de CDD?

Cognitive-Driven Development (CDD) é uma técnica de design de código que visa reduzir o esforço cognitivo que devs empregam na compreensão de uma determinada unidade de código, por exemplo, uma classe. 

A ideia que CDD defende é que deve existir um limite no tamanho das unidades de código, mas esse limite deve ser definido de forma disciplinada.

CDD tem suas raízes em duas teorias psicológicas bem conhecidas: a Teoria do Número Mágico Sete e a Teoria da Carga Cognitiva (CLT). Conheça um pouco mais sobre elas:

  • Na “Teoria do Número Mágico Sete”, é explicado que provavelmente temos uma dura limitação no processamento simultâneo de informações. Estudos experimentais têm sugerido que os humanos geralmente possuem apenas sete unidades de informação (podendo ser duas a mais ou a menos) na memória de curto prazo. 
  • A CLT, por outro lado, explica que qualquer elemento de informação tem uma complexidade intrínseca, dependendo da quantidade e disposição dos elementos que o compõem. De acordo com Sweller, criador da teoria, conhecer o número de elementos de informação e sua interatividade é crucial para apoiar pessoas no processo de ensino e aprendizagem.

Quando se fala do processo de desenvolvimento de software, pode não ser uma surpresa que um grande número de construções de código possam dificultar a compreensão de um programa. 

Quem nunca precisou ler várias e várias vezes um método com vários if alinhados e um ou dois fors para compreender o objetivo de um método?

Quanto maior o número de construções de código concentradas na mesma unidade de código, maior tende a ser o esforço para compreendê-la. 

Tudo tem limite, CDD também

O CDD tem como objetivo limitar a quantidade de elementos de código que podem aumentar a sua complexidade.

Para isso, pessoas que usam a abordagem do CDD precisam definir os “pontos de complexidade intrínseca”, do inglês, Intrinsic Complexity Points ou ICP, do código. 

ICPs são elementos de código que podem afetar o entendimento das pessoas desenvolvedoras, de acordo com sua frequência de uso. Exemplos de ICPs são:

  • Condicionais de código (como ifelse ou switch-case);
  • Laços de repetição (como for ou while);
  • Tratadores de exceção (como try-catch).

Isoladamente, estes elementos de código podem não embaraçar o entendimento de um trecho. No entanto, se usados com frequência em uma mesma unidade de código, eles potencialmente podem dificultar o seu entendimento.

É importante notar que o CDD é uma abstração, ela apenas fornece mecanismos para que devs decidam o que deve ser limitado. Pessoas desenvolvedoras precisam antes de tudo decidir quais são os ICPs e quantos podem ser tolerados. E essa não é necessariamente uma tarefa simples.

Por exemplo, quantas declarações de ifs devem existir em uma determinada unidade de código? 

Como várias pessoas estão envolvidas na criação de um produto de software, o limite de complexidade deve ser definido de forma colaborativa, levando em consideração o grau de especialização de devs do time e a natureza do projeto.

Estudo sobre CDD e legibilidade

Embora evidências iniciais indiquem que a prática de CDD possa facilitar a manutenção do software, uma vez que o código fonte criado usando as premissas do CDD utiliza menos elementos de código, pouco se sabe se o código que foi desenvolvido usando o mecanismo também tem melhor legibilidade.

Para entender até que ponto a utilização de CDD pode melhorar a legibilidade de um código fonte, conduziu-se um estudo empírico com o objetivo de avaliar a legibilidade de trechos de código que foram conduzidos utilizando CDD como prática de desenvolvimento de software.

Primeiros passos

O estudo foi primariamente baseado em um questionário que foi respondido por 133 devs profissionais, sendo 73 zuppers. Nesse questionário, apresentamos 10 pares de trechos de código, como ilustrado na figura abaixo.

Figura que exemplifica a metodologia do estudo, no topo da imagem está a pergunta: “In your opinion, which code snippet is the most readable?” que podemos traduzir para português como: “Na sua opinião, qual trecho de código é o mais legível?”. Abaixo estão dois exemplos de linhas de código, um do lado direito e outro do esquerdo.
Legenda: Figura que exemplifica a metodologia do estudo.

Nessa figura, o trecho de código à esquerda representa um trecho de código original, extraído de um projeto Java existente. Por outro lado, o trecho de código à direita representa o mesmo trecho de código, mas refatorado utilizando as diretrizes do CDD. 

Para encontrar estes 10 pares de código, utilizamos como base um outro estudo sobre CDD. Nele, 18 zuppers realizaram refatorações – utilizando as diretrizes do CDD – em alguns projetos de código aberto bem conhecidos, como o feign, por exemplo. 

Esse conjunto de refatorações originadas nesse outro estudo foi analisado e filtrado por três pessoas pesquisadoras, no fim, apenas 10 pares foram selecionados. 

Para selecionar esses pares, levou-se em consideração critérios como:

  • tamanho, pois estes deveriam ser pequenos o suficiente para caber na tela do formulário; 
  • coesão, pois estes deveriam transmitir o seu significado sem que a pessoa participante precisasse conhecer outros detalhes técnicos do programa.

Os 10 pares foram então apresentados para devs profissionais, que deveriam apenas indicar qual trecho era mais legível. Após indicar o trecho mais legível, a pessoa também deveria apresentar seu raciocínio. 

Esse questionário foi divulgado internamente na Zup, mas também em redes sociais como Twitter e LinkedIn.

Os resultados

Será que um código desenvolvido com CDD tem melhor legibilidade de código?

De maneira geral, nós observamos que 7 das 10 refatorações de código guiadas com CDD foram mais positivamente avaliadas do que os trechos de código originais. 

Em dois casos, não foi possível decidir quais trechos de código eram mais legíveis. Por fim, somente um trecho de código original foi considerado mais legível do que sua versão refatorada utilizando CDD.  A seguir exemplificamos cada um desses casos.

Encapsulando tratadores de exceção

Neste exemplo, o código original tinha um bloco try-catch que tratava a exceção SendFailedException. A versão CDD deste código extraiu a responsabilidade pelo tratamento da exceção e moveu o bloco try-catch para um novo método chamado sendMessageToAdvisor, como pode ser visto a seguir. 

public void method() {
    final EarlyAlert saved = getDao().save(earlyAlert);
    try {
        sendMessageToAdvisor(saved, earlyAlert.getEmailCC());
        } catch (final SendFailedException e) {
        LOGGER.warn("Could not send Early Alert message to advisor.", e);
        throw new ValidationException("Early Alert was NOT created.", e);
    }
}

Versão original

public void method() {
  final EarlyAlert saved = getDao().save(earlyAlert);

  sendMessageToAdvisorService.sendMessageToAdvisor(
    saved, earlyAlert.getEmailCC(), LOGGER);
}

Versão refatorada usando CDD

Observamos uma semelhança notável nas votações: 66 participantes preferiram o código original, enquanto 66 preferiram a versão refatorada usando CDD. 

Ao analisar os comentários das pessoas que participaram, notamos que devs que favoreceram o código original têm um certo hábito com esta prática de codificação. 

Como foi argumentado por uma pessoa: “registrar com try-catch é tão comum que eu nem precisei ler o código para entender o propósito”. Outra mencionou que: “há mais visibilidade das ações realizadas no código, o que está sendo executado é mais explícito”.

Por outro lado, participantes que favoreceram a versão refatorada usando CDD indicaram que o código refatorado é próximo do inglês, como mencionou uma pessoa: “o código é mais curto e com uma linguagem mais próxima da natural”. 

Também mencionaram que “o código tem um propósito claro: a remoção do try-catch evita a distração da parte mais importante do código”, e “isolar a manipulação de erros dentro de um método me dá uma visão parcial, de acordo com a responsabilidade de cada método”. 

Concatenando expressões lógicas

Neste caso, apresentamos um método com quatro declarações de ifs (três destas aninhadas). Na versão refatorada com CDD, as expressões lógicas destes quatro ifs foram agrupadas em uma única declaração de if.  

A figura abaixo exemplifica os dois trechos de código.

public void method() {
  for (BaseStudentReportTO person:persons) {
    if (!map.containsKey(person.getSchoolId()) && StringUtils.isNotBlank(person.getCoachSchoolId())) {
      boolean addStudent = true;
      if (personSearchForm.getJournalSourceIds()!=null) {
       if (getDao().getJournalCountForPersonForJournalSourceIds(person.getId(), personSearchForm.getJournalSourceIds()) == 0) {
        addStudent = false;
       }
      }
      if (addStudent) {
        final JournalCaseNotesStudentReportTO entry = new JournalCaseNotesStudentReportTO(person);
        personsWithJournalEntries.add(entry);
        map.put(entry.getSchoolId(), entry);
      }
    }
  }
}

Versão original

public void method() {
  for (BaseStudentReportTO person:persons) {
    if (!map.containsKey(person.getSchoolId())
    && StringUtils.isNotBlank(person.getCoachSchoolId())
    && personSearchForm.getJournalSourceIds() == null
    && getDao().getJournalCountForPersonForJournalSourceIds(person.getId(), personSearchForm.getJournalSourceIds()) != 0) {
      final JournalCaseNotesStudentReportTO entry = new JournalCaseNotesStudentReportTO(person);
      personsWithJournalEntries.add(entry);
      map.put(entry.getSchoolId(), entry);
    }
  }
}

Versão refatorada usando CDD

Um total de 124 participantes (93%) votaram a favor da versão usando CDD como a mais legível. Uma das pessoas comentou que este era um “exemplo clássico de complexidade ciclomática. Eu não conseguia entender o exemplo com vários ifs aninhados”. Outras também comentaram que a excessiva indentação dos ifs aninhados dificulta a legibilidade.

No entanto, uma pessoa que participou votou na versão original trouxe uma perspectiva interessante: “é mais fácil depurar o código quando os ifs são separados […] Também facilita o uso de funcionalidades do InteliJ, como ‘evaluate expression'”.

Listando todos os imports

Aqui comparamos um trecho de código no qual:

  • as importações das classes estão implícitas, quando usamos o asterístico (*), como wildcard para ocultar classes do mesmo pacote;
  • e outro, a versão usando CDD, o qual as importações são explícitas, quando listamos todas as classes importadas. 

A seguir apresentamos estes casos:

import java.io.Serializable;
import java.net.URI;
import java.nio.charset.Charset;
import feign.Request.HttpMethod;
import feign.template.*;
import static feign.Util.*;

Versão original

import java.io.Serializable;
import java.net.URI;
import java.nio.charset.Charset;
import feign.Request.HttpMethod;
import feign.template.BodyTemplate;
import feign.template.HeaderTemplate;
import feign.template.TemplateChunk;
import feign.template.UriTemplate;
import feign.template.UriUtils;
import static feign.Util.CONTENT_LENGTH;
import static feign.Util.checkNotNull;

Versão refatorada usando CDD

Esta pergunta teve alguns comentários interessantes.  A maioria foi favorável ao código original e acreditava que as importações implícitas trazem melhor legibilidade, pois utilizam menos linhas de código e, então, facilitam a identificação de grupos de importações.

A esse respeito, uma pessoa mencionou que “a lista mais curta é claramente mais legível, embora possa estar importando classes que não serão utilizadas”. 

Por outro lado, devs que favoreceram o uso de CDD argumentam que é importante entender o que está sendo importado, como foi dito: “Apesar de tornar os arquivos mais extensos, é preferível importar classes individualmente, pois facilita sua localização e compreensão. Além disso, a abordagem explícita não importa classes desnecessárias para o código”. 

Algumas pessoas também mencionaram que a abordagem explícita facilita a manutenção do código, uma vez que reduz as chances de importação da classe errada.

O que aprendemos?

O Cognitive-Driven Development é uma técnica de codificação que visa reduzir a complexidade do código através da redução da carga cognitiva da pessoa desenvolvedora. 

Embora com essa pesquisa não seja possível afirmar que o CDD por si só seja responsável por melhorar a legibilidade, notamos que o CDD potencialmente favorece o uso de outras boas práticas de projeto. Por exemplo:

  • Participante que mencionou o benefício de utilizar métodos com nomes autoexplicativos. Outras 30 pessoas mencionaram benefícios similares com a descrição dos nomes dos métodos. Embora CDD não garanta que devs criem métodos com nomes autoexplicativos, o uso do CDD tende a forçar a criação de novos métodos. Dessa forma, acreditamos que devs tendem a tomar cuidado adicional com as convenções de nomes.
  • Também observamos que o CDD tem chances de fomentar o reuso de código, visto a estratégia de encapsular regras de negócio, tratadores de exceção e entre outros em novos métodos. 
  • Da mesma forma, o uso de classes estáveis como List e Assert também favorecem o uso de CDD, pois naturalmente diminuem o número de elementos de complexidade intrínseca.
  • Finalmente, também observamos que os trechos de código inspirados em CDD são menores, na vertical, do que os trechos de código originais. Entretanto, percebemos também que CDD tende a tornar o código mais alinhado horizontalmente. Uma possível razão é porque a maioria dos refatoramentos resulta em novos métodos ou classes, potencialmente com nomes autoexplicativos, logo, longos.

É importante também mencionar que, dada a flexibilidade do CDD, estes benefícios podem (ou não) serem observados dependendo dos ICPs definidos. 

Quer saber mais?

Se você se interessou pelo trabalho e quiser saber mais detalhes sobre a pesquisa, você pode consultar aqui o artigo científico completo.

No trabalho, além de aprofundar em algumas das percepções reportadas por participantes, apresentamos também um outro estudo envolvendo modelos avançados de avaliação automática de legibilidade. 

Curiosamente, os resultados dos modelos automáticos não estão alinhados com a perceção de devs. Mas isso é assunto para outro dia. 😉

E aí, o que achou do CDD e de sua aplicação na legibilidade de código? Conta pra gente nos comentários!

Capa do artigo sobre CDD e legibilidade do código. Nela, aparece um closeup da tela do computador do desenvolvedor de software digitando a linguagem de programação
Foto de Gustavo Pinto
Pesquisador
Uso metodologia científica pra testar as crenças dos times de desenvolvimento. Será que Kotlin é mesmo melhor que Java? Será que multirepos é melhor que monorepos? Será mesmo?

Este site utiliza cookies para proporcionar uma experiência de navegação melhor. Consulte nossa Política de Privacidade.