← Volver al blog
· 4 min de lectura · 749 palabras

Clean Code práctico: principios para código Python mantenible

Introducción

Hay dos formas de escribir y mantener código: la forma en que solo importa que funcione, y la forma en que consideras que otras personas (o tú dentro de 6 meses) van a tener que leer y modificar ese código.

Clean Code no es un lujo ni una moda. Es una cuestión de economía: el tiempo de lectura es mucho mayor que el tiempo de escritura. Según estudios, los desarrolladores pasan ~60% de su tiempo leyendo código existente y ~20% escribiendo código nuevo. Invertir en claridad es invertir en velocidad a largo plazo.

En este artículo no voy a repetir el libro de Robert Martin. Voy a cubrir los principios que he visto marcar la diferencia en equipos reales.

Nombres que comunican

Malo

def proc(d):
    r = []
    for i in d:
        if i[1] > 0:
            r.append(i[0])
    return r

Bueno

def obtener_usuarios_activos(usuarios):
    return [u.id for u in usuarios if u.esta_activo]
``\]

### Reglas para nombres

1. **Comunican intención**: Un nombre debe responder "¿qué hace esto?" sin mirar el cuerpo.
2. **Busca y reemplaza**: `get_user_by_email` > `get_user` (específico > genérico).
3. **Longitud proporcional al ámbito**: `i` en un list comprehension de 1 línea está bien. `i` en una función de 50 líneas no.
4. **Consistencia**: Si usas `crear_usuario`, no uses `generate_user` en otra parte.

## Funciones pequeñas con una responsabilidad

### Malo

```python
def procesar_pedido(pedido):
    # Validar
    if not pedido.items:
        raise ValueError("Pedido vacío")
    if pedido.total <= 0:
        raise ValueError("Total inválido")
    
    # Calcular impuestos
    impuesto = pedido.total * 0.16
    
    # Guardar en BD
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute("INSERT INTO pedidos ...", pedido)
    
    # Enviar email
    send_email(pedido.usuario.email, "Pedido confirmado")

Bueno

def procesar_pedido(pedido):
    validar_pedido(pedido)
    pedido.impuesto = calcular_impuesto(pedido)
    guardar_pedido(pedido)
    notificar_pedido_confirmado(pedido)

Cada subfunción se prueba por separado, se lee sin contexto, y se modifica sin riesgo de romper otras partes.

Evita efectos secundarios

# Malo: modifica el argumento y retorna algo
def procesar_usuarios(usuarios):
    for u in usuarios:
        u.procesado = True  # efecto secundario
    return usuarios

# Bueno: separa mutación de consulta
def marcar_como_procesados(usuarios):
    for u in usuarios:
        u.procesado = True

def obtener_marcados(usuarios):
    return [u for u in usuarios if u.procesado]

Manejo de errores explícito

# Malo
def get_user(user_id):
    try:
        return db.query(User).filter(id=user_id).one()
    except:
        return None

# Bueno
def get_user(user_id):
    try:
        return db.query(User).filter(id=user_id).one()
    except NoResultFound:
        return None
    except MultipleResultsFound:
        logger.error(f"Múltiples usuarios con id {user_id}")
        raise
  • No captures Exception genéricamente
  • No silencies errores
  • Usa excepciones específicas

Comentarios: menos es más

Comentarios que sobran

# Suma a y b
def suma(a, b):
    return a + b

Comentarios útiles

# La API externa retorna códigos en lugar de HTTP status
# Mapeo según documentación v2.3 sección 4.1
CODIGOS_ERROR = {0: "ok", 1: "error_autenticacion", 2: "error_validacion"}
``\)

## Principio DRY con cuidado

DRY (Don't Repeat Yourself) es importante, pero la abstracción prematura es peor que la duplicación.

```python
# Duplicación aceptable (solo 2 veces)
def calcular_precio_base(producto):
    return producto.precio * (1 - producto.descuento)

def calcular_precio_con_envio(producto):
    base = producto.precio * (1 - producto.descuento)
    return base + producto.costo_envio

vs:

# Abstracción prematura
def aplicar_descuento(precio, descuento, costo_envio=0):
    return precio * (1 - descuento) + costo_envio

La regla: la tercera vez que repites algo, abstrae. Las dos primeras veces, la duplicación es más barata que la abstracción incorrecta.

Niveles de abstracción

Cada función debe operar a un solo nivel de abstracción:

# Malo: mezcla niveles
def init_app():
    config = load_config()  # alto nivel
    db_url = f"postgres://{config['db_user']}:{config['db_pass']}@localhost/db"  # bajo nivel
    engine = create_engine(db_url)  # alto nivel
    print("App iniciada")  # bajo nivel

# Bueno: separa niveles
def init_app():
    config = load_config()
    engine = crear_conexion_db(config)
    loggear_inicio()

def crear_conexion_db(config):
    db_url = construir_url_db(config)
    return create_engine(db_url)

def construir_url_db(config):
    return f"postgres://{config['db_user']}:{config['db_pass']}@localhost/db"

def loggear_inicio():
    print("App iniciada")

Principio de mínima sorpresa

El código debería comportarse como se espera:

# Sorprendente: append retorna None
lista = [1, 2, 3]
nueva = lista.append(4)  # nueva es None

# No sorprendente: sorted retorna nueva lista
ordenada = sorted([3, 1, 2])  # [1, 2, 3]

Conclusión

Clean Code no es perfección. Es pragmatismo. Código limpio es código que:

  • Se lee como una buena prosa
  • Hace una cosa y la hace bien
  • Es fácil de cambiar sin romper otras partes
  • Comunica intención sin necesidad de comentarios

La próxima vez que escribas una función de 200 líneas con variables de una letra y efectos secundarios, pregúntate: ¿cómo me sentiría si tuviera que debuggear esto a las 3 AM?