El conocimiento es el nuevo dinero.
Aprender es la nueva manera en la que inviertes
Acceso Cursos

El cursor cambió nuestra forma de programar. Esta herramienta cambia nuestra forma de diseñar.

Los ayudantes funcionales no son malos. Lo que es malo es tener colecciones desestructuradas de ellos. En lugar de amontonar los ayudantes, agrúpalos por canalización o transformación.

· 5 min de lectura
El cursor cambió nuestra forma de programar. Esta herramienta cambia nuestra forma de diseñar.

Las funciones de utilidad parecen productivas, hasta que silenciosamente convierten tu código limpio en un vertedero. Hay un patrón mejor y más escalable que los desarrolladores de Python deberían utilizar en su lugar.

La seductora mentira de utils.py

En algún momento de cada proyecto de Python, surge un pensamiento familiar:

«Esta lógica puede ser útil más adelante. La pondré en utils.py».

Parece responsable. Con visión de futuro. Casi profesional.

Y durante un tiempo, funciona.

Luego pasan seis meses. utils.py tiene 1200 líneas. Nadie recuerda para qué sirve la mitad de las funciones. Los nuevos desarrolladores tienen miedo de tocarlo. Los desarrolladores existentes copian y pegan en lugar de reutilizar, porque encontrar la utilidad adecuada es más difícil que reescribirla.

Si esto te suena familiar, no estás solo.

Las funciones de utilidad son uno de los patrones más comunes, y más perjudiciales, en las bases de código Python en crecimiento.

Este artículo no dice que nunca se debe reutilizar el código.

Lo que dice es: deja de centralizar comportamientos no relacionados en funciones de utilidad genéricas.

Las funciones de utilidad suelen empezar con buenas intenciones:

  • «Evitar la duplicación».
  • «Mantener la lógica en un solo lugar».
  • «Hacer que las cosas sean reutilizables».
  • «Escribir código DRY».

Verás archivos como:

utils.py
helpers.py
common.py
shared.py

¿En su interior?

  • Ayudas para el formateo de cadenas
  • Análisis de fechas
  • Lógica de validación
  • Configuración de respuestas API
  • Reglas de negocio (disfrazadas de ayudas)

El problema no es la reutilización.

El problema es la reutilización sin contexto.

Las utilidades prometen flexibilidad, pero eliminan silenciosamente el significado.

El verdadero problema: las utilidades eliminan el contexto

Cuando lees código como este:

from utils import format_date, validate_input, process_data

No aprendes casi nada.

  • ¿Qué tipo de fecha?¿Validar la entrada para qué?
  • ¿Procesar qué datos?

Las funciones de utilidad ocultan la intención. Simplifican el conocimiento del dominio en verbos vagos.

Ahora compáralo con:

from billing.dates import format_invoice_date
from auth.validation import validate_login_payload
from orders.processing import process_pending_orders

La misma reutilización.

Una claridad radicalmente diferente.

El contexto es la diferencia entre un código legible y un código misterioso.

Por qué las funciones de utilidad no escalan

Las bases de código con muchas funciones de utilidad fallan de maneras predecibles.

1. Se convierten en cajones de sastre

Todo lo que no «encaja» en ningún otro sitio se vierte en utilidades.

Al final:

  • No hay una propiedad clara
  • No hay una responsabilidad clara
  • No hay límites claros

Cada nueva función parece justificada. Nunca se elimina nada.

2. Acumulan lógica empresarial oculta

Lo que comienza como «solo una ayuda» poco a poco va adquiriendo reglas:

def calculate_discount(price, user):
    if user.is_premium:
        return price * 0.8
    return price

Eso no es una utilidad.

Es lógica empresarial que pretende ser genérica.

Una vez que las reglas de negocio residen en las utilidades, se vuelven:

  • Difíciles de probar
  • Fáciles de usar incorrectamente
  • Peligrosas de cambiar

3. Crean un acoplamiento estrecho sin que te des cuenta

Las funciones de utilidad a menudo:

  • Importan modelos
  • Dependen de la configuración
  • Asumen formas de datos
  • Dependen de efectos secundarios

Pero como son «ayudas», los desarrolladores no las tratan con la misma precaución que la lógica central.

¿El resultado?

Los cambios en un área rompen el código en lugares inesperados.

El problema más profundo: las utilidades son un indicio de mal diseño

En los sistemas bien diseñados:

  • El comportamiento se encuentra cerca de los datos con los que opera
  • La lógica tiene un lugar claro
  • Los nombres reflejan la intención, no la implementación

Las funciones de utilidad violan las tres reglas.

Existen porque estamos evitando una pregunta más difícil:

«¿Dónde pertenece esta lógica?»

El mejor patrón: el comportamiento pertenece al significado

En lugar de preguntar

