Einführung
Grafana ist eine der beliebtesten Open-Source-Plattformen zur Beobachtung von Daten, mit über 70k GitHub-Sternen und Tausenden von Mitwirkenden, die sie täglich verbessern. Mit mehr als 3k 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 der Codebasis können wir einige der ungeschriebenen Regeln und bewährten Verfahren aufdecken, die dem Team helfen, die Qualität hoch zu halten und gleichzeitig schnell zu arbeiten. Viele dieser Regeln konzentrieren sich auf Sicherheit, Wartbarkeit und Zuverlässigkeit. Einige von ihnen befassen sich mit Problemen, die herkömmliche statische Analysetools (SAST) nicht aufspüren können, z. B. unsachgemäße asynchrone Verwendung, Ressourcenlecks oder inkonsistente Muster im gesamten Code. Dies sind die Arten von Problemen, die menschliche Prüfer oder KI-gestützte Tools bei einer Codeüberprüfung erkennen können.
Die Herausforderungen
Große Projekte wie dieses sind mit mehreren Herausforderungen konfrontiert: massives 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 Korrekturen können versteckte Fehler, Sicherheitslücken oder verwirrende Codepfade mit sich bringen. Freiwillige Prüfer kennen möglicherweise nicht jeden Teil der Codebasis, was dazu führt, dass Entwurfsmuster oder bewährte Verfahren übersehen werden. Kurz gesagt: Umfang und Vielfalt der Beiträge machen es schwierig, Konsistenz und Zuverlässigkeit durchzusetzen.
Warum diese Regeln wichtig sind
Ein klarer Satz von Überprüfungsregeln wirkt sich direkt auf die Gesundheit von Grafana aus. Erstens verbessert sich die Wartbarkeit: Konsistente Muster (Ordnerlayout, Namensgebung, Fehlerbehandlung) machen den Code leichter lesbar, testbar und erweiterbar. Die Prüfer verbringen weniger Zeit damit, die Absicht zu erraten, wenn sich alle an gemeinsame Konventionen halten. Zweitens wird die Sicherheit verbessert: Regeln wie "Benutzereingaben immer validieren" oder "offene Weiterleitungen vermeiden" verhindern Sicherheitslücken (CVE-2025-6023/4123 usw.), die in Grafana gefunden wurden. Und schließlich werden neue Mitarbeiter schneller eingearbeitet: Wenn Beispiele und Überprüfungen konsequent die gleichen Praktiken verwenden, lernen Neulinge schnell und sicher den "Grafana-Weg".
Überbrückungskontext zu diesen Regeln
Diese Regeln stammen aus realen Problemen im Code von Grafana und der Community. Sicherheitshinweise und Fehlerberichte haben Muster aufgedeckt (z. B. Pfadüberquerung, die zu XSS führt), die wir in präventive Regeln umwandeln. Jede der folgenden Regeln hebt einen konkreten Fallstrick hervor, erklärt, warum er wichtig ist (Leistung, Klarheit, Sicherheit usw.), und zeigt ein klares ❌ nicht konformes vs. ✅ konformes Snippet in Grafanas Sprachen (Go oder TypeScript/JS).
Lassen Sie uns nun die 10 Regeln erkunden, die dazu beitragen, dass die Codebasis von Grafana robust, sicher und verständlich bleibt.
10 praktische Code-Qualitätsregeln, inspiriert von Grafana
1. Verwenden Sie Umgebungsvariablen für die Konfiguration (vermeiden Sie fest kodierte Werte).
VermeidenSie es, Ports, Anmeldedaten, URLs oder andere umgebungsspezifische Werte fest zu kodieren. Lesen Sie sie aus Umgebungsvariablen oder Konfigurationsdateien, um den Code flexibel zu halten und secrets aus dem Quellcode herauszuhalten.
❌ Nicht konform:
// server.js
const appPort = 3000;
app.listen(appPort, () => Konsole.log("Lauschen auf Port " + appPort));✅ Konform:
// server.ts
const PORT = Number(process.env.PORT) || 3000;
app.listen(PORT, () => console.log(`Listening on port ${PORT}`));Warum dies wichtig ist: Durch die Verwendung von Umgebungsvariablen werden sensible Daten aus dem Quellcode herausgehalten, der Einsatz in verschiedenen Umgebungen wird flexibel und ein versehentliches Durchsickern von secrets wird vermieden. Außerdem wird sichergestellt, dass Konfigurationsänderungen keine Codeänderungen 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 Injektionsangriffe 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 dies wichtig ist: Die korrekte Bereinigung von Eingaben verhindert XSS, Injektionsangriffe und unerwartetes Verhalten, das durch fehlerhafte Eingaben verursacht wird. Sie schützt sowohl die Benutzer als auch das System und stellt sicher, dass nachgelagerte Prozesse, Protokollierung und Speicherung die Daten sicher verarbeiten.
3. Verhinderung von offenen Umleitungen und Path Traversal.
Stellen Sie sicher, dass alle URLs oder Dateipfade, die in Ihrem Code verwendet werden, ordnungsgemäß validiert und bereinigt werden. Lassen Sie nicht zu, dass Benutzereingaben Umleitungen oder Dateisystempfade direkt 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 dies wichtig ist: Die Verhinderung von offenen Umleitungen und Pfadüberquerungen schützt Benutzer vor Phishing, Datenlecks und unbefugtem Dateizugriff. Es reduziert die Angriffsfläche, setzt Sicherheitsgrenzen durch und verhindert die versehentliche Offenlegung sensibler Serverressourcen.
4. Aktivieren Sie eine strenge Inhaltssicherheitsrichtlinie (CSP).
Erzwingen Sie eine Inhaltssicherheitsrichtlinie in den Kopfzeilen der Anwendung, die nur Skripte, Stile, Bilder und andere Ressourcen aus vertrauenswürdigen Quellen zulässt. Verhindern Sie unsafe-inline-, eval- und Wildcard-Quellen.
❌ Nicht konform: (kein LSP oder zu freizügig)
# grafana.ini (nicht konform)
content_security_policy = false✅ Konform: (Strong CSP in config)
# 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 das wichtig ist: Ein strikter CSP blockiert viele Arten von clientseitigen Angriffen, einschließlich XSS. Sie erzwingt ein vorhersehbares Verhalten für Ressourcen, verringert die Wahrscheinlichkeit der Ausführung von bösartigem Code und bietet eine klare Sicherheitsgrenze im Browser-Kontext.
5. Behandlung von Fehlern und Nil-Checks (Vermeidung von Panik).
Prüfen Sie immer auf Fehler und Nullwerte in Funktionsaufrufen, API-Antworten und Datenstrukturen. Ersetzen Sie Panics durch eine angemessene 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 ordnungsgemäße Fehlerbehandlung verhindert Abstürze und stellt sicher, dass das System auch dann zuverlässig bleibt, wenn unerwartete Eingaben oder Bedingungen auftreten. Sie verbessert die Wartbarkeit, reduziert Ausfallzeiten und erleichtert die Fehlersuche, indem sie aussagekräftige Fehlerinformationen liefert.
6. Aufschieben der Ressourcensäuberung (Vermeidung von Leckagen).
Stellen Sie sicher, dass alle geöffneten Ressourcen wie Dateien, Netzwerkverbindungen oder Datenbank-Handles sofort nach der Zuweisung mit defer 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 dies wichtig ist: Eine ordnungsgemäße Bereinigung verhindert Speicherlecks, eine Erschöpfung der Dateideskriptoren und eine Sättigung des Verbindungspools. Dadurch wird die Systemstabilität aufrechterhalten, eine Leistungsverschlechterung im Laufe der Zeit vermieden und Betriebsprobleme in der Produktion reduziert.
7. Verwenden Sie parametrisierte Abfragen (vermeiden Sie SQL-Injection).
Verwenden Sie bei der Interaktion mit der Datenbank immer parametrisierte Abfragen oder vorbereitete Anweisungen anstelle der Stringverkettung für SQL-Befehle.
❌ Nicht konform:
// Gefährlich: userID könnte ein SQL-Zeichen oder eine Injektion 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 dies wichtig ist: Parametrisierte Abfragen verhindern SQL-Injection-Angriffe, eine der häufigsten Sicherheitslücken. Sie schützen sensible Daten, verringern das Risiko einer Datenbankbeschädigung und machen Abfragen wartungsfreundlicher und einfacher zu überprüfen. Dies gewährleistet sowohl die Sicherheit als auch die Zuverlässigkeit Ihrer Anwendung.
8. Richtiges Verwenden von async/await in TypeScript (Handhabung von Versprechen).
WartenSie immer auf Versprechen und behandeln Sie Fehler mit try/catch, anstatt Ablehnungen zu ignorieren oder eine Callback-ähnliche Behandlung zu verwenden.
❌ 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 ordnungsgemäße asynchrone Behandlung stellt sicher, dass Fehler in asynchronem Code nicht unbemerkt bleiben, verhindert die Ablehnung unbehandelter Versprechen und sorgt für einen vorhersehbaren Programmablauf. Dadurch wird der Code lesbarer und leichter zu debuggen, und es werden subtile Fehler vermieden, die zu Datenbeschädigung, inkonsistenten Zuständen oder unerwarteten Laufzeitabstürzen führen können.
9. Bevorzugen Sie strikte Typen in TypeScript (vermeiden Sie alle).
Verwenden Sie präzise TypeScript-Typen anstelle von beliebigen, 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 Zuverlässigkeit des Codes. Sie macht den Code selbstdokumentierend, erleichtert das Refactoring und stellt sicher, dass alle Teile des Systems auf vorhersehbare, typsichere Weise interagieren, was bei großen, komplexen Codebasen wie der von Grafana entscheidend ist.
10. Anwendung eines einheitlichen Codestils und einer einheitlichen Namensgebung.
Durchsetzung einheitlicher Formatierungen, Namenskonventionen und Dateistrukturen in der gesamten Codebasis.
❌ Nicht konform: (Mischformen)
const ApiData = await getdata(); // PascalCase for variable? function name not camelCase.
function Fetch_User() { ... } // Unusual naming.✅ Konform:
const apiData = await fetchData();
function fetchUser() { ... }Warum dies wichtig ist: Ein einheitlicher Stil und eine einheitliche Benennung verbessern die Lesbarkeit und erleichtern es mehreren Beteiligten, den Code zu verstehen und zu pflegen. Es reduziert den kognitiven Overhead bei der Navigation im Projekt, verhindert subtile Fehler, die durch Missverständnisse verursacht werden, und stellt sicher, dass automatisierte Tools (Linters, Formatierer, Code-Reviewer) Qualitätsstandards in einer großen Teamumgebung zuverlässig durchsetzen können.
Schlussfolgerung
Jede der oben genannten Regeln befasst sich mit einer wiederkehrenden Herausforderung in der Codebasis von Grafana. Ihre konsequente Anwendung während der Code-Reviews hilft dem Team, sauberen und vorhersehbaren Code zu erhalten, die Sicherheit durch die Vermeidung häufiger Schwachstellen zu verbessern und das Onboarding durch die Bereitstellung klarer Muster für neue Mitarbeiter zu vereinfachen. Wenn das Projekt skaliert, sorgen diese Praktiken dafür, dass die Codebasis zuverlässig und wartbar bleibt und für alle Beteiligten einfacher zu handhaben ist. Die Befolgung dieser Regeln kann jedem Entwicklungsteam dabei helfen, qualitativ hochwertige Software in großem Umfang zu erstellen und zu erhalten.
.avif)
