Inicio / Artículos / Diez hábitos de arquitectura que mantienen las bases de código frontend mantenibles durante años

Diez hábitos de arquitectura que mantienen las bases de código frontend mantenibles durante años

Explica hábitos estructurales como la optimización para la eliminabilidad, el flujo de datos explícito y el aislamiento de la lógica empresarial, que ayudan a que los conjuntos de código sigan siendo mantenibles a lo largo de los años de cambios.

3870 palabras

Cada código fuente de frontend que sobrevive el tiempo suficiente termina dividiéndose en dos regiones distintas.

La primera puede llamarse la Zona de Peligro.

Es una mezcla caótica de gestores de estado, ganchos de ciclo de vida parcheados, abstracciones globales ingeniosas pero opacas, experimentos a medias y funciones auxiliares escritas hace años que ya nadie puede explicar completamente.

Nadie quiere acercarse a ella.

Cuando llega una nueva solicitud de funcionalidad a esa parte de la aplicación, el equipo no evalúa su complejidad en función de lo difícil que sea realmente el trabajo.

Lo evalúan según el nivel de riesgo que implica tocar ese código.

Un cambio que debería tomar dos días se convierte en una tarea de dos semanas porque todos saben que la mayor parte del tiempo se dedicará a pruebas de regresión en lugar de a la implementación.

Luego está la otra región.

Llámela la Fundación Estable.

Estos son los módulos desarrollados hace años que han sobrevivido silenciosamente a varias migraciones de frameworks, rediseños, cambios en la dirección del producto y transformaciones en el liderazgo técnico.

Casi nunca causan interrupciones en el servicio.

No hay nada especialmente ingenioso en su forma de estar escritos.

Y cuando alguien nuevo se une al equipo, puede abrir uno de estos archivos, comprender su función sin necesidad de una explicación detallada y enviar una primera solicitud de integración en un par de días.

Esa es la parte que merece atención.

El código que perdura no suele ser el más avanzado del sistema.

Suele ser el más sencillo.

Los ingenieros experimentados saben que el software nunca permanece en las mismas condiciones en las que fue creado.

Los requisitos cambian.

Los equipos se reorganizan.

Las dependencias se sustituyen.

Los frameworks evolucionan.

Las empresas cambian de estrategia.

La gente abandona la empresa.

Llegan nuevos ingenieros sin conocer en absoluto por qué las cosas se construyeron de cierta manera.

Por lo tanto, la pregunta que realmente importa no es:

"¿Qué tan limpio se ve este diseño en este momento?"

Sino:

"¿Cuánto costará cambiarlo dentro de cinco años?"

A continuación se detallan los hábitos estructurales que hacen posible esa longevidad.

1. Optimizar para la eliminabilidad, no para la reutilización

Muchas guías de arquitectura se centran en la reutilización.

Haga que sus componentes sean reutilizables.

Cree servicios genéricos.

Agregue puntos de extensión.

Diseñe arquitecturas de complementos.

Escriba abstracciones para implementaciones que aún no ha creado.

La reutilización tiene su lugar.

Pero hay otra calidad que a menudo es más importante en un producto que sigue evolucionando:

Qué tan fácil es eliminar algo.

Las funcionalidades no son permanentes.

Son reemplazadas.

Son reconstruidas.

Se integran en otras funcionalidades.

A veces la empresa simplemente pierde interés en ellas.

Imagínese un proyecto React organizado estrictamente por tipo de archivo:

src/
  components/
    BillingTable.tsx
    UserModal.tsx
    SubscriptionCard.tsx
  hooks/
    useBillingData.ts
    useUserData.ts
    useSubscription.ts  services/
    billingApi.ts
    userApi.ts
    subscriptionApi.ts

A primera vista, esto parece ordenado.

Cada tipo de archivo tiene su carpeta designada.

Pero supongamos que la empresa decide, dieciocho meses después, eliminar por completo el flujo de facturación.

¿Dónde se encuentra realmente todo lo relacionado con la facturación?

