Como testar a Qualidade do seu código?

Tempo de Leitura: 9 Minutos

Se você já coordenou uma equipe ou atua como um líder de produto (Product Owner) ou Gerente de Projeto e tem uma equipe principalmente formada ou integrada com programadores mais jovens ou recém formados, já deve ter ouvido absurdos como “O código funciona? Não toque nele” ou simplesmente “Funciona? Sim, então passa para o teste” ou “Conclua o mais rápido possível, voltaremos a refatorar mais pra frente” e em contra partida, devolutivas de programadores como “Na minha máquina funciona perfeitamente” ou “Ninguém vai fazer isso dessa maneira” ou “Com a biblioteca X seria bem mais fácil”. Se você não gosta de ouvir isso, ou já está cansado desse tipo de conversa, é hora de aceitar a verdade em vez de protelar, é hora de avaliar a qualidade geral da sua base de código e vamos falar sobre algumas métricas e ferramentas que podemos utilizar para mudar ou amenizar essa e algumas outras soluções desagradáveis.

À medida que um sistema de qualquer tipo evolui, sua complexidade aumenta, a menos que seja feito algum trabalho para mantê-lo ou reduzi-la e resolver os problemas de código o quanto antes é o principal ponto para a mudança. Usar boas métricas de código para encontrar código de baixa qualidade é uma decisão que pode aumentar um pouco o tempo de produção, mais vai reduzir drasticamente o tempo gasto com manutenções, e correções, além de evitar um prejuízo causado por bugs ou falhas de segurança. Abaixo, falamos de algumas das métricas que devemos aplicar para entender o qualidade geral de nossa base de código.

1. Complexidade Ciclamática – Complexidade do Código

A complexidade ciclamática é uma métrica de complexidade de código. A maioria das ferramentas de análise estática o usa hoje em dia, para prever códigos complexos. Suas regras são baseadas em:
Mais ramificação – código complexo.
Alta métrica – código complexo.
Código mais Complexo – mais testes. A complexidade ciclomática é uma boa métrica inicial.

Gráfico de fluxo de complexidade ciclomáticaNa imagem, você consegue entender, o fluxo básico de um programa sendo executado, e a complexidade é o número de possíveis caminhos que  o software pode tomar para chegar a sua conclusão de processamento. Essa métrica pode ser realizada para uma função, ou objeto ou mesmo método específico.

A complexidade ciclamática pode ser calculada manualmente se o programa for pequeno, porém, existem ferramentas automatizadas que precisam ser usadas se o programa for muito complexo, pois envolve mais gráficos de fluxo. Com base no número de complexidade, a equipe pode concluir sobre as ações que precisam ser tomadas para a medição.

A tabela a seguir fornece uma visão geral sobre a complexidade, de acordo com o número calculado, que as ferramentas calculam entre 1 e 100, ou mais comummente resultam entre 1 e 50, sendo definido que:
Valores entre 1 e 10 – Código estruturado e bem escrito, alta escalabilidade e baixo custo de manutenção. De 11 a 20 – Código mediano, ainda pode ser escalado e a maior dificuldade é na realização de testes, que precisam ser muito bem escritos para cobrir todas as possibilidades e garantirem a qualidade final do processo. Valores de 21 a 40 indicam códigos complexos, dificilmente os testes conseguirão abranger toda a extensibilidade gerando esforços consideráveis de manutenção e escalabilidade. Valores acima de 40 indicam complexidade excessiva, e impossibilidade de garantia de testabilidade, escalabilidade e são um dos indicativos de que este processo ou parte do código deve ser refatorado ou substituído.

Algumas ferramentas de cálculo de complexidade são usadas para tecnologias específicas. A complexidade pode ser encontrada pelo número de pontos de decisão em um programa. Os pontos de decisão são if, for, for-each, while, do, catch, instruções case em um código-fonte. Algumas dessas ferramentas contam a complexidade, simplesmente somando o número de caminhos que podem ser percorridos e usando os laços e aninhamentos como multiplicadores, por exemplo um código com um IF/ELSE tem valor 2, um IF/ELSEIF/ELSE tem peso 3. Um CASE tem peso, dependendo do número de opções. Um laço, tem peso 2, por exemplo um WHILE, um FOR. Se for um aninhamento o valor é o produto da multiplicação, por exemplo um WHILE com um IF/ELSE dentro teria valor 4.

