"É uma falha pré-existente" é como você lança a sua própria regressão
O CI vermelho que você não causou esconde o CI vermelho que você causou, a menos que você faça o diff do conjunto exato de falhas antes e depois.
Apollo Space Research
Apollo Space
A suíte de testes estava vermelha antes de você tocá-la. Quarenta e uma falhas, todas de teardown de rede instável, todas não relacionadas à sua mudança. Então você abre o pull request, roda a suíte, vê quarenta e três vermelhas, e seu olho faz a coisa que o olho de todo engenheiro cansado faz: ele arredonda quarenta e três para “ainda a mesma bagunça de hoje de manhã”. Você mergeia. Duas dessas quarenta e três são suas.
Essa é a forma mais comum de um engenheiro cuidadoso lançar uma regressão. Não ignorando os testes. Olhando direto para eles e vendo ruído onde havia um sinal novo.
O CI vermelho que você não causou esconde o CI vermelho que você causou, a menos que você faça o diff do conjunto exato de falhas antes e depois.
Este post é sobre esse diff: por que um veredito de verde-ou-vermelho é a pergunta errada, por que uma contagem é mal melhor, e o que construímos para que um agent nunca possa deixar passar uma falha nova chamando-a de pré-existente.
Por que uma suíte vermelha fica cega
Uma suíte que está vermelha há um tempo para de ser uma suíte de testes e vira papel de parede.
O mecanismo é puramente humano e acontece com todo time que deixa as falhas se acumularem. A primeira vez que o CI fica vermelho numa mudança que você não fez, você investiga. A quinta vez, você passa o olho. Na vigésima, o X vermelho no topo da página não carrega informação nenhuma, está vermelho há semanas, todo mundo sabe que são “as instáveis”, e o cérebro quietamente reclassificou o sinal como decoração. A luz que deveria significar pare ficou acesa por tanto tempo que significa nada.
Agora solte uma regressão de verdade nesse ambiente. Sua mudança quebra duas asserções. A suíte vai de quarenta e uma vermelhas para quarenta e três vermelhas. Não há alarme, porque a página parecia exatamente igualmente alarmante ontem. As duas falhas que você introduziu estão camufladas pelas quarenta e uma que você não introduziu, mesma cor, mesma parede de vermelho, mesmo dar de ombros.
Uma falha escondida dentro de uma falha conhecida é o bug mais barato de introduzir e o mais caro de achar.
É caro de achar depois precisamente porque ninguém o acha agora. Ele pega carona no merge, fica na branch principal por uma semana atrás da vermelhidão permanente, e aparece quando algum esforço não relacionado finalmente limpa as instáveis e descobre duas falhas que não pertencem a ninguém. Agora é um exercício forense: bisect, blame, reconstruir. O bug estava visível o tempo todo. Ele só estava de pé numa multidão.
O veredito ingênuo: verde ou vermelho
O primeiro instinto é o portão que todo mundo constrói primeiro. Bloqueie o merge a menos que o CI esteja verde.
É o instinto certo e funciona lindamente, até o momento em que a suíte nunca está verde. No instante em que você tem um único teste instável, ou uma falha lenta de consertar que “não vale a pena bloquear”, o portão verde tem dois ajustes e você saiu do prédio. Ou você o impõe e nada mergeia, porque algo está sempre vermelho em algum lugar, ou você o desliga “só por agora”, e agora é para sempre. Um portão binário contra uma realidade não-binária é desabilitado dentro de uma semana. Todo time que rodou CI de verdade em escala conhece a sensação do check obrigatório que todo mundo aprendeu a sobrescrever.
Então o portão é relaxado para algo mais suave e pior: não piore. Vermelho está ok, só não adicione vermelho. Isso soa razoável. É a armadilha.
Porque “não adicione vermelho” é medido da forma preguiçosa, pela contagem. A suíte tinha quarenta e uma falhas, agora tem quarenta e uma ou menos, manda ver. E a contagem é uma mentirosa.
Por que a contagem mente
Aqui está o modo de falha que lança regressões por um portão baseado em contagem, e ele não é exótico. Acontece sempre que duas coisas se movem ao mesmo tempo.
Suponha que sua mudança conserta um teste instável e quebra um real. Antes: quarenta e uma vermelhas. Depois: quarenta e uma vermelhas. A contagem é idêntica, o portão está satisfeito, e você acabou de mergear uma regressão genuína enquanto deletava a evidência que a teria pego. O número não se moveu. A composição sim. Um teste que falhava por ruído ficou verde, e um teste que passava ficou vermelho no lugar dele, uma troca limpa de um-por-um que nenhum contador consegue ver.
Isto não é um caso de canto que você pode descartar. Em qualquer suíte grande o bastante para ser instável, o conjunto de testes que falham muda a cada run por conta própria, um timeout aqui cura, uma race ali inflama, então a contagem está derivando para mais ou menos alguns antes de sua mudança fazer qualquer coisa. Contra essa deriva de fundo, uma única falha nova e real é estatisticamente invisível. A contagem te dá um número que parece rigor e não contém nenhum, porque responde “quantas?” quando a única pergunta que pega uma regressão é “quais?”
A correção não é um número melhor. É uma unidade diferente. Pare de contar falhas e comece a nomeá-las.
O diff é a ideia inteira
O CI vermelho que você não causou esconde o CI vermelho que você causou, a menos que você faça o diff do conjunto exato de falhas antes e depois. A defesa é exatamente tão literal quanto a frase: capture o conjunto das identidades dos testes que falham na branch base, capture o conjunto na sua branch, e olhe a diferença entre eles.
Não a diferença na contagem. A diferença na composição. Dois conjuntos, nomeados, comparados.
Dessa comparação caem três baldes, e o veredito inteiro mora em qual teste cai onde. Há as falhas presentes tanto na base quanto na branch, pré-existentes, genuinamente não suas, as que você tem permissão de herdar. Há falhas presentes na base mas ausentes na sua branch, testes que você consertou, o que é bom trazer à tona mas nunca é desculpa. E há o único balde que decide tudo: falhas presentes na sua branch que não estavam falhando na base. Essas são novas. Essas são suas. Existe exatamente um desses baldes que pode bloquear um merge, e “a suíte já estava vermelha” não pode esvaziá-lo, porque a comparação foi feita contra precisamente o vermelho que já estava ali.
É por isso que “é uma falha pré-existente” para de ser uma desculpa usável. A frase só funcionava quando a evidência era uma parede de vermelho indiferenciado. Uma vez que cada falha carrega uma identidade e o conjunto da branch base está registrado, “pré-existente” não é mais uma vibe que você afirma, é um teste de pertencimento que você ou passa ou falha. Uma falha é pré-existente se e somente se estava falhando na base. Se não estava, é uma regressão usando a fantasia de uma.
“Pré-existente” é uma afirmação sobre um conjunto, não um sentimento sobre uma cor. Ou o teste estava vermelho na base ou não estava.
O snapshot da branch base é a parte que as pessoas pulam, e pulá-la é o que reabre o buraco inteiro. Se você só olha as falhas da sua própria branch, todo teste vermelho parece igualmente culpado e igualmente inocente, e você está de volta argumentando pela cor. O snapshot é o que transforma o argumento em aritmética. Capture o conjunto nomeado de falhas na base primeiro, essa gravação é o mecanismo inteiro, e o diff tem algo verdadeiro contra o que comparar.
Por que um agent precisa disto mais do que um humano
Um revisor humano, num dia bom, consegue segurar a suspeita de que a contagem está mentindo. Um agent revisando sua própria mudança não pode ser confiado para segurá-la, e é exatamente por isso que a disciplina tem que morar na estrutura em vez de no revisor.
A razão é a que corre por baixo de toda a confiabilidade de agents: a mente que escreveu a mudança é a pior posicionada para julgar se ela quebrou algo. Um agent que acabou de produzir uma mudança que parece passar está motivado, da forma estatística suave em que esses sistemas são motivados, a ler a evidência caridosamente. Mostre a ele quarenta e três testes vermelhos e pergunte “você introduziu uma regressão?” e a história disponível, a suíte já era uma bagunça, essas são as instáveis, minha mudança está bem, é fluente, plausível, e exatamente a história que um humano cansado também conta. “É uma falha pré-existente” é a única frase mais natural para um revisor auto-avaliador gerar, porque resolve a tensão e deixa o trabalho ser dado por feito.
Então não pedimos ao agent que julgue. Entregamos a ele o diff e deixamos o diff julgar.
O agent grava o conjunto de falhas na branch base antes de fazer qualquer coisa, roda sua mudança, grava o novo conjunto de falhas, e computa a diferença de composição. A decisão de merge lê de um único balde: testes vermelhos na branch que estavam verdes na base. Se esse balde está vazio, uma suíte vermelha não é obstáculo, toda falha permanente está contabilizada contra o snapshot da base, e a mudança herda a bagunça honestamente. Se esse balde tem sequer um teste, a mudança é bloqueada, pelo nome, com a identidade do teste impressa, e nenhuma frase sobre falhas pré-existentes pode mudar a matemática. A desculpa que lança a regressão não é refutada por um revisor mais esperto. É tornada impronunciável pela unidade de medida.
Esse é o movimento que fazemos onde quer que possamos: quando uma afirmação é o ponto fraco, substitua a afirmação por um artefato. “Funciona” vira um trace. “Está pronto” vira um flow que rodou. E “já estava quebrado” vira um diff de dois conjuntos nomeados que ou contém o seu teste ou não.
O que custa fazer certo
O preço honesto é que você tem que rodar a suíte duas vezes, uma na base, uma na sua branch, e tem que manter as falhas como identidades, não como uma soma. Isso é mais burocracia que uma luz verde e mais armazenamento que um número.
Também é a única versão que sobrevive ao contato com uma suíte instável, que é o único tipo de suíte que uma base de código real tem. Um portão binário morre no dia em que o primeiro teste fica instável. Um portão de contagem lança a primeira troca de um-por-um. O diff de conjuntos continua funcionando não importa quão vermelho o papel de parede fique, porque ele nunca pede que a suíte esteja limpa, ele só pede que a suíte esteja no mesmo tom de vermelho que estava na base, menos nada que você adicionou. Quanto mais sujo o baseline, mais valioso o diff, porque quanto mais sujo o baseline, melhor uma única falha nova consegue se esconder.
Há um dividendo mais quieto, também. Uma vez que “pré-existente” é um conjunto gravado em vez de uma desculpa falada, as falhas pré-existentes param de ser estruturais. Você consegue ver, run a run, a vermelhidão permanente como uma lista nomeada que não está crescendo, e um conjunto conhecido, limitado e nomeado de instáveis é um backlog que você de fato consegue queimar, em vez de uma névoa que todo mundo concordou em ignorar.
A virada
Nós vimos o engenheiro mais esperto de uma sala lançar uma regressão por uma suíte vermelha, e não teve nada a ver com o quão bom ele era. Teve a ver com como a evidência estava moldada. Uma parede de vermelho te pede para julgar, e julgar enquanto cansado, tarde e confiando que “essas são as instáveis” é uma coisa que nenhuma quantidade de talento sobrevive de forma confiável. O bug não passou porque alguém foi descuidado. Passou porque o veredito era uma cor e uma contagem, e ambas podem ser verdadeiras e mentirosas ao mesmo tempo.
O que de fato protege uma base de código não é um revisor mais vigilante, humano ou agent. É um formato de evidência que não depende de vigilância. Um diff de dois conjuntos nomeados não fica cansado às 23h, não estende o benefício da dúvida a ninguém, e não acha “já estava quebrado” mais reconfortante que a verdade. Ele só responde a única pergunta que importa, quais testes estão vermelhos agora que estavam verdes antes?, e deixa a resposta valer quer alguém estivesse prestando atenção ou não. A disciplina é pequena. A coisa que ela remove é o erro mais humano e mais caro que existe: ver um problema novo e lê-lo como um velho.
O CI vermelho que você não causou esconde o CI vermelho que você causou, a menos que você faça o diff do conjunto exato de falhas antes e depois. Construa o diff uma vez, e a desculpa que lança a sua própria regressão para de estar disponível para você, na noite em que você mais gostaria de acreditar nela.
É isso que estamos construindo na Apollo Space: um sistema operacional onde os vereditos são artefatos, não sentimentos, para que o trabalho seja honesto mesmo quando as pessoas estão exaustas. Se você já mergeou uma parede de vermelho e descobriu uma semana depois que duas daquelas falhas tinham o seu nome, você já sabe por que “já estava quebrado” merece uma máquina que verifica.
A Apollo cuida da operação repetitiva da sua empresa pro seu time não precisar.
Entre na lista de espera: acesso antecipado, preço de usuário fundador e um lugar na primeira fila enquanto a gente constrói.
Entrar na lista de esperaO imposto oculto dos agents em paralelo é um diamante de migrations
Seis agents escrevendo para um schema conflitam no banco de dados, não no código, e a CI morre em "multiple heads".
EngenhariaUm orchestrator que não sobrevive ao próprio crash não é um
Um crash que apaga o raciocínio do orchestrator perde a única coisa que você não consegue reconstruir.
EngenhariaColoque um portão determinístico na frente do seu revisor mais esperto
A pega-defeito mais barata é um script burro que checa se duas branches mergeadas ainda sobem antes de qualquer julgamento.