Código Python limpio no es código decorado: es código que revela intención

El código Python “limpio” suele presentarse como una colección de mandamientos: cuatro espacios, nombres descriptivos, líneas cortas, comentarios y funciones pequeñas. Esas prácticas importan, pero no son el resultado final. Un archivo puede cumplir una lista de estilo y seguir escondiendo qué decisión toma, qué entrada espera o qué parte se puede cambiar sin romper otra.

La tesis de este artículo es más concreta: el código limpio revela intención y límites cuando alguien necesita leerlo, probarlo o modificarlo. El PEP 8 dice que el código se lee mucho más a menudo de lo que se escribe y que las convenciones buscan mejorar legibilidad y consistencia. La pregunta útil no es “¿cumplí cada regla?”, sino “¿puede otra persona reconstruir el motivo de esta línea sin ejecutar una arqueología del archivo?”.

Empieza con un archivo que funciona, pero cuesta modificar

Observa este fragmento:

def p(d):
    x = []
    for i in d:
        if i["a"] and i["a"] not in x:
            x.append(i["a"])
    return x

El código puede devolver un resultado correcto, pero exige varias preguntas. ¿Qué representa d? ¿Qué es a? ¿Por qué se eliminan repetidos? ¿El orden importa? ¿Qué ocurre si un registro no tiene esa clave? La indentación está bien, pero la intención está comprimida.

Una primera reescritura hace visible el dominio:

def categorias_unicas(productos):
    categorias = []
    for producto in productos:
        categoria = producto.get("categoria")
        if categoria and categoria not in categorias:
            categorias.append(categoria)
    return categorias

Ahora aparece una decisión adicional: se usa get(), se descartan valores vacíos y se conserva el primer orden observado. Si cualquiera de esas decisiones no es correcta, resulta más fácil discutirla porque tiene un nombre y una ubicación. El objetivo de un nombre descriptivo no es hacer que el archivo parezca profesional; es reducir el número de hipótesis que el lector debe inventar.

Una práctica de nombres: nombra el significado, no el recipiente

datos, resultado y temp no son siempre malos nombres, pero suelen aparecer cuando el programador nombra el recipiente y todavía no ha nombrado el significado. Compara:

# Antes
x = [p for p in pedidos if p["total"] > 100]

# Después
pedidos_de_alto_valor = [
    pedido for pedido in pedidos
    if pedido["total"] > 100
]

El segundo nombre no evita que la condición cambie, pero permite entender qué espera el código siguiente. Si el umbral es una regla de negocio, puede merecer también un nombre:

UMBRAL_PEDIDO_ALTO_VALOR = 100

pedidos_de_alto_valor = [
    pedido for pedido in pedidos
    if pedido["total"] > UMBRAL_PEDIDO_ALTO_VALOR
]

El PEP 8 contiene convenciones para funciones, variables, constantes, clases y módulos, pero también insiste en que la consistencia del proyecto es más importante que una obediencia aislada. Antes de renombrar, busca el vocabulario que ya usa el dominio. Un nombre que cumple snake_case y contradice el lenguaje del equipo no mejora la comunicación.

Cuatro espacios son el suelo, no el edificio

Python usa la indentación para delimitar bloques. El apartado de indentación del PEP 8 recomienda cuatro espacios por nivel y preferir espacios a tabuladores. Seguirlo evita errores sintácticos y hace que la estructura se vea de forma consistente.

def calcular_total(pedidos):
    total = 0
    for pedido in pedidos:
        if pedido["estado"] == "pagado":
            total += pedido["total"]
    return total

Pero la indentación no explica si una función está haciendo demasiadas cosas. Si el mismo bloque también registra una métrica, envía un correo y actualiza una base de datos, el problema es de responsabilidad, no de espacios. El formato permite ver la estructura; no decide si la estructura es manejable.

Una función pequeña debe tener un límite que puedas probar

