El trabajo de cualquier aplicación se puede dividir en tres etapas, a través de las cuales pasa durante su ciclo de vida:
- Inicialización
- Ejecución
- Finalización
Dependiendo de cómo estén implementadas, el código puede ser fácil de probar y mantener, o por el contrario, complicado y prácticamente imposible de probar. En esta lección, vamos a analizar la parte más importante: las diferencias entre la inicialización y la ejecución, veremos ejemplos concretos y aprenderemos a dividir las responsabilidades correctamente.
¿Qué es la inicialización? Antes de que una aplicación comience a funcionar, es necesario configurar todas las bibliotecas y frameworks necesarios, conectarlos entre sí y ponerlos en marcha. En el backend, esto es mucho más sencillo, ya que este proceso está completamente controlado por los frameworks. En el frontend, muchas cosas se dejan en manos del desarrollador, por lo que a menudo surgen situaciones en las que el proceso de inicialización no está separado o solo está parcialmente separado y de manera inadecuada.
¿Qué incluye la inicialización? Todo lo que se necesita hacer una vez para su uso posterior en la aplicación:
- Creación del estado inicial
- Configuración de i18next
- Carga y ejecución del framework, si existe
- Conexión y configuración de diversas bibliotecas: clientes HTTP, websockets, manejo de fechas, etc.
Esta lista está lejos de ser completa, en cada situación concreta, se incluirá algo propio en la inicialización.
En la práctica, el proceso de inicialización se organiza de la siguiente manera: se crea una función en la que se realiza toda la configuración necesaria. Para mayor comodidad, esto se puede hacer en un archivo llamado init.js.
// Ejemplo hipotético
// archivo init.js
import i18next from 'i18next';
import io from 'socket.io-client';
// Función inicial
export default async () => {
// Creación de una instancia de i18next
const i18nextInstance = i18next.createInstance();
await i18nextInstance.init({
lng: 'es',
resources: /* traducciones */
});
const state = {
/* descripción del estado */
}
// Creación del socket
const socket = new io();
socket.on(/* configuración de los websockets */);
const form = document.querySelector('some-form');
form.addEventListener('submit', (e) => {
// Aquí es donde se encuentra la lógica de la aplicación, se puede extraer en una función
// separada o varias funciones en un módulo separado
// en algún lugar de estos controladores se utiliza el estado y el socket
});
};
La ejecución de esta función se realiza en otro lugar, por ejemplo, en el archivo index.js, que es el punto de entrada de la aplicación:
import runApp from './init.js';
runApp();
¿Por qué este tipo de separación? En el frontend, generalmente la aplicación es global. Esto significa que hay una sola instancia de la aplicación en toda la página: se carga una vez, se inicia una vez y controla todo como si no hubiera otras aplicaciones con las que preocuparse.
Sin embargo, esta situación no siempre es la norma. Por ejemplo, los widgets pueden aparecer múltiples veces en una misma página, por lo que cada widget debe ser una entidad independiente. La inicialización de cada widget ocurre en su propio entorno con sus propios objetos, sin afectar a los objetos globales. De lo contrario, podrían surgir conflictos y un widget podría interferir con otro.
Por otro lado, en las pruebas, cada caso de prueba está diseñado para ser independiente de los demás, funcionando como si no hubiera otros casos de prueba. Este enfoque asegura que los cambios en el estado de la aplicación realizados en un caso de prueba no afecten a otros casos de prueba. Un ejemplo claro es la inicialización de i18next.
Esta biblioteca exporta un objeto global que solo se puede inicializar una vez. Las inicializaciones repetidas del mismo objeto (por ejemplo, al reiniciar la aplicación en las pruebas) pueden causar errores, lo cual está prohibido según la documentación. Por esta razón, en el ejemplo mencionado anteriormente, cada inicio de la aplicación crea su propia instancia de i18next, que luego se pasa a las funciones que lo utilizan.
// Los detalles de las pruebas se estudian en otros cursos
// En algún lugar de las pruebas
import runApp from '../src/init.js';
// Esta función se ejecuta antes de cada prueba
beforeEach(() => {
runApp(); // inicialización
});
test(/* aquí van las pruebas */)
test(/* aquí van las pruebas */)
En el ejemplo anterior, trabajamos con la función que permite inicializar la aplicación, no con el archivo index.js, donde se llama a esta función. Si hubiéramos importado index.js, la inicialización se habría producido inmediatamente durante la importación y sería global desde el punto de vista de las pruebas. En ese caso, las pruebas normales serían prácticamente imposibles.
Para acceder completo a curso necesitas un plan básico
El plan básico te dará acceso completo a todos los cursos, ejercicios y lecciones de Códica, proyectos y acceso de por vida a la teoría de las lecciones completadas. La suscripción se puede cancelar en cualquier momento.