Tendría que buscar en varios directorios diferentes.

Encuentra y elimina BillingTable.tsx.

Luego encuentra useBillingData.ts.

Luego, algunas definiciones de tipo escritas específicamente para la facturación.

A continuación, una función auxiliar a la que solo accede el módulo de facturación.

Luego, una hoja de estilos.

Luego, una llamada a la API.

Luego, un fixture de prueba.

Luego, un gancho que inicialmente era para la facturación pero fue renombrado con el tiempo.

Esa funcionalidad ya no está en el producto, pero sus restos siguen dispersos por todo el código.

Así es exactamente como se acumula el código muerto con el paso del tiempo.

Organizar por funcionalidad hace que los límites sean mucho más evidentes:

src/
  features/
    billing/
      components/
        BillingTable.tsx
      hooks/
        useBillingData.ts
      services/
        billingApi.ts
      types.ts
      index.ts

Ahora la facturación tiene un lugar definido.

Si la empresa decide eliminar esa funcionalidad, el primer paso es sencillo:

src/features/billing/

Eliminar la carpeta.

TypeScript mostrará entonces cualquier otro elemento que aún dependa de ella.

Esa es una forma mucho más saludable de manejar dependencias.

La eliminabilidad es una forma de mantenibilidad

Un módulo se vuelve más fácil de mantener cuando se puede saber de inmediato dónde residen sus responsabilidades.

Esa es una de las razones por las cuales las estructuras de carpetas basadas en funcionalidades siguen apareciendo en las conversaciones sobre cómo escalar grandes bases de código React. Los equipos que trabajan en aplicaciones de gran tamaño suelen mencionar la colocación conjunta precisamente porque reduce el alcance de cualquier cambio realizado.

El objetivo no es perseguir un árbol de directorios perfectamente organizado.

El objetivo es poder responder rápidamente a esta pregunta:

“Si esta funcionalidad desapareciera mañana, ¿qué tendría que eliminar?”

Si es difícil responder a esa pregunta, es probable que los límites de las funcionalidades estén demasiado flexibles.

2. No convierta shared/ en un cajón de basura

Existe una segunda trampa que suele aparecer una vez que los equipos pasan a una arquitectura basada en funcionalidades.

Cualquier elemento que obviamente no pertenezca a una función específica termina en shared/.

Unos meses después, se acaba con algo como esto:

shared/
  utils/
  helpers/
  common/
  services/
  components/
  hooks/
  types/

Y en ese punto, shared/ representa silenciosamente la mitad del código total.

Esto genera su propio tipo de problema de acoplamiento.

Una buena guía a seguir es:

El código debe trasladarse a una carpeta compartida porque varias funciones realmente dependen del mismo concepto, no porque no se pueda decidir dónde más debería ir.

Un componente genérico de botón encaja perfectamente en un sistema de diseño.

Un cliente de autenticación podría razonablemente ubicarse en una capa de infraestructura compartida.

También se puede compartir una herramienta para formatear fechas.

Pero una función como esta:

calculateEnterpriseRenewalDiscount()

casi con certeza pertenece a la función que maneja esa regla de negocio específica.

Resista la tentación de mover elementos a carpetas globales solo para que el árbol de directorios se vea más ordenado.

El código compartido no es gratuito, ya que cada funcionalidad que lo utiliza se convierte en un dependiente potencial.

Cuanto más crece la carpeta shared/, más difícil resulta saber quién es realmente responsable de un comportamiento específico.

3. Cree adaptadores defensivos alrededor de las dependencias externas

Es muy probable que su aplicación siga funcionando mucho tiempo después de que algunas de sus dependencias ya no estén disponibles.

Hoy podría depender de Axios y mañana cambiar a fetch nativo.

Puede que ahora utilice un proveedor de análisis y, en unos años, su empresa decida migrar a otro.

Puede que hoy integre una biblioteca de autenticación y, más adelante, nuevas exigencias de seguridad lo lleven a utilizar otra distinta.

La forma frágil de construir cosas es importar estos paquetes externos directamente en docenas de componentes.

