Buscar/Crear y modificar máquinas de estados orientadas a eventos sin romper su comportamiento

Crear y modificar máquinas de estados orientadas a eventos sin romper su comportamiento

Para bibliotecas de máquinas de estados. La IA termina cada evento antes del siguiente, gestiona errores sin saltarse la limpieza, protege hilos y máquinas secundarias, escribe primero pruebas para motores síncronos y asíncronos y alinea código, documentación y esquemas.

Para usar estas reglas de IA
## Decidir qué módulo se encarga de cada parte de la lógica de la máquina de estados Distribuye las responsabilidades antes de cambiar el código para que cada comportamiento permanezca en el componente que posee el conocimiento necesario. ### Definir límites claros - Separa las definiciones de estados y eventos, la composición de transiciones, el despacho de funciones de devolución de llamada, la adaptación de firmas, los motores de ejecución, los registros, las integraciones y la serialización. - Mantén la infraestructura general, como el despacho y la adaptación de firmas, independiente del dominio de las máquinas de estados. - Da a cada módulo, clase y función un único motivo claro para cambiar. - Prioriza módulos pequeños y específicos con interfaces explícitas, en vez de ampliar por comodidad un módulo no relacionado. ### Situar la lógica de forma deliberada - Aplica Information Expert (Experto en información): calcula un resultado donde ya se encuentre el conocimiento necesario y luego devuélvelo o señálalo, en vez de volver a calcularlo en otro lugar. - Aplica la Law of Demeter (Ley de Deméter): pasa el valor específico que necesita un colaborador, no su objeto padre. - Aplica Interface Segregation (Segregación de interfaces): acepta un campo o protocolo limitado cuando una función auxiliar no necesite el objeto completo. - Conserva el comportamiento público al refactorizar e identifica las pruebas que lo caracterizan antes de trasladar la lógica. ## Terminar de procesar un evento antes de pasar al siguiente Modela el procesamiento de eventos en dos niveles distintos y no los combines durante la implementación. ### Distinguir los niveles de procesamiento - Un micropaso ejecuta atómicamente un conjunto de transiciones seleccionado en el orden definido: funciones de devolución de llamada previas, funciones de salida, acciones de transición, funciones de entrada y funciones posteriores. - Un macropaso procesa un evento externo y repite micropasos hasta que la máquina alcanza una configuración estable. - Una configuración es estable únicamente cuando no hay ninguna transición sin evento habilitada y la cola interna está vacía. ### Mantener explícita la semántica de colas y eventos - Los eventos enviados desde el exterior entran en la cola externa y esperan hasta que termine el macropaso actual. - Los eventos generados internamente entran en la cola interna y se ejecutan dentro del macropaso actual antes que los eventos externos posteriores. - Una declaración de transición sin asignar puede representar una transición sin evento que se activa cuando su condición de guarda se vuelve verdadera. - Una transición asignada crea un evento con nombre; si los alias se derivan de convenciones de nomenclatura, prevalece un identificador explícito. - Prueba por separado el orden de las colas, la progresión sin eventos y la finalización en una configuración estable. ## Tratar los errores como eventos sin saltarse las funciones de finalización Define dónde se interceptan los errores y qué funciones de devolución de llamada del ciclo de vida siguen ejecutándose después de un fallo. ### Establecer el límite de errores - Explicita para cada tipo de máquina la política de capturar los errores como eventos; las variantes orientadas a diagramas y las básicas pueden utilizar intencionadamente valores predeterminados opuestos. - Captura los errores en el nivel del bloque de acciones, en vez de tratar un micropaso completo como un único límite de captura, y sigue ejecutando las funciones de finalización tanto al producirse un resultado correcto como un fallo cuando el ciclo de vida lo garantice. - Despacha un error de ejecución capturado como evento interno para que el diagrama de estados pueda gestionarlo mediante transiciones declaradas. - Evita los bucles de fallo recursivos suprimiendo o terminando los errores que surjan durante la gestión del evento interno de error de ejecución. ### Conservar las diferencias entre funciones de devolución de llamada - Separa las funciones genéricas del ciclo de vida de las específicas de eventos y de estados. - Una función de retorno cuyo nombre haga referencia a un error no es genérica automáticamente; exige la transición de error correspondiente antes de invocarla por convención. - Si las acciones de transición desactivan la interceptación local de errores durante el procesamiento de un evento de error, mantén esa regla separada de los bloques de entrada y salida que siempre usan su gestor de errores. - Prueba de forma independiente el resultado correcto, el fallo ordinario, el fallo dentro de la gestión de errores y el orden de finalización. ## Proteger los hilos y las máquinas secundarias al cambiar colas o bloqueos Mantén intactas las garantías de seguridad entre hilos y de enrutamiento de invocaciones cuando cambies colas, bloqueos o la gestión de máquinas secundarias. ### Mantener un procesamiento concurrente seguro - Permite que, como máximo, un hilo ejecute transiciones para una instancia de la máquina al mismo tiempo. - Usa una cola de prioridad segura entre hilos cuando se admitan emisores concurrentes. - No sustituyas una cola segura entre hilos por una lista normal ni una cola de doble extremo no segura sin rediseñar y demostrar el contrato de concurrencia. - Somete la contención real a pruebas de estrés con hilos emisores coordinados y barreras. - En pruebas con hilos bloqueados, usa esperas de eventos interrumpibles con tiempos de espera explícitos, en vez de pausas fijas que puedan dejar hilos activos durante el desmontaje. ### Gestionar con precisión las máquinas secundarias invocadas - Retira una invocación secundaria únicamente cuando esté terminada y cancelada. - Mantén enrutable una máquina secundaria terminada pero no cancelada mientras su estado propietario siga activo. - Si la construcción de la máquina secundaria se bloquea dentro del bucle de procesamiento, captura su referencia mediante un observador temprano del ciclo de vida antes de que continúe el bucle. - Enruta los eventos dirigidos a una máquina secundaria mediante el gestor de invocaciones y el controlador secundario, en lugar de eludir el control del ciclo de vida. - Prueba como casos distintos la cancelación, la terminación natural, la conservación del enrutamiento y la entrega tardía de eventos. ## Ejecutar las pruebas en el entorno fijado del proyecto y medir la cobertura de ramas Ejecuta la instalación, las pruebas, los controles de calidad y la documentación mediante el entorno fijado y el ejecutor de tareas del proyecto. ### Ejecutar las pruebas de forma segura - Aplica un tiempo de espera explícito a toda la batería de pruebas para detectar hilos que quedan activos o bucles infinitos. - Usa la ejecución paralela admitida para toda la batería cuando las pruebas contengan esperas intencionadas o tiempos de espera alternativos. - Invoca la batería canónica desde la raíz del proyecto cuando una ejecución restringida por ruta omita doctests de módulos o documentación. - Investiga las ejecuciones que superen la referencia establecida, en vez de repetirlas sin diagnosticar la causa. - Activa el registro de depuración detallado únicamente en investigaciones concretas cuando el registro global altere las mediciones de rendimiento. - Captura una sola vez las salidas extensas y después analiza el resultado guardado, en vez de ejecutar repetidamente la misma batería costosa. ### Medir la cobertura prevista - Activa explícitamente la recopilación de cobertura cuando sea necesaria y luego genera un informe de líneas sin cubrir o legible por máquinas para los módulos afectados; seleccionar únicamente un formato de informe puede no activar la recopilación. - Verifica la cobertura de ramas, no solo la cobertura de líneas, antes de confirmar cambios. ## Escribir primero una prueba que falle y ejecutarla en motores síncronos y asíncronos Trata las pruebas como requisitos de implementación, no como una limpieza posterior a la escritura del código. ### Seguir un ciclo basado en pruebas 1. Identifica durante la planificación las ramas nuevas, los casos límite, las rutas de error y los riesgos de concurrencia. 2. Añade una prueba específica que falle ante el comportamiento ausente o incorrecto. 3. Implementa el cambio de comportamiento mínimo que haga pasar la prueba. 4. Ejecuta la prueba específica, la cobertura de los módulos afectados y los controles generales requeridos. 5. Refactoriza solo después de que el comportamiento y la cobertura estén protegidos. ### Reutilizar la infraestructura de pruebas - Usa un ejecutor parametrizado compartido para probar el mismo diagrama de estados con motores síncronos y asíncronos. - Deja que el ejecutor abstraiga el inicio del motor y la entrega de eventos, en vez de duplicar clases de prueba o añadir observadores sin operación. - Usa elementos de prueba para rutas temporales, cambios de entorno y estados mutables; no codifiques rutas de forma fija ni dependas de variables globales mutables. - Cubre con pruebas las ramas defensivas siempre que sean alcanzables; excluye una rama de la cobertura únicamente cuando se haya demostrado que es imposible. - Mantén el umbral obligatorio de cobertura de ramas aplicado por los controles estándar de confirmación de cambios. ## Ejecutar las comprobaciones de código y mantener alineados el analizador y el esquema Aplica un único sistema de calidad coherente a la implementación, los ejemplos, los contratos generados y la documentación para desarrolladores. ### Aplicar las convenciones de código - Ejecuta el formateador, el analizador de estilo, el ordenador de importaciones, el comprobador de tipos y las pruebas del proyecto mediante la cadena de herramientas canónica. - Da preferencia a las importaciones de nivel superior ordenadas; usa importaciones diferidas solo cuando sean necesarias para romper una dependencia circular. - Sigue las convenciones del proyecto para nombres y docstrings, y documenta la semántica oculta en lugar de repetir nombres o tipos evidentes. - Coloca un docstring de atributo directamente debajo del atributo cuando lo exija el generador de documentación; reserva los comentarios en línea para la lógica no evidente. ### Probar la documentación y los formatos declarativos - Convierte los ejemplos de documentación en doctests ejecutables cuando sea práctico. - Coloca en pruebas unitarias los ejemplos que requieran concurrencia real y haz referencia a ellos desde la documentación. - Mantén sincronizados el analizador, el comportamiento en tiempo de ejecución y el esquema publicado cuando cambie el vocabulario declarativo. - Trata los conjuntos de claves aceptadas por el analizador como fuente de verdad y compruébalos frente a las propiedades correspondientes del esquema. - Valida cada ejemplo estructurado y cada elemento de prueba con el esquema para que cualquier divergencia entre analizador y esquema haga fallar la batería de pruebas. ## Terminar los cambios sin saltarse los hooks de commit ni subir secretos Usa el proceso de contribución del repositorio para preservar la trazabilidad, los artefactos generados y los límites de seguridad. ### Completar el control de calidad - Trabaja sobre la rama de desarrollo designada del repositorio y sigue su convención de mensajes de confirmación. - Usa confirmaciones firmadas cuando el proyecto las prefiera. - Nunca eludas los ganchos de confirmación ni desactives los controles obligatorios. - Si un gancho vuelve a generar o modifica archivos, revisa y prepara esos resultados y luego vuelve a confirmar los cambios hasta que pasen todos los ganchos. - Revisa la diferencia final y confirma que la implementación, las pruebas, la documentación y los contratos generados coinciden. ### Proteger los límites de confianza - Nunca incluyas secretos, credenciales ni archivos de entorno en una confirmación de cambios. - Valida los datos no fiables en los límites del sistema y mantén explícitas las invariantes internas. - No debilites la seguridad de las colas, la prevención de bucles de error, la validación de esquemas ni la aplicación de la cobertura solo para conseguir que un cambio pase. - Informa de cualquier control obligatorio que no pueda ejecutarse y no afirmes que el trabajo está completo sin resultados directos.