“Función pequeña” no significa contar líneas hasta alcanzar un número mágico. Significa que la función tiene un propósito que puede describirse sin usar una lista de “y además”. El tutorial oficial de Python sobre definición de funciones presenta argumentos, retorno y docstrings como herramientas para expresar cómo se usa una operación.

Si una función lee un archivo, transforma registros, decide permisos y crea una respuesta HTTP, dividirla no sirve si solo repartes la confusión en cuatro nombres. Empieza por sus entradas y efectos:

def cargar_pedidos(ruta):
    """Devuelve pedidos válidos leídos desde ruta."""
    with open(ruta, encoding="utf-8") as archivo:
        return [normalizar_pedido(linea) for linea in archivo]


def pedidos_pagados(pedidos):
    """Selecciona pedidos cuyo estado es pagado."""
    return [p for p in pedidos if p["estado"] == "pagado"]

Ahora cada función puede probarse con una entrada más pequeña. También aparece una decisión importante: abrir el archivo pertenece a la carga; filtrar pagos pertenece a otra operación. Si una función necesita demasiados comentarios para explicar el orden de sus pasos, quizá el límite todavía no está claro.

Comentarios: explica decisiones que el código no puede mostrar

Un comentario que traduce la línea siguiente no aporta mucho:

# Incrementa total en uno
total += 1

Un comentario que explica una restricción externa sí puede ser valioso:

# El proveedor redondea a dos decimales antes de aplicar el impuesto.
importe = round(subtotal, 2)
impuesto = importe * tasa

El PEP 8 distingue comentarios de bloque, comentarios en línea y docstrings, y sugiere que los comentarios deben mantenerse actualizados. Si la razón puede expresarse mejor en un nombre o en una función, cambia el código. Si la razón depende de una API, un requisito legal o una decisión que no se deduce del fragmento, documentarla evita que alguien “simplifique” y reintroduzca el fallo.

Las docstrings tienen otra función: describen una interfaz que otras partes pueden usar. El PEP 257 sobre convenciones de docstrings ayuda a separar la documentación de módulos, clases y funciones de los comentarios que acompañan un detalle de implementación.

La longitud de línea es un límite de revisión, no una religión

El PEP 8 recomienda limitar las líneas de código a 79 caracteres y los comentarios o docstrings a 72, en parte para que varias versiones puedan verse lado a lado durante una revisión. El número es útil como señal de que una expresión puede necesitar una composición distinta:

total_con_impuestos = (
    subtotal
    + subtotal * tasa_impuesto
    - descuento_aplicado
)

Romper una línea no vuelve una expresión comprensible automáticamente. Si el lector todavía debe descifrar cuatro conversiones, una llamada anidada y una regla de negocio, extraer nombres intermedios puede ser mejor:

impuesto = subtotal * tasa_impuesto
total_antes_del_descuento = subtotal + impuesto
total_con_impuestos = (
    total_antes_del_descuento - descuento_aplicado
)

El objetivo no es ganar puntos por tener líneas cortas. Es crear lugares donde una prueba, un log o una conversación pueda señalar una decisión concreta.

Imports: el orden reduce el trabajo de lectura

Los imports cuentan la historia de las dependencias del módulo. Agrupar biblioteca estándar, dependencias externas y módulos propios ayuda a reconocer qué parte del archivo depende del entorno:

import json
from pathlib import Path

import requests

from proyecto.pedidos import cargar_pedidos

No conviertas la organización en un reemplazo de la revisión. Un import ordenado puede esconder una dependencia innecesaria, una función que hace demasiadas cosas o un ciclo de importación. Después de agrupar, pregunta si el módulo necesita realmente cada dependencia y si el nombre importado comunica el límite.

DRY no significa abstraer cada diferencia

Evitar duplicación es útil cuando dos bloques representan la misma decisión y deben cambiar juntos. Pero dos fragmentos parecidos pueden tener motivos diferentes. Una función genérica con diez parámetros puede reducir líneas y aumentar el coste mental.