import axios from 'axios';
import { trackMixpanelEvent } from 'mixpanel-browser';
export function CheckoutCard() {
  const handlePurchase = async () => {
    await axios.post('/api/checkout', payload);    trackMixpanelEvent('checkout_completed');
  };
}

En este punto, la capa de interfaz de usuario conoce exactamente qué cliente HTTP y qué proveedor de análisis se está utilizando. Si se aplica este patrón a cuarenta y cinco componentes, cambiar de proveedor deja de ser una tarea aislada; se convierte en un cambio que afecta a todo el repositorio.

Una capa de separación permite que las dependencias sean intercambiables. Por ejemplo:

// src/shared/lib/analytics.ts
import mixpanel from 'mixpanel-browser';export const analytics = {
  trackCheckoutCompleted(
    orderId: string,
    amount: number
  ) {
    mixpanel.track('checkout_completed', {
      orderId,
      amount,
    });
  },
};

El componente ahora interactúa con un concepto definido por la propia aplicación:

analytics.trackCheckoutCompleted(orderId, amount);

No tiene idea de si debajo está Mixpanel, PostHog, Segment o alguna otra herramienta. Si el proveedor cambia, el contrato en el que se basa la aplicación puede permanecer exactamente igual.

Pero no abstraigas todo

Este punto es tan importante como el anterior.

Envolver una dependencia en un adaptador no constituye necesariamente un buen diseño. Si creas una interfaz personalizada para cada pequeña biblioteca que uses, podrías terminar escribiendo más código del que contiene la propia biblioteca.

La verdadera pregunta a hacer es:

“¿Sería costoso reemplazar esta dependencia más adelante, o sería arriesgado dejar que se extienda sin control por todo el código?”

Si la respuesta es sí, vale la pena invertir en un adaptador. Si no, llamar directamente a la dependencia probablemente sea más sencillo y suficiente.

Ser ingeniero senior no significa aplicar abstracciones a todo lo que tocas. Significa establecer límites específicamente donde omitirlos probablemente te costará caro más tarde.

4. El flujo de datos explícito es mejor que la solución mágica

Una de las formas más rápidas de hacer que un código sea confuso es ocultar de dónde provienen realmente los valores.

Los emisores de eventos globales son un ejemplo típico.

eventBus.emit('USER_UPDATED', {
  id: user.id,
});

Esta línea indica que se ha disparado un evento. No dice nada sobre quién lo está escuchando.

Se podría buscar en todo el código y eventualmente encontrar algo como:

eventBus.on('USER_UPDATED', handler);

Pero podría haber tres oyentes separados. Uno podría haberse añadido hace dos años. Otro podría estar modificando el estado global. Un tercero podría estar realizando una llamada de análisis. De repente, el comportamiento real desencadenado por esa función original está disperso por toda la aplicación en lugar de estar en un solo lugar.

Ahora compare eso con un contrato que se establece de forma explícita:

interface UserCardProps {
  user: User;
  onUserRoleChange: (
    userId: string,
    newRole: Role
  ) => Promise<void>;
}

Aquí, el componente declara abiertamente qué acción admite, y el padre declara abiertamente qué ocurre cuando se dispara esa acción. El flujo de datos es visible en la página.

Sí, esto es más verboso que disparar un evento anónimo. Pero esa mayor verbosidad tiene su justificación, ya que deja un camino rastreable a través del código.

Cuando alguien que no está familiarizado con el componente abre el archivo, debería poder responder a tres preguntas sin tener que revisar el resto del repositorio:

¿De dónde proviene estos datos?

Típicamente son props, parámetros de ruta, un hook o alguna capa de acceso a datos claramente definida.

¿Qué puede modificarlos?

Una llamada a función visible, una mutación, una acción o una actualización explícita de estado.

¿Qué ocurre cuando el usuario realiza esta acción?

Una llamada directa a una función cuya implementación se puede rastrear.

Cuanto menos comportamiento se oculte, más fácil resulta comprender todo el sistema.

