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.
.avif)
