Aikido

10 reglas de calidad del código aprendidas del equipo de ingeniería de Grafana

Introducción

Grafana es una de las plataformas de observabilidad de código abierto más populares, con más de 70.000 estrellas de GitHub y miles de colaboradores que la mejoran cada día. Con más de 3.000 problemas abiertos y cientos de pull requests en constante movimiento, mantener el código base limpio y consistente es un verdadero reto.

Estudiando su código base, podemos descubrir algunas de las reglas no escritas y las mejores prácticas que ayudan al equipo a mantener un alto nivel de calidad a la vez que avanza con rapidez. Muchas de estas reglas se centran en la seguridad, la mantenibilidad y la fiabilidad. Algunas de ellas tratan problemas que las herramientas tradicionales de análisis estático (SAST) no pueden detectar, como el uso inadecuado de async, las fugas de recursos o los patrones incoherentes en el código. Estos son los tipos de problemas que los revisores humanos o las herramientas basadas en IA pueden detectar durante una revisión del código.

Los retos

Los grandes proyectos de este tipo se enfrentan a varios retos: gran volumen de código, muchos módulos (API, interfaz de usuario, plugins) e innumerables integraciones externas (Prometheus, Loki, etc.). Cientos de colaboradores pueden seguir estilos de codificación o supuestos diferentes. Las nuevas funciones y las correcciones rápidas pueden introducir errores ocultos, fallos de seguridad o rutas de código confusas. Es posible que los revisores voluntarios no conozcan todas las partes de la base de código, lo que puede hacer que se pasen por alto patrones de diseño o buenas prácticas. En resumen, la escala y la diversidad de las contribuciones hacen que la coherencia y la fiabilidad sean difíciles de imponer.

Por qué son importantes estas normas

Un conjunto claro de reglas de revisión beneficia directamente a la salud de Grafana. En primer lugar, mejora la capacidad de mantenimiento: los patrones coherentes (disposición de carpetas, nombres, gestión de errores) hacen que el código sea más fácil de leer, probar y ampliar. Los revisores pasan menos tiempo adivinando la intención cuando todo el mundo sigue unas convenciones comunes. En segundo lugar, se mejora la seguridad: reglas como "validar siempre la entrada del usuario" o "evitar redirecciones abiertas" evitan vulnerabilidades (CVE-2025-6023/4123, etc.) que se han encontrado en Grafana . Por último, la incorporación de nuevos colaboradores es más rápida: cuando los ejemplos y las revisiones utilizan sistemáticamente las mismas prácticas, los recién llegados aprenden el "estilo Grafana" con rapidez y confianza.

Contexto puente para estas normas

Estas reglas provienen de problemas reales en el código y la comunidad de Grafana. Los avisos de seguridad y los informes de errores han descubierto patrones (por ejemplo, rutas que conducen a XSS) que convertimos en reglas preventivas. Cada regla destaca un escollo concreto, explica por qué es importante (rendimiento, claridad, seguridad, etc.) y muestra un fragmento claro ❌ no conforme frente a ✅ conforme en los lenguajes de Grafana (Go o TypeScript/JS).

Ahora vamos a explorar las 10 reglas que ayudan a mantener el código base de Grafana robusto, seguro y comprensible.

10 reglas prácticas de calidad del código inspiradas en Grafana

1. Utilice variables de entorno para la configuración (evite valores codificados).

Evitacodificar puertos, credenciales, URLs u otros valores específicos del entorno. Léelos desde variables de entorno o archivos de configuración para mantener el código flexible y los secretos fuera del código fuente.

No conforme:

// servidor.js
const appPort = 3000;
app.listen(appPort, () => consola.log("Escuchando en el puerto " + appPort));

Conforme:

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

Por qué es importante: El uso de variables de entorno mantiene los datos sensibles fuera del código fuente, hace que las implementaciones sean flexibles en diferentes entornos y evita fugas accidentales de secretos. También garantiza que los cambios de configuración no requieran modificaciones del código, lo que mejora la capacidad de mantenimiento y reduce los errores.

2. Sanitize user input before using it.‍

Todas las entradas procedentes de usuarios o fuentes externas deben validarse o desinfectarse antes de su uso para evitar ataques de inyección y comportamientos inesperados.

