Buscar/Fazer a IA escrever código de sistemas com cuidado, até os tipos e o tratamento de erros

Fazer a IA escrever código de sistemas com cuidado, até os tipos e o tratamento de erros

Para usar IA em código de sistemas exigente. Ela revisa o código pelo menos o triplo do tempo que levou para escrevê-lo, cobre todos os erros, leva as regras aos tipos, mantém formatos antigos e novos compatíveis e faz commits pequenos que compilam.

Para usar estas regras de IA
## Revisar o código escrito pela IA pelo menos o triplo do tempo que levou para escrevê-lo Trate o assistente como um multiplicador de qualidade, não como um multiplicador de velocidade. Gaste o tempo que ele economiza em um rigor além do que o trabalho sem auxílio alcançaria. - Busque a excelência: cubra mais casos de borda, refatore em favor da compreensibilidade, resolva os TODOs pendentes, faça as coisas tediosas e mire em zero bugs. - Revise por muito mais tempo do que você gera. Gaste pelo menos três vezes mais tempo revisando o código gerado do que levou para produzi-lo, ponderando cada linha e cada decisão de design, e procurando ativamente maneiras de quebrá-lo. - O autor é dono do resultado. Não entregue um primeiro rascunho como trabalho finalizado, a menos que você esteja explicitamente apenas pedindo opinião sobre uma direção. - Trabalho que mostra sinais de ter sido produzido sem cuidado, como casos ausentes que uma revisão humana atenta teria pego, pode ser recusado de imediato em vez de revisado. - Declare o padrão no início de qualquer conversa que envolva mudanças de código, e novamente antes de abrir um pull request, para que autor e revisor compartilhem o mesmo nível de exigência. ## Levar a correção para os tipos e cobrir todos os casos-limite Prefira a implementação correta à conveniente, e empurre o máximo de correção possível para dentro do sistema de tipos, de modo que os erros falhem na compilação em vez de falharem em produção. - Modele todo o espaço de erros. Sem atalhos e sem tratamento de erros simplificado. - Trate todos os casos de borda, incluindo condições de corrida, temporização de sinais e diferenças de comportamento entre sistemas operacionais. - Codifique restrições de correção em tipos: tipos invólucro para valores de domínio, padrões builder para construções complexas, estados de tipo em genéricos quando as transições de estado importam, e empréstimo em vez de clonagem. - Prefira garantias em tempo de compilação a verificações em tempo de execução sempre que a escolha existir. - Mantenha a visibilidade a mais estreita possível. Amplie um item apenas quando algo fora de seu módulo genuinamente precisar dele. - Marque os tipos públicos de pacotes estáveis e consumidos externamente como extensíveis, para compatibilidade futura. Pacotes internos que não fazem promessa de estabilidade não precisam disso, embora tipos de erro ainda possam querer. - Use passagem de mensagens ou o modelo de atores em vez de estado mutável compartilhado, para que corridas de dados não possam surgir. - Acertar os detalhes importa mais do que terminar rápido. ## Dar mensagens de erro úteis e o mesmo comportamento em cada sistema operacional O comportamento voltado ao usuário é uma entrada de design, não um retoque final. - Produza diagnósticos estruturados e úteis em vez de simples strings de erro, e use uma biblioteca de diagnósticos rica sempre que a mensagem chegar a uma pessoa. - Faça o relato de progresso responsivo e informativo. - Mantenha o comportamento consistente entre plataformas mesmo onde as capacidades subjacentes diferem, e implemente cada plataforma com seus próprios mecanismos nativos em vez de emular uma plataforma sobre outra. - Escreva as mensagens voltadas ao usuário em presente claro: "a ferramenta agora oferece suporte", não "a ferramenta agora ofereceu suporte". - Centralize a pluralização em um módulo auxiliar compartilhado cujas funções retornam a forma singular ou plural para uma contagem. Adicione novos auxiliares ali em vez de embutir a lógica de pluralização nos pontos de chamada. ## Evoluir o design passo a passo e registrar o porquê das escolhas Mantenha o design específico e componível em vez de abstrato e excessivamente genérico. - Evolua o design de forma incremental em vez de tentar uma arquitetura perfeita logo de início. - Registre as decisões de design e seus compromissos em documentos de design mantidos junto ao projeto, para que leitores posteriores possam ver por que um formato foi escolhido. - Quando houver incerteza, explore e itere em vez de congelar um palpite dentro de uma abstração. ## Separar erros do usuário de falhas internas e anexar contexto detalhado Os erros fazem parte da superfície que o usuário vê e merecem a mesma atenção de design que o caminho bem-sucedido. - Use tipos de erro baseados em derivação em vez de erros tipados como strings, e agrupe erros relacionados sob uma enumeração de espécie assim que o conjunto crescer. - Carregue contexto rico e estruturado nos erros em vez de achatar o detalhe em uma única mensagem. - Use um modelo de dois níveis. Erros causados pelo usuário e erros externos formam um tipo distinto que mapeia para códigos de saída semânticos; erros internos de programação são separados e podem gerar pânico. - Escreva o texto de exibição do erro como um fragmento de frase em minúsculas que se leia corretamente quando embutido em uma frase envolvente como "falha ao ...". ## Avisar sobre chaves desconhecidas na configuração e dar erros de configuração claros As decisões de desserialização determinam a qualidade de todo erro de configuração que um usuário verá. - Rastreie e reporte caminhos ignorados ou desconhecidos na configuração, para que um erro de digitação apareça como aviso em vez de silenciosamente não fazer nada. - Nunca achate estruturas aninhadas durante a desserialização. O buffering interno que isso exige degrada os avisos de campo desconhecido; copie os campos para a struct explicitamente em vez disso. - Nunca dependa da desserialização de variantes sem etiqueta, que produz mensagens de erro ruins. Escreva um visitante personalizado com uma string de expectativa precisa em vez disso. ## Antes de mudar um formato salvo, verificar se versões antigas e novas leem os dados uma da outra Qualquer mudança em uma estrutura escrita em disco ou enviada pela rede deve ser rastreada por toda a matriz de compatibilidade antes de entrar. - Leitor antigo com dados novos: ele ainda consegue desserializar, e perde informação? - Leitor novo com dados antigos: os valores padrão declarados produzem valores corretos, e não apenas válidos? - Escritor antigo com dados novos: ele consegue completar o ciclo de leitura e escrita sem descartar campos? Este é o caso mais frequentemente esquecido. - Incremente a versão do formato de forma proativa. Se um novo campo for semanticamente importante, eleve a versão quando o campo for adicionado, e não quando um valor diferente do padrão for escrito pela primeira vez; caso contrário, builds mais antigos corrompem os dados silenciosamente ao reescrevê-los. - Trate os valores padrão como necessários, mas não suficientes. Eles permitem que um leitor antigo analise dados novos, mas um escritor antigo ainda descarta os campos que desconhece. ## Usar async só onde for preciso e não disparar tarefas sem limite A assincronia é uma ferramenta para entrada e saída e para concorrência, não um estilo padrão. - Use um runtime assíncrono multithread, mas confine o código assíncrono àquilo que realmente precisa dele e mantenha todo o resto síncrono. - Use concorrência estruturada para que o trabalho concorrente não precise receber um tempo de vida artificialmente longo. - Agende o trabalho enfileirado com consciência de contrapressão em vez de criar tarefas sem limite. - Forneça primitivas de temporização pausáveis onde o controle de tarefas possa suspender e retomar o processo, para que o tempo suspenso não seja contabilizado contra os tempos limite. ## Não pôr lógica em módulos agregadores e escrever as importações no topo Os limites de módulo são impostos por estrutura e visibilidade, não apenas por convenção. - Use arquivos de módulo agregadores somente para reexportar itens públicos. Lógica não trivial pertence a um submódulo de implementação ou a um mais específico. - Mantenha o código específico de plataforma em arquivos separados por plataforma, atrás de compilação condicional, com uma interface compartilhada entre eles. - Mantenha os auxiliares de teste em módulos ou arquivos dedicados. - Importe tipos e funções bem no topo do módulo. Nunca importe dentro do corpo de uma função; a única exceção é uma função compilada condicionalmente que precisa de uma importação condicionada. - Prefira importar o próprio tipo, e importe um módulo apenas onde essa for a grafia convencional. Evite caminhos totalmente qualificados nas assinaturas: importe o módulo pai e qualifique apenas com ele. - Importar variantes de enumeração para correspondência de padrões é aceitável. ## Emprestar ou compartilhar dados em vez de copiá-los Escolha a representação que evita trabalho, em vez de otimizar depois do fato. - Compartilhe dados imutáveis por contagem de referências ou empréstimo, em vez de copiá-los. - Use um tipo otimizado para strings pequenas onde o código lida com muitas strings curtas. - Preste atenção à clonagem e à referenciação. Evite clonar onde os dados têm uma estrutura natural de árvore ou de posse que o empréstimo já expressa. - Faça os dados fluírem por iteradores sempre que possível, em vez de armazená-los inteiramente em memória. ## Formatar antes de cada commit e gerenciar as versões das dependências em um só lugar A consistência mecânica é barata de impor e cara de aplicar retroativamente. - Comece todo arquivo-fonte com a linha de copyright do projeto e o identificador de licença. - Fixe a edição da linguagem e execute o comando de formatação do projeto antes de cada commit. A formatação é imposta na integração contínua, então um commit não formatado falha lá. - Suprima um lint com a forma de expectativa em vez da forma de permissão, para que a própria supressão avise quando o lint deixar de disparar e supressões mortas não se acumulem. - Onde um lint proibir anexar texto formatado a uma string, use a macro de escrita do projeto em vez de acrescentar uma string formatada. - Gerencie todas as versões de dependências de forma centralizada na raiz do workspace, e fixe os pacotes internos em versões exatas. - Comente escolhas não óbvias de dependências e de features, especialmente onde uma feature é desativada por motivo de segurança ou de tamanho. ## Escrever comentários que expliquem o porquê, não só o quê - Comentários explicam o porquê, não meramente o quê. - Não narre dentro do corpo das funções. Adicione um comentário apenas onde algo é não óbvio ou especial, ou onde a intenção precisa de uma explicação mais profunda. - A documentação em nível de módulo declara o propósito e as responsabilidades do módulo. - Sempre termine os comentários de código com um ponto final. - Nunca use caixa de título em cabeçalhos e títulos; use sempre caixa de frase. - Use sempre a vírgula de Oxford. - Não omita artigos. Escreva "o arquivo tem uma versão mais recente", não "arquivo tem versão mais recente". ## Rodar os testes com o runner do projeto e reaproveitar os fixtures existentes Teste do jeito que o projeto executa testes, e reutilize o modelo de testes que já existe. - Use o executor de testes designado pelo projeto para testes unitários e de integração. Não recorra ao executor padrão da linguagem quando a suíte depender do modelo de execução do executor designado. - Use uma cópia da ferramenta construída localmente apenas ao verificar mudanças no comportamento da própria ferramenta, como uma nova flag ou um novo formato de saída. Execute a suíte em si com o executor padrão, já que os testes de integração criam seus próprios processos internos a partir dos artefatos de build. - Execute os testes de documentação com o comando que os suporta quando o executor principal não os suportar. - Mantenha os testes unitários ao lado do código que testam, os testes de integração em um pacote dedicado, as fixtures compartilhadas em um pacote de fixtures e os utilitários de teste em um pacote de suporte. - Prefira o modelo de resultados esperados do pacote de fixtures a verificações pontuais escritas à mão. - Recorra ao ferramental que já existe: casos parametrizados, testes baseados em propriedades, asserções de snapshot, harnesses personalizados e saída de asserção melhorada. Verifique o que está disponível antes de adicionar um novo mecanismo. ## Começar o assunto do commit pelo escopo e manter cada commit compilável O histórico é um artefato revisável, então trate cada commit como uma unidade de revisão. - Prefixe o assunto de cada commit com o escopo que ele toca, entre colchetes, seguido de uma breve descrição. Use um escopo meta para trabalhos transversais, como mínimos da cadeia de ferramentas, releases e mudanças de integração contínua. - Commits de incremento de versão e de preparação de release seguem seus próprios assuntos fixos e normalmente são produzidos pela ferramenta de release. - Escreva os corpos em pretérito simples e presente: o que costumava acontecer, o que acontece agora e quais testes foram adicionados. - Mensagens de commit são Markdown. Não use crases na linha de assunto, mas use-as no corpo. - Faça commits atômicos. Cada commit é uma unidade lógica de mudança. - Mantenha o histórico bissecável. Todo commit deve compilar e passar em todas as verificações. - Separe as preocupações. Correções de formatação e refatorações pertencem a commits próprios, à parte das mudanças de comportamento. ## Dividir um runner concorrente em despachante e executor que se falam por mensagens Um executor concorrente é mais fácil de raciocinar quando um componente é dono do mundo externo e outro é dono da execução. - Um despachante interage com o mundo externo, lineariza os eventos de todas as fontes, multiplexa essas fontes com uma construção select e mantém o estado autoritativo do que está em execução no momento. Pontos de sincronização mantêm o relato livre de corridas. - Um executor agenda e roda unidades de trabalho como máquinas de estado assíncronas, tratando retentativas, tempos limite, criação de processos e captura de saída. Cada unidade é a fonte da verdade do próprio estado e tem um canal dedicado ao despachante em vez de uma difusão. - Não compartilhe nenhum estado diretamente; tudo cruza entre os componentes como mensagens. - Modele todo o espaço de falhas, e use temporizadores pausáveis para que os sinais de suspensão e retomada não distorçam a temporização. - Use a contenção de processos e as facilidades de sinal nativas de cada plataforma, proteja-as com compilação condicional e módulos por plataforma atrás de uma interface compartilhada, e documente as diferenças e os compromissos entre plataformas em comentários.