Aikido

10 Regras de Qualidade de Código Aprendidas com a Equipe de Engenharia da Grafana

Introdução

Grafana é uma das plataformas de observabilidade open-source mais populares, com mais de 70 mil estrelas no GitHub e milhares de contribuidores a aprimorando todos os dias. Com mais de 3 mil issues abertas e centenas de pull requests constantemente em movimento, manter a base de código limpa e consistente é um verdadeiro desafio.

Ao estudar sua base de código, podemos descobrir algumas das regras não escritas e melhores práticas que ajudam a equipe a manter a alta qualidade enquanto se move rapidamente. Muitas dessas regras focam em segurança, manutenibilidade e confiabilidade. Algumas delas lidam com problemas que ferramentas tradicionais de análise estática (SAST) não conseguem detectar, como uso assíncrono inadequado, vazamentos de recursos ou padrões inconsistentes no código. Esses são os tipos de problemas que revisores humanos ou ferramentas impulsionadas por IA podem identificar durante uma revisão de código.

Os desafios

Grandes projetos como este enfrentam diversos desafios: volume massivo de código, muitos módulos (API, UI, plugins) e inúmeras integrações externas (Prometheus, Loki, etc.). Centenas de contribuidores podem seguir diferentes estilos de codificação ou premissas. Novas funcionalidades e correções rápidas podem introduzir bugs ocultos, falhas de segurança ou caminhos de código confusos. Revisores voluntários podem não conhecer todas as partes da base de código, levando a padrões de design ou melhores práticas perdidos. Em suma, a escala e a diversidade das contribuições tornam a consistência e a confiabilidade difíceis de serem impostas.

Por que essas regras são importantes

Um conjunto claro de regras de revisão beneficia diretamente a saúde do Grafana. Primeiro, a manutenibilidade melhora: padrões consistentes (estrutura de pastas, nomenclatura, tratamento de erros) tornam o código mais fácil de ler, testar e estender. Os revisores gastam menos tempo adivinhando a intenção quando todos seguem as convenções comuns. Segundo, a segurança é aprimorada: regras como “sempre validar a entrada do usuário” ou “evitar redirecionamentos abertos” previnem vulnerabilidades (CVE-2025-6023/4123, etc.) que foram encontradas no Grafana. Finalmente, o onboarding de novos colaboradores é mais rápido: quando exemplos e revisões usam consistentemente as mesmas práticas, os recém-chegados aprendem o “jeito Grafana” de forma rápida e confiante.

Conectando o contexto a estas regras

Essas regras derivam de problemas reais no código e na comunidade do Grafana. Avisos de segurança e relatórios de bugs revelaram padrões (por exemplo, path traversal levando a XSS) que transformamos em regras preventivas. Cada regra abaixo destaca uma armadilha concreta, explica por que ela é importante (desempenho, clareza, segurança, etc.) e mostra um trecho claro ❌ não-conforme vs ✅ conforme nas linguagens do Grafana (Go ou TypeScript/JS).

Agora, vamos explorar as 10 regras que ajudam a manter a base de código do Grafana robusta, segura e compreensível.

10 Regras Práticas de Qualidade de Código Inspiradas no Grafana

1. Use variáveis de ambiente para configuração (evite valores hard-coded).

Evite codificar diretamente portas, credenciais, URLs ou outros valores específicos do ambiente. Leia-os de variáveis de ambiente ou arquivos de configuração para manter o código flexível e os Secrets fora do código-fonte.

Não-conforme:

// server.js
const appPort = 3000;
app.listen(appPort, () => console.log("Escutando na porta " + appPort));

Conforme:

// server.ts
const PORT = Number(process.env.PORT) || 3000;
app.listen(PORT, () => console.log(`Listening on port ${PORT}`));

Por que isso importa: Usar variáveis de ambiente mantém dados sensíveis fora do código-fonte, torna as implantações flexíveis em diferentes ambientes e evita vazamentos acidentais de Secrets. Também garante que as alterações de configuração não exijam modificações no código, melhorando a manutenibilidade e reduzindo erros.

2. Sanitize a entrada do usuário antes de usá-la.

Todo input de usuários ou fontes externas deve ser validado ou sanitizado antes do uso para prevenir ataques de injeção e comportamento inesperado.

Não-conforme:

// frontend/src/components/UserForm.tsx
const handleSubmit = (username: string) => {
  setUsers([...users, { name: username }]);
};

Conforme:

// frontend/src/utils/sanitize.ts
export function sanitizeInput(input: string): string {
  return input.replace(/<[^>]*>/g, ''); // removes HTML tags
}

// frontend/src/components/UserForm.tsx
import { sanitizeInput } from '../utils/sanitize';

const handleSubmit = (username: string) => {
  const cleanName = sanitizeInput(username);
  setUsers([...users, { name: cleanName }]);
};

Por que isso importa: A sanitização adequada de entrada previne XSS, ataques de injeção e comportamento inesperado causado por entrada malformada. Protege tanto os usuários quanto o sistema, e garante que processos a jusante, logging e armazenamento lidem com os dados de forma segura.

