Aikido

10 règles de qualité du code apprises par l'équipe d'ingénieurs de Grafana

Introduction

Grafana est l'une des plateformes d'observabilité open-source les plus populaires, avec plus de 70k étoiles GitHub et des milliers de contributeurs qui l'améliorent chaque jour. Avec plus de 3 000 problèmes ouverts et des centaines de demandes de modifications en permanence, maintenir la base de code propre et cohérente est un véritable défi.

En étudiant sa base de code, nous pouvons découvrir certaines des règles non écrites et des meilleures pratiques qui aident l'équipe à maintenir un niveau de qualité élevé tout en avançant rapidement. Nombre de ces règles portent sur la sécurité, la maintenabilité et la fiabilité. Certaines d'entre elles traitent de problèmes que les outils d'analyse statique traditionnels (SAST) ne peuvent pas détecter, comme l'utilisation inappropriée de l'asynchrone, les fuites de ressources ou les schémas incohérents dans le code. Il s'agit du type de problèmes que les réviseurs humains ou les outils dotés d'une intelligence artificielle peuvent repérer lors d'une révision de code.

Les défis

Les grands projets de ce type sont confrontés à plusieurs défis : un volume de code considérable, de nombreux modules (API, interface utilisateur, plugins) et d'innombrables intégrations externes (Prometheus, Loki, etc.). Des centaines de contributeurs peuvent suivre des styles de codage ou des hypothèses différentes. Les nouvelles fonctionnalités et les corrections rapides peuvent introduire des bogues cachés, des failles de sécurité ou des chemins de code confus. Les réviseurs bénévoles peuvent ne pas connaître toutes les parties de la base de code, ce qui peut entraîner l'oubli de modèles de conception ou de meilleures pratiques. En bref, l'ampleur et la diversité des contributions rendent la cohérence et la fiabilité difficiles à mettre en œuvre.

L'importance de ces règles

Un ensemble clair de règles de révision bénéficie directement à la santé de Grafana. Tout d'abord, la maintenabilité est améliorée : des modèles cohérents (disposition des dossiers, nommage, gestion des erreurs) rendent le code plus facile à lire, à tester et à étendre. Les réviseurs passent moins de temps à deviner les intentions lorsque tout le monde suit des conventions communes. Deuxièmement, la sécurité est améliorée : des règles comme "toujours valider l'entrée de l'utilisateur" ou "éviter les redirections ouvertes" empêchent les vulnérabilités (CVE-2025-6023/4123, etc.) qui ont été trouvées dans Grafana . Enfin, l 'intégration des nouveaux contributeurs est plus rapide : lorsque les exemples et les révisions utilisent systématiquement les mêmes pratiques, les nouveaux arrivants apprennent la "manière Grafana" rapidement et en toute confiance.

Contexte de transition vers ces règles

Ces règles proviennent de problèmes réels dans le code et la communauté de Grafana. Les avis de sécurité et les rapports de bogues ont mis en évidence des modèles (par exemple, la traversée de chemin menant à XSS ) que nous transformons en règles préventives. Chaque règle ci-dessous met en évidence un piège concret, explique pourquoi il est important (performance, clarté, sécurité, etc.) et montre un extrait ❌ non conforme vs ✅ conforme dans les langages de Grafana (Go ou TypeScript/JS).

Explorons maintenant les 10 règles qui permettent de maintenir la base de code de Grafana robuste, sécurisée et compréhensible.

10 règles pratiques de qualité du code inspirées de Grafana

1. Utiliser des variables d'environnement pour la configuration (éviter les valeurs codées en dur).

‍Évitezde coder en dur les ports, les identifiants, les URL ou d'autres valeurs spécifiques à l'environnement. Lisez-les à partir de variables d'environnement ou de fichiers de configuration afin de préserver la flexibilité du code et les secrets des sources.

Non conforme :

// server.js
const appPort = 3000;
app.listen(appPort, () => console.log("Listening on port " + appPort)) ;

Conforme:

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

Pourquoi c'est important : L'utilisation de variables d'environnement permet de conserver les données sensibles en dehors du code source, de rendre les déploiements flexibles dans différents environnements et d'éviter les fuites accidentelles de secrets. Elle permet également de s'assurer que les changements de configuration ne nécessitent pas de modifications du code, ce qui améliore la maintenabilité et réduit les erreurs.

2. Assainir les entrées utilisateur avant de les utiliser.‍