A complexidade ciclomática pode ser muito útil em ajudar os desenvolvedores e testadores a determinar execuções de caminhos independentes. Os desenvolvedores a poderem garantir que todos os caminhos foram testados pelo menos uma vez durante a fase de produção do código. Nos ajuda a nos concentrar mais nos caminhos descobertos. Melhorar a cobertura de código em Engenharia de Software. Avaliar o risco associado ao aplicativo ou programa.

Usar essas métricas no início do ciclo reduz mais o risco do programa, se os pontos de decisão forem maiores, então a complexidade do programa será maior. Se o programa tiver um número de alta complexidade, a probabilidade de erro será alta com o aumento do tempo para manutenção e solução de problemas também.

Uma alternativa é o uso de uma métrica chamada “A métrica de estradas acidentadas e esburacadas” que mede um valor para construções aninhadas, e aumenta com o nível de aninhamento. A estrada esburacada mede o seguinte, Número de linhas sob uma colisão, Profundidade dos solavancos, Número de saliências.

2. Identifique os aspectos de qualidade do código

Seguindo o que diz a norma ISO:25010 O modelo de qualidade determina quais características de qualidade serão levadas em consideração ao avaliar as propriedades de um produto de software, a qualidade de um sistema é o grau em que o sistema satisfaz as necessidades declaradas e implícitas de suas várias partes. As necessidades dessas partes como funcionalidade, desempenho, segurança, manutenibilidade, etc são exatamente o que está representado no modelo de qualidade, que categoriza a qualidade do produto em características e sub-característica.

O modelo de qualidade do produto definido na ISO / IEC 25010 compreende as oito características de qualidade relacionadas a seguir:

  1. Adequação Funcional – Essa característica representa o grau em que um produto ou sistema fornece funções que atendem às necessidades declaradas e implícitas quando usadas sob condições especificadas. Essa característica é composta das seguintes sub-características:
    • Completude Funcional (Functional Completeness)
    • Correção Funcional (Functional Correctness)
    • Adequação Funcional (Functional Appropriateness)
  2. Eficiência de Desempenho – Essa característica representa o desempenho em relação à quantidade de recursos usados nas condições declaradas. Essa característica é composta das seguintes sub-características:
    • Comportamento Temporal (Time Behavior)
    • Utilização de Recursos (Resource Utilization)
    • Capacidade (Capacity)
  3. Compatibilidade – Grau para o qual um produto, sistema ou componente pode trocar informações com outros produtos, sistemas ou componentes, e / ou executar suas funções necessárias, enquanto compartilha o mesmo ambiente de hardware ou software. Essa característica é composta das seguintes sub-características:
    • Co-existência (Co-existence)
    • Interoperabilidade (Interoperability)
  4. Usabilidade – Grau em que um produto ou sistema pode ser usado por usuários específicos para atingir metas especificadas com eficácia, eficiência e satisfação em um contexto específico de uso. Essa característica é composta das seguintes sub-características:
    • Reconhecimento de Adequabilidade (Appropriateness Recognizability)
    • Aprendizagem (Learnability)
    • Operacionalidade (Operability)
    • Proteção Contra Erros do Usuário (User Error Protection)
    • Estética da Interface do Usuário (User Interface Aesthetics)
    • Acessibilidade (Accessibility)
  5. Confiabilidade – Grau para o qual um sistema, produto ou componente executa funções especificadas sob condições especificadas por um período de tempo especificado. Essa característica é composta das seguintes sub-características:
    • Maturidade (Maturity)
    • Disponibilidade (Availability)
    • Tolerância à Falhas (Fault Tolerance)
    • Recuperabilidade (Recoverability)
  6. Segurança – Grau para o qual um produto ou sistema protege informações e dados para que pessoas ou outros produtos ou sistemas tenham o grau de acesso a dados adequado aos seus tipos e níveis de autorização. Essa característica é composta das seguintes sub-características:
    • Confidencialidade (Confidentiality)
    • Integridade (Integrity)
    • Não Repúdio (Non-repudiation)
    • Autenticidade(Authenticity)
    • Prestação de Contas(Accountability)
  7. Manutenção / Manutenibilidade – Essa característica representa o grau de eficácia e eficiência com o qual um produto ou sistema pode ser modificado para melhorá-lo, corrigi-lo ou adaptá-lo a mudanças no ambiente e nos requisitos. Essa característica é composta das seguintes sub-características:
    • Modularidade(Modularity)
    • Reusabilidade(Reusability)
    • Analisabilidade(Analysability)
    • Modificabilidade(Modifiability)
    • Testabilidade(Testability)
  8. Portabilidade – Grau de eficácia e eficiência com o qual um sistema, produto ou componente pode ser transferido de um hardware, software ou outro ambiente operacional ou de uso para outro. Essa característica é composta das seguintes sub-características:
    • Adaptabilidade(Adaptability)
    • Instalabilidade(Installability)
    • Replacibilidade(Replaceability)

