Inicio / Artículos / Cómo las decisiones pequeñas se acumulan en bases de código React de larga duración

Cómo las decisiones pequeñas se acumulan en bases de código React de larga duración

Quince hábitos de mantenimiento para aplicaciones React que duran años: código legible, componentes enfocados, estado delimitado, dependencias mínimas, pruebas y monitoreo.

2206 palabras

Iniciar un proyecto con React es la parte fácil. La verdadera prueba llega meses o años después, cuando la aplicación cuenta con más usuarios, más funciones, más colaboradores y más dependencias, y algunos soluciones “temporales” se han convertido silenciosamente en elementos esenciales. En ese momento, rara vez el problema radica en React en sí; el verdadero desafío es mantener la base de código comprensible mientras todo a su alrededor sigue cambiando. Esta guía presenta quince hábitos, agrupados según los lugares donde realmente se acumulan los costos de mantenimiento, para que puedas identificar las decisiones que se van agravando antes de que conviertan la base de código en algo con lo que nadie quiera trabajar.

Código escrito para el próximo lector

Preferir código evidente sobre código ingenioso

Las frases cortas y densas, las abstracciones profundas y los auxiliares creados para docenas de casos hipotéticos parecen productivos cuando se escriben. Seis meses después se convierten en un acertijo, a menudo para quien los escribió y que ya no recuerda por qué.

La alternativa es un código deliberadamente sencillo. Considere diseñar una pantalla que pueda estar cargándose, haber fallado o estar lista. Los retornos anticipados hacen que cada estado sea explícito, una condición a la vez:

if (isLoading) {
  return <LoadingState />;
}

La rama de error sigue la misma estructura, y el camino exitoso viene al final:

if (error) {
  return <ErrorState />;
}
return <Dashboard />;

Nada aquí es impresionante, y ese es el punto: cualquiera puede ver en segundos qué componente se renderiza cuándo. En una aplicación grande, la rapidez con la que el próximo desarrollador entienda su código es mucho más importante que impresionar al actual.

Los nombres son documentación que nunca se vuelve obsoleta

Mientras escribes código, nombrar variables parece un detalle trivial, pero se vuelve sumamente importante al depurarlo medio año después. Compara buscar esto en la base de código:

handleData()

con buscar esto:

calculateMonthlyRevenue()

El segundo nombre te indica qué calcula la función antes incluso de abrir el archivo. Las bases de código grandes funcionan en gran medida como canales de comunicación entre desarrolladores; un nombre preciso expresa la intención, mientras que uno vago convierte a todo lector en un detective.

Cuidado con los nombres genéricos que surgen bajo presión de plazos:

data
item
temp
helper
value
thing

Sí, thing aparece en bases de código reales. Una regla práctica: si un nombre podría aplicarse igualmente bien en cualquier archivo del proyecto, probablemente no transmita suficiente información sobre ese archivo específico.

Límites de componentes y estado

Los componentes demasiado grandes son costosos de manejar

Casi todos los proyectos React de larga duración terminan teniendo un componente con cuatro dígitos de líneas de código. Rara vez comienzan así; empiezan con unas 150 líneas razonables, luego se añaden modales, filtros, funcionalidades de obtención de datos, verificaciones de permisos y un segundo modal. Con el tiempo, al abrir el archivo, encontrarás algo como esto:

Dashboard.tsx
1,247 lines

Los componentes de ese tamaño son difíciles de entender, probar, reutilizar y depurar, y es arriesgado modificarlos porque cualquier cambio puede afectar al estado del cual dependen otras partes del archivo. Divide el código antes de lo que parezca necesario. En lugar de un único archivo:

Dashboard.tsx

divide la pantalla en secciones, cada una con una función específica:

DashboardHeader.tsx
DashboardStats.tsx
DashboardFilters.tsx
RecentActivity.tsx
DashboardTable.tsx

Cada archivo ahora tiene una única responsabilidad y un alcance de impacto mucho menor. Un criterio práctico: si no puedes describir un componente en una oración sin usar varios “y”, divídelo.