def exportar(registros, formateador, destino, modo="w"):
    with open(destino, modo, encoding="utf-8") as archivo:
        for registro in registros:
            archivo.write(formateador(registro))
            archivo.write("\n")

La abstracción puede ser razonable si varios exportadores comparten el mismo contrato. Si solo se creó para eliminar dos líneas, compárala con la versión explícita. La pregunta de mantenimiento es: cuando una regla cambie, ¿quiero que estas operaciones cambien juntas? Si la respuesta es no, la duplicación puede estar protegiendo diferencias legítimas.

Un proceso de cinco pasadas para limpiar un archivo

  1. Describe el propósito: escribe en una frase qué debería poder hacer el módulo y qué queda fuera.
  2. Marca entradas y salidas: identifica tipos, valores ausentes, errores y efectos externos.
  3. Renombra decisiones: sustituye abreviaturas que obliguen al lector a consultar contexto.
  4. Separa responsabilidades: extrae una función solo cuando el nuevo límite tenga un propósito verificable.
  5. Revisa formato y documentación: aplica PEP 8 y docstrings para reforzar, no para sustituir, la intención.

Haz una prueba pequeña después de cada pasada. Si una limpieza cambia el resultado, no la llames “solo refactorización”. El cambio puede ser necesario, pero debe entrar con una prueba que explique el comportamiento que se quiere conservar.

El caso incómodo: código limpio que esconde una mala regla

Un bloque perfectamente indentado puede aplicar un descuento incorrecto. Un nombre descriptivo puede describir una regla equivocada. Un comentario claro puede quedar obsoleto. Por eso la limpieza no reemplaza pruebas, revisión de requisitos o observación del programa.

Antes de cerrar un pull request, lee el código desde tres perspectivas: una persona que llama a la función, otra que debe modificarla y otra que debe diagnosticar un fallo. Si las tres pueden encontrar entradas, resultados, efectos y restricciones sin reconstruir una historia privada, el estilo está apoyando al diseño.

Para continuar, revisa nuestra guía sobre errores comunes de principiantes en Python y contrasta qué hábitos de nombres, límites y pruebas evitarían cada fallo. La guía de conceptos básicos de Python puede servir para repasar funciones, listas y diccionarios antes de reorganizar un archivo más grande. Si trabajas con datos agrupados, conecta esta práctica con nuestra guía de estructuras de datos en Python.

Preguntas para revisar código sin convertirlo en decoración

¿Código limpio significa seguir PEP 8 al pie de la letra?

No. PEP 8 ofrece convenciones para legibilidad y consistencia, pero el proyecto y el juicio técnico pueden justificar una excepción si una regla vuelve el código menos claro.

¿Todas las funciones deben ser cortas?

No existe un número universal de líneas. Una función debe tener un propósito y límites que puedan explicarse y probarse; dividirla sin aclarar responsabilidades solo reparte la confusión.

¿Debo comentar cada línea?

No. Comenta decisiones no obvias, restricciones externas y razones que el código no puede mostrar. Usa nombres, funciones y docstrings para comunicar lo demás.

¿DRY siempre mejora el código?

No. La abstracción ayuda cuando dos decisiones deben cambiar juntas. Si solo elimina líneas parecidas con motivos distintos, puede hacer que el diseño sea más difícil de entender.

¿Un nombre descriptivo garantiza código mantenible?

No. Es un punto de partida. El nombre debe coincidir con el comportamiento, los límites de la función deben ser claros y las pruebas deben comprobar la regla que importa.

¿Qué reviso primero en un archivo confuso?

Empieza por propósito, entradas, salidas y efectos. Después renombra decisiones, separa responsabilidades y aplica formato. El orden evita pulir una estructura que todavía no comprendes.

El código limpio no es el que parece obediente desde lejos. Es el que permite que otra persona descubra qué decisión toma, dónde puede cambiarla y cómo comprobar que sigue funcionando.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio