Einleitung
Grafana ist eine der beliebtesten Open-Source-Observability-Plattformen mit über 70.000 GitHub-Sternen und Tausenden von Mitwirkenden, die sie täglich verbessern. Mit mehr als 3.000 offenen Issues und Hunderten von Pull Requests, die ständig in Bewegung sind, ist es eine echte Herausforderung, die Codebasis sauber und konsistent zu halten.
Durch die Untersuchung seiner Codebasis können wir einige der ungeschriebenen Regeln und Best Practices aufdecken, die dem Team helfen, die Qualität hoch zu halten und gleichzeitig schnell voranzukommen. Viele dieser Regeln konzentrieren sich auf Sicherheit, Wartbarkeit und Zuverlässigkeit. Einige davon befassen sich mit Problemen, die traditionelle statische Analysetools (SAST) nicht erkennen können, wie unsachgemäße Async-Nutzung, Ressourcenlecks oder inkonsistente Muster im Code. Dies sind die Arten von Problemen, die menschliche Reviewer oder KI-gestützte Tools während eines Code-Reviews erkennen können.
Die Herausforderungen
Große Projekte wie dieses stehen vor mehreren Herausforderungen: ein enormes Codevolumen, viele Module (API, UI, Plugins) und unzählige externe Integrationen (Prometheus, Loki usw.). Hunderte von Mitwirkenden können unterschiedliche Codierungsstile oder Annahmen verfolgen. Neue Funktionen und schnelle Fehlerbehebungen können versteckte Fehler, Sicherheitslücken oder verwirrende Codepfade einführen. Freiwillige Reviewer kennen möglicherweise nicht jeden Teil der Codebasis, was zu übersehenen Designmustern oder Best Practices führen kann. Kurz gesagt, Umfang und Vielfalt der Beiträge erschweren die Durchsetzung von Konsistenz und Zuverlässigkeit.
Warum diese Regeln wichtig sind
Ein klares Regelwerk für Reviews kommt der Integrität von Grafana direkt zugute. Erstens verbessert sich die Wartbarkeit: Konsistente Muster (Ordnerstruktur, Benennung, Fehlerbehandlung) machen den Code leichter lesbar, testbar und erweiterbar. Reviewer verbringen weniger Zeit damit, Absichten zu erraten, wenn alle gängigen Konventionen folgen. Zweitens wird die Sicherheit erhöht: Regeln wie „Benutzereingaben immer validieren“ oder „offene Redirects vermeiden“ verhindern Schwachstellen (CVE-2025-6023/4123 usw.), die in Grafana gefunden wurden. Schließlich wird das Onboarding neuer Mitwirkender beschleunigt: Wenn Beispiele und Reviews konsequent dieselben Praktiken verwenden, lernen Neulinge den „Grafana-Weg“ schnell und sicher.
Kontext zu diesen Regeln herstellen
Diese Regeln stammen aus realen Problemen im Code und in der Community von Grafana. Sicherheitswarnungen und Fehlerberichte haben Muster (z. B. Path Traversal, das zu XSS führt) aufgedeckt, die wir in präventive Regeln umwandeln. Jede Regel unten hebt eine konkrete Falle hervor, erklärt, warum sie wichtig ist (Leistung, Klarheit, Sicherheit usw.), und zeigt ein klares ❌ nicht-konformes vs. ✅ konformes Code-Snippet in den Sprachen von Grafana (Go oder TypeScript/JS).
Lassen Sie uns nun die 10 Regeln erkunden, die dazu beitragen, die Codebasis von Grafana robust, sicher und verständlich zu halten.
10 Praktische Code-Qualitätsregeln, inspiriert von Grafana
1. Verwenden Sie Umgebungsvariablen für die Konfiguration (vermeiden Sie fest codierte Werte).
Vermeiden Sie das Hardcodieren von Ports, Anmeldeinformationen, URLs oder anderen umgebungsspezifischen Werten. Lesen Sie diese aus Umgebungsvariablen oder Konfigurationsdateien, um den Code flexibel zu halten und Secrets aus dem Quellcode fernzuhalten.
❌ Nicht konform:
// server.js
const appPort = 3000;
app.listen(appPort, () => console.log("Listening on Port " + appPort));✅ Konform:
// server.ts
const PORT = Number(process.env.PORT) || 3000;
app.listen(PORT, () => console.log(`Listening on port ${PORT}`));Warum das wichtig ist: Die Verwendung von Umgebungsvariablen hält sensible Daten aus dem Quellcode fern, macht Bereitstellungen über verschiedene Umgebungen hinweg flexibel und vermeidet versehentliches Offenlegen von Secrets. Sie stellt außerdem sicher, dass Konfigurationsänderungen keine Code-Modifikationen erfordern, was die Wartbarkeit verbessert und Fehler reduziert.
2. Bereinigen Sie Benutzereingaben, bevor Sie sie verwenden.
Alle Eingaben von Benutzern oder externen Quellen sollten vor der Verwendung validiert oder bereinigt werden, um Injection-Angriffe und unerwartetes Verhalten zu verhindern.
❌ Nicht konform:
// frontend/src/components/UserForm.tsx
const handleSubmit = (username: string) => {
setUsers([...users, { name: username }]);
};✅ Konform:
// 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 }]);
};Warum das wichtig ist: Eine ordnungsgemäße Input-Sanitisierung verhindert XSS, Injection-Angriffe und unerwartetes Verhalten, das durch fehlerhafte Eingaben verursacht wird. Sie schützt sowohl Nutzende als auch das System und stellt sicher, dass nachgelagerte Prozesse, Logging und Speicherung Daten sicher verarbeiten.
3. Open Redirects und Path Traversal verhindern.
Stellen Sie sicher, dass alle in Ihrem Code verwendeten URLs oder Dateipfade ordnungsgemäß validiert und bereinigt werden. Erlauben Sie nicht, dass Benutzereingaben direkt Umleitungen oder Dateisystempfade bestimmen.
❌ Nicht konform:
// Express route in Grafana plugin
app.get("/goto", (req, res) => {
const dest = req.query.next; // attacker can supply any URL
res.redirect(dest);
});✅ Konform:
// 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");
}
});Warum das wichtig ist: Das Verhindern von offenen Redirects und Path Traversal schützt Benutzer vor Phishing, Datenlecks und unbefugtem Dateizugriff. Es reduziert die Angriffsfläche, erzwingt Sicherheitsgrenzen und vermeidet die unbeabsichtigte Offenlegung sensibler Serverressourcen.
4. Eine strikte Content Security Policy (CSP) aktivieren.
Eine Content Security Policy in den Anwendungs-Headern durchsetzen, die nur Skripte, Styles, Bilder und andere Ressourcen von vertrauenswürdigen Quellen erlaubt. Unsafe-inline, eval und Wildcard-Quellen nicht zulassen.
❌ Nicht konform: (Kein CSP oder zu permissiv)
# grafana.ini (nicht konform)
content_security_policy = false✅ Konform: (Starkes CSP in der Konfiguration)
# 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';
"""Warum dies wichtig ist: Eine strikte CSP blockiert viele Arten von clientseitigen Angriffen, einschließlich XSS. Sie erzwingt ein vorhersehbares Verhalten für Ressourcen, reduziert die Wahrscheinlichkeit der Ausführung bösartigen Codes und bietet eine klare Sicherheitsgrenze im Browser-Kontext.
5. Fehler und Nil-Prüfungen behandeln (Panics vermeiden).
Überprüfen Sie immer Funktionsaufrufe, API-Antworten und Datenstrukturen auf Fehler und Nullwerte. Ersetzen Sie Panics durch eine ordnungsgemäße Fehlerbehandlung und geben Sie aussagekräftige Fehlermeldungen oder Codes zurück.
❌ Nicht konform:
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✅ Konform:
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}Warum das wichtig ist: Eine korrekte Fehlerbehandlung verhindert Abstürze und stellt sicher, dass das System auch bei unerwarteten Eingaben oder Bedingungen zuverlässig bleibt. Sie verbessert die Wartbarkeit, reduziert Ausfallzeiten und erleichtert das Debugging durch die Bereitstellung aussagekräftiger Fehlerinformationen.
6. Ressourcenbereinigung aufschieben (Leaks verhindern).
Sicherstellen, dass alle geöffneten Ressourcen wie Dateien, Netzwerkverbindungen oder Datenbank-Handles mithilfe von defer unmittelbar nach der Zuweisung ordnungsgemäß geschlossen werden. Verlassen Sie sich nicht auf eine manuelle Bereinigung später im Code.
❌ Nicht konform:
resp, err := http.Get(url)
// ... resp.Body verwenden ...
// vergessen: resp.Body.Close()✅ Konform:
resp, err := http.Get(url)
if err != nil {
// handle error
}
defer resp.Body.Close()
// ... use resp.Body ...Warum das wichtig ist: Eine ordnungsgemäße Bereinigung verhindert Speicherlecks, die Erschöpfung von Dateideskriptoren und die Sättigung von Verbindungspools. Dies erhält die Systemstabilität, vermeidet eine Leistungsverschlechterung im Laufe der Zeit und reduziert betriebliche Probleme in der Produktion.
7. Parametrisierte Abfragen verwenden (SQL Injection vermeiden).
Verwenden Sie bei der Interaktion mit der Datenbank immer parametrisierte Abfragen oder Prepared Statements anstelle von String-Verkettung für SQL-Befehle.
❌ Nicht konform:
// Gefährlich: userID könnte ein SQL-Anführungszeichen oder eine Injection enthalten
query := "DELETE FROM sessions WHERE user_id = '" + userID + "';"
db.Exec(query)✅ Konform:
// Sicher: userID wird als Parameter übergeben
db.Exec("DELETE FROM sessions WHERE user_id = ?", userID)Warum das wichtig ist: Parametrisierte Abfragen verhindern SQL-Injection-Angriffe, eine der häufigsten Sicherheitslücken. Sie schützen sensible Daten, reduzieren das Risiko von Datenbankkorruption und machen Abfragen wartbarer und leichter auditierbar. Dies gewährleistet sowohl die Sicherheit als auch die Zuverlässigkeit Ihrer Anwendung.
8. async/await in TypeScript korrekt verwenden (Promises handhaben).
Promises immer abwarten und Fehler mit try/catch behandeln, anstatt Rejections zu ignorieren oder Callback-basiertes Handling zu mischen.
❌ Nicht konform:
async function fetchData() {
// Missing await: fetch returns a Promise, not the actual data
const res = fetch('/api/values');
console.log(res.data); // undefined
}✅ Konform:
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);
}
}Warum das wichtig ist: Eine korrekte asynchrone Fehlerbehandlung stellt sicher, dass Fehler in asynchronem Code nicht unbemerkt bleiben, verhindert unbehandelte Promise-Rejections und erhält einen vorhersehbaren Programmfluss. Dies macht den Code lesbarer, leichter debuggbar und verhindert subtile Fehler, die zu Datenkorruption, inkonsistentem Zustand oder unerwarteten Laufzeitabstürzen führen können.
9. Strikte Typen in TypeScript bevorzugen (any vermeiden).
Verwenden Sie präzise TypeScript-Typen anstelle von 'any', um Variablen, Funktionsparameter und Rückgabetypen zu definieren.
❌ Nicht konform:
// No types specified
function updateUser(data) {
// ...
}
let config: any = loadConfig();✅ Konform:
interface User { id: number; name: string; }
function updateUser(data: User): Promise<User> {
// ...
}
interface AppConfig { endpoint: string; timeoutMs: number; }
const config: AppConfig = loadConfig();Warum das wichtig ist: Strikte Typisierung fängt typbezogene Fehler zur Kompilierzeit ab, reduziert Laufzeitfehler und verbessert die Code-Zuverlässigkeit. Sie macht den Code selbstdokumentierend, erleichtert das Refactoring und stellt sicher, dass alle Systemteile auf vorhersehbare, typsichere Weise interagieren, was in großen, komplexen Codebasen wie der von Grafana entscheidend ist.
10. Einheitlichen Codestil und Benennung anwenden.
Einheitliche Formatierung, Namenskonventionen und Dateistrukturen in der gesamten Codebasis durchsetzen.
❌ Nicht konform: (gemischte Stile)
const ApiData = await getdata(); // PascalCase for variable? function name not camelCase.
function Fetch_User() { ... } // Unusual naming.✅ Konform:
const apiData = await fetchData();
function fetchUser() { ... }Warum das wichtig ist: Ein konsistenter Stil und eine einheitliche Benennung verbessern die Lesbarkeit und erleichtern es mehreren Mitwirkenden, den Code zu verstehen und zu warten. Dies reduziert den kognitiven Aufwand beim Navigieren im Projekt, verhindert subtile Fehler, die durch Missverständnisse entstehen, und stellt sicher, dass automatisierte Tools (Linter, Formatierer, Code-Reviewer) Qualitätsstandards in einer großen Teamumgebung zuverlässig durchsetzen können.
Fazit
Jede der oben genannten Regeln befasst sich mit einer wiederkehrenden Herausforderung in der Codebasis von Grafana. Ihre konsequente Anwendung bei Code-Reviews hilft dem Team, sauberen und vorhersehbaren Code zu pflegen, die Sicherheit durch die Vermeidung gängiger Schwachstellen zu verbessern und das Onboarding durch die Bereitstellung klarer Muster für neue Mitwirkende zu erleichtern. Mit der Skalierung des Projekts sorgen diese Praktiken dafür, dass die Codebasis zuverlässig, wartbar und für alle Beteiligten leichter zu navigieren bleibt. Die Befolgung dieser Regeln kann jedem Engineering-Team helfen, hochwertige Software in großem Maßstab zu entwickeln und zu pflegen.
.avif)
