1

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.

Carregando publicação patrocinada...