El principio de colocalización es bastante sencillo, se puede resumir de la siguiente manera:
Sitúa el código tan cerca como sea posible de donde es relevante.
Si te paras a pensarlo, este principio no es nada nuevo. Más bien es la conclusión natural de una tendencia que se lleva observando en la industria del desarrollo web desde sus orígenes. Los frameworks más populares en la actualidad (como React, Vue, Svelte o Solid), a pesar de sus diferencias, comulgan todos con esta filosofía. Es más, la popularidad de JSX y del concepto de CSS in JS bien podría atribuirse a este principio. Incluso en alguna ocasión he visto argumentar que la colocalización es una de las ventajas de utilizar TailwindCSS (si esto es cierto o no lo dejamos para otro día).
En nuestro caso, la misma filosofía ya estaba dando forma a nuestras bases de código mucho antes de conocer el concepto de colocalización, con lo que mi colega Víctor Míguez (GitHub, LinkedIn) tuvo a bien en bautizar como el patrón carpeta.
Introduciendo el patrón carpeta 📁
Para ilustrar este patrón, supón que necesitamos crear una función de utilidad [.rr-code]doSomething()[.rr-code] para extender una funcionalidad ya existente. Lo primero que haces es escribirla en el mismo fichero y, una vez completada la funcionalidad, toca decidir dónde debería situarse esta nueva pieza de código.
Desde luego, siguiendo el principio de colocalización, podría quedarse donde está. Pero, por el bien de esta explicación, supongamos también que el fichero que acabas de modificar empieza a ser demasiado grande y difícil de leer. Para paliar esto y además mantener una buena separación de responsabilidades, decides mover la función de utilidad a su propio fichero [.rr-code]doSomething.js[.rr-code], y lo sitúas al lado del fichero donde se utiliza.
Ahora bien, pasado un tiempo se descubre un bug en tu función. Después de arreglarlo (o incluso antes) aprovechas la oportunidad para añadir algunos tests. Aquí es donde entra en juego el patrón carpeta: creas una nueva carpeta, renombras el fichero a [.rr-code]doSomething/index.js[.rr-code] y añades los tests justo al lado, en [.rr-code]doSomething/index.test.js[.rr-code].
Puede —y espero que así sea— que crear una carpeta te resulte demasiado simple como para ser catalogado de principio, pero ¿no se supone que los principios deben ser sencillos? Crear una nueva carpeta en el mismo sitio y con el mismo nombre y situar el artefacto de código en el fichero índice (o exportarlo desde el mismo) tiene varias ventajas:
- No tenemos que modificar el código que hace uso de dicho artefacto, lo que repercute positivamente en la legibilidad del histórico de nuestro repositorio
- Evitamos que los directorios se conviertan en cajones de sastre, conteniendo combinaciones arbitrarias de ficheros, unos relacionados y otros no
Una vez creada la nueva carpeta, podemos añadir tantos ficheros auxiliares como necesitemos. En nuestra experiencia, el patrón carpeta resulta muy práctico al trabajar con componentes, porque a menudo nos encontramos dividiéndolos en componentes más pequeños, añadiendo estilos, stories o tests a los mismos.
Organización interna del código dentro de un directorio, sin seguir ningún patrón (izquierda) y siguiendo el patrón carpeta (derecha).
Cada carpeta es un módulo 🤔💭
Aunque el proceso descrito refleja cómo se aplica el patrón carpeta (además de dar nombre al mismo), este patrón se sustenta también en otro principio: en nuestra base de código, cada directorio es un módulo (o submódulo) de nuestra aplicación. Cada módulo expone una API pública, aquello que se exporta desde su fichero índice, y la estructura interna de cada directorio se considera un detalle de implementación.
Para cumplir con este principio, a la hora de importar cualquier pieza de código, podemos movernos tantos niveles hacia arriba como necesitemos, pero no debemos acceder a ninguna carpeta anidada dentro de otra:
Organizar nuestro código de esta manera nos permite total libertad a la hora de refactorizar y reorganizar el código dentro de un directorio, sin miedo a que los cambios afecten a otras partes de la aplicación. Además, nos obliga a pensar sobre la naturaleza del código que debe ser reutilizado.
Siguiendo con el ejemplo del apartado anterior, imaginemos que en algún momento del futuro necesitamos volver a utilizar la función [.rr-code]doSomething[.rr-code] desde otra parte de la aplicación. En lugar de importarla directamente, tenemos que tomar una decisión: ¿de verdad la función pertenece a este módulo y debemos exportarla? ¿O debería pertenecer a otro, uno accesible desde los dos módulos que la necesitan?
En nuestra opinión, este es el tipo de preguntas que nos ayudan a mantener nuestras bases de código organizadas, fáciles de navegar, sostenibles y escalables.
Pero… ¿por qué? 🎩🪄
Pensar en términos de módulos con APIs públicas nos ayuda a estructurar mejor nuestro código. Siguiendo esta filosofía y el principio de colocalización, evitamos agrupar nuestro código por taxonomías. A la larga, carpetas como [.rr-code]components/[.rr-code], [.rr-code]hooks/[.rr-code] y [.rr-code]utils/[.rr-code] terminan colmándose de ficheros, sin que resulte evidente para qué sirven o dónde y cómo se utilizan.
Puede que a simple vista estas agrupaciones transmitan cierta sensación de orden 🧘 🕉️ pero la realidad es que no encajan con el flujo que seguimos a la hora de leer nuestro propio código o el de otros. En su lugar, los módulos tienden a coincidir con conceptos o entidades del dominio de la aplicación, y las relaciones (dependencias) entre ellos adquieren una nueva dimensión semántica. Nuestra base de código ofrecerá mucho más contexto y permitirá una mejor aproximación al problema que se pretende resolver.
No queremos decir que las agrupaciones por taxonomías no tengan cabida en una base de código, más bien que deberíamos recurrir a ellas en última instancia, solo cuando una pieza de código no pertenece a ninguno de los conceptos del dominio de nuestra aplicación. Dicho de otra manera, para artefactos que podrían delegarse a una librería de terceros (hooks genéricos, componentes puramente visuales, utilidades de uso recurrente en el proyecto, etcétera).
Por otra parte, el crecimiento de nuestra base de código resulta mucho más orgánico. En lugar de crecer sólo en amplitud, añadiendo más y más ficheros a cada taxonomía, los módulos permiten que la base de código crezca también en profundidad.
A partir de un momento dado en la evolución de un proyecto, será más difícil que aparezcan nuevos módulos y, en su lugar, la tendencia será a crear más submódulos. Es decir, la creciente complejidad de la aplicación no contaminará el directorio raíz ni se esparcirá por todo el árbol de directorios, sino que irá enmarcándose en el contexto de uno u otro módulo en particular.
En nuestra experiencia, esta estructura encaja muy bien con el modo en que consumimos el código. Mientras no tengamos que modificar un módulo, toda su complejidad queda recogida fuera de nuestro foco de atención, de manera que ya no interfiere en nuestro trabajo. Ahora bien, cuando tengamos que hacerlo, podremos sumergirnos más y más en ella, hasta llegar al punto en el que tenemos que actuar. En el proceso habremos recogido todo el contexto necesario, y desde el módulo (o submódulo) en cuestión, podremos ver sus dependencias con otros módulos.
Una última palabra 💬
En RedRadix procuramos no ser dogmáticos en cuanto a cómo se deben hacer las cosas. El patrón carpeta surge de manera natural en muchos proyectos, pero también hay otros en los que no. Más que ofrecer una receta para seguir paso a paso, nuestra intención con este artículo es la de abrir una ventana al modo en que pensamos nosotros sobre cómo organizar nuestras bases de código.
No tomes nuestra palabra, pero quédate con todas las ideas que te hayan gustado. Dales una vuelta, compártelas con tu equipo, probad lo que queráis probar y, si llegáis a otras conclusiones, ¡nos encantará que las compartáis con nosotros por email o en nuestras redes sociales (LinkedIn o X)!
*La imagen de este artículo es una fotografía de Maksym Kaharlytskyi en Unsplash.