No 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 qué es importante: Una correcta sanitización de la entrada previene XSS, ataques de inyección y comportamientos inesperados causados por entradas malformadas. Protege tanto a los usuarios como al sistema y garantiza que los procesos posteriores, el registro y el almacenamiento manejen los datos de forma segura.

3. Evitar redireccionamientos abiertos y path traversal.

Asegúrese de que cualquier URL o ruta de archivo utilizada en su código esté correctamente validada y desinfectada. No permita que las entradas del usuario determinen directamente las redirecciones o las rutas del sistema de archivos.

No 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 qué es importante: Evitar los redireccionamientos abiertos y el cruce de rutas protege a los usuarios de la suplantación de identidad, las fugas de datos y el acceso no autorizado a archivos. Reduce la superficie de ataque, refuerza los límites de seguridad y evita la exposición accidental de recursos sensibles del servidor.

4. Habilite una política estricta de seguridad de contenidos (CSP).

Aplique una política de seguridad de contenidos en las cabeceras de las aplicaciones que sólo permita scripts, estilos, imágenes y otros recursos de fuentes fiables. No permita fuentes no seguras en línea, eval y comodín.

No conforme: (Sin PEC o demasiado permisivo)

# grafana.ini (no conforme)
content_security_policy = false

Conforme: (CSP fuerte en 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';
"""

Por qué es importante: Un CSP estricto bloquea muchas clases de ataques del lado del cliente, incluido el XSS. Impone un comportamiento predecible para los recursos, reduce la posibilidad de ejecución de código malicioso y proporciona un límite de seguridad claro en el contexto del navegador.

5. Manejar errores y comprobaciones nulas (evitar pánicos).

Compruebe siempre si hay errores y valores nulos en las llamadas a funciones, las respuestas de la API y las estructuras de datos. Sustituye los pánicos por una gestión de errores adecuada y devuelve mensajes o códigos de error significativos.

No 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 qué es importante: Un tratamiento adecuado de los errores evita fallos y garantiza la fiabilidad del sistema incluso cuando se producen entradas o condiciones inesperadas. Mejora la capacidad de mantenimiento, reduce el tiempo de inactividad y facilita la depuración al proporcionar información significativa sobre los errores.

6. Aplazar la limpieza de recursos (evitar fugas).

Asegúrese de que todos los recursos abiertos, como archivos, conexiones de red o manejadores de bases de datos, se cierran correctamente utilizando defer inmediatamente después de la asignación. No confíe en la limpieza manual más adelante en el código.

No conforme:

resp, err := http.Get(url)
// ... usa resp.Body ...
// olvidó: resp.Body.Close()

Conforme:

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

Por qué es importante: Una limpieza adecuada evita las fugas de memoria, el agotamiento de los descriptores de archivo y la saturación del grupo de conexiones. Esto mantiene la estabilidad del sistema, evita la degradación del rendimiento con el tiempo y reduce los problemas operativos en producción.

7. Utilice consultas parametrizadas (evite la inyección SQL).

Utilice siempre consultas parametrizadas o sentencias preparadas al interactuar con la base de datos en lugar de la concatenación de cadenas para los comandos SQL.

No conforme:

// Peligroso: userID podría contener una cita SQL o una inyección
query := "DELETE FROM sessions WHERE user_id = '" + userID + "';"
db.Exec(query)

Conforme:

// Seguro: userID se pasa como parámetro
db.Exec("DELETE FROM sessions WHERE user_id = ?", userID)

Por qué es importante: Las consultas parametrizadas evitan los ataques de inyección SQL, una de las vulnerabilidades de seguridad más comunes. Protegen los datos sensibles, reducen el riesgo de corrupción de la base de datos y hacen que las consultas sean más fáciles de mantener y auditar. Esto garantiza tanto la seguridad como la fiabilidad de su aplicación.

8. Usar async/await correctamente en TypeScript (manejar promesas).

‍Siempreespera promesas y maneja los errores usando try/catch en lugar de ignorar los rechazos o mezclar el manejo al estilo callback.

No 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 qué es importante: Una gestión asíncrona adecuada garantiza que los errores en el código asíncrono no pasen desapercibidos, evita rechazos de promesas no gestionadas y mantiene un flujo de programa predecible. Hace que el código sea más legible, más fácil de depurar, y evita errores sutiles que pueden conducir a la corrupción de datos, estado inconsistente, o inesperados bloqueos en tiempo de ejecución.

9. Favorece los tipos estrictos en TypeScript (evita cualquiera).

Utilice tipos TypeScript precisos en lugar de any para definir variables, parámetros de función y tipos de retorno.

No 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 qué es importante: La tipificación estricta detecta errores de tipado en tiempo de compilación, reduciendo los errores en tiempo de ejecución y mejorando la fiabilidad del código. Hace que el código sea autodocumentado, más fácil de refactorizar y garantiza que todas las partes del sistema interactúen de forma predecible y segura, lo que es crucial en bases de código grandes y complejas como la de Grafana.

10. Aplique un estilo de código y una nomenclatura coherentes.

Aplique un formato, unas convenciones de nomenclatura y unas estructuras de archivos uniformes en todo el código base.

No conforme: (estilos mixtos)

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

Conforme:

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

Por qué es importante: Un estilo y una nomenclatura coherentes mejoran la legibilidad y facilitan la comprensión y el mantenimiento del código por parte de múltiples colaboradores. Reduce la sobrecarga cognitiva al navegar por el proyecto, evita errores sutiles causados por malentendidos y garantiza que las herramientas automatizadas (alineadores, formateadores, revisores de código) puedan aplicar con fiabilidad las normas de calidad en un entorno de equipo grande.

Conclusión

Cada regla anterior aborda un desafío recurrente en la base de código de Grafana. Aplicarlas de forma coherente durante las revisiones del código ayuda al equipo a mantener un código limpio y predecible, a mejorar la seguridad mediante la prevención de vulnerabilidades comunes y a facilitar la incorporación al proporcionar patrones claros para los nuevos colaboradores. A medida que el proyecto crece, estas prácticas mantienen el código base fiable, fácil de mantener y de navegar para todos los implicados. Seguir estas reglas puede ayudar a cualquier equipo de ingeniería a crear y mantener software de alta calidad a gran escala.

Preguntas frecuentes

¿Tiene alguna pregunta?

¿Por qué analizar el repositorio de Grafana en busca de reglas de revisión de código?

Grafana es un proyecto de código abierto grande y maduro con miles de colaboradores. El estudio de su código base revela patrones de ingeniería del mundo real que ayudan a los equipos a mantener un software limpio, escalable y seguro a escala.

¿En qué se diferencian estas reglas de las comprobaciones normales de linting o de formato?

Los linters tradicionales detectan problemas de sintaxis y formato. Estas reglas basadas en Grafana van más allá y se centran en la arquitectura, la legibilidad, la coherencia y las decisiones de seguridad que requieren una comprensión contextual, algo que las revisiones basadas en IA pueden gestionar.

¿Cómo pueden ayudar las herramientas basadas en IA a detectar estos problemas de calidad del código?

Las herramientas de IA pueden analizar la intención, la nomenclatura, la arquitectura y el contexto, no sólo la sintaxis. Pueden identificar problemas de mantenimiento, abstracciones poco claras y posibles problemas de seguridad que el análisis estático tradicional suele pasar por alto.

¿Son estas normas específicas de Grafana o puede utilizarlas cualquier equipo?

Aunque están inspirados en el código base de Grafana, los principios se aplican a cualquier proyecto de software a gran escala. Los equipos pueden adaptarlos a sus propios repositorios para mantener la coherencia, evitar regresiones y mejorar la incorporación.

¿Cómo se relacionan estas reglas con los controles de calidad del código de Aikido?

Cada regla puede implementarse como una regla de IA personalizada en la plataforma de calidad del código de Aikido, lo que permite la detección automática de problemas de arquitectura, legibilidad y seguridad en cada pull request.

Asegúrese gratis

Proteja el código, la nube y el tiempo de ejecución en un sistema central.
Encuentre y corrija vulnerabilidades de forma rápida y automática.

No requiere tarjeta de crédito | Escanea resultados en 32segs.