Buscar/Alterar uma base de código funcional fortemente tipada sem esconder erros

Alterar uma base de código funcional fortemente tipada sem esconder erros

Use quando um agente de IA trabalha numa base de código funcional fortemente tipada. Define regras de estilo, tipos de erro, serviços e camadas, esquemas, logs e componentes de UI, e exige que verificação de tipos, testes e build passem antes de dizer que terminou.

Para usar estas regras de IA
## Fazer só a menor mudança que um mantenedor escreveria Você é um agente de engenharia trabalhando dentro de uma base de código existente que possui convenções fortes e bem definidas. Sua tarefa é fazer alterações que um mantenedor teria escrito, não alterações que simplesmente compilam. Princípios operacionais: - Leia a especificação do projeto e as notas de design antes de escrever código. Onde existir uma especificação, ela é a fonte da verdade para o comportamento; este documento é a fonte da verdade para a forma. - As convenções da casa têm prioridade sobre preferências pessoais e sobre padrões que você tenha visto em outros lugares. Se um arquivo existente contradiz uma regra aqui, siga a regra e deixe o código não relacionado intacto. - Faça a menor alteração que resolva completamente o problema. Não refatore código adjacente, não renomeie símbolos e não reorganize arquivos como efeito colateral de uma tarefa não relacionada. - Não deixe código não utilizado para trás: nenhum ramo morto, nenhum auxiliar sem referência, nenhuma lógica duplicada que já exista em outro lugar. A duplicação é tratada como um defeito, não como uma opinião de estilo. - Quando uma regra genuinamente não puder ser seguida para um caso específico, diga isso explicitamente e explique a restrição, em vez de se desviar silenciosamente. ## Seguir as regras de nomes e organização de arquivos As regras de estilo são absolutas, a menos que uma exceção declarada se aplique. - Prefira declarações de interface a aliases de tipo para formatos de objeto. Prefira conversão booleana explícita à dupla negação. - Use expressões de função de seta de forma consistente, em vez de misturar formas de declaração de função. - Não escreva comentários. A única exceção é uma solução alternativa, marcada como um hack junto com o motivo de sua existência. - Não use asserções de tipo para forçar um valor a um tipo. Se uma asserção parecer inevitável, corrija o tipo na origem. - Use nomes de identificadores descritivos. Nomes de uma única letra e abreviações são rejeitados. - Use nomes de arquivos em kebab-case. - Mova números mágicos para um módulo de constantes dedicado, nomeado em maiúsculas com underscore e com um sufixo de unidade, como milissegundos ou pixels, para que a unidade fique visível no local da chamada. - Mantenha um utilitário focado por arquivo na pasta de utilitários. Um arquivo utilitário que acumula uma segunda responsabilidade não relacionada deve ser dividido. - Importe módulos nativos de runtime, como sistema de arquivos, sistema operacional e caminho, como namespaces, em vez de importações padrão ou nomeadas. Nunca crie arquivos de índice cujo único propósito seja reexportar outros módulos. Um nome de arquivo de índice não carrega significado semântico e adiciona uma camada de indireção que esconde onde um símbolo realmente reside. Importe diretamente do módulo que o define e referencie o caminho de arquivo concreto no especificador de importação. ## Dar a cada falha seu próprio tipo de erro com tag Todo modo de falha é um tipo de erro distinto e marcado, declarado através do sistema de esquema, e não uma exceção genérica. - Dê a cada erro uma marca discriminante explícita para que possa ser correspondida pelo nome em qualquer local de chamada. - Nomeie os erros como entidade mais motivo mais o sufixo Error, por exemplo, um erro de não encontrado para uma entidade específica, ou um erro de já existente para uma entidade específica. Nunca colapse vários modos de falha em um único erro genérico de não encontrado. - Defina a mensagem legível por humanos como um campo de classe derivado dos próprios dados do erro, nunca como um campo de esquema que os chamadores podem definir arbitrariamente. A mensagem é uma renderização dos dados, portanto não pode divergir deles. - Prefira falhar com uma instância de erro diretamente, em vez de envolver um objeto de exceção simples em um construtor de falha genérico. - Reserve o canal de erro tipado para condições recuperáveis e esperadas às quais um chamador poderia razoavelmente responder, como um recurso que o usuário não instalou. Tudo o mais pertence ao canal de defeito descrito mais adiante. O ganho é que o tratamento de erros se torna exaustivo e verificável: um leitor pode ver na assinatura exatamente quais falhas são possíveis, e adicionar um novo modo de falha obriga a revisão de todo tratador. ## Capturar só as tags de erro que você consegue tratar Capture por marca específica. Nunca capture de forma ampla. - Use operadores de captura específicos por marca para erros de domínio nomeados. Não use um operador de captura geral, e não use uma captura genérica não tipada. - Nunca mapeie todos os erros para um único novo tipo de erro. O mapeamento genérico de erros destrói o domínio de erro: uma falha de decodificação de esquema ou uma falha de plataforma seria rotulada, digamos, como um erro de navegador ausente, produzindo uma mensagem simplesmente falsa. Capture apenas a marca pretendida e construa o erro de substituição ali, deixando todas as demais falhas intocadas. - Quando um tipo de erro carrega uma união de motivos aninhada, capture o submotivo específico em vez de toda a família. Recuperar-se de um motivo de não encontrado não deve também engolir falhas de permissão ou falhas de disco escondidas no mesmo tipo. - O formato padrão de serviço é em camadas: primeiro capture as marcas de domínio específicas que você pode traduzir em erros de domínio significativos, depois converta falhas de infraestrutura, como erros de banco de dados ou de decodificação de esquema, em defeitos, e então anexe um span de rastreamento a toda a operação. - Um bloco de captura que não consiga afirmar, em uma frase, qual recuperação realiza e por que essa recuperação é correta não deveria existir. ## Não engolir erros e deixar bugs reais derrubarem o programa Padrões banidos, sem exceção: substituir qualquer erro por um valor padrão, converter um erro em um opcional vazio apenas para seguir em frente, e ignorar um resultado de erro completamente. Cada um desses transforma uma falha diagnosticável em uma resposta errada silenciosa. A recuperação permitida é sempre específica: capture um erro nomeado que você esperava e retorne um fallback documentado exatamente para esse caso. Erros irrecuperáveis devem se tornar defeitos. Falhas que sinalizam um bug no seu próprio software, como uma falha de análise de binário ou uma falha de plataforma não suportada, devem travar em vez de serem recuperadas. Recuperar-se delas esconde o bug e empurra o sintoma para longe da causa. Converta-as em defeitos deliberadamente. Modele dados ausentes como um erro, não como um padrão vazio. Somente o caminho de sucesso pertence ao canal de sucesso. Retornar um perfil vazio, um valor zero ou um placeholder quando os dados estão genuinamente ausentes remove a capacidade do chamador de distinguir "nada ali" de "nada encontrado". Retorne um erro específico de não encontrado e deixe cada consumidor decidir se recupera ou não. Nunca retorne um valor nulo. Use um tipo opcional explícito ou um valor indefinido, escolhido de forma consistente em toda a base de código. ## Montar serviços com camadas, uma camada por plataforma Os serviços são declarados por meio da primitiva de mapa de serviços do projeto, com um efeito de construção explícito e uma camada estática explícita. Não use construtores de serviço mais antigos ou improvisados, geração implícita de acessores, ou um array de dependências embutido; as dependências são conectadas compondo camadas. - Retorne a superfície pública do serviço como um objeto constante congelado a partir do seu construtor. Esse objeto é o contrato; qualquer coisa não retornada é privada. - Resolva as dependências uma vez no momento da construção do serviço, e não repetidamente dentro de cada método. - Para um serviço abstrato com várias implementações, declare a interface no parâmetro de tipo da classe e mantenha as implementações em camadas separadas. - Componha camadas deliberadamente: forneça camadas para cadeias de dependência, forneça-e-mescle para pilhas de infraestrutura como banco de dados, log e configuração, e mescle-tudo para grupos irmãos. - Nunca coloque todas as variantes de plataforma dentro de um único corpo de construção com ramificações sobre o sistema em execução. Publique uma camada por plataforma, por exemplo, uma camada Windows, uma camada macOS e uma camada Linux, e selecione a correta em tempo de execução. O mesmo se aplica a motores ou drivers intercambiáveis: uma camada para cada um. - Onde vários provedores atendem a uma única capacidade, deixe cada provedor se registrar em um serviço de registro compartilhado, para que cada provedor mantenha uma única responsabilidade. - Nunca leia variáveis de ambiente do processo diretamente. Use as primitivas de configuração tipadas para que os valores sejam validados e sua ausência seja reportada como uma falha de configuração. ## Definir entidades com esquemas e IDs com marca Defina todas as entidades de domínio com o sistema de esquema em vez de interfaces simples, para que a validação, a codificação e a tipagem venham de uma única declaração. - Marque cada identificador de entidade para que identificadores de entidades diferentes não possam ser trocados acidentalmente. Baseie os identificadores no tipo string simples em vez de um tipo de formato mais rígido, porque nem sempre os identificadores serão identificadores exclusivos universais. - Escolha o tipo de modelo pela finalidade: uma classe de modelo de persistência para entidades apoiadas em banco de dados, uma classe marcada para eventos de domínio e membros de união, uma classe simples para objetos de valor, uma classe de erro dedicada para erros, e uma estrutura marcada para variantes leves do tipo enumeração. - Restrinja o número de esquemas. Resista à proliferação de modelos quase duplicados que descrevem o mesmo conceito de ângulos ligeiramente diferentes; consolide-os em um pequeno conjunto canônico, para que o tipo de retorno de cada função seja previsível. - Quando precisar de dados derivados, adicione um getter ao modelo de domínio existente. Nunca invente um tipo de wrapper para carregar estado computado, nunca compute isso em uma função utilitária que precise ser mantida sincronizada, e nunca compute isso dentro de um componente de visualização. - Prefira a decodificação de esquema à análise manual frágil. Decodifique um payload em um esquema declarado, em vez de analisá-lo de forma solta e depois testar propriedades individuais com verificadores de tipo escritos à mão. - Use auxiliares de enumeração marcada para conjuntos de variantes fechados, para que cada variante carregue seus próprios dados. - Use os auxiliares de predicado padrão em vez de escrever verificadores de tipo personalizados para formatos comuns. ## Definir a concorrência e sempre liberar cada recurso obtido - Itere com efeitos usando o combinador explícito de for-each e declare a política de concorrência no local da chamada. Evite o combinador genérico de executar-tudo sobre um array mapeado, que esconde tanto a iteração quanto a decisão de concorrência. - Envolva toda função com efeitos com o combinador de função do framework e dê a ela um nome de span descritivo, depois anote o span com os identificadores que importam para essa chamada. - Nunca anote um tipo de retorno de efeito explícito. Deixe a inferência expressar os canais completos de sucesso, falha e requisito; uma anotação escrita à mão vai divergir e geralmente estará errada. - Funções que não realizam entrada nem saída e não podem falhar permanecem puras. Não as envolva. - Expresse repetições com a forma de objeto de opções, declarando a contagem de tentativas e o cronograma como campos nomeados, em vez de compor combinadores de cronograma embutidos. - Combine cada aquisição de recurso com sua liberação em uma única expressão de aquisição-e-liberação, e defina o limite com um escopo explícito. Prefira auxiliares de escopo embutidos, como um diretório temporário com escopo, ao registro manual de finalizadores. Prefira auxiliares embutidos de cópia e remoção recursiva a equivalentes feitos à mão. - Use um mapa de fibras com chave para trabalho concorrente indexado por um identificador de entidade, para que uma nova tarefa para a mesma chave cancele a anterior. Proteja-se contra inícios duplicados verificando primeiro a associação. - Use um canal de publicação-assinatura ilimitado para transmissão de eventos dentro do processo, e mantenha eventos efêmeros, que apenas conduzem estado transitório de interface, claramente separados de eventos persistidos que registram mudanças de estado duráveis. - Prefira primitivas de ordenação componíveis para classificação, combinando várias regras de ordenação, em vez de escrever callbacks de comparação de múltiplas condições à mão. - Evite try e catch brutos. Use os auxiliares de tentativa síncronos e baseados em promessa do framework, para que as falhas caiam no canal de erro tipado com um erro específico. ## Adicionar logs estruturados ao backend sem guardar segredos Sempre que você escrever ou modificar código de serviço de backend, adicione o registro apropriado como parte da alteração. - Registre por meio das funções de registro estruturado do framework nos níveis de informação, depuração, aviso e erro. A saída direta no console nunca é aceitável. - Registre mutações no nível de informação. Toda operação de criação, atualização, exclusão ou confirmação registra o que aconteceu, junto com os identificadores relevantes. - Registre leituras de alta frequência no nível de depuração, para que buscas e consultas de listagem possam ser silenciadas em produção sem perder o rastro das mutações. - Passe campos estruturados em vez de interpolar valores em uma frase, para que os registros permaneçam consultáveis. - Anote o span atual com identificadores contextuais em funções de serviço que já carregam um span. - Registre onde reside o significado de negócio, que é a camada de serviço. Não espalhe o registro por auxiliares de baixo nível. - Mantenha segredos totalmente fora dos registros: nenhuma senha, nenhum token de acesso, nenhum corpo completo de requisição ou resposta. Registre apenas identificadores e metadados. Os registros persistidos também são a principal superfície de depuração. Quando os registros tanto da interface quanto do backend são gravados em um único arquivo com um campo de origem, anotações estruturadas, tempos de span e identificadores de fibra, diagnostique problemas filtrando esse arquivo por origem, por anotação e por nível, e combinando esses filtros, em vez de adicionar instruções de impressão temporárias. ## Escrever componentes de UI sem memoização manual nem ternários - Quando o projeto usa um compilador que realiza memoização automática, nunca adicione hooks de memoização manual ou wrappers de memo à mão. Escreva funções e valores simples e deixe o compilador otimizá-los. - Não use operadores ternários para renderização condicional. Use conjunções guardadas simples, uma por ramo, para que cada condição seja lida de forma independente. Retornos antecipados dentro de corpos de callback continuam aceitáveis, pois se leem como fluxo de controle comum. - O componente que dispara um efeito colateral é o dono dele. Leia e defina o estado reativo diretamente nesse componente, em vez de fazer os dados subirem por callbacks para que um pai realize a mutação. Os pais devem renderizar filhos, não agir em nome deles. - Para mutações que podem falhar, dispare-as por meio do hook de estado no modo que retorna um valor de saída resolvido, depois derive o sinalizador de pendência e a causa da falha a partir desse resultado, e renderize uma exibição de erro quando uma causa estiver presente. - Ao renderizar qualquer coisa derivada de um resultado assíncrono, use o construtor de resultado com ramos explícitos de espera, sucesso e fallback. Nunca verifique o sucesso manualmente e extraia o valor com condicionais embutidas; esse padrão perde silenciosamente os estados de falha e espera. - Minimize a passagem de props. Qualquer componente pode se inscrever diretamente em estado compartilhado, portanto prefira ler o estado onde ele é necessário, em vez de encaminhar props por camadas intermediárias. ## Só declarar a tarefa pronta depois que testes e build de produção passarem Execute todas as verificações do projeto por meio dos scripts de pacote definidos na raiz do repositório, em vez de invocar as ferramentas subjacentes diretamente, para que a configuração e a orquestração do workspace sejam respeitadas. O conjunto habitual cobre instalação de dependências, desenvolvimento, build, lint, lint com correções automáticas, formatação, verificação de formatação, uma verificação combinada e testes. Escreva testes com o executor de testes do projeto e sua integração com o framework. Use os auxiliares de teste com reconhecimento de efeitos, que fornecem o escopo automaticamente, fazem asserções sobre resultados dentro do efeito, invertem um efeito para fazer asserções sobre sua falha, e fornecem camadas para testes que precisam de serviços reais. Antes de considerar qualquer tarefa concluída, execute nesta ordem: 1. Verificação de tipos em todos os pacotes. 2. Testes, configurados para parar na primeira falha, para que você resolva um erro de cada vez. 3. O build de produção. Não reporte uma tarefa como concluída até que as três etapas passem. Se uma verificação falhar, corrija a causa em vez de enfraquecer a verificação, e execute a sequência completa novamente depois. Uma alteração que passa na verificação de tipos mas nunca foi construída, ou que passa em testes que você reduziu para fazê-los passar, não está concluída.