Reutilice patrones que ya haya visto, no aquellos que imagine

Los componentes reutilizables son una buena idea, pero es fácil exagerar con ellos. Un error común es usar un único botón que intenta ser todos los botones del producto:

<UniversalButton
  type="primary"
  variant="rounded"
  size="medium"
  iconPosition="left"
  loadingStyle="spinner"
/>

Cada nueva propiedad parece inofensiva, pero juntas crean un espacio combinatorio de variantes que nadie prueba completamente, y el componente “reutilizable” termina siendo más difícil de usar que tres botones pequeños y específicos. La reutilización es valiosa; la generalización prematura, no.

Extraiga una abstracción compartida solo cuando vea que un patrón se repite realmente, no porque una página futura pueda necesitarla. Si esa necesidad surge, diseñará la abstracción a partir de ejemplos reales en lugar de con suposiciones.

Ordene el estado según a dónde pertenezca

La gestión del estado tiende a deteriorarse de forma silenciosa. Una aplicación pequeña comienza con el estado de los componentes:

useState()

Luego agrega estado compartido a través del contexto:

useContext()

Y con el tiempo el proyecto contiene Redux, varios contextos, estado local, estado de URL y estado del servidor al mismo tiempo, sin una respuesta clara sobre cuál de ellos controla la barra lateral. Cada herramienta era razonable cuando se añadió; lo que falta es una regla sobre qué va dónde.

Una forma sencilla de restablecer el orden es clasificar el estado según su naturaleza. El estado local de la interfaz cubre cosas como estas:

modal open
dropdown selected
input value

Debería permanecer dentro del componente que lo utiliza. El estado del servidor es datos que se encuentran en el backend y solo se almacenan en caché en el navegador:

users
products
analytics

Para esa categoría, utilice una biblioteca diseñada para obtener, almacenar en caché y volver a validar datos remotos en lugar de copiar las respuestas manualmente en un almacén global. Si su equipo está considerando ese cambio, las ventajas y desventajas se explican en nuestra comparación entre React Query y Redux para el estado del servidor. Finalmente, el estado verdaderamente global es algo muy limitado:

theme
authenticated user
app-wide preferences

Mantenga ese último grupo al mínimo: cualquier componente puede leer o modificar el estado global, por lo que cuanto menos haya, menos errores inexplicables surgirán. Los filtros, pestañas y paginación suelen pertenecer a la URL, donde sobreviven a las recargas y pueden compartirse.

Estructura y dependencias

Haga que la estructura de carpetas sea predecible

Imagínese unirse a un proyecto y encontrar esto en la raíz:

src/
components/
shared/
common/
helpers/
utils/
services/
core/
misc/
new/
new2/

¿Dónde debe ir un nuevo componente de perfil de usuario? Nadie puede decírmelo, por lo que cada desarrollador elige de manera diferente y la estructura sigue cambiando. Las categorías superpuestas como shared, common, helpers y utils demuestran que nadie decidió qué significa cada una.

Un diseño basado en funcionalidades elimina gran parte de esa ambigüedad al agrupar el código según la parte del producto a la que sirve:

features/
  auth/
  dashboard/
  users/
  billing/

Dentro de cada funcionalidad, un pequeño conjunto repetido de subcarpetas mantiene todo familiar:

components/
hooks/
services/
types/

Ahora, quienquiera que trabaje en la facturación sabe dónde se encuentra el código relacionado con ella, y al reescribir una función solo se modifica un directorio en lugar de diez. También pueden funcionar otros diseños (véase nuestra comparación de estructuras de carpetas en React); lo importante es que la regla sea predecible y esté por escrito.

Trata cada dependencia como una tarea de mantenimiento futura

Añadir un paquete solo requiere una orden:

npm install something-cool

