


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.
