flowchart TB
Q[Qualidade de Produto<br/>ISO/IEC 25010]
Q --> F[Adequação Funcional]
Q --> P[Eficiência de Desempenho]
Q --> C[Compatibilidade]
Q --> U[Usabilidade]
Q --> R[Confiabilidade]
Q --> S[Segurança]
Q --> M[Manutenibilidade]
Q --> T[Portabilidade]
style Q fill:#cfe2ff,stroke:#084298,stroke-width:2px
style F fill:#d1e7dd,stroke:#0f5132
style P fill:#d1e7dd,stroke:#0f5132
style C fill:#fff3cd,stroke:#664d03
style U fill:#fff3cd,stroke:#664d03
style R fill:#e2d9f3,stroke:#59359a
style S fill:#e2d9f3,stroke:#59359a
style M fill:#f8d7da,stroke:#842029
style T fill:#f8d7da,stroke:#842029
Módulo 14 — Qualidade de Software: fatores e métricas
Onde estamos. Já aprendemos a especificar, projetar, implementar e testar software. Agora vem a pergunta que costura tudo isso: como saber se o que construímos é bom? Ao fim deste módulo você deve saber definir qualidade de software, reconhecer os modelos que a organizam, medir atributos internos do código e nomear os sintomas de código adoecido.
Deixe-me abrir com uma provocação. Você aprendeu a fazer software funcionar. Mas se eu lhe perguntasse agora “este seu sistema tem qualidade?”, o que responderia? A maioria diz “sim, ele funciona” — e essa resposta é insuficiente. Um sistema pode passar em todos os testes, atender a todos os requisitos e ainda ser um péssimo software: lento demais, impossível de manter, inseguro, ininteligível para o próximo desenvolvedor. Qualidade é mais do que ausência de defeitos, e transformar essa palavra vaga em algo que se pode discutir, comparar e medir é o trabalho deste módulo.
O que é, afinal, qualidade de software
Comecemos pelo significado, porque “qualidade” é traiçoeira: cada pessoa a preenche com um sentido diferente. Na engenharia de software, convergimos para uma definição de duas pernas. A primeira é a conformidade com os requisitos: um software de qualidade faz o que foi especificado e atende às restrições combinadas com o cliente. Se o requisito diz que o sistema deve calcular juros de certa forma e ele calcula de outra, há um defeito — e defeito é ausência de qualidade no sentido mais básico. É a visão que Pressman associa à conformidade explícita.
A segunda perna separa o profissional do amador: a adequação ao uso. Um software pode estar em perfeita conformidade com o documento de requisitos e ainda ser inadequado, porque os requisitos escritos nunca capturam tudo o que o usuário precisa. Existem expectativas implícitas — que o sistema seja rápido, que não perca dados, que seja compreensível — que raramente aparecem por escrito, mas cuja violação o usuário sente na pele. Qualidade de verdade é a soma das duas: conformidade com o que foi pedido mais adequação ao que o uso real exige.
Qualidade de software não é “funcionar”. É a combinação de duas coisas: conformidade com os requisitos especificados e adequação ao uso pretendido, incluindo as expectativas implícitas que nenhum documento chega a registrar. Um sistema que atende à letra do requisito mas frustra o uso real ainda é um software de baixa qualidade.
Essa definição embute uma dificuldade prática. Se metade da qualidade mora em expectativas não escritas, não basta um checklist: precisamos de modelos que decomponham a palavra “qualidade” em atributos nomeáveis e de métricas que os tornem observáveis. É para esses dois instrumentos que caminhamos agora.
Modelos de qualidade de produto
Se qualidade é grande demais para avaliar de uma vez, a saída é decompô-la. Um modelo de qualidade faz isso: quebra a noção abstrata em características e subcaracterísticas que podemos discutir e medir separadamente. O primeiro grande esforço veio de Jim McCall, que na década de 1970 propôs fatores de qualidade organizados em três perspectivas: operação, revisão e transição do produto. Sob a operação estavam correção, confiabilidade, eficiência, integridade e usabilidade; sob a revisão, manutenibilidade, flexibilidade e testabilidade; sob a transição, portabilidade, reusabilidade e interoperabilidade. McCall foi pioneiro por tentar ligar esses fatores externos, percebidos pelo usuário, a critérios internos mensuráveis no código.
Décadas depois, a comunidade internacional consolidou um modelo mais completo na norma ISO/IEC 25010, que descreve a qualidade de produto por meio de oito características, cada uma desdobrada em subcaracterísticas. Conheça as oito pelo nome, porque formam o vocabulário padrão da discussão profissional de qualidade.
A adequação funcional é o software cumprir as funções pretendidas de modo completo e correto. A eficiência de desempenho é o uso econômico de tempo e recursos, incluindo tempo de resposta e capacidade. A compatibilidade é conviver e trocar informação com outros sistemas. A usabilidade mede o quanto pessoas operam o sistema com eficácia, eficiência e satisfação. A confiabilidade é manter o desempenho sob condições determinadas ao longo do tempo, incluindo tolerância a falhas e recuperação. A segurança protege informações e operações contra acessos e usos indevidos. A manutenibilidade mede a facilidade de modificar o software, abrangendo modularidade, reusabilidade, analisabilidade, modificabilidade e testabilidade. E a portabilidade é a facilidade de transferir o software de um ambiente para outro.
Quando você precisar avaliar, comparar ou negociar a qualidade de um sistema, essas oito características dão a você uma linguagem comum e completa. Em vez de dizer “o sistema é ruim”, você passa a dizer “a adequação funcional está boa, mas a manutenibilidade e a segurança estão comprometidas” — e um diagnóstico específico assim já aponta para a solução.
Note que a ISO/IEC 25010 separa a qualidade do produto da qualidade em uso, esta voltada ao que o usuário experimenta ao operar o sistema em contexto real. Aqui o foco é a qualidade de produto, que se conecta diretamente às métricas de código a seguir.
Qualidade de produto e qualidade de processo
Há uma distinção que confunde muitos estudantes e preciso deixar clara. Tudo até aqui é qualidade de produto: atributos do artefato que entregamos. Mas existe uma segunda frente, a qualidade de processo, que trata do modo como construímos o software — se seguimos práticas disciplinadas, revisamos código, testamos sistematicamente, controlamos versões e gerenciamos requisitos com rigor.
A relação entre as duas é a espinha dorsal da garantia de qualidade moderna, e Pressman insiste nela: processos de boa qualidade tendem a produzir produtos de boa qualidade. Não dá para “consertar” qualidade no fim, inspecionando um produto pronto e devolvendo o ruim; software é caro demais para isso. É muito mais eficaz construir a qualidade ao longo do caminho, com cada etapa sólida. A tabela abaixo contrasta as duas dimensões.
| Aspecto | Qualidade de produto | Qualidade de processo |
|---|---|---|
| O que avalia | O software entregue e seus atributos | O modo de trabalhar da equipe |
| Exemplos | Confiabilidade, manutenibilidade, desempenho | Revisões de código, cobertura de testes, controle de versões |
| Quando se observa | Sobre o artefato pronto ou em construção | Ao longo de todo o desenvolvimento |
| Referência típica | ISO/IEC 25010, modelo de McCall | Modelos de maturidade de processo |
| Suposição central | É o que o cliente recebe | Bom processo tende a gerar bom produto |
Guarde a ponte: não investimos em processo por burocracia, e sim porque é a forma economicamente viável de obter produtos de qualidade de modo repetível, sem depender do heroísmo de um desenvolvedor.
Métricas de software: tornando a qualidade observável
Chegamos ao coração quantitativo do módulo. Uma frase resume o espírito: não se gerencia o que não se mede. Se qualidade é feita de atributos, precisamos de métricas que os tornem observáveis. Uma métrica é uma medida atribuída, segundo regra clara, a uma característica do software, para que possamos acompanhá-la, compará-la e agir sobre ela. Vejamos as clássicas.
A mais antiga e ingênua é a contagem de linhas de código, ou LOC. Mede tamanho e serve para estimar esforço, comparar módulos e normalizar outras métricas. Mas use-a com humildade: LOC mede volume, não valor nem qualidade. Um sistema com mais linhas não é melhor; muitas vezes é pior, porque mais código significa mais superfície para defeitos e mais peso de manutenção. Usar LOC como medida de produtividade individual é um erro clássico que premia o comportamento errado.
Bem mais informativa é a complexidade ciclomática, de Thomas McCabe. Mede o número de caminhos linearmente independentes por um trecho de código, o que se traduz em quão difícil é entendê-lo e testá-lo. Modelando o programa como um grafo de fluxo de controle com E arestas e N nós, a complexidade de um componente é
V(G) = E - N + 2
Cada estrutura de decisão — cada if, while, case — acrescenta um caminho e aumenta V(G). Complexidade 1 é uma linha reta sem desvios; complexidade 15 é um labirinto que exige muitos testes e resiste ao entendimento. A métrica também informa quantos casos de teste, no mínimo, cobrem todos os caminhos independentes — por isso liga qualidade de código a esforço de teste de forma direta.
Um atalho útil: comece em 1 e some 1 por ponto de decisão. Como referência qualitativa, valores baixos indicam código simples e testável; valores altos indicam uma função que faz coisas demais e pede para ser quebrada. Quando V(G) cresce muito, o número está lhe dizendo que a função virou um problema de manutenibilidade.
Duas métricas estruturais completam o quadro e caminham juntas: coesão e acoplamento. A coesão mede o quanto os elementos de um módulo pertencem uns aos outros — se a classe ou função tem responsabilidade única e bem definida. O acoplamento mede o quanto um módulo depende de outros. O ideal, perseguido desde o módulo de projeto, é alta coesão e baixo acoplamento: cada peça faz uma coisa bem feita e conhece o mínimo das demais. Módulos com baixa coesão e alto acoplamento são frágeis, porque uma mudança em um ponto se propaga imprevisivelmente. Por fim, a densidade de defeitos normaliza a contagem pelo tamanho — tipicamente defeitos por mil linhas — permitindo comparar a confiabilidade de módulos de tamanhos diferentes de forma justa.
| Métrica | O que mede | Direção desejável |
|---|---|---|
| Linhas de código (LOC) | Tamanho do software | Menor, para a mesma função |
| Complexidade ciclomática V(G) | Caminhos independentes de execução | Menor por função |
| Coesão | Unidade de propósito de um módulo | Maior |
| Acoplamento | Dependência entre módulos | Menor |
| Densidade de defeitos | Defeitos por unidade de tamanho | Menor |
Dívida técnica: o custo do atalho
Antes de olharmos o código em si, preciso apresentar um conceito que reorganiza como você tomará decisões de engenharia pelo resto da carreira: a dívida técnica. A metáfora, de Ward Cunningham, é financeira e certeira. Quando escolhemos uma solução mais rápida e menos correta para entregar logo — um atalho, uma gambiarra, uma estrutura mal projetada porque “depois a gente arruma” —, tomamos um empréstimo. Ganhamos velocidade agora, mas contraímos uma dívida que cobra juros: cada modificação futura naquele trecho fica mais cara, lenta e arriscada.
O ponto sutil é que dívida técnica nem sempre é ruim. Como um empréstimo consciente pode ser bom negócio, um atalho deliberado para validar uma ideia com rapidez pode ser legítimo — desde que a dívida seja reconhecida e paga antes que os juros sufoquem o projeto. O que destrói sistemas é a dívida invisível e não paga, acumulada sem registro nem plano, até que mudar qualquer coisa se torna proibitivo. Aí a equipe gasta toda a energia só pagando juros, sem avançar. Dívida técnica é, no fundo, um ataque contínuo à manutenibilidade.
Maus cheiros de código e a ideia de Código Limpo
Se a dívida técnica é o conceito econômico, os maus cheiros de código — os code smells — são seus sintomas concretos e observáveis. O termo, popularizado na refatoração, descreve padrões que não são defeitos, porque o programa funciona, mas sinalizam problemas mais profundos de estrutura. São como o cheiro de algo estragado na cozinha: não a doença em si, mas um aviso confiável de que algo precisa de atenção.
Você já conhece vários intuitivamente. Métodos longos demais, que fazem coisas demais. Classes gigantes que concentram responsabilidades que deveriam estar separadas. Listas de parâmetros extensas. Código duplicado, de modo que uma correção precisa ser feita em cinco pontos. Nomes obscuros que não revelam intenção. Comentários que existem para explicar um código que deveria ser claro por si. Cada cheiro aponta para um princípio de projeto violado.
É aqui que entra Robert Martin e a ideia de Código Limpo. Sua tese central é que código é lido muito mais vezes do que é escrito, e portanto legibilidade não é luxo, é requisito profissional. Escrevemos o código uma vez; nós e outros o leremos dezenas de vezes ao longo dos anos. Martin defende práticas que convergem para esse objetivo: nomes que revelam intenção, para o código se explicar sem comentários; funções pequenas que fazem uma única coisa e a fazem bem; ausência de duplicação; e preocupação artesanal com a forma, tratando o código como comunicação entre humanos, não apenas instruções para a máquina. É a lição do primeiro módulo do curso, agora com nome e método: entre agradar a máquina e agradar o leitor humano, a boa engenharia favorece o leitor humano, pois é ele quem sustenta o sistema no tempo.
Um cheiro de código não é um erro que quebra o programa. É um sinal de que a estrutura interna está se deteriorando e de que a dívida técnica está crescendo. Ignorar cheiros por serem “só estética” é como ignorar um vazamento por a casa ainda estar de pé: o custo chega, com juros.
Do cheiro à métrica: um exemplo em Dart
Vamos tornar isso concreto. Considere uma função que classifica o risco de um pedido num sistema de vendas. Ela funciona, passa nos testes, entrega o resultado certo. E, ainda assim, cheira mal.
Conte comigo os cheiros. Os nomes não revelam nada: o que são v, a, n, f? Há aninhamento profundo de condicionais, a “seta” que o código desenha na tela. Há valores mágicos como 10000 e 30 sem explicação. Há strings soltas como retorno, sujeitas a erro de digitação. E a complexidade ciclomática é elevada: de 1, somando os pontos de decisão — cinco if com desvios —, chegamos a V(G) = 6 nesta função pequena, o que exige seis caminhos de teste.
Agora a mesma regra, reescrita com as ideias de Código Limpo e com atenção às métricas.
enum NivelRisco { baixo, medio, alto }
class Pedido {
final double valor;
final int idadeContaDias;
final bool clienteNovo;
final bool pagamentoAntecipado;
const Pedido({
required this.valor,
required this.idadeContaDias,
required this.clienteNovo,
required this.pagamentoAntecipado,
});
bool get ehAltoValor => valor > limiteAltoValor;
bool get ehContaRecente => idadeContaDias < diasContaConfiavel;
static const double limiteAltoValor = 10000;
static const int diasContaConfiavel = 30;
}
NivelRisco classificarRisco(Pedido p) {
if (p.ehAltoValor && p.ehContaRecente && p.clienteNovo) {
return NivelRisco.alto;
}
if (p.ehAltoValor && p.ehContaRecente && !p.pagamentoAntecipado) {
return NivelRisco.alto;
}
if (p.ehAltoValor) {
return NivelRisco.medio;
}
return p.pagamentoAntecipado ? NivelRisco.baixo : NivelRisco.medio;
}O que mudou vale a análise. Os nomes revelam intenção: ehAltoValor e ehContaRecente explicam o que eram comparações mágicas. Os números viraram constantes nomeadas. O retorno virou enum, eliminando strings frágeis. O aninhamento desapareceu: em vez da seta, temos cláusulas de guarda que retornam cedo, prática central de Código Limpo. A coesão subiu, porque Pedido agrupa os dados que pertencem juntos e responde por seus predicados. E a complexidade? classificarRisco tem poucos pontos de decisão em sequência, sem aninhamento, o que reduz V(G) e torna cada caminho legível de imediato. Note que não reduzimos linhas — escrevemos mais. Mas trocamos linhas baratas e obscuras por linhas que comunicam, e é essa troca que a manutenibilidade recompensa.
flowchart LR
A[Code smells<br/>nomes ruins, aninhamento,<br/>valores mágicos] --> B[Dívida técnica<br/>cresce]
B --> C[Manutenibilidade<br/>cai]
C --> D[Custo de mudança<br/>sobe com juros]
D -. refatoração guiada<br/>por Código Limpo .-> A
style A fill:#f8d7da,stroke:#842029
style B fill:#fff3cd,stroke:#664d03
style C fill:#fff3cd,stroke:#664d03
style D fill:#f8d7da,stroke:#842029
Perceba como os conceitos deste módulo se encadeiam. Um cheiro de código sinaliza dívida técnica; a dívida corrói a manutenibilidade, uma das oito características da qualidade de produto; a manutenibilidade se mede por métricas como complexidade ciclomática, coesão e acoplamento; e a refatoração guiada pelas ideias de Código Limpo é o que paga a dívida e restaura a métrica a um patamar saudável. Qualidade, medida e prática formam um único circuito.
Síntese
Retenha três ideias. A primeira é que qualidade de software é a soma de conformidade com os requisitos e adequação ao uso, noção que se torna gerenciável quando a decompomos nas oito características da ISO/IEC 25010 — herdeiras do esforço pioneiro de McCall. A segunda é que qualidade de produto e de processo são dimensões distintas e complementares: bom processo tende a gerar bom produto, e por isso construímos qualidade ao longo do caminho, não por inspeção no fim. A terceira é que temos instrumentos concretos para agir — métricas como complexidade ciclomática, coesão, acoplamento e densidade de defeitos tornam a qualidade observável, enquanto dívida técnica, code smells e Código Limpo dão o vocabulário e a prática para diagnosticar e curar o código adoecido.
Para consolidar antes da aula: pegue uma função de qualquer código que você já escreveu, conte à mão sua complexidade ciclomática somando os pontos de decisão a partir de 1, e identifique ao menos dois cheiros de código nela. Depois, reescreva-a com nomes que revelem intenção e cláusulas de guarda, e observe se V(G) caiu. Se você conseguir explicar por que a nova versão é mais fácil de manter, entendeu o essencial deste módulo.