- A qualidade de código não é acompanhada durante o projeto
- Software é entregue com má qualidade interna (Arquitetura frágil e design rígido dificultando a manutenção)
- Coleta sistemática e periódica das medições de qualidade do código
- Maximização da automação da coleta das métricas
- Nortear análises de acordo com o objetivo (desempenho, reuso, simplicidade)
- É uma convenção de medida do grau que um software tem para uma propriedade
- Métricas: funções utilizadas para medir a propriedade em questão
- Medidas: valores das métricas
- Métricas e Medidas são só indicadores, e devem ser observados para cada contexto (em particular)
- Complexidade: o grau de dificuldade para entender o projeto ou implementação
de um sistema
- Complicado: difícil compreensão, mas se torna compreensível com o tempo e esforço
- Complexo: grande número de interações entre um dado número de entidades
- Complexidade maior → dificuldade de utilização, entendimento e manutenção (aumenta os custos e o tempo necessário)
- Códigos complexos possuem baixa rubustez e maior propensão a erros
- As métricas de complexidade tentam traduzir de forma quantitativa a dificuldade de entendimento e o nível de complicação de um código
Métricas estáticas:
- Linhas de Código (SLOC)
- Complexidade ciclomática
- Fan-in/Fan-out
- Métricas de Halstead
- Índice de manutenibilidade
- Calcula o volume de código de um sistema contabilizando o total de linhas de
código no programa
- Prícipio de que quanto maior um programa, mais complexo e propenso a erros ele provavelmente será
- Utilizado pra predizer a quantidade de esforço necessário para o
desenvolvimento, produtividade ou manutenibilidade de um software
- Uma das métricas mais confiáveis
- Variações:
- Número total de linhas (incluindo comentários, linhas em branco, chaves e parênteses isolados)
- Número de linhas de código efetivas (desconsiderando o que não é codigo)
- Número de linhas de código que representem instruções completas finalizadas e com ";" ou outro símbolo que determine o fim de instrução
- Pontos positivos:
- Simplicidade
- Fácil coleta (automatizada ou não)
- Uso popular para medição de complexidade
- Pontos negativos:
- Falta de padronização
- Desconsideração da qualidade e estruturação do código
- Dependência de linguagem de programação
- Mede a complexidade estrutural de acordo com a quantidade de caminhos independentes que podem ser executados até o seu fim
- Caminho independente é aquele que apresenta pelo menos uma nova condição
(possibilidade de desvio de fluxo) ou um novo conjunto de comandos executados
- Correlação com o grau de compreensão do programa
- Usa um grafo de fluxo de controle (GFC), que é um grafo dirigido que
representa todos os caminhos do programa que podem ser executados
- Nós representam uma ou mais instruções sequenciais
- Arestas representam o fluxo de controle entre blocos de instruções (nós)
- Existe um nó de entrada (início) e um nó de saída (fim)
- É necessário determinar os componentes conexos no GFC
- Programa ou rotina únicos, número de componentes conexos é sempre igual a 1
- Fórmula Geral:
CC = E - N + (2 * P)
- E → número de arestas
- N → número de nós
- P → número de componentes conexos
- Caso o nó de saída seja conectado ao nó de entrada por meio de uma aresta, o
GFC se torna fortemente conexo, e a complexidade ciclomática passa a ser
calculada como
CC = E - N + P
- Interpretação:
- 1 - 4: Simples (risco baixo)
- 5 - 10: Bem estruturado e estável (risco baixo)
- 11 - 20: Mais complexo (risco moderado)
- 21 - 50: Complexo (risco alto)
- 50+: Altamente problemático (risco muito alto)
- Pontos Positivos:
- Indicador de custo de manutenção e riscos
- Indicador de qualidade em termos de complexidade
- Ênfase em partes que demandariam mais testes e/ou eventuais refatorações
- Utilização para programas procedurais ou OO
- Pontos Negativos:
- Medição apenas da complexidade do fluxo e não dos dados
- Possibilidade de obtenção de diferentes medidas de acordo com a ferramenta
- Complexidade para determinação do GFC
- Representa a quantidade de informação que flui como entrada e saída em uma programa, possibilitando determinar a interconexão das unidades lógicas
- Fan-In: número de rotinas que chamam a rotina (entram na rotina)
- Valor alto → alto acoplamento entre as rotinas, grandes efeitos colaterais
- Fan-Out: número de rotinas chamadas pela rotina (saem da rotina)
- Valor alto → alta complexidade da rotina
- Fórmula:
C = L * (Fi * Fo)²
- L → Qualquer medida de tamanho (tipicamente SLOC)
- Fi → Fan-In
- Fo → Fan-Out
- Pontos Fortes:
- Considera o fluxo de dados na mensuração da complexidade
- Pontos Fracos:
- Possui valor zero quando os valores de Fan-In e/ou Fan-Out são 0
- Partem da interpretação do código fonte como uma sequência de tokens classificados entre operadores e operandos
- Medidas determinadas através da contagem dos operadores e operandos distintos
de um programa, resultando em diferentes aspectos
- Operandos → Variáveis, constantes e nomes de tipos (primitivo ou composto)
- Operadores → quaisquer outros elementos
- Contabilizam:
- n1 → Número de operadores distintos
- n2 → Número de operandos distintos
- N1 → Número total de operadores
- N2 → Número total de operandos
- Computadas como:
- Tamanho do programa (N) →
N = N1 + N2
- Tamanho do vocabulário (n) →
n = n1 + n2
- Volume do programa (V) →
V = N * log2(n)
(tamanho da implementação) - Nível de dificuldade (D) →
n1/2 * N1/n2
- Esforço (E) →
E = D * V
(esforço para implementação e compreensão) - Tempo para implementação (T) →
E/18
(segundos)
- Tamanho do programa (N) →
- Métrica composta que indica o nível de dificuldade geral para a manutenção de um código fonte
- Calculado como uma correlação entre o tamanho do programa (SLOC), Métricas de Halstead e Complexidade Ciclomática
- Fórmula:
MI = 171 - (5.2 * ln(V')) - (0.23 * C') - (16.2 * ln(L'))
- V' → Média da métrica de Halstead de volume por módulo
- C' → Média da complexidade ciclomática por módulo
- L' → Número médio de linhas de código fonte (SLOC) por módulo
- Interpretação:
- 85+ → Boa manutenibilidade
- 65 - 85 → Moderada manutenibilidade
- 65- → Difícil manutenção
Conceitos importantes em OO:
- Coesão → Grau de conectividade entre os elementos de uma classe ou objeto
- Acoplamento → Grau de relacionamento ou interdependência de módulos, classes ou objetos
- Herança → Compartilhamento de atributos e métodos entre classes
- Encapsulamento → Abstração de dados para ocultar a especificação interna de um objeto e apenas exibir sua interface externa
Principais Métricas OO:
- Métricas de pacotes
- Métricas CK (Chidamber & Kemerer)
- Métricas MOOD (Metrics for OO Design)
Analisa classes e interfaces em pacotes quanto a:
- Acoplamento/Dependência
- Grau de abstração
- Instabilidade
- Acoplamento aferente → número de classes de outros pacotes que dependem das classes do pacote analisado (nível de responsabilidade do pacote)
- Acoplamento eferente → número de classes de outros pacotes que as classes do pacote analisado dependem (grau de dependências externas do pacote)
- Resiliencia de uma classe/pacote a eventuais mudanças (estabilidade é o esforço necessário para realizar mudanças em uma classe/pacote sem impactar outras classes/pacotes com os quais ela está relacionada)
- Fórmula:
I = Ce / (Ca + Ce)
- Ca → Acoplamento Aferente
- Ce → Acoplamento Eferente
- A instabilidade I possui valor entre 0 e 1, sendo:
- I = 0 → máxima estabilidade (Ce = 0 → não depende de pacotes externos)
- I = 1 → máxima instabilidade (Ca = 0 → possui apenas dependência extena)
- Interpretação
- Classes/Pacotes com múltiplas dependências externas, porém com poucas dependências deles são menos estáveis devido a consequências de mudanças
- Classes/Pacotes com mais dependências deles são mais estáveis por serem de difícil mudança
- Mede a extensibilidade de um pacote, ou seja, o quão abstratas são suas classes para permitir extensões
- O grau de abstração A de um pacote P é calculado como:
A = Na/Nr
- Na → número de classes abstratas contidas no pacote
- Nr → número total de classes contidas no pacote
- O grau de Abstração A possui valor entre 0 e 1, sendo:
- A = 0 → completamente concreto (Na = 0 → não possui classes abstratas)
- A = 1 → completamente abstrato (Na = Nr → não possui classes concretas)
- Interpretação:
- Grau de abstração mais próximo de 0 indica que o pacote é mais aberto a mudanças na implentação de suas classes
- Deve ser correlacionado com a medida de instabilidade
Representa o conjunto mais popular de métricas utilizadas para analisar programas OO:
- Identificação de classes
- Identificação da semantica de classes
- Identificação dos relacionamentos entre classes
- Implementação de classes
- Métrica originalmente proposta como a soma das medidas de complexidade ciclomática dos métodos da classe
- Interpretação
- WMC Maior → Maior a complexidade
- Muitos métodos → Maior o potencial de impacto em possíveis subclasses
- Representa o número de níveis na hierarquia de classes
- Calculada pela contabilização do número de classes que ela deriva (Indica o número de superclasses que podem afetar potencialmente a classe em questão)
- Interpretação:
- Maior a profundidade de uma classe na hierarquia (DIT maior) → maior o número de métodos que provavelmente ela irá herdar, tornando mais complexo compreender seu comportamento e maior o potencial de reuso dos métodos herdados
- DIT maior → maior a complexidade do projeto (+ classes/métodos envolvidos)
- É a medida do número de subclasses imediatas de uma classe, medindo sua ramificação na hierarquia de classes
- Interpretação:
- NOC alto → maior reuso (herança pode ser uma forma de reuso)
- Quanto mais alto o NOC → maior a probabilidade de abstrações impróprias
- Ele provê indícios da influência potencial de uma classe no projeto, demandando assim um maior número de testes
É a medida do grau de acoplamento entre classes (Uma classe é acoplada a outra quando uma utiliza métodos ou atributos da outra)
- Calculado pela contabilização do número de classes acopladas a ela
- Contabilização bidirecional → o CBO de uma classe A é o tamanho do conjunto de classes que A referencia e das classes que fazem referência a A
- Cada classe é contabilizada apenas uma vez, mesmo se a referência entre elas for bidirecional
- Interpretação:
- COB alto → classes altamente dependentes, menos modulares
- Maior acoplamento → menor manutenibilidade, pois mudanças em uma classe impactarão (negativamente) nas demais
- Maior número de relacionamentos entre classes → maior complexidade do código e da dificuldade para realização de testes
- É a medida do número de métodos invocados em resposta a uma mensagem recebida por um objeto dessa classe
- Interpretação:
- Maior o número de métodos → maior a complicação em teste e depuração
- Maior RFC → maior a complexidade e a propensão a erros
- Originalmente calculada como a diferença entre o número de pares de métodos sem atributos compartilhados (não similaridade)
- Motivo de controvérsia
- Diversas variações para medição
- Não há evidências sobre a sua real utilidade em comparação às demais métricas CK
- Interpretação:
- Falta de coesão → classe deveria ser subdividida em duas ou mais subclasses
- Baixo LCOM → boa subdivisão da classe analisada (simples, + reuso, + coesa)
- Alto LCOM → maior complexidade e propensão a erros
Conjunto complementar/alternativo às métricas CK para analisar programas OO em:
- Encapsulamento
- Herança
- Polimorfismo
- Troca de Mensagens
- Definido como a soma das invisibilidades de todos os métodos das classes
- A invisibilidade de um método diz respeito ao percentual da classe do qual o método é ocultado
- Interpretação:
- Maior MHF → mais métodos privados, diminuindo potencial de reuso
- Baixo MHF → maior parte dos métodos possui visibilidade públic ou protected
- Definido como a soma das invisibilidades de todos os atributos das classes
- Interpretação:
- Maior AHF → mais atributos privados, menor potencial de reuso
- Baixo AHF → maior parte dos atributos possui visib. pública ou protectegida
- Definido como a soma dos métodos herdados em todas as classes
- Definido como a soma dos atributos herdados em todas as classes
- Refere-se à razão entre o número de métodos que redefinem métodos herdados e
o número máximo de possíveis situações polimórficas distintas
- Caso em que todos os novos métodos em uma superclasse são sobrescritos
- Interpretação:
- Baixo PF → pouco ou nenhum uso de polimorfismo
- Alto PF → maior número de métodos de uma superclasse são sobrescritos
- Refere-se à determinação do grau de acoplamento entre classes, desconsiderando os relacionamentos de herança
- Interpretação:
- Alto CF → maior complexidade → pior compreensão, manutenibilidade e reuso
- Deve-se reduzir acoplamento o quanto possível
Ambas tratam sobre classes, atributos, métodos, acoplamento e herança, mas apenas o MOOD trata sobre encapsulamento e polimorfismo
- É um conjunto de técnicas de projeto e programação objetivando a estabilidade e a segurança de um software independentemente de seu imprevisível
- Pode ser vista como forma de reduzir ou eliminar os efeitos da Lei de Murphy
- Consiste em cuidadosamente pensar antes de codificar e estabelecer todas as situações que poderão ocorrer no código antes de fazê-lo
- Promove melhoria do software e seu código em termos de:
- Qualidade Geral → reduzindo o número de bugs e problemas
- Grau de compreensão → código legível e compreensível
- Previsibilidade → software se comporte de forma previsível mesmo com entradas ou ações do usuário inesperadas
- Proteção contra entradas inválidas
- "Programas problemáticos não mentem"
- Design by Contract (DbC)
- Assertivas
- Alocação/Liberação
- Manipulação de erros
- Uso e tratamento de exceções
Verificar os valores de todos os dados vindos de fontes externas (entrada do usuário, arquivos, etc...) em termos de:
- Intervalos
- Tipos de dados
- Tamanho de dados
- Caracteres Inválidos
- Expressões Válidas
Assim, é necessário:
- Verificar os valores de todos os parmetros passados a uma rotina
- Decidir adequadamente o que fazer em caso de entradas inválidas
- Se em algum momento o programa apresentou um comportamento inesperado, há uma
falha no código
- Se um arquivo não pode ser aberto, ele não deve ter sido fechado corretamente em outro ponto do programa
- Se não foi possível alocar memória, o programa não deve estar a liberando
- Nunca ignorar mensagens de avisos (warnings) emitidas em tempo de compilação
- Foca na documentação e verificação dos direitos e deveres de cada módulo de
software, em termos de:
- Pré-condições → condições para que a rotina possa ser chamada
- Pós-condições → o que deve ser verdadeiro após a rotina ser executada
- Invariantes → o que deve ser verdadeiro antes, durante e após a execução
- É um código usado durante o desenvolvimento para confirmar que o programa
está executando como esperado
- Não possui o objetivo de substituir if/else ou try/catch
- Assertivas devem ser usadas somente para situações que nunca devem acontecer
- No ambiente de debug, não de produção
- Retornar valor padrão (neutro)
- Substituir pelo próximo dado válido
- Retornar a última resposta válida
- Substituir por um valor válido mais próximo
- Registrar um log em um arquivo (e continuar operando)
- Retornar um código de erro para indicar o erro a quem chamou, se não for tratado localmente
- Centralizar o tratamento de erros em uma rotina
- Tratar os erros localmente, na rotina que produziu o erro
- Mostrar uma mensagem de erro significativa (informativa)
- Abortar o programa se um erro severo ocorreu
- Exceções são um meio específico pelo qual o código pode passar erros ou eventos excepcionais ao código que o chamou
- Exceções, quando usadas com sabedoria, reduzem a complexidade, porém, quando usadas imprudentemente podem tornar o código quase impossível de seguir
- Devem ser usadas para notificar outras partes do programa sobre erros que não devem ser ignorados
- Exceções devem ser lançadas apenas para as condições que sejam realmente excepcionais
- Incluir na mensagem de exceção todas as informações que levam à exceção