Exportei 1.284 bugs de um board que ganhou campo de "responsável": a causa raiz caiu de 41% para 16%
Tem uma pergunta no Software Engineering Stack Exchange com 716 pontos, 29 respostas e 138 mil visualizações: o chefe decidiu colocar um campo de "pessoa a culpar" em todo relatório de bug, e o autor quer argumento para convencer ele de que é má ideia. Ela está aqui.
As respostas são boas e quase todas culturais. Medo, retaliação, time que se fecha. O problema é que argumento cultural não ganha essa discussão, porque do outro lado da mesa a promessa também é nobre: responsabilidade. Então eu fui medir.
Recebi acesso de leitura ao board de um sistema em produção que fez exatamente isso. Em algum momento entrou um campo obrigatório chamado "responsável pelo defeito", com lista de usuários do projeto. Ele continua lá. Exportei 14 meses de issues fechadas, 7 meses antes e 7 depois, e contei uma coisa só: quantas issues têm, no fechamento, uma causa técnica escrita.
Como eu classifiquei
O board tem um campo livre de causa raiz desde sempre. Ele não é obrigatório. Classifiquei o conteúdo dele em quatro baldes: vazio, nome de pessoa, narrativa sem causa ("erro de digitação do dev", "falta de atenção na revisão") e causa técnica (alguma condição de sistema que dá para reproduzir).
// classify.ts
import { readFileSync } from "node:fs";
type Issue = {
key: string;
closedAt: string;
fields: Record<string, string | null>;
};
const USUARIOS = new Set(
readFileSync("usuarios.txt", "utf8")
.split("\n")
.map((l) => l.trim().toLowerCase())
.filter(Boolean)
);
const SINAL_TECNICO =
/\b(timeout|null|undefined|deadlock|migra\w+|deploy|query|índice|indice|regex|fila|cache|limite|rollback|versão|versao|encoding|fuso|concorr\w+)\b/;
function classify(raw: string | null) {
if (!raw) return "vazio";
const texto = raw.trim();
if (texto.length < 12) return "vazio";
const palavras = texto.toLowerCase().split(/[^a-zà-ú0-9]+/).filter(Boolean);
const soNome = palavras.every((p) => USUARIOS.has(p) || p.length <= 2);
if (soNome) return "nome";
if (!SINAL_TECNICO.test(texto.toLowerCase())) return "narrativa";
return "tecnico";
}
const issues: Issue[] = JSON.parse(readFileSync(process.argv[2], "utf8"));
for (const i of issues) {
console.log([i.key, i.closedAt.slice(0, 7), classify(i.fields.causa_raiz)].join("\t"));
}
A heurística é grosseira e eu sei. Revisei 120 issues à mão para calibrar e ela errou em 9, quase todas narrativas que citavam uma tabela de passagem e caíram como técnicas. Ou seja, o número de causa técnica que vem aí embaixo está otimista, não pessimista.
A saída
$ npx tsx classify.ts bugs.json | npx tsx resumo.ts
período A (sem campo de responsável)
issues fechadas 731
causa técnica 301 41,2%
narrativa sem causa 188 25,7%
vazio 242 33,1%
nome de pessoa 0 0,0%
período B (com campo obrigatório)
issues fechadas 553
causa técnica 87 15,7%
narrativa sem causa 96 17,4%
vazio 355 64,2%
nome de pessoa 15 2,7%
campo "responsável pelo defeito" preenchido: 521/553 94,2%
O campo obrigatório é preenchido em 94,2% das issues. O campo opcional de causa é preenchido em 35,8%. Era 66,9%.
Dois achados que eu não esperava. O primeiro são os 15 casos em que alguém escreveu nome de pessoa dentro do campo de causa raiz, coisa que não acontecia uma única vez nos 731 registros anteriores. O campo novo ensinou qual é a resposta certa para a pergunta "por que isso quebrou". O segundo é a queda de volume, de 731 para 553. Eu não consigo separar quanto disso é bug que deixou de existir e quanto é bug que deixou de ser aberto, e qualquer um que afirme isso com esse dado está inventando. Mas a direção combina com o resto.
O custo aparece na reincidência. Filtrei as issues do período B que foram reabertas ou que repetem a mesma assinatura de exceção de uma anterior: 23 casos. Dezenove deles não têm nenhuma causa escrita nas duas passagens. O defeito voltou, alguém corrigiu de novo do zero, e as duas investigações estão perdidas. Nos resgates de sistema que chegam até mim, causa raiz perdida sai mais caro que o bug: o bug você acha rodando o sistema, a investigação de oito meses atrás você não acha em lugar nenhum.
O que o campo substituiu
Essa é a parte que serve para a conversa com o chefe, e ela não é sobre clima do time. O campo de responsável ocupa o lugar de informação que só existe se alguém escrever na hora, e a informação que ele apaga é recuperável do git e do log sem depender de ninguém assinar embaixo. Quatro perguntas dão conta:
- em que momento o erro aparece pela primeira vez no log, com timestamp
- qual deploy, migração ou mudança de configuração entrou antes disso
- qual condição de dado ou de ambiente reproduz o caso
- o que detectou primeiro: alerta, suporte, teste ou alguém olhando
Nenhuma delas pede nome. Todas são verificáveis por terceiro. E a quarta é a que mais paga, porque ela mede detecção, que é o único eixo em que o time inteiro melhora junto.
Se o board é GitHub, isso vira formulário obrigatório sem reunião nenhuma:
# .github/ISSUE_TEMPLATE/bug.yml
name: Bug
description: Defeito em produção ou homologação
body:
- type: input
id: primeira-ocorrencia
attributes:
label: Primeira ocorrência no log (timestamp UTC)
placeholder: 2026-10-14T03:12:48Z
validations:
required: true
- type: input
id: mudanca-anterior
attributes:
label: Último deploy ou migração antes disso (sha, tag ou "nenhum")
placeholder: 9f3c1ab
validations:
required: true
- type: textarea
id: condicao
attributes:
label: Condição de dado ou ambiente que reproduz
placeholder: pedido com frete nulo e cupom expirado no mesmo carrinho
validations:
required: true
- type: dropdown
id: deteccao
attributes:
label: Quem detectou primeiro
options:
- alerta automático
- suporte ou cliente
- teste automatizado
- alguém do time olhando
validations:
required: true
O argumento que eu levaria para a mesa não menciona moral da equipe. Ele é de operação: com o campo de responsável, 64,2% dos bugs fecham sem causa, 23 voltaram em sete meses e dezenove deles voltaram cegos. Trocar um campo de nome por quatro campos de fato não tira responsabilidade de ninguém, e ainda responde a pergunta que o chefe realmente quer fazer, que é se a mesma coisa vai quebrar de novo.
O que eu ainda não sei medir é o caminho de volta. Alguém aqui já conseguiu tirar um campo desse tipo depois que ele entrou? Quero saber o que funcionou: número, incidente grande ou troca de gestor.