5. Mantener la lógica de negocio fuera de los ciclos de vida del framework

Los frameworks no son elementos fijos permanentes; esa es una de las suposiciones más fiables que se pueden hacer al trabajar en el frontend.

Solo React ha experimentado varios cambios importantes. Las componentes de clase dejaron de ser populares. Los hooks reorganizaron la forma en que se estructura la lógica con estado. Create React App fue reemplazado en muchos proyectos por herramientas como Vite o configuraciones nativas de framework. El renderizado server-first y los enfoques de enrutamiento más recientes han transformado la forma en que los equipos piensan sobre la obtención de datos y dónde se sitúan los límites de la aplicación.

El framework que estás utilizando en este momento podría no parecerse en nada a lo que sea estándar dentro de cinco años. Mientras tanto, tus reglas empresariales deben seguir funcionando sin importar nada más.

Toma el cálculo de impuestos como ejemplo. Una versión frágil esconde la lógica real dentro de un hook de React:

export function useTaxCalculator(
  cartItems: CartItem[]
) {
  const [tax, setTax] = useState(0);
  useEffect(() => {
    let calculated = 0;    // 60 lines of tax calculation,
    // rounding rules,
    // country logic,
    // exemptions...    setTax(calculated);
  }, [cartItems]);  return tax;
}

Ahora el cálculo de impuestos está vinculado a React. Probarlo implica crear un entorno de React. Llamarlo desde una acción del servidor resulta engorroso. Ejecutarlo dentro de un Web Worker también es complicado. Transferirlo a otro framework de interfaz gráfica se convierte en una tarea costosa.

Un enfoque mejor separa estas dos áreas:

export function calculateTax(
  cartItems: CartItem[],
  countryCode: string
): number {
  // Pure business logic
  return totalTax;
}

La capa de React simplemente hace una llamada a él:

const tax = calculateTax(cartItems, countryCode);

Con esta estructura, la lógica que realmente importa no tiene conocimiento alguno de React. Puede ejecutarse en cualquier entorno. Se puede probar con pruebas unitarias simples. Un proceso servidor puede reutilizarla directamente. Además, sobrevive a una migración de framework de interfaz sin necesidad de ser reescrita.

Los frameworks deben estar en los bordes

Una forma útil de visualizar esto es mediante un diagrama en capas:

┌──────────────────────────────┐
│          UI Layer            │
│      React / Next.js         │
├──────────────────────────────┤
│       Application Logic      │
├──────────────────────────────┤
│        Domain Logic          │
│     Pure TypeScript          │
├──────────────────────────────┤
│       Infrastructure        │
│ APIs / DB / Vendors / SDKs   │
└──────────────────────────────┘

Cuanto más cerca esté un fragmento de código del centro, menos debería depender de algún framework o biblioteca específica del proveedor.

Esto no significa que todo proyecto React requiera una configuración completa de “Arquitectura Limpia”.

Sí significa que debe tener claro qué partes de su código son realmente específicas de React y cuáles representan sus reglas comerciales reales.

Esas son dos categorías distintas, y tratarlas como una es donde comienzan los problemas.

6. Evite convertir los hooks en miniaplicaciones

Este patrón aparece repetidamente en los códigos de React.

Suele comenzar de forma inocente:

function useUser() {
  // fetch user
}

Luego, con el tiempo, las necesidades van acumulándose.

function useUser() {
  // fetch user
  // loading state  // error handling  // permissions  // analytics  // transformations  // caching  // retry logic  // business rules  // notifications  // feature flags
}

En poco tiempo, lo que comenzó como un simple hook se ha convertido silenciosamente en una aplicación de 500 líneas oculta detrás de un nombre de función que parece inocente.

Los hooks son realmente útiles.

Pero un hook no debería convertirse en un lugar donde deshacerse de cualquier problema solo porque tiene acceso fácil al estado y a los efectos de React.

Un enfoque mejor es que el hook delegue tareas a componentes más pequeños y específicos:

