Principios de Código Limpio: Escribir Software Mantenible
Una guía práctica de código limpio: nombres significativos, funciones cortas, DRY, fundamentos SOLID y hábitos que hacen las bases de código más fáciles de leer y mantener.
Introducción
El código limpio es código que es fácil de entender, fácil de cambiar y fácil de probar. No se trata de ser inteligente, se trata de ser claro. A continuación: los hábitos fundamentales que hacen una base de código sostenible.
Nombres Significativos
Los nombres son la forma más importante de documentación en el código.
Usa Nombres que Revelen Intención
# Malo
x = 10 # que es x?
# Bueno
dias_hasta_expiracion = 10
# Malo
def calc(a, b):
return a * b
# Bueno
def calcular_precio_total(cantidad, precio_unitario):
return cantidad * precio_unitario
Evita la Desinformación
# Malo
lista_cuentas = {} # es un dict, no una lista
# Bueno
cuentas_por_id = {}
Usa Nombres Pronunciables
# Malo
gen_ymdhms = datetime.now()
# Bueno
timestamp_generacion = datetime.now()
Elige Una Palabra Por Concepto
| Concepto | Elige Una | Evita Mezclar |
|---|---|---|
| Obtener datos | get, fetch | No uses ambos |
| Crear objeto | create, make, build | Elige uno |
| Insertar datos | insert, add, append | Elige uno |
Funciones Cortas
Las funciones deben hacer una cosa, hacerla bien, y solo eso.
La Regla de Responsabilidad Única
# Malo: una función hace validación, cálculo y persistencia
def procesar_orden(orden):
if not orden.items:
raise ValueError("Orden vacía")
total = sum(item.precio * item.cant for item in orden.items)
if orden.cliente.is_vip:
total *= 0.9
db.execute("INSERT INTO orders ...", total)
enviar_email(orden.cliente.email, f"Orden {total} confirmada")
# Bueno: componer funciones pequeñas
def validar_orden(orden):
if not orden.items:
raise ValueError("Orden vacía")
def calcular_total(orden):
total = sum(item.precio * item.cant for item in orden.items)
return aplicar_descuento_vip(total, orden.cliente)
def aplicar_descuento_vip(total, cliente):
return total * 0.9 if cliente.is_vip else total
def guardar_orden(orden, total):
db.execute("INSERT INTO orders ...", total)
def confirmar_orden(orden, total):
validar_orden(orden)
total = calcular_total(orden)
guardar_orden(orden, total)
enviar_email(orden.cliente.email, f"Orden {total} confirmada")
Mantén las Funciones Cortas
Apunta a 20 líneas o menos. Si una función excede esto, probablemente hace más de una cosa.
Minimiza los Parámetros
| Numero de Args | Legibilidad |
|---|---|
| 0-1 | Ideal |
| 2 | Razonable |
| 3 | Sospechoso |
| >3 | Requiere justificación (usa struct/objeto) |
DRY — Don’t Repeat Yourself
La duplicación es la raíz del dolor de mantenimiento. Cuando la lógica se repite, una corrección de bug en un lugar suele faltar en otros.
# Malo: lógica de validación repetida
def crear_usuario(email, password):
if "@" not in email:
raise ValueError("Email inválido")
...
def actualizar_email_usuario(user_id, email):
if "@" not in email:
raise ValueError("Email inválido")
...
# Bueno: extraer lógica compartida
def validar_email(email):
if "@" not in email:
raise ValueError("Email inválido")
def crear_usuario(email, password):
validar_email(email)
...
def actualizar_email_usuario(user_id, email):
validar_email(email)
...
Comentarios
Los comentarios deben explicar por qué, no qué. El código mismo debe explicar el qué.
# Malo: el comentario repite lo obvio
count = count + 1 # incrementar count
# Malo: comentario explica lo que el código hace
# Verifica si el usuario está activo y tiene permiso
if usuario.is_active and usuario.has_permission("read"):
...
# Bueno: comentario explica por qué
# Saltar usuarios inactivos porque pueden tener permisos obsoletos
# después de un retraso de baja (ver política RH-2024-03)
if usuario.is_active and usuario.has_permission("read"):
...
Prefiere Código Auto-Documentado
# Malo
# retorna 1 si el usuario puede acceder al recurso
if check(u, r) == 1:
...
# Bueno
if usuario.can_access(recurso):
...
Manejo de Errores
Los errores son parte del dominio, no una ocurrencia tardía.
Usa Excepciones, No Códigos de Retorno
# Malo
def leer_archivo(path):
if not os.path.exists(path):
return None # el llamador debe verificar None
return open(path).read()
resultado = leer_archivo("config.txt")
if resultado is None:
... # manejo de errores disperso
# Bueno
def leer_archivo(path):
if not os.path.exists(path):
raise FileNotFoundError(f"{path} no encontrado")
return open(path).read()
try:
contenido = leer_archivo("config.txt")
except FileNotFoundError as e:
logger.error(e)
...
No Tragues Excepciones
# Malo
try:
operacion_riesgosa()
except Exception:
pass # fallo silencioso
# Bueno
try:
operacion_riesgosa()
except NetworkError as e:
logger.warning("Problema de red, se reintentará", exc_info=e)
reintentar()
Formato
La consistencia importa más que el estilo específico. Elige un estándar, automatízalo y sigue adelante.
- Usa un linter/formatter (Prettier, Black, gofmt)
- Mantén el código relacionado verticalmente cercano — declaración y uso deben estar cerca
- Limita la longitud de línea — 80-100 caracteres es un rango legible
- Usa líneas en blanco para separar grupos lógicos
Objetos y Estructuras de Datos
Dile, No Preguntes
# Malo: preguntar sobre el estado, luego decidir
if cuenta.estado == "sobregirada":
cuenta.bloquear()
# Bueno: dile al objeto qué hacer
cuenta.verificar_sobregiro_y_bloquear()
La Ley de Demeter
Un método solo debe llamar:
- Métodos sobre sí mismo
- Métodos sobre parámetros
- Métodos sobre objetos que crea
- Métodos sobre componentes directos (campos)
# Malo: navegando profundo en un grafo de objetos
cliente.ordenes[-1].items[0].precio
# Bueno: encapsular la navegación
cliente.precio_primer_item_ultima_orden()
Lo que funciona
- Deja el código más limpio de lo que lo encontraste (Regla del Boy Scout)
- Elimina código muerto — código comentado, funciones no usadas, ramas inaccesibles
- Escribe tests primero — obligan a escribir código testable (y por tanto limpio). Consulta estrategias de testing.
- El código se lee 10 veces más de lo que se escribe — optimiza para el lector
- Programación en pareja — dos ojos detectan complejidad antes de que se acumule. Complementa las code reviews.
Errores Comunes
- Optimizar por brevedad en lugar de claridad
- Usar abreviaturas que solo el autor entiende
- Funciones con efectos secundarios que sorprenden al llamador
- Números y strings mágicos dispersos por el código
- Comentarios que se desfasan del código que describen
- Anidación profunda (“código flecha”) que oscurece el camino feliz
Troubleshooting
- Pattern does not fit the problem: re-evaluate the forces (performance, scalability, team size, coupling). A pattern is only appropriate when its trade-offs match your constraints.
- Too many abstractions: if adding a pattern increases complexity without a clear benefit, simplify. Not every module needs a factory, decorator, or strategy.
- Tight coupling after refactoring: check that interfaces are stable and dependencies point inward. Use dependency inversion to break accidental coupling.
- Tests break when the design changes: favor stable contracts over internal structure. Test observable behavior, not private helpers.
- Performance regression from indirection: measure before and after. Layers, decorators, and adapters can add latency; cache or inline hot paths if needed.
Errores Comunes en Producción
- Tratar la guía como un checklist para completar una vez en lugar de una práctica por evolucionar.
- Adoptar cada recomendación de golpe en lugar de comenzar con un cambio medido.
- Saltar la evaluación de madurez e imponer prácticas avanzadas a un equipo no preparado.
- No actualizar runbooks y expectativas de guardia al introducir nuevas prácticas.
- Ignorar datos reales de incidentes al priorizar qué partes de la guía aplicar primero.
- No asignar un responsable que revise decisiones trimestralmente.
- Copiar ejemplos sin adaptarlos a las herramientas y restricciones reales del equipo.
- Olvidar medir resultados antes de agregar la siguiente mejora.
Preguntas frecuentes
¿Cómo empiezo con esto en un proyecto existente?
Empieza con una parte pequeña y aislada de tu codebase. Aplica los conceptos de esta guía a un módulo o servicio. Mide el impacto, luego expande a otras áreas.
¿Qué herramientas necesito?
Las herramientas mencionadas throughout esta guía se listan en cada sección. La mayoría son open-source y ampliamente adoptadas. Consulta los recursos relacionados para instrucciones de setup.
¿Cómo mido el éxito después de implementar esto?
Define métricas claras antes de empezar: benchmarks de rendimiento, tasas de error o indicadores de mantenibilidad. Compara antes y después. Itera basándote en datos, no en suposiciones.
Recursos Relacionados
Principios SOLID Explicados con Ejemplos
Aprende los cinco principios SOLID con ejemplos prácticos de código: Responsabilidad Única, Abierto/Cerrado, Sustitución de Liskov, Segregación de Interfaces e Inversión de Dependencias.
GuideLo que Funciona en Code Review — Para Autores y Revisores
Una guía práctica para revisiones de código útiles: cómo escribir código revisable, dar feedback constructivo y mantener las revisiones rápidas y enfocadas.
GuideGuía de Estrategia de Testing
Una guía práctica para construir una estrategia de testing en capas con unit, integration y end-to-end tests.
DocPlantilla de User Story y Criterios de Aceptación
Plantilla de user story que conecta necesidades de usuarios con implementación mediante criterios de aceptación claros, definición de done y principios INVEST.
GuideDesarrollo Guiado por Pruebas (TDD)
Aprende TDD paso a paso: escribe un test que falle, hazlo pasar, refactoriza. Red-Green-Refactor con ejemplos reales en Python, JavaScript y Java.