El costo se hace evidente más tarde: los paquetes son abandonados, introducen cambios que rompen el funcionamiento del sistema, exponen problemas de seguridad e inflan el tamaño del archivo compilado, y tu equipo debe seguir actualizando código que nadie dentro de él ha leído.

Así que, antes de instalarlo, pregúntese si realmente lo necesita. Un paquete que ahorra un esfuerzo considerable o resuelve un problema realmente difícil, como el manejo de fechas con consideración a las zonas horarias, merece su lugar. Uno que solo se encarga de capitalizar una cadena de texto no lo hace.

Redes de seguridad que crecen con la aplicación

Pruebe los flujos que causarían más daño si fallaran

En una aplicación pequeña, navegar por las pantallas antes del lanzamiento parece suficiente. En una grande, editar una sola función puede provocar errores en los procesos de facturación por razones que nadie puede explicar, y es entonces cuando las pruebas automatizadas resultan útiles: le permiten modificar código que no escribió con confianza.

No es necesario abarcar cada detalle de implementación. Enfóquese en los flujos de usuario donde un fallo tiene consecuencias graves:

  • iniciar sesión
  • el proceso de pago
  • enviar formularios importantes
  • verificaciones de permisos
  • cálculos en los que depende el negocio

Las pruebas no demostrarán que la aplicación esté sin defectos; detectan las regresiones obvias antes de que lo hagan los usuarios. Las pruebas de comportamiento, en lugar de las internas, también resisten mucho mejor el refactoring.

Esté atento a la decadencia acumulativa y lenta del rendimiento

Las aplicaciones grandes de React rara vez se vuelven lentas en una sola versión. El rendimiento empeora poco a poco debido a decisiones razonables: una dependencia pesada, una imagen enorme, actualizaciones innecesarias de la interfaz, diez solicitudes en una sola pantalla. Juntas pueden hacer que la carga de un panel de control tarde cinco segundos, por lo que debe observar continuamente:

  • componentes que se actualizan cuando nada de lo que muestran ha cambiado
  • el tamaño del paquete que aumenta con cada versión
  • la misma solicitud enviada más de una vez por pantalla
  • componentes individuales que tardan en renderizarse
  • listas largas renderizadas sin virtualización
  • imágenes de tamaño excesivo

Las pequeñas victorias se suman, y detectar una regresión en el momento en que ocurre es mucho más económico que tener que buscarla posteriormente.

Diseña intencionadamente el camino hacia situaciones problemáticas

La mayor parte del esfuerzo se invierte en el camino “feliz”, donde cada solicitud tiene éxito. En producción, las conexiones se interrumpen, las APIs fallan, los permisos cambian y el backend devuelve resultados inesperados. El usuario debería ver un mensaje claro y recuperable del tipo “No pudimos cargar sus datos. Inténtelo de nuevo.” en lugar de un error bruto del sistema como:

TypeError: Cannot read properties of undefined

En términos de React, eso significa estados de error explícitos para la obtención de datos, límites de error que impidan que un componente defectuoso vacíe toda la página, y acciones de reintento donde sean apropiadas.

Considera la supervisión en producción como parte del desarrollo

El lanzamiento no marca el final del trabajo. La producción revela problemas que el desarrollo local nunca mostrará, y se necesita visibilidad sobre:

  • errores en tiempo de ejecución y dónde ocurren
  • solicitudes que son más lentas de lo esperado
  • llamadas a la API que fallan
  • rendimiento general de las páginas y las interacciones
  • cómo utilizan realmente los usuarios el producto

Sin ello, un informe de error no es más que “un usuario dijo que algo se rompió ayer”. El seguimiento de errores y los registros contextuales convierten eso en un rastro de ejecución y una marca de tiempo con los que se puede actuar.

Hábitos que mantienen al equipo alineado

Escribir documentación para las personas que ya forman parte del equipo

A menudo la documentación se considera una tarea de traspaso, pero en realidad ayuda a todos los miembros del equipo hoy en día, especialmente en lo que respecta a permisos complejos, arquitecturas inusuales, integraciones con terceros y reglas comerciales importantes.