function useUser() {
  const user = useUserQuery();
  const permissions =
    calculatePermissions(user.data);  return {
    user: user.data,
    permissions,
    isLoading: user.isLoading,
  };
}

Con esta estructura, el hook se convierte en una capa de orquestación que conecta las diferentes partes.

Ya no es toda la arquitectura amontonada en una sola función.

Ese límite es mucho más fácil de mantener con el tiempo.

7. Escriba registros de decisiones arquitectónicas, no wikis interminables

Una de las fuentes más comunes del deterioro arquitectónico no es en absoluto el código desordenado.

Es el contexto perdido.

Así es como suele ocurrir: un desarrollador toma una decisión poco evidente. La decisión es acertada y todos en el equipo en ese momento comprenden la lógica detrás de ella. Luego esa persona se va.

Meses después, un nuevo ingeniero se topa con esa implementación inusual y piensa:

"¿Por qué lo hacemos de esta manera? Debe haber una solución más limpia."

Así que lo vuelven a escribir, reintroduciendo sin darse cuenta exactamente el mismo problema que la decisión original estaba diseñada para evitar.

Tomemos como ejemplo un panel de control construido con Server-Sent Events en lugar de WebSockets.

Sin ese contexto histórico, un desarrollador nuevo podría concluir razonablemente:

"WebSockets son el estándar más actual. Cambiemos a ellos."

Pero el equipo original podría haber elegido SSE específicamente porque muchos clientes empresariales utilizan proxies corporativos restrictivos que manejan mal las conexiones WebSocket.

Ese razonamiento es invisible si solo se examina el código en sí.

Este es exactamente el vacío que están destinados a llenar los registros de decisiones arquitectónicas.

Por ejemplo:

# ADR 003: Use Server-Sent Events for Dashboard Feeds
## ContextOur dashboard requires real-time metric updates.We evaluated WebSockets and Server-Sent Events.## DecisionWe chose Server-Sent Events because:1. Communication is strictly server-to-client.
2. SSE uses standard HTTP infrastructure.
3. Browser reconnection is supported natively.
4. The solution works reliably within our enterprise network environment.## ConsequencesIf we later require client-to-server
bi-directional streaming, we should
re-evaluate this decision.

Con un registro como este en su lugar, el siguiente ingeniero no tiene que realizar ingeniería inversa del razonamiento desde cero.

Puede ver el porqué de inmediato.

Es algo sencillo

/docs/adr/

Una carpeta en su repositorio puede conservar años de conocimiento institucional que, de lo contrario, se perdería junto con quien abandone el equipo.

Documente las decisiones, no todo

No es necesario mantener una wiki extensa de cien páginas.

En la mayoría de los casos, el propio código debe ser lo suficientemente claro como para explicar qué hace.

Lo que la documentación debe capturar en su lugar es el razonamiento que el código no puede expresar por sí solo:

  • por qué se eligió una tecnología en particular
  • por qué se descartó una alternativa más obvia
  • por qué existe alguna restricción inusual
  • por qué se sigue necesitando una solución temporal que parece innecesaria

La documentación cobra sentido precisamente cuando captura el contexto que, de lo contrario, desaparecería junto con las personas que lo conocían.

8. Diseño para el ingeniero que se une después de usted

Este podría ser la prueba más sencilla para determinar si una arquitectura está destinada a perdurar.

Imagínese un escenario en el que, a partir de mañana, todas las personas que actualmente comprenden el funcionamiento interno del sistema abandonen la empresa de inmediato.

¿Podría un equipo recién incorporado seguir operándolo?

Si su respuesta honesta es no, eso no significa necesariamente que le falten desarrolladores. Significa que el sistema depende de forma oculta del conocimiento de personas específicas.

Un sistema diseñado para durar debe permitir que su comportamiento importante sea detectable por sí mismo.