«¿Puedo reutilizar esto?»,

pregunta

«¿Qué concepto representa este comportamiento?».

A continuación, vincula el comportamiento a ese concepto.

Veamos qué significa esto en la práctica.

Patrón 1: mueve el comportamiento a los módulos de dominio

En lugar de un utils.pyglobal, organiza por dominio.

Antes:

# utils.py
def is_valid_email(email):
    ...

Después:

# users/validation.py
def is_valid_email(email: str) -> bool:
    ...

Ahora la función tiene un hogar.

Cuenta una historia: esta lógica existe porque existen los usuarios.

Patrón 2: Prefiere módulos pequeños y con un propósito específico a grandes utilidades

Los módulos de Python son baratos. Úsalos.

En lugar de un archivo de ayuda gigante:

utils.py

Uso:

dates.py
money.py
strings.py
serialization.py

Aún mejor:

billing/money.py
orders/serialization.py
reports/dates.py

No solo estás organizando código.

Estás codificando intención.

Patrón 3: Usa clases cuando el estado o las reglas sean importantes

Si una función depende de reglas, configuración o comportamiento evolutivo, probablemente no sea una utilidad.

Antes:

def calculate_tax(amount, country):
    ...

Después:

class TaxCalculator:
    def __init__(self, country):
        self.country = country

    def calculate(self, amount):
        ...

Esto te proporciona:

  • Dependencias explícitas
  • Pruebas más sencillas
  • Puntos de extensión claros

Las clases no siempre son necesarias, pero a menudo se utilizan utilidades en lugar de abstracciones adecuadas.

Patrón 4: Deja que los datos sean dueños de su comportamiento

Uno de los patrones de Python más infrautilizados es la proximidad de comportamiento.

Si la lógica opera sobre un objeto, considera la posibilidad de colocarla en ese objeto.

Antes:

def is_order_refundable(order):
    ...

Después:

class Order:
    def is_refundable(self) -> bool:
        ...

Ahora tu código se lee como inglés:

if order.is_refundable():
    ...

No solo es más limpio.

Es un código más honesto.

Patrón 5: Utiliza la composición funcional, no los contenedores de utilidades

Los ayudantes funcionales no son malos.

Lo que es malo es tener colecciones desestructuradas de ellos.

En lugar de amontonar los ayudantes, agrúpalos por canalización o transformación.

# parsing.py
def parse_csv(...)
def normalize_headers(...)

# validation.py
def validate_schema(...)
def validate_constraints(...)

Ahora la reutilización se produce a través de la composición, no de la conveniencia.

«Pero las utilidades son más rápidas de escribir».

Por supuesto.

Y por eso son tan peligrosas.

Las funciones de utilidad se optimizan para la velocidad actual, no la claridad futura..

El coste no se nota de inmediato.

Se nota cuando:

  • La incorporación se ralentiza
  • Las refactorizaciones parecen arriesgadas
  • Aparecen errores en el código «compartido»
  • Nadie sabe qué es seguro cambiar

Un buen diseño parece más lento al principio, pero mucho más rápido después.

Cuándo son aceptables las funciones de utilidad

Sí, hay excepciones.

Las funciones de utilidad tienen sentido cuando son:

  • Puras (sin efectos secundarios)
  • Sin estado
  • Independientes del contexto
  • Verdaderamente genéricas

Ejemplos:

  • Ayudas matemáticas
  • Normalización simple de cadenas
  • Formateo de primitivas
  • Ayudas genéricas para el análisis sintáctico

Si una función tiene sentido en cualquier proyecto, puede que sea una función de utilidad.

Si solo tiene sentido en este proyecto, merece un lugar adecuado.

Una prueba de fuego sencilla

Antes de crear una función de utilidad, pregúntate:

  1. ¿Esta lógica pertenece a un dominio específico?
  2. ¿Codifica reglas de negocio?
  3. ¿Asume ciertas formas de datos?
  4. ¿Cambiaría su nombre si cambiara el contexto del proyecto?

Si respondes «sí» a cualquiera de estas preguntas,

no es una función de utilidad.

Conclusión: deja de ocultar el significado en los ayudantes

Las funciones de utilidad parecen inofensivas.

Parecen útiles.

Parecen productivas.

Pero con el tiempo, borran silenciosamente el significado de tu código.

Python brilla cuando el código es explícito, legible y honesto en cuanto a su intención.

Eso no proviene de archivos auxiliares gigantes, sino de un comportamiento bien situado.

Así que la próxima vez que tus dedos escriban utils.py, haz una pausa por un segundo.

Pregúntate dónde pertenece realmente la lógica.

Gracias por leer Código en Casa.
Si esto te a ayudado y te sumo algo Dale un 👏 , compártelo con tu red o dejame un comentario para saber tu opinión.