Toutes les entrées provenant d'utilisateurs ou de sources externes doivent être validées ou assainies avant d'être utilisées afin de prévenir les attaques par injection et les comportements inattendus.

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

Pourquoi cela est-il important ? Une bonne vérification des entrées permet d'éviter les XSS, les attaques par injection et les comportements inattendus provoqués par des entrées mal formées. Elle protège à la fois les utilisateurs et le système, et garantit que les processus en aval, la journalisation et le stockage traitent les données en toute sécurité.

3. Empêcher les redirections ouvertes et la traversée des chemins.

Veillez à ce que les URL ou les chemins d'accès aux fichiers utilisés dans votre code soient correctement validés et assainis. Ne permettez pas à l'utilisateur de déterminer directement les redirections ou les chemins d'accès au système de fichiers.

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

Pourquoi cela est-il important ? La prévention des redirections ouvertes et de la traversée de chemin protège les utilisateurs contre l'hameçonnage, les fuites de données et l'accès non autorisé aux fichiers. Elle réduit la surface d'attaque, renforce les limites de sécurité et évite l'exposition accidentelle des ressources sensibles du serveur.

4. Mettre en place une politique de sécurité du contenu (CSP) stricte.

Appliquer une politique de sécurité du contenu dans les en-têtes de l'application qui n'autorise que les scripts, les styles, les images et les autres ressources provenant de sources fiables. Interdire les sources de type "unsafe-inline", "eval" et "wildcard".

Non conforme : (pas de DSP ou trop permissif)

# grafana.ini (non conforme)
content_security_policy = false

Conforme : (CSP fort dans la configuration)

# 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' ;
"""

Pourquoi cela est-il important ? A strict CSP blocks many classes of client-side attacks, including XSS. It enforces predictable behavior for resources, reduces the chance of malicious code execution, and provides a clear security boundary in the browser context.

5. Gérer les erreurs et les contrôles nuls (éviter les paniques).

Vérifiez toujours la présence d'erreurs et de valeurs nulles dans les appels de fonction, les réponses de l'API et les structures de données. Remplacez les paniques par une gestion correcte des erreurs et renvoyez des messages ou des codes d'erreur significatifs.

Non 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}

En quoi cela est-il important ? Une bonne gestion des erreurs permet d'éviter les pannes et de s'assurer que le système reste fiable même lorsque des entrées ou des conditions inattendues se produisent. Elle améliore la maintenabilité, réduit les temps d'arrêt et facilite le débogage en fournissant des informations significatives sur les erreurs.

6. Différer le nettoyage des ressources (prévenir les fuites).

Assurez-vous que toutes les ressources ouvertes, telles que les fichiers, les connexions réseau ou les handles de base de données, sont correctement fermées à l'aide de defer immédiatement après l'allocation. Ne comptez pas sur un nettoyage manuel plus tard dans le code.

Non conforme :

resp, err := http.Get(url)
// ... utiliser resp.Body ...
// oublié : resp.Body.Close()

Conforme:

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

Pourquoi cela est-il important ? Un nettoyage adéquat permet d'éviter les fuites de mémoire, l'épuisement des descripteurs de fichiers et la saturation du pool de connexions. Cela permet de maintenir la stabilité du système, d'éviter la dégradation des performances au fil du temps et de réduire les problèmes opérationnels en production.

7. Utiliser des requêtes paramétrées (éviter l'injection SQL).

Utilisez toujours des requêtes paramétrées ou des instructions préparées lorsque vous interagissez avec la base de données, plutôt que la concaténation de chaînes de caractères pour les commandes SQL.

Non conforme :

// Dangereux : userID peut contenir une citation SQL ou une injection
query := "DELETE FROM sessions WHERE user_id = '" + userID +"';" db.Exec(query ) + userID + "' ;"
db.Exec(query)

Conforme:

// Sûr : userID est passé en tant que paramètre
db.Exec("DELETE FROM sessions WHERE user_id = ?", userID)

Pourquoi c'est important : Les requêtes paramétrées empêchent les attaques par injection SQL, l'une des failles de sécurité les plus courantes. Elles protègent les données sensibles, réduisent le risque de corruption de la base de données et rendent les requêtes plus faciles à maintenir et à auditer. Cela garantit à la fois la sécurité et la fiabilité de votre application.

8. Utiliser correctement async/await en TypeScript (gérer les promesses).

‍Toujoursattendre les promesses et gérer les erreurs en utilisant try/catch au lieu d'ignorer les rejets ou de mélanger une gestion de type callback.

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

Pourquoi c'est important : Une gestion asynchrone correcte garantit que les erreurs dans le code asynchrone ne passent pas inaperçues, évite les rejets de promesses non gérées et maintient un flux de programme prévisible. Elle rend le code plus lisible, plus facile à déboguer et prévient les bogues subtils qui peuvent entraîner une corruption des données, un état incohérent ou des pannes d'exécution inattendues.

9. Favoriser les types stricts en TypeScript (éviter tout).

Utilisez des types TypeScript précis au lieu de n'importe lesquels pour définir les variables, les paramètres des fonctions et les types de retour.

Non 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();

Pourquoi c'est important : Le typage strict permet d'éviter les erreurs de typage à la compilation, ce qui réduit les erreurs d'exécution et améliore la fiabilité du code. Il rend le code auto-documenté, plus facile à remanier et garantit que toutes les parties du système interagissent de manière prévisible et sûre, ce qui est crucial dans les bases de code complexes et de grande taille comme celle de Grafana.

10. Appliquer un style de code et une dénomination cohérents.

Appliquer un formatage, des conventions de dénomination et des structures de fichiers uniformes dans l'ensemble de la base de code.

Non conforme : (styles mixtes)

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

Conforme:

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

Pourquoi c'est important : Un style et une dénomination cohérents améliorent la lisibilité et facilitent la compréhension et la maintenance du code par de multiples contributeurs. Cela réduit la charge cognitive lors de la navigation dans le projet, évite les bogues subtils causés par des malentendus et garantit que les outils automatisés (linters, formateurs, réviseurs de code) peuvent appliquer de manière fiable les normes de qualité dans un environnement d'équipe de grande taille.

Conclusion

Chaque règle ci-dessus répond à un défi récurrent dans la base de code de Grafana. Leur application systématique lors des révisions de code aide l'équipe à maintenir un code propre et prévisible, à améliorer la sécurité en prévenant les vulnérabilités courantes et à faciliter l'intégration en fournissant des modèles clairs aux nouveaux contributeurs. Au fur et à mesure que le projet évolue, ces pratiques permettent à la base de code de rester fiable, maintenable et plus facile à naviguer pour toutes les personnes impliquées. Le respect de ces règles peut aider n'importe quelle équipe d'ingénieurs à créer et à maintenir des logiciels de haute qualité à grande échelle.

FAQ

Vous avez des questions ?

Pourquoi analyser le référentiel de Grafana pour les règles de révision de code ?

Grafana est un grand projet open-source mature avec des milliers de contributeurs. L'étude de sa base de code révèle des modèles d'ingénierie réels qui aident les équipes à maintenir des logiciels propres, évolutifs et sécurisés à grande échelle.

Qu'est-ce qui différencie ces règles des contrôles de formatage ou des vérifications habituelles ?

Les linters traditionnels détectent les problèmes de syntaxe et de formatage. Ces règles basées sur Grafana vont plus loin, en se concentrant sur l'architecture, la lisibilité, la cohérence et les décisions de sécurité qui nécessitent une compréhension contextuelle - ce que les révisions basées sur l'IA peuvent gérer.

Comment les outils basés sur l'IA peuvent-ils aider à détecter ces problèmes de qualité du code ?

Les outils d'IA peuvent analyser l'intention, la dénomination, l'architecture et le contexte, et pas seulement la syntaxe. Ils peuvent identifier les problèmes de maintenabilité, les abstractions peu claires et les problèmes de sécurité potentiels que l'analyse statique traditionnelle laisse souvent de côté.

Ces règles sont-elles spécifiques à Grafana ou peuvent-elles être utilisées par n'importe quelle équipe ?

Bien qu'ils soient inspirés par la base de code de Grafana, les principes s'appliquent à tout projet logiciel à grande échelle. Les équipes peuvent les adapter à leurs propres référentiels pour maintenir la cohérence, prévenir les régressions et améliorer l'intégration.

Quel est le lien entre ces règles et les contrôles de qualité du code d'Aikido ?

Chaque règle peut être implémentée comme une règle d'IA personnalisée dans la plateforme de qualité de code d'Aikido, permettant la détection automatique des problèmes d'architecture, de lisibilité et de sécurité dans chaque demande d'extraction.

Obtenir la sécurité gratuitement

Sécurisez votre code, votre cloud et votre environnement d'exécution dans un système central.
Trouvez et corrigez rapidement et automatiquement les vulnérabilités.

Aucune carte de crédit n'est requise | Scanner les résultats en 32sec.