Alguien nuevo debería poder abrir el repositorio y ir encontrando gradualmente respuestas a preguntas como:

  • ¿Dónde se encuentra esta función específica en el código?
  • ¿Qué módulo es responsable de este comportamiento?
  • ¿De dónde provienen realmente estos datos?
  • ¿En qué sistemas externos depende este código?
  • ¿Qué suposiciones está haciendo silenciosamente este código?
  • ¿Por qué se eligió esta opción arquitectónica en particular?
  • ¿Qué se puede cambiar sin dañar algo más?
  • Por eso es precisamente por lo que los límites explícitos tienen tanta importancia.

    Un recién llegado no debería tener que aprender toda la historia de la empresa solo para entender qué hace el código.

    La propia base de código necesita llevar consigo suficiente información sobre esa historia.

    9. Mantener pequeño el radio de impacto del cambio

    Una buena forma de evaluar una arquitectura es contar cuántos archivos obliga a modificar un cambio rutinario.

    Imagínese una solicitud de funcionalidad sencilla: alguien pide un botón que permita a los usuarios exportar el informe de facturación como archivo CSV.

    En un sistema estrechamente acoplado, satisfacer esta solicitud podría significar editar archivos dispersos en:

    components/
    hooks/
    services/
    utils/
    types/
    global state/
    shared helpers/
    

    El ingeniero podría terminar modificando una docena de archivos solo para lanzar un botón.

    Compare eso con una estructura de funcionalidades bien delimitada:

    features/
      billing/
        components/
        hooks/
        services/
        utils/
    

    Aquí, el mismo cambio puede quedar casi por completo dentro de la propia carpeta de la funcionalidad de facturación.

    Esto es a lo que se refieren cuando hablan de reducir el radio de explosión de un cambio.

    Un radio de explosión pequeño ofrece:

    • Menos regresiones
    • Revisiones de código más sencillas
    • Lanzamientos más rápidos
    • Menos conflictos de fusión
    • Pruebas más fáciles
    • Refactorizaciones más seguras

    Lograr esto no requiere una arquitectura elaborada. Requiere límites que reflejen realmente cómo evoluciona el producto en la práctica.

    10. Deje de optimizar para el diagrama de arquitectura

    Un hermoso diagrama de arquitectura aún puede ocultar una base de código en la que es difícil trabajar.

    Puedes marcar cada una de estas casillas:

    • seguir un diseño de Arquitectura Limpia
    • aplicar los principios de diseño SOLID
    • invertir correctamente las dependencias
    • envolver el acceso a los datos en patrones de repositorio
    • generar objetos mediante patrones de fábrica
    • conectar las partes con eventos
    • apilar varias capas de abstracción una sobre otra

    y aun así convertir una funcionalidad sencilla en un trabajo agotador que dura varios días.

    La arquitectura debe reducir la complejidad, no aumentarla. Si tu capa arquitectónica introduce más conceptos de los que tiene el producto en sí, algo no está yendo bien.

    A menudo, la arquitectura más sólida es aquella de la que nadie se molesta en hablar, porque los ingenieros pueden simplemente leer el código y seguirlo. En la práctica, esto podría verse así:

    features/
      billing/
      checkout/
      accounts/
    

    junto con algo modesto:

    shared/
      ui/
      lib/
    

    además de un par de funciones puramente relacionadas con la lógica empresarial.

    Nada de eso suena impresionante. Pero si sigue funcionando bien y tiene sentido cuatro años después, está haciendo exactamente lo que se supone que debe hacer una arquitectura.

    Por qué los ingenieros senior realmente optimizan

    Los ingenieros senior no necesariamente crean código más elaborado. Lo que difiere es el conjunto de preguntas que se plantean antes de escribirlo.

    Un ingeniero menos experimentado podría preguntar:

    "¿Cómo hago que esto sea reutilizable?"

    En cambio, un ingeniero senior pregunta:

    "¿Es realmente necesario que sea reutilizable?"

    Un ingeniero menos experimentado podría preguntar:

    "¿Cómo debería abstraer esto?"

    En cambio, un ingeniero senior pregunta:

    “¿Qué problema específico se supone que debe resolver esta abstracción?”

    Un ingeniero menos experimentado podría preguntar:

    “¿Dónde debería ir esta función utilitaria?”

    En cambio, un ingeniero senior pregunta:

    “¿Quién es realmente el responsable de este comportamiento?”

    Un ingeniero menos experimentado podría preguntar:

    “¿Cómo nos preparamos para los requisitos que puedan surgir después?”

    En cambio, un ingeniero senior pregunta:

    “¿Qué cambio futuro es lo suficientemente probable como para justificar la adición de esta complejidad ahora?”

    Y quizás la pregunta más reveladora de todas:

    “¿Cómo será este código una vez que la persona que lo escribió se haya ido?”

    Esa pregunta marca realmente el inicio del pensamiento a largo plazo en ingeniería.

    Conclusión: El código duradero suele parecer algo común

    El código que sigue funcionando bien tras años de cambios rara vez es aquel construido con el framework más reciente, el patrón de diseño más ingenioso o la abstracción más elegante. Por lo general, se trata simplemente de código con límites claros y decisiones sensatas y sin pretensiones; el tipo de código en el que otro ingeniero puede abrir el repositorio y comprender cómo funciona sin necesidad de averiguar quién lo escribió originalmente.

    Los principios subyacentes son sencillos:

    1. Organice por funcionalidad y responsable. Las funcionalidades deben ser fáciles de localizar y, cuando sea necesario, fáciles de eliminar.
    2. Prefiera la posibilidad de eliminación sobre maximizar la reutilización. No todo fragmento de lógica merece convertirse en una abstracción compartida.
  • Proteja su aplicación de las dependencias de terceros. Utilice adaptadores siempre que un cambio en el proveedor pudiera afectar una gran parte de la base de código.
  • Prefiera un flujo de datos explícito. Un poco más de código visible suele costar menos que un comportamiento oculto e implícito.
  • Separe la lógica de negocio de los ciclos de vida del framework. La función de React es renderizar y coordinar, no gestionar todas las reglas en las que depende su negocio.
  • Mantenga los hooks con un alcance limitado. Evite que un hook personalizado se convierta en una pequeña aplicación por sí mismo.
  • Registre las decisiones arquitectónicas. Guarde la razón detrás de las elecciones inusuales, no solo una descripción del estado actual.
  • Limite el alcance de los cambios. Idealmente, una función puede modificarse sin que sea necesario tocar la mitad del repositorio.
  • No confunda la complejidad con la calidad. Añadir más capas no hace que una arquitectura sea automáticamente mejor.
  • El mayor elogio que puede recibir un código no es:

    "Esta arquitectura es increíblemente ingeniosa."

    Sino:

    "Yo entiendo esto."

    Porque cinco años después, los ingenieros originales probablemente ya no estarán allí. El framework seguramente será diferente. El diseño habrá cambiado. El producto habrá evolucionado. Incluso el negocio en sí puede no parecerse en nada a como es hoy.

    Pero mientras las fronteras sigan siendo claras, la lógica permanezca sencilla y las razones detrás de las decisiones clave estén anotadas en algún lugar, el código puede seguir evolucionando junto con todo lo demás a su alrededor.

    Eso es lo que realmente se ve en un software duradero.

    Lecturas relacionadas

  • Errores comunes en el contrato de API que dañan la confiabilidad del frontend — Aprenda diez defectos recurrentes en el diseño de APIs backend, desde formas de respuesta incoherentes hasta una paginación frágil, que socavan la confianza del frontend y cómo solucionarlos.
  • Errores en la arquitectura backend que complican a las equipos de React con enfoque en el frontend — Explica cinco defectos comunes en el diseño backend en proyectos liderados por React, desde un uso incorrecto del paradigma API hasta despliegues frágiles, y las soluciones arquitectónicas para lograr una confiabilidad de nivel profesional.
  • Por qué las abstracciones frontend se convierten silenciosamente en deuda técnica — Aprenda por qué las abstracciones frontend prematuras añaden complejidad oculta y cómo determinar cuándo realmente vale la pena crear componentes compartidos, hooks o utilidades.