Já, o ISO / IEC 25012 define o modelo geral de Qualidade de Dados para dados retidos em um formato estruturado dentro de um sistema de computador. Ele se concentra na qualidade dos dados como parte de um sistema de computador e define as características de qualidade para dados de destino usados por seres humanos e sistemas.

  1. Qualidade Inerente de Dados – A qualidade de dados inerente refere-se ao grau em que as características de qualidade dos dados têm o potencial intrínseco de satisfazer as necessidades declaradas e implícitas quando os dados são usados ​​sob condições especificadas. Suas características são compostas por:
    • Precisão Sintática;
    • Precisão semântica;
    • Completude;
    • Consistência;
    • Credibilidade;
    • Atualidade
  2. Qualidade de Dados Inerente e Dependente do Sistema – A qualidade de dados inerente e dependentes do Sistema é a transição entre dados Inerentes e dependentes que é composto pelas seguintes características:
    • Acessibilidade;
    • Conformidade;
    • Confidencialidade;
    • Eficiência;
    • Precisão;
    • Rastreabilidade;
    • Compreensibilidade.
  3. Qualidade de Dados Dependente do Sistema – A qualidade de dados dependentes do sistema refere-se ao grau em que a qualidade dos dados é alcançada e preservada dentro de um sistema de computador, quando os dados são usados ​​sob condições especificadas. Suas características são compostas por:
    • Disponibilidade;
    • Portabilidade;
    • Recuperabilidade.

3. Versão e Controle de Versionamento

Podemos usar o controle de versão para gerenciar as alterações de código, reverter um commit incorreto, ou
Você pode usar o controle de versão para medir a qualidade do código, combine-o com as métricas de complexidade do código e use a correlação para medir a qualidade final do código.

Mudanças frequentes no código afetam a complexidade, fazendo-a aumentar (inclusão de funcionalidades não, somente alterações estruturais ou organizacionais) explorar essa opção para criar um gráfico, por exemplo relacionando o número de Patchs e de minor versions (correções / implementações) e o numero de versões major que quebram a compatibilidade.

A correção de pontos de acesso tem um alto impacto na qualidade do código, a maioria das bases de código segue a mesma distribuição de pontos de acesso e os arquivos menos alterados têm um pequeno impacto na qualidade geral. Usar esse tipo de métrica por função ou por método/objeto ou mesmo por arquivo, ao longo do tempo, pode mostrar os pontos que provavelmente precisam de mais atenção e onde devemos investir mais tempo desenvolvendo ou refatorando ou mesmo reescrevendo para analisar onde está o ponto mais frágil de qualidade de nossos dados.

4. Distância de acoplamento

O encapsulamento incorreto produz código procedural, se você consulta o estado do objeto, obtém resultados e trabalha com os resultados, o código deveria ter uma abordagem procedimental, pois não segue os princípios OOP. O encapsulamento incorreto cria um software tagarela, uma enorme quantidade de getters e setters expõe os objetos e cria um acoplamento forte e dependente, pois a mudança em um objeto desencadeia uma cascata de outras mudanças em novas partes, que ferem o principio principal da OO que é a independência.

Objetos menos faladores, possuem um acoplamento fraco. Você oculta o estado interno e deixa uma interface mais fina. Ter que se preocupar muito com o estado interno e getters/setters te afasta do domínio do problema, por exemplo radio.tuners.volume_tuner.up(); e radio.volumeUp(); O segundo exemplo não consulta o estado interno, o primeiro sim. O cliente agora sabe qual objeto interno rádio possui, e isso é algo com o qual o chamador não tem de se importar.

Você deve medir o número de usos dos objetos retornados, quanto mais você usa os objetos retornados, maior é a distância e isso significa acoplamento mais apertado. Usar essa métrica, pode ser um ponto para encontrar pontos fracos e falhos em seu código.

Conclusão

Você pode usar essas e outras métricas para medir a qualidade final de suas bases de código, quanto maior a experiência, maior a facilidade em encontrar medições que façam uma avaliação geral da saúdo do código auxiliando a encontrar pontos de falha. O que você pode ser tentado a usar, mais que minha experiência, trouxe mais problemática do que soluções, são medições AdHoc ou pontuais pois podem causar viés já que realmente algumas partes podem ter complexidades maiores que outras, e a qualidade pode ser medida pontualmente, porém, avaliada globalmente.