StackPractices
beginner Por Mathias Paulenko

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.

Temas: design

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

ConceptoElige UnaEvita Mezclar
Obtener datosget, fetchNo uses ambos
Crear objetocreate, make, buildElige uno
Insertar datosinsert, add, appendElige 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 ArgsLegibilidad
0-1Ideal
2Razonable
3Sospechoso
>3Requiere 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:

  1. Métodos sobre sí mismo
  2. Métodos sobre parámetros
  3. Métodos sobre objetos que crea
  4. 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.