Buscar/Criar e alterar máquinas de estados orientadas a eventos sem quebrar seu comportamento

Criar e alterar máquinas de estados orientadas a eventos sem quebrar seu comportamento

Para bibliotecas de máquinas de estados. A IA conclui cada evento antes do próximo, trata erros sem pular a limpeza, protege threads e máquinas filhas, escreve testes primeiro para mecanismos síncronos e assíncronos e alinha código, documentação e esquemas.

Para usar estas regras de IA
## Decidir qual módulo cuida de cada parte da lógica da máquina de estados Mapeie as responsabilidades antes de alterar o código para que o comportamento permaneça no componente que detém o conhecimento necessário. ### Definir limites claros - Separe definições de estados e eventos, composição de transições, despacho de callbacks, adaptação de assinaturas, mecanismos de execução, registros, integrações e serialização. - Mantenha a infraestrutura geral, como despacho e adaptação de assinaturas, independente do domínio da máquina de estados. - Dê a cada módulo, classe e função um único motivo claro para mudar. - Prefira módulos pequenos e focados, com interfaces explícitas, a ampliar um módulo sem relação apenas por conveniência. ### Posicionar a lógica deliberadamente - Aplique Information Expert: calcule um resultado onde o conhecimento necessário já existe e então o retorne ou sinalize, em vez de recalculá-lo em outro lugar. - Aplique a Law of Demeter: passe o valor específico de que um colaborador precisa, não seu objeto pai. - Aplique Interface Segregation: aceite um campo ou protocolo restrito quando uma função auxiliar não precisar do objeto completo. - Preserve o comportamento público ao refatorar e identifique os testes que o caracterizam antes de mover a lógica. ## Terminar de processar um evento antes de passar ao próximo Modele o processamento de eventos em dois níveis distintos e não os combine durante a implementação. ### Distinguir os níveis de processamento - Um micropasso executa atomicamente um conjunto selecionado de transições na ordem definida: callbacks anteriores, callbacks de saída, ações de transição, callbacks de entrada e callbacks posteriores. - Um macropasso trata um evento externo e repete micropassos até que a máquina alcance uma configuração estável. - Uma configuração só é estável quando nenhuma transição sem evento está habilitada e a fila interna está vazia. ### Manter explícita a semântica de filas e eventos - Eventos enviados externamente entram na fila externa e aguardam até o macropasso atual terminar. - Eventos gerados internamente entram na fila interna e são executados no macropasso atual antes de eventos externos posteriores. - Uma declaração de transição não atribuída pode representar uma transição sem evento que dispara quando sua condição de guarda se torna verdadeira. - Uma transição atribuída cria um evento nomeado; se os aliases forem derivados de convenções de nomenclatura, um identificador explícito terá precedência. - Teste a ordem das filas, a progressão sem eventos e o término em configuração estável como comportamentos separados. ## Tratar erros como eventos sem pular os callbacks de finalização Defina onde os erros são interceptados e quais callbacks do ciclo de vida ainda são executados após uma falha. ### Definir o limite de erro - Torne explícita para cada tipo de máquina a política de capturar erros como eventos; variantes orientadas a diagramas e variantes básicas podem usar padrões opostos de forma intencional. - Capture erros no nível do bloco de ação, em vez de tratar um micropasso inteiro como um único limite de captura, e ainda execute os callbacks de finalização tanto em caso de sucesso quanto de falha quando o ciclo de vida os garantir. - Despache um erro de execução capturado como evento interno para que o diagrama de estados possa tratá-lo por meio das transições declaradas. - Evite ciclos recursivos de falhas suprimindo ou encerrando erros gerados durante o tratamento do evento interno de erro de execução. ### Preservar as diferenças entre callbacks - Separe callbacks genéricos do ciclo de vida de callbacks específicos de eventos e específicos de estados. - Um callback chamado `error` não é automaticamente genérico; exija a transição de erro correspondente antes de invocá-lo por convenção. - Se as ações de transição desativarem a interceptação local de erros durante o processamento do evento de erro, mantenha essa regra separada dos blocos de entrada e saída, que sempre usam seu tratador de erros. - Teste de forma independente o sucesso, a falha comum, a falha dentro do tratamento de erros e a ordem de finalização. ## Proteger threads e máquinas filhas ao mudar filas ou bloqueios Mantenha intactas as garantias de segurança entre threads e de roteamento de invocações ao alterar filas, bloqueios ou o gerenciamento de máquinas filhas. ### Manter o processamento concorrente seguro - Permita que no máximo uma thread execute transições para uma instância da máquina por vez. - Use uma fila de prioridade segura entre threads quando houver suporte a remetentes concorrentes. - Não substitua uma fila segura entre threads por uma lista simples ou uma fila de duas pontas não segura entre threads sem reprojetar e comprovar o contrato de concorrência. - Faça testes de estresse com contenção real usando threads remetentes coordenadas e barreiras. - Em testes com threads bloqueadas, use esperas de eventos interrompíveis com tempos limite explícitos em vez de pausas fixas que possam deixar threads em execução durante a desmontagem. ### Gerenciar com precisão as máquinas filhas invocadas - Remova uma invocação filha somente quando ela estiver terminada e cancelada. - Mantenha uma máquina filha terminada, mas não cancelada, acessível por roteamento enquanto o estado que a possui permanecer ativo. - Se a construção da máquina filha bloquear dentro do ciclo de processamento, capture a referência à filha por meio de um ouvinte inicial do ciclo de vida antes que o ciclo continue. - Encaminhe eventos destinados à filha pelo gerenciador de invocações e pelo tratador da filha, em vez de contornar o controle do ciclo de vida. - Teste cancelamento, término natural, roteamento mantido e entrega tardia de eventos como casos distintos. ## Rodar os testes no ambiente fixo do projeto e medir a cobertura de ramificações Execute instalação, testes, verificações de qualidade e documentação por meio do ambiente fixado e do executor de tarefas do projeto. ### Executar testes com segurança - Aplique um tempo limite explícito à suíte completa para expor threads remanescentes ou ciclos infinitos. - Use a execução paralela compatível para a suíte completa quando os testes contiverem esperas intencionais ou tempos limite de contingência. - Invoque a suíte canônica a partir da raiz do projeto quando uma execução restrita por caminho omitir doctests de módulos ou da documentação. - Investigue execuções que ultrapassem a referência estabelecida, em vez de repeti-las continuamente sem diagnóstico. - Ative o registro detalhado de depuração somente em investigações específicas quando o registro global de depuração alterar as medições de desempenho. - Capture uma saída extensa uma única vez e depois analise o resultado salvo, em vez de executar repetidamente a mesma suíte dispendiosa. ### Medir a cobertura pretendida - Ative explicitamente a coleta de cobertura quando necessário e então gere, para os módulos afetados, um relatório de linhas ausentes ou legível por máquina; escolher apenas um formato de relatório pode não ativar a coleta. - Verifique a cobertura de ramificações, não apenas a cobertura de linhas, antes do commit. ## Escrever primeiro um teste que falhe e rodá-lo em mecanismos síncronos e assíncronos Trate os testes como requisitos de implementação, não como uma limpeza realizada depois que o código estiver escrito. ### Seguir um ciclo orientado por testes 1. Identifique novos ramos, casos extremos, caminhos de erro e riscos de concorrência durante o planejamento. 2. Adicione um teste focado que falhe diante do comportamento ausente ou incorreto. 3. Implemente a menor alteração de comportamento que faça o teste passar. 4. Execute o teste focado, a cobertura do módulo afetado e as verificações mais amplas exigidas. 5. Refatore somente depois que o comportamento e a cobertura estiverem protegidos. ### Reutilizar a infraestrutura de testes - Use um executor parametrizado compartilhado para testar o mesmo diagrama de estados em mecanismos síncronos e assíncronos. - Deixe o executor abstrair a inicialização do mecanismo e a entrega de eventos, em vez de duplicar classes de teste ou adicionar ouvintes sem efeito. - Use fixtures para caminhos temporários, alterações no ambiente e estado mutável; não fixe caminhos no código nem dependa de variáveis globais mutáveis. - Cubra ramos defensivos com testes sempre que forem alcançáveis; exclua um ramo da cobertura somente quando sua impossibilidade estiver comprovada. - Mantenha o limite exigido de cobertura de ramificações imposto pelas verificações padrão de commit. ## Rodar as verificações de código e manter o analisador e o esquema alinhados Aplique um sistema de qualidade consistente à implementação, aos exemplos, aos contratos gerados e à documentação para desenvolvedores. ### Aplicar as convenções de código - Execute o formatador, o linter, o ordenador de imports, o verificador de tipos e os testes do projeto por meio da cadeia de ferramentas canônica. - Prefira imports ordenados no nível superior; use imports tardios somente quando forem necessários para romper uma dependência circular. - Siga as convenções de nomenclatura e docstrings do projeto e documente a semântica oculta, em vez de repetir nomes ou tipos óbvios. - Use uma docstring de atributo diretamente abaixo do atributo quando o gerador de documentação assim exigir; reserve comentários na mesma linha para lógica não óbvia. ### Testar a documentação e os formatos declarativos - Transforme exemplos da documentação em doctests executáveis quando for viável. - Coloque exemplos que exijam concorrência real em testes unitários e faça referência a eles na documentação. - Mantenha o analisador, o comportamento em tempo de execução e o esquema publicado sincronizados sempre que o vocabulário declarativo mudar. - Trate os conjuntos de chaves aceitos pelo analisador como fonte da verdade e teste-os em relação às propriedades correspondentes do esquema. - Valide cada exemplo estruturado e cada fixture em relação ao esquema para que divergências entre analisador e esquema façam a suíte de testes falhar. ## Concluir alterações sem contornar hooks de commit nem incluir segredos no commit Use o processo de contribuição do repositório para preservar a rastreabilidade, os artefatos gerados e os limites de segurança. ### Concluir o controle de qualidade - Direcione o trabalho à branch de desenvolvimento designada do repositório e siga a convenção de mensagens de commit. - Use commits assinados quando o projeto os preferir. - Nunca contorne hooks de commit nem desative verificações obrigatórias. - Se um hook regenerar ou modificar arquivos, revise e prepare esses resultados e então faça um novo commit até que todos os hooks sejam aprovados. - Revise o diff final e confirme que a implementação, os testes, a documentação e os contratos gerados estejam de acordo. ### Proteger os limites de confiança - Nunca inclua segredos, credenciais ou arquivos de ambiente em um commit. - Valide dados não confiáveis nos limites do sistema e mantenha explícitas as invariantes internas. - Não enfraqueça a segurança das filas, a prevenção de ciclos de erro, a validação de esquemas ou a aplicação da cobertura apenas para fazer uma alteração passar. - Informe qualquer verificação obrigatória que não tenha sido executada e não declare a conclusão sem resultados diretos.