3. Prevenir redirecionamentos abertos e path traversal.

Garanta que quaisquer URLs ou caminhos de arquivo usados em seu código sejam devidamente validados e sanitizados. Não permita que a entrada do usuário determine diretamente redirecionamentos ou caminhos do sistema de arquivos.

Não-conforme:

// Express route in Grafana plugin
app.get("/goto", (req, res) => {
  const dest = req.query.next;    // attacker can supply any URL
  res.redirect(dest);
});

Conforme:

// Express route with safe redirect
app.get("/goto", (req, res) => {
  const dest = req.query.next;
  // Only allow relative paths starting with '/'
  if (dest && dest.startsWith("/")) {
    res.redirect(dest);
  } else {
    res.status(400).send("Invalid redirect URL");
  }
});

Por que isso importa: Prevenir redirecionamentos abertos (open redirects) e path traversal protege os usuários contra phishing, vazamento de dados e acesso não autorizado a arquivos. Reduz a superfície de ataque, impõe limites de segurança e evita a exposição acidental de recursos sensíveis do servidor.

4. Habilitar uma política de segurança de conteúdo (CSP) rigorosa.

Aplique uma Política de Segurança de Conteúdo nos cabeçalhos da aplicação que permite apenas scripts, estilos, imagens e outros recursos de fontes confiáveis. Não permita unsafe-inline, eval e fontes curinga.

Não-conforme: (Sem CSP ou muito permissivo)

# grafana.ini (não-conforme)
content_security_policy = false

Conforme: (CSP forte na configuração)

# grafana.ini
content_security_policy = true
content_security_policy_template = """
  script-src 'self' 'unsafe-eval' 'unsafe-inline' 'strict-dynamic' $NONCE;
  object-src 'none';
  font-src 'self';
  style-src 'self' 'unsafe-inline' blob:;
  img-src * data:;
  base-uri 'self';
  connect-src 'self' grafana.com ws://$ROOT_PATH wss://$ROOT_PATH;
  manifest-src 'self';
  media-src 'none';
  form-action 'self';
"""

Por que isso importa: Uma CSP rígida bloqueia muitas classes de ataques do lado do cliente, incluindo XSS. Ela impõe um comportamento previsível para os recursos, reduz a chance de execução de código malicioso e fornece um limite de segurança claro no contexto do navegador.

5. Lide com erros e verificações de nil (evite panics).

Sempre verifique por erros e valores nulos em chamadas de função, respostas de API e estruturas de dados. Substitua panics por tratamento de erros adequado e retorne mensagens ou códigos de erro significativos.

Não-conforme:

rows, _ := db.Query("SELECT * FROM users WHERE id=?", id)  // ignored error
user := &User{}
rows.Next()
rows.Scan(&user.Name)  // rows might be empty => user is nil => panic

Conforme:

rows, err := db.Query("SELECT * FROM users WHERE id=?", id)
if err != nil {
    return nil, err
}
defer rows.Close()
if !rows.Next() {
    return nil, errors.New("user not found")
}
var name string
if err := rows.Scan(&name); err != nil {
    return nil, err
}
user := &User{Name: name}

Por que isso importa: O tratamento adequado de erros previne falhas e garante que o sistema permaneça confiável mesmo quando entradas ou condições inesperadas ocorrem. Melhora a manutenibilidade, reduz o tempo de inatividade e facilita a depuração ao fornecer informações de erro significativas.

6. Adie a limpeza de recursos (evite vazamentos).

Garanta que todos os recursos abertos, como arquivos, conexões de rede ou manipuladores de banco de dados, sejam devidamente fechados usando defer imediatamente após a alocação. Não dependa de limpeza manual posteriormente no código.

Não-conforme:

resp, err := http.Get(url)
// ... use resp.Body ...
// forgot: resp.Body.Close()

Conforme:

resp, err := http.Get(url)
if err != nil {
    // handle error
}
defer resp.Body.Close()
// ... use resp.Body ...

Por que isso importa: A limpeza adequada previne vazamentos de memória, exaustão de descritores de arquivo e saturação de pool de conexões. Isso mantém a estabilidade do sistema, evita a degradação do desempenho ao longo do tempo e reduz problemas operacionais em produção.

7. Use consultas parametrizadas (evite injeção SQL).

Sempre use consultas parametrizadas ou prepared statements ao interagir com o banco de dados, em vez de concatenação de strings para comandos SQL.

Não-conforme:

// Perigoso: userID pode conter aspas SQL ou injeção
query := "DELETE FROM sessions WHERE user_id = '" + userID + "';"
db.Exec(query)

Conforme:

// Seguro: userID é passado como parâmetro
db.Exec("DELETE FROM sessions WHERE user_id = ?", userID)

Por que isso importa: Consultas parametrizadas previnem ataques de SQL injection, uma das vulnerabilidades de segurança mais comuns. Elas protegem dados sensíveis, reduzem o risco de corrupção de banco de dados e tornam as consultas mais fáceis de manter e auditar. Isso garante tanto a segurança quanto a confiabilidade da sua aplicação.

8. Use async/await corretamente em TypeScript (lida com promises).

Sempre aguarde promises e trate erros usando try/catch, em vez de ignorar rejeições ou misturar o tratamento no estilo callback.

Não-conforme:

async function fetchData() {
  // Missing await: fetch returns a Promise, not the actual data
  const res = fetch('/api/values');
  console.log(res.data); // undefined
}

Conforme:

async function fetchData() {
  try {
    const res = await fetch('/api/values');
    const data = await res.json();
    console.log(data);
  } catch (err) {
    console.error("Fetch failed:", err);
  }
}

Por que isso importa: O tratamento adequado de operações assíncronas (async handling) garante que erros em código assíncrono não passem despercebidos, previne rejeições de promises não tratadas e mantém um fluxo de programa previsível. Isso torna o código mais legível, mais fácil de depurar e previne bugs sutis que podem levar à corrupção de dados, estado inconsistente ou falhas inesperadas em tempo de execução.

9. Prefira tipos estritos em TypeScript (evite 'any').

Use tipos TypeScript precisos em vez de `any` para definir variáveis, parâmetros de função e tipos de retorno.

Não-conforme:

// No types specified
function updateUser(data) {
  // ...
}
let config: any = loadConfig();

Conforme:

interface User { id: number; name: string; }
function updateUser(data: User): Promise<User> {
  // ...
}
interface AppConfig { endpoint: string; timeoutMs: number; }
const config: AppConfig = loadConfig();

Por que isso importa: A tipagem estrita (strict typing) detecta erros relacionados a tipos em tempo de compilação, reduzindo erros em tempo de execução e melhorando a confiabilidade do código. Torna o código autodocumentado, mais fácil de refatorar e garante que todas as partes do sistema interajam de forma previsível e type-safe, o que é crucial em grandes e complexas bases de código como a do Grafana.

10. Aplique um estilo de código e nomenclatura consistentes.

Imponha formatação uniforme, convenções de nomenclatura e estruturas de arquivo em toda a base de código.

Não-conforme: (estilos mistos)

const ApiData = await getdata();   // PascalCase for variable? function name not camelCase.
function Fetch_User() { ... }      // Unusual naming.

Conforme:

const apiData = await fetchData();
function fetchUser() { ... }

Por que isso importa: Estilo e nomenclatura consistentes melhoram a legibilidade e facilitam para múltiplos contribuidores entenderem e manterem o código. Reduz a sobrecarga cognitiva ao navegar pelo projeto, previne bugs sutis causados por mal-entendidos e garante que ferramentas automatizadas (linters, formatadores, revisores de código) possam aplicar padrões de qualidade de forma confiável em um ambiente de equipe grande.

Conclusão

Cada regra acima aborda um desafio recorrente na base de código do Grafana. Aplicá-las consistentemente durante as revisões de código ajuda a equipe a manter um código limpo e previsível, melhorar a segurança prevenindo vulnerabilidades comuns e tornar o onboarding mais suave, fornecendo padrões claros para novos contribuidores. À medida que o projeto escala, essas práticas mantêm a base de código confiável, fácil de manter e mais fácil de navegar para todos os envolvidos. Seguir essas regras pode ajudar qualquer equipe de engenharia a construir e sustentar software de alta qualidade em escala.

FAQs

Dúvidas?

Por que analisar o repositório do Grafana para regras de revisão de código?

Grafana é um projeto open-source grande e maduro, com milhares de contribuidores. Estudar sua base de código revela padrões de engenharia do mundo real que ajudam as equipes a manter software limpo, escalável e seguro em larga escala.

O que torna essas regras diferentes das verificações regulares de linting ou formatação?

Linters tradicionais detectam problemas de sintaxe e formatação. Essas regras baseadas em Grafana vão mais fundo, focando em arquitetura, legibilidade, consistência e decisões de segurança que exigem compreensão contextual — algo que revisões baseadas em IA podem lidar.

Como ferramentas baseadas em IA podem ajudar a detectar esses problemas de qualidade de código?

Ferramentas de IA podem analisar intenção, nomenclatura, arquitetura e contexto — não apenas sintaxe. Elas podem identificar problemas de manutenibilidade, abstrações pouco claras e potenciais problemas de segurança que a análise estática tradicional frequentemente ignora.

Essas regras são específicas do Grafana ou qualquer equipe pode usá-las?

Embora sejam inspirados pela base de código do Grafana, os princípios se aplicam a qualquer projeto de software em larga escala. As equipes podem adaptá-los aos seus próprios repositórios para manter a consistência, prevenir regressões e melhorar o onboarding.

Como essas regras se relacionam com as verificações de qualidade de código do Aikido?

Cada regra pode ser implementada como uma regra de IA personalizada na plataforma de qualidade de código do Aikido, permitindo a detecção automatizada de problemas de arquitetura, legibilidade e segurança em cada pull request.

Fique seguro agora

Proteja seu código, Cloud e runtime em um único sistema centralizado.
Encontre e corrija vulnerabilidades rapidamente de forma automática.

Não é necessário cartão de crédito | Resultados da varredura em 32 segundos.