Introdução ao SonarQube [Estudo]
1. O que é o SonarQube?
O SonarQube é uma plataforma de revisão automatizada e análise estática de código. Ele examina o código sem precisar executar a aplicação e identifica problemas relacionados a:
- confiabilidade;
- segurança;
- manutenibilidade;
- duplicação;
- cobertura de testes;
- complexidade;
- possíveis falhas de implementação.
O SonarQube Server é instalado e administrado pela própria organização. Existe também o SonarQube Cloud, hospedado pela Sonar.
Uma forma simples de entendê-lo:
O compilador verifica se o código é válido.
Os testes verificam se os comportamentos esperados funcionam.
O SonarQube verifica se o código apresenta riscos técnicos, problemas de segurança e dificuldades de manutenção.
Ele não substitui revisão de código, testes ou análise arquitetural. Ele automatiza parte dessas verificações.
2. Como o SonarQube funciona?
O fluxo básico é:
Código-fonte
↓
SonarScanner
↓
Análise estática
↓
SonarQube Server
↓
Dashboard, métricas e Quality Gate
O SonarScanner lê o projeto e envia o resultado da análise ao servidor. Existem scanners específicos para ferramentas como Maven, Gradle e .NET, além do scanner genérico por linha de comando. Para projetos Maven, o scanner pode ser executado diretamente como um objetivo do Maven.
Exemplo com Maven:
mvn clean verify sonar:sonar
Exemplo genérico:
sonar-scanner
O processo normalmente acontece no pipeline:
Checkout
↓
Compilação
↓
Testes
↓
Geração da cobertura
↓
Análise SonarQube
↓
Validação do Quality Gate
↓
Deploy
3. Principais conceitos
3.1 Issue
Uma issue é um problema encontrado por alguma regra do SonarQube.
Exemplo:
public boolean isActive(User user) {
if (user.isActive()) {
return true;
} else {
return false;
}
}
O SonarQube pode sugerir simplificar:
public boolean isActive(User user) {
return user.isActive();
}
Outro exemplo:
String password = "admin123";
Uma credencial escrita diretamente no código pode ser apontada como problema de segurança.
3.2 Rule
Uma rule, ou regra, define algo que o SonarQube procurará no código.
Exemplos de regras:
- não deixar recursos abertos;
- não usar credenciais fixas;
- não ignorar exceções;
- não repetir condições desnecessárias;
- não manter código morto;
- evitar métodos excessivamente complexos;
- não realizar comparações incorretas;
- remover variáveis não utilizadas.
Cada linguagem possui seu próprio conjunto de regras.
3.3 Quality Profile
O Quality Profile determina quais regras serão aplicadas na análise de uma linguagem. Um projeto Java pode possuir um perfil Java, enquanto o mesmo projeto pode usar outro perfil para JavaScript ou Kotlin.
Pense no Quality Profile como:
Quality Profile = conjunto de regras
Exemplo:
Perfil Java da empresa
├── Credenciais fixas são proibidas
├── Métodos não devem ter complexidade excessiva
├── Recursos devem ser fechados
├── Exceções não podem ser ignoradas
└── Código duplicado deve ser reduzido
O perfil padrão normalmente é chamado de Sonar way.
Uma empresa pode criar um perfil próprio, mas não é recomendável ativar centenas de regras sem avaliar seu impacto. Isso costuma gerar excesso de alertas e faz com que os desenvolvedores passem a ignorar a ferramenta.
3.4 Quality Gate
O Quality Gate é um conjunto de condições que determina se o projeto foi aprovado ou reprovado.
Quality Gate = critérios de aprovação
Um Quality Gate pode verificar, por exemplo:
Cobertura no código novo ≥ 80%
Duplicação no código novo ≤ 3%
Nenhuma nova vulnerabilidade
Nenhum novo bug crítico
Todos os Security Hotspots revisados
Quando alguma condição não é satisfeita:
Quality Gate: FAILED
Quando todas são satisfeitas:
Quality Gate: PASSED
A documentação define o Quality Gate como um conjunto de condições comparadas com os resultados da análise, indicando se o código deve ser corrigido antes de ser integrado ou liberado.
Diferença essencial
Quality Profile → o que será analisado
Quality Gate → o que precisa ser aprovado
Exemplo:
- o Quality Profile possui uma regra que detecta métodos muito complexos;
- o Quality Gate determina que nenhum problema crítico novo pode existir.
4. Categorias de problemas
A classificação pode variar conforme a versão e o modo de configuração, mas os resultados normalmente são apresentados em dimensões relacionadas a confiabilidade, segurança e manutenibilidade.
4.1 Problemas de confiabilidade
São situações que podem provocar comportamento incorreto ou falha na aplicação.
Exemplo:
public int getSize(List<String> names) {
return names.size();
}
Se names puder ser null, existe risco de NullPointerException.
Possível correção:
public int getSize(List<String> names) {
return names == null ? 0 : names.size();
}
Mas a melhor solução depende do contrato do método. Às vezes, aceitar null é o problema real.
4.2 Problemas de segurança
São vulnerabilidades com impacto potencialmente explorável.
Exemplo:
String sql = "SELECT * FROM users WHERE name = '" + name + "'";
Esse código pode permitir SQL Injection.
Correção:
String sql = "SELECT * FROM users WHERE name = ?";
E usar parâmetros preparados.
Outro exemplo:
String token = "meu-token-secreto";
Segredos não devem ficar no repositório.
4.3 Security Hotspot
Um Security Hotspot não significa necessariamente que existe uma vulnerabilidade confirmada. Significa que um trecho sensível precisa ser revisado por uma pessoa.
Exemplo:
MessageDigest md = MessageDigest.getInstance("MD5");
O SonarQube pode pedir que o desenvolvedor verifique se o algoritmo é adequado ao contexto.
Após a revisão, o hotspot pode ser marcado como:
- seguro;
- corrigido;
- reconhecido;
- pendente de análise.
A ideia é diferente de uma vulnerabilidade automática:
Vulnerabilidade → problema de segurança detectado
Hotspot → trecho sensível que exige revisão
4.4 Problemas de manutenibilidade
São problemas que dificultam entender, alterar ou testar o código.
Exemplo:
if (user != null) {
if (user.getCompany() != null) {
if (user.getCompany().isActive()) {
if (user.hasPermission()) {
// ...
}
}
}
}
Esse código pode funcionar, mas apresenta alta complexidade.
Uma refatoração possível:
if (!isAuthorized(user)) {
return;
}
// ...
private boolean isAuthorized(User user) {
return user != null
&& user.getCompany() != null
&& user.getCompany().isActive()
&& user.hasPermission();
}
5. Severidade dos problemas
As issues recebem prioridades ou severidades para orientar a correção.
Uma interpretação prática:
| Nível | Interpretação |
|---|---|
| Baixo | Melhoria pequena |
| Médio | Deve ser avaliado |
| Alto | Risco relevante |
| Crítico | Pode gerar falha grave |
| Bloqueador | Não deveria chegar à produção |
A severidade ajuda na priorização, mas não substitui a análise do contexto.
Um alerta classificado como médio pode ser extremamente importante em um sistema financeiro. Um alerta alto pode não representar risco real em um teste isolado.
6. Métricas importantes
6.1 Cobertura de testes
A cobertura indica quanto do código foi executado pelos testes.
Exemplo:
Linhas executáveis: 100
Linhas cobertas: 80
Cobertura: 80%
Entretanto:
Cobertura alta ≠ testes bons
Este teste aumenta cobertura, mas é fraco:
@Test
void test() {
service.process();
}
Um teste melhor valida o comportamento:
@Test
void shouldRejectOrderWhenCustomerHasNoCredit() {
var order = createOrderWithoutAvailableCredit();
assertThrows(
InsufficientCreditException.class,
() -> service.process(order)
);
}
O SonarQube geralmente não executa os testes nem calcula sozinho a cobertura. A ferramenta de testes gera um relatório, e o scanner o importa.
Em Java, uma combinação comum é:
JUnit + JaCoCo + SonarQube
Em Flutter:
flutter test --coverage
Isso gera:
coverage/lcov.info
Depois o SonarQube importa o arquivo.
6.2 Duplicação
O SonarQube identifica blocos semelhantes ou repetidos.
Exemplo:
void validateCustomer(Customer customer) {
if (customer == null) {
throw new IllegalArgumentException();
}
if (!customer.isActive()) {
throw new IllegalStateException();
}
}
void validateSupplier(Supplier supplier) {
if (supplier == null) {
throw new IllegalArgumentException();
}
if (!supplier.isActive()) {
throw new IllegalStateException();
}
}
Pode existir oportunidade de abstração. Porém, nem toda duplicação deve ser removida imediatamente.
Duas regras de negócio apenas coincidentemente parecidas podem evoluir em direções diferentes. Criar uma abstração prematura também gera acoplamento.
6.3 Complexidade cognitiva
A complexidade cognitiva tenta representar o esforço necessário para entender o fluxo de um método.
Este método é difícil de acompanhar:
void process(Order order) {
if (order != null) {
if (order.isValid()) {
for (Item item : order.getItems()) {
if (item.isAvailable()) {
if (item.getPrice() > 0) {
// ...
}
}
}
}
}
}
Uma alternativa:
void process(Order order) {
validate(order);
order.getItems().stream()
.filter(Item::isAvailable)
.filter(item -> item.getPrice() > 0)
.forEach(this::processItem);
}
O objetivo não é apenas diminuir um número. É tornar o código mais fácil de compreender.
6.4 Débito técnico
O SonarQube estima o esforço necessário para corrigir determinados problemas.
Exemplo:
12 issues
Dívida técnica estimada: 3h20
Essa estimativa não é um orçamento preciso. Ela serve principalmente para comparação, tendência e priorização.
Não significa necessariamente:
Um desenvolvedor levará exatamente 3 horas e 20 minutos.
6.5 Ratings
O SonarQube pode apresentar classificações para dimensões como:
- confiabilidade;
- segurança;
- manutenibilidade;
- revisão de segurança.
Essas avaliações podem participar das condições do Quality Gate.
7. Código geral versus código novo
Um dos conceitos mais importantes é separar:
Overall Code → código completo do projeto
New Code → código novo ou alterado
Imagine um sistema legado com:
10.000 problemas existentes
Cobertura geral: 18%
Duplicação geral: 12%
Exigir imediatamente:
Cobertura geral ≥ 80%
Nenhuma issue existente
pode tornar o Quality Gate impossível de cumprir.
A estratégia recomendada é impedir que novas alterações aumentem a dívida:
Cobertura do código novo ≥ 80%
Nenhum bug novo
Nenhuma vulnerabilidade nova
Duplicação nova ≤ 3%
Hotspots novos revisados
Essa abordagem é chamada de Clean as You Code: manter limpo o código criado ou alterado agora, melhorando o projeto de forma incremental. A documentação recomenda priorizar métricas de código novo no Quality Gate.
A lógica é:
Não consigo corrigir todo o legado hoje.
Mas posso impedir que o código novo piore o sistema.