No se necesita un manual extenso. Notas breves sobre cómo funciona la autenticación, cómo fluyen los datos, dónde se encuentran las funciones principales y por qué se tomaron ciertas decisiones ahorran horas de trabajo. Preguntarle al desarrollador original deja de ser útil una vez que se va, y en un proyecto a largo plazo eso siempre ocurre.

El refactoring es mantenimiento rutinario, no una admisión de fracaso

El refactoring no demuestra que el código original fuera malo. Los requisitos, los equipos y los productos cambian, y un código que funcionaba bien hace un año puede ya no ser adecuado para resolver sus problemas.

El peligro radica en argumentar que algo “ya funciona”. Lo mismo ocurre con una silla de tres patas, hasta que alguien se sienta en ella. Los refactores pequeños y regulares, respaldados por pruebas, mantienen la deuda técnica bajo control.

La consistencia es más importante que el gusto personal

Un desarrollador escribe identificadores de esta manera:

camelCase

otro prefiere esto:

snake_case

Y un tercero inventa un nuevo patrón de componentes cada pocas semanas. Un equipo grande no puede funcionar cuando cada archivo sigue los gustos de su autor, por lo que se debe lograr la consistencia automáticamente a través de:

  • un linter con reglas acordadas
  • un formateador automático
  • convenios de nomenclatura documentados
  • patrones compartidos y revisados para tareas comunes

La base de código debería leerse como un único proyecto, y no como una docena de desarrolladores discutiendo sobre las estructuras de los archivos.

Elija la arquitectura que su equipo realmente pueda implementar

Los debates sobre arquitectura son un pasatiempo favorito: microservicios, monorepos, Arquitectura Limpia, diseño orientado a dominios, y siempre hay alguien con un diagrama listo. Pero la opción más sofisticada no es necesariamente la mejor. Una buena elección cumple tres criterios:

  • aborda los problemas reales que tienen
  • todo el equipo puede explicarla
  • el equipo puede mantenerlo en funcionamiento y hacerlo evolucionar
  • Un diseño simple aplicado de manera consistente supera siempre a uno brillante que solo su creador comprende.

    Por qué nada de esto es emocionante, y por qué funciona

    El mantenimiento a largo plazo cambia el significado de lo que es un “desarrollo bueno”: la velocidad de escritura importa menos que la legibilidad, la facilidad para realizar cambios futuros, las necesidades de otros desarrolladores y el comportamiento en producción. Como lista de verificación práctica:

    • Los componentes permanecen pequeños y con un único propósito
    • El estado global es una lista breve y deliberada
    • Los nombres describen la intención
    • Cada nuevo paquete tiene una justificación
    • La estructura de carpetas sigue una regla documentada
    • El refactoring se realiza continuamente
    • Los flujos de usuario críticos cuentan con pruebas
    • Los errores en producción son visibles
    • La opción más simple gana en caso de empate

    Estas reglas no son atractivas, y probablemente por eso perduran. Para conocer prácticas estructurales a nivel arquitectónico, consulte diez hábitos arquitectónicos que mantienen las bases de código frontend mantenibles.

    Puntos clave

    • Las aplicaciones grandes con React rara vez tienen problemas debido al propio React; los problemas surgen cuando se acumulan atajos pequeños: un componente gigantesco, una utilidad misteriosa, un paquete innecesario, un truco que se suponía era temporal.
    • La mantenibilidad no puede añadirse al final. Es el resultado de muchas decisiones pequeñas, tomadas una tras otra.
    • El momento más económico para aplicar estos hábitos es antes de que aparezcan los problemas, cuando dividir un componente o rechazar una dependencia aún solo lleva minutos en lugar de semanas.
  • Cuando sienta la tentación de recurrir a una solución ingeniosa, recuerde que la persona encargada de mantenerla en seis meses, posiblemente usted mismo, necesitará entender por qué funciona sin el contexto actual.
  • Lecturas relacionadas