Aikido

10 regras de qualidade de código aprendidas com a equipe de engenharia da Grafana

Introdução

O Grafana é uma das plataformas de observabilidade de código aberto mais populares, com mais de 70 mil estrelas no GitHub e milhares de colaboradores que o melhoram todos os dias. Com mais de 3k questões abertas e centenas de pull requests em constante movimento, manter a base de código limpa e consistente é um verdadeiro desafio.

Ao estudar a sua base de código, podemos descobrir algumas das regras não escritas e as melhores práticas que ajudam a equipa a manter a qualidade elevada enquanto avança rapidamente. Muitas destas regras centram-se na segurança, facilidade de manutenção e fiabilidade. Algumas delas lidam com problemas que as ferramentas tradicionais de análise estática (SAST) não conseguem detetar, como uso inadequado de assíncrono, vazamentos de recursos ou padrões inconsistentes no código. Estes são os tipos de problemas que os revisores humanos ou as ferramentas alimentadas por IA podem detetar durante uma revisão de código.

Os desafios

Grandes projectos como este enfrentam vários desafios: grande volume de código, muitos módulos (API, UI, plugins) e inúmeras integrações externas (Prometheus, Loki, etc.). Centenas de colaboradores podem seguir diferentes estilos de codificação ou pressupostos. Novas funcionalidades e correcções rápidas podem introduzir bugs ocultos, falhas de segurança ou caminhos de código confusos. Os revisores voluntários podem não conhecer todas as partes da base de código, levando à perda de padrões de design ou melhores práticas. Em suma, a escala e a diversidade das contribuições tornam a consistência e a fiabilidade difíceis de aplicar.

Porque é que estas regras são importantes

Um conjunto claro de regras de revisão beneficia diretamente a saúde do Grafana. Primeiro, a manutenção melhora: padrões consistentes (layout 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 convenções comuns. Em segundo lugar, a segurança é melhorada: regras como "validar sempre a entrada do utilizador" ou "evitar redireccionamentos abertos" previnem vulnerabilidades (CVE-2025-6023/4123, etc.) que foram encontradas no Grafana . Por fim, a integração de novos colaboradores é mais rápida: quando exemplos e revisões usam consistentemente as mesmas práticas, os recém-chegados aprendem o "jeito Grafana" com rapidez e confiança.

Contexto de ligação a estas regras

Essas regras vêm de problemas reais no código e na comunidade do Grafana. Os avisos de segurança e os relatórios de bugs descobriram padrões (por exemplo, a travessia do caminho que leva ao 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 compatível vs ✅ compatível 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 em Grafana

1. Utilizar as variáveis de ambiente para a configuração (evitar valores codificados).

‍Evitecodificar portas, credenciais, URLs ou outros valores específicos do ambiente. Leia-os a partir de variáveis de ambiente ou ficheiros de configuração para manter o código flexível e secrets fora da fonte.

Não conforme:

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

Conformidade:

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

Por que isso é importante: O uso de variáveis de ambiente mantém os dados confidenciais fora do código-fonte, torna as implantações flexíveis em diferentes ambientes e evita vazamentos acidentais de secrets. Ele também garante que as alterações de configuração não exijam modificações no código, melhorando a capacidade de manutenção e reduzindo erros.

2. Higienizar a entrada do utilizador antes de a utilizar.

Todas as entradas de utilizadores ou fontes externas devem ser validadas ou higienizadas antes de serem utilizadas para evitar ataques de injeção e comportamentos inesperados.

Não conforme:

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

Conformidade:

// 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 }]);
};

Porque é que isto é importante: A sanitização adequada das entradas evita XSS, ataques de injeção e comportamentos inesperados causados por entradas malformadas. Protege os utilizadores e o sistema, e garante que os processos a jusante, o registo e o armazenamento tratam os dados de forma segura.

3. Impedir redireccionamentos abertos e a passagem de caminhos.

Certifique-se de que quaisquer URLs ou caminhos de ficheiros utilizados no seu código são devidamente validados e higienizados. Não permita que a entrada do utilizador determine diretamente redireccionamentos ou caminhos do sistema de ficheiros.

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);
});

Conformidade:

// 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 é importante: Impedir redireccionamentos abertos e a passagem de caminhos protege os utilizadores contra phishing, fugas de dados e acesso não autorizado a ficheiros. Reduz a superfície de ataque, reforça os limites de segurança e evita a exposição acidental de recursos sensíveis do servidor.

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

Imponha uma Política de Segurança de Conteúdo nos cabeçalhos da aplicação que apenas permita scripts, estilos, imagens e outros recursos de fontes fiáveis. Não permita fontes inseguras em linha, eval e wildcard.

Não conforme: (Sem PEC ou demasiado permissivo)

# grafana.ini (não compatível)
content_security_policy = false

Conformidade: (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';
"""

Porque é que isto é importante: Um CSP rigoroso bloqueia muitas classes de ataques do lado do cliente, incluindo XSS. Ele 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. Gerir os erros e as verificações nulas (evitar pânicos).

Verifique sempre a existência de erros e valores nulos nas chamadas de função, nas respostas da API e nas estruturas de dados. Substitua os panics por um tratamento de erros adequado e devolva 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

Conformidade:

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}

Porque é que isto é importante: O tratamento adequado de erros evita falhas e garante que o sistema permanece fiável mesmo quando ocorrem entradas ou condições inesperadas. Melhora a capacidade de manutenção, reduz o tempo de inatividade e facilita a depuração, fornecendo informações de erro significativas.

6. Adiar a limpeza dos recursos (evitar fugas).

Certifique-se de que todos os recursos abertos, como ficheiros, ligações de rede ou identificadores de bases de dados, são devidamente fechados utilizando o deferimento imediatamente após a atribuição. Não confie na limpeza manual mais tarde no código.

Não conforme:

resp, err := http.Get(url)
// ... usa resp.Body ...
// esqueci-me: resp.Body.Close()

Conformidade:

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

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

7. Utilizar consultas parametrizadas (evitar a injeção de SQL).

Utilize sempre consultas parametrizadas ou instruções preparadas ao interagir com a base de dados, em vez da concatenação de cadeias de caracteres para comandos SQL.

Não conforme:

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

Conformidade:

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

Porque é que isto é importante: As consultas parametrizadas evitam ataques de injeção de SQL, uma das vulnerabilidades de segurança mais comuns. Protegem dados sensíveis, reduzem o risco de corrupção da base de dados e tornam as consultas mais fáceis de manter e de auditar. Isto garante tanto a segurança como a fiabilidade da sua aplicação.

8. Utilizar corretamente async/await em TypeScript (lidar com promessas).

‍Aguardesempre as promessas e trate os erros utilizando try/catch em vez de ignorar as rejeições ou misturar o tratamento ao estilo de 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
}

Conformidade:

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 é importante: O tratamento assíncrono adequado garante que os erros no código assíncrono não passem despercebidos, evita rejeições de promessas não tratadas e mantém o fluxo previsível do programa. Ele torna o código mais legível, mais fácil de depurar e evita erros sutis que podem levar à corrupção de dados, estado inconsistente ou falhas inesperadas no tempo de execução.

9. Favorecer tipos estritos em TypeScript (evitar qualquer).

Utilize tipos TypeScript precisos em vez de qualquer tipo 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();

Conformidade:

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

Porque é que isto é importante: A tipagem estrita captura erros relacionados ao tipo em tempo de compilação, reduzindo erros em tempo de execução e melhorando a confiabilidade do código. Isso torna o código autodocumentado, mais fácil de refatorar e garante que todas as partes do sistema interajam de maneira previsível e segura, o que é crucial em bases de código grandes e complexas como a do Grafana.

10. Aplicar um estilo de código e uma nomenclatura coerentes.

Aplicar formatação uniforme, convenções de nomenclatura e estruturas de ficheiros 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.

Conformidade:

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

Por que isso é importante: Um estilo e uma nomenclatura consistentes melhoram a legibilidade e facilitam a compreensão e a manutenção do código por parte de vários colaboradores. Reduz a sobrecarga cognitiva ao navegar no projeto, evita bugs subtis causados por mal-entendidos e garante que as ferramentas automatizadas (linters, formatadores, revisores de código) podem aplicar de forma fiável os padrões de qualidade num ambiente de equipa grande.

Conclusão

Cada regra acima aborda um desafio recorrente na base de código do Grafana. Aplicá-las de forma consistente durante as revisões de código ajuda a equipe a manter o código limpo e previsível, melhorar a segurança, evitando vulnerabilidades comuns, e tornar a integração mais suave, fornecendo padrões claros para novos colaboradores. À medida que o projeto cresce, estas práticas mantêm a base de código fiável, passível de manutenção e mais fácil de navegar para todos os envolvidos. Seguir estas regras pode ajudar qualquer equipa de engenharia a criar e manter software de alta qualidade em escala.

FAQs

Tem perguntas?

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

O Grafana é um projeto de código aberto grande e maduro com milhares de colaboradores. O estudo de sua base de código revela padrões de engenharia do mundo real que ajudam as equipes a manter um software limpo, escalável e seguro em escala.

O que é que torna estas regras diferentes das verificações normais de linting ou de formatação?

Os linters tradicionais capturam problemas de sintaxe e formatação. Estas regras baseadas na Grafana vão mais fundo, concentrando-se na arquitetura, legibilidade, consistência e decisões de segurança que requerem compreensão contextual - algo que as revisões baseadas em IA podem tratar.

Como é que as ferramentas baseadas em IA podem ajudar a detetar estes problemas de qualidade do código?

As ferramentas de IA podem analisar a intenção, a designação, a arquitetura e o contexto, e não apenas a sintaxe. Podem identificar problemas de manutenção, abstracções pouco claras e potenciais problemas de segurança que a análise estática tradicional muitas vezes não detecta.

Estas regras são específicas do Grafana ou qualquer equipa pode utilizá-las?

Embora sejam inspirados na base de código do Grafana, os princípios se aplicam a qualquer projeto de software em grande escala. As equipas podem adaptá-los aos seus próprios repositórios para manter a consistência, evitar regressões e melhorar a integração.

Como é que estas regras se relacionam com as verificações de qualidade do 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 deteção automática de problemas de arquitetura, legibilidade e segurança em todos os pedidos de transferência.

Obter segurança gratuitamente

Proteja seu código, nuvem e tempo de execução em um sistema central.
Encontre e corrija vulnerabilidades rapidamente de forma automática.

Não é necessário cartão de crédito | Resultados do scan em 32secs.