← Voltar ao Blog

autoFixable: transformei 30 min de bate-e-volta em revisão de código em 47 segundos

O comentário chegou às 15h47 de uma terça-feira. “nitpick: import não usado no arquivo user-service.ts”. O autor do PR estava em reunião. Voltou às 16h12, viu o comentário, tirou o import, subiu novo commit. O CI rodou de novo por 8 minutos. O revisor (eu) só notou às 16h34, aprovou, mergeu.

Da abertura do comentário ao merge: 47 minutos. O trabalho de fato: apagar uma linha.

Eu já sabia que isso era ridículo faz uns dois anos. Todo mundo sabe. Mas a gente sempre trata como um problema de disciplina — “revisor, seja mais rápido”, “autor, verifique antes de abrir PR” — quando na verdade é um problema de classificação. Existem coisas em código review que não deveriam ter comentário nenhum. Elas deveriam ter um commit.

O padrão que peguei do Capítulo 12 do meu book de code review chama isso de autoFixable. Depois de aplicar por dois meses no time, o mesmo tipo de correção que levava 30-47 minutos agora leva 47 segundos. Só que aí apareceu um problema novo que eu não tinha previsto. Vou contar essa parte também.

O que é autoFixable, na prática

A ideia é simples e chata: separar as coisas que uma máquina pode arrumar sem consultar ninguém, das coisas que precisam de julgamento humano. Em vez de comentar “tira esse import”, você deixa o pipeline aplicar eslint --fix e commitar.

Classificação autoFixable vs non-autoFixable

A tabela que uso no time, resumida:

autoFixable (a máquina resolve)non-autoFixable (humano decide)
Formatação (Prettier / Biome)Desenho de arquitetura
Lint auto-corrigível (eslint --fix)Correção de N+1
Ordenação de importsNome de variável ou função
Remoção de imports não usadosBug de lógica
Cast redundante que TypeScript infereRegra de negócio

O critério é meio brutal: se existe uma resposta única e mecânica, não é um comentário, é um commit. Se existe mais de uma resposta razoável, aí sim precisa de humano.

O workflow que eu subi (e que quase quebrou)

Meu primeiro workflow foi ingênuo. Um GitHub Action que rodava a cada PR aberto, aplicava Prettier + ESLint —fix, e commitava direto no branch do PR.

# .github/workflows/auto-fix.yml
name: Auto Fix
on:
  pull_request:
    types: [opened, synchronize]

jobs:
  autofix:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.head_ref }}
          token: ${{ secrets.GITHUB_TOKEN }}

      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'

      - run: npm ci

      - name: Format
        run: npx biome check --write .

      - name: Commit if changed
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git diff --quiet || (
            git add -A &&
            git commit -m "chore: auto-fix format and lint issues"
          )

Rodou lindo por três dias. No quarto dia, um dev do time abriu um PR de refatoração grande, o Action commitou 400 linhas de reformatação em cima, e a diff dele virou uma sopa. Quando ele foi rebasear, teve conflito com o próprio commit dele. Ficou puto (com razão).

A lição foi que autoFixable precisa acontecer ANTES do PR, não depois. Movi o mesmo lint para um pre-commit hook local + uma checagem gate no CI. O CI não fixa mais, ele só bloqueia o merge se algo autoFixable ainda passar. Isso muda todo o equilíbrio: o autor conserta na hora localmente, o revisor nunca vê o problema, o histórico do PR fica limpo.

Se você já leu documentação sobre Claude Code hooks, esse é exatamente o padrão que a Anthropic recomenda para PostToolUse — deixar biome check --write rodar depois de cada Edit do agente. Junto com um pre-commit hook local, dá para nunca gerar um commit sujo pra começar.

O que mudou nos números do time

Peguei 40 PRs antes de subir o padrão e 40 depois. Contei só os comentários puramente autoFixable (formatação, import não usado, ordenação, e afins). Ignorei bugs, decisões de nome, etc.

MétricaAntesDepois
Comentários autoFixable por PR (mediana)40
Tempo de fluxo autoFixable (mediana)32 min47 seg
Rodadas de CI por PR (mediana)32
PRs bloqueados > 24h por nitpick6 / 400 / 40

O número que me impressionou não foi o tempo. Foi o 6 PRs bloqueados por mais de 24 horas por causa de nitpick. Um sexto do backlog do time estava esperando um humano tirar um import { useState } que ninguém usava. Multiplique isso por 12 devs e você tem sua estimativa de trimestre queimada em bikeshedding.

Fluxo autoFixable vs fluxo tradicional de revisão

O problema novo que apareceu

Aqui vem a parte que ninguém conta.

Quando você tira todo o nitpick da revisão, sobra só o que exige julgamento. E aí acontece uma coisa engraçada: os revisores começam a se sentir inseguros. Um revisor experiente do meu time, que revisava 8 PRs por dia sem suar, veio me falar depois de duas semanas: “eu acho que estou revisando pior. Antes eu sempre tinha algo pra apontar. Agora eu leio o PR, não vejo nada, e me sinto mal aprovando”.

O que ele estava sentindo era o fim do teatro de revisão. Metade dos comentários de “nitpick” que a gente escreve não é sobre o código. É sobre provar que a gente leu. Quando o autoFixable tira 4 dos 5 comentários possíveis, sobra 1 comentário real por PR, ou zero. E aí “aprovar sem comentar” começa a parecer displicência, mesmo quando é a resposta correta.

Resolvi com dois combinados no time:

  1. Ficou combinado escrever “revisei, sem observações” em vez de só clicar em Approve. Textualmente. Faz diferença cultural.
  2. Nas 1
    da retrospectiva, o “comentário-métrica” foi aposentado
    . Ninguém mais é medido por quantidade de comentários por PR. Só por defeitos que apareceram em produção depois de aprovado.

O segundo item foi mais difícil que o primeiro. Empresas gostam de contar coisas. Contar comentários de revisão é fácil. Contar julgamento é difícil.

CodeRabbit e o batch apply do GitHub

Se você usa CodeRabbit, tem um plus interessante. O CodeRabbit gera as sugestões como blocos aplicáveis nativos do GitHub, então o revisor (ou o autor) clica em “Apply suggestion” e vira commit sem tocar em nada.

Em março de 2026 o GitHub liberou o batch apply de Code Quality suggestions, e em abril fez o mesmo para alertas de code scanning. Isso mudou a economia do padrão: em vez de aplicar 15 sugestões uma por uma, você seleciona todas e vira um único commit. É o mesmo padrão autoFixable, só que com o humano puxando o gatilho em vez do CI.

Eu ainda prefiro o pre-commit hook, porque prevenir é mais barato que remediar. Mas para times que já operam com CodeRabbit no fluxo, batch apply é uma boa evolução incremental — você não precisa mexer no pipeline de CI, só ativa e pronto.

A linha que eu não cruzo

Vale falar o contrário também. Não é que “tudo que dá pra automatizar, deve ser automatizado”. Duas coisas eu deixei explicitamente fora do autoFixable, mesmo que ferramentas modernas consigam fazer:

Rename automático. Ferramentas como TypeScript LSP fazem rename com segurança em cima do grafo de símbolos. Mas rename é uma decisão. Se um campo se chama user_id e o linter sugere userId, isso pode estar certo tecnicamente e errado semanticamente — se aquele campo veio de uma API externa que devolve snake_case, o rename cria um bug sutil. Deixei rename como sugestão manual, nunca no CI.

Remover código morto. Analisadores estáticos (dead-code-elimination do Rollup, ts-prune, etc.) apontam código não referenciado. Mas “não referenciado no grafo de imports” não é o mesmo que “não usado”. Um export pode ser consumido por um pacote externo, por reflexão, ou por um build tool específico. Deixei código morto na categoria non-autoFixable também.

O critério na dúvida: se o pior caso do fix errado é um bug em produção, não é autoFixable. Fim.

Resumo do que sobrou

  • Formatação, lint, ordenação de imports, remoção de imports não usados: pre-commit hook local + gate no CI. Zero comentário de revisão sobre isso.
  • Decisões de nome, arquitetura, N+1, regra de negócio: revisor humano, como antes.
  • Rename e remoção de código morto: manual, sob julgamento.

O impacto no time foi menos “economizamos tempo” e mais “paramos de discutir coisas que ninguém queria discutir”. O número que mais importa não é o 47 segundos. É o fato de que ninguém no time mais reclama de code review sendo lento por causa de bobagem. Sobrou só a discussão que valia a pena.

E, honestamente, o problema secundário — revisor se sentindo inseguro quando não tem o que comentar — vale meses de debate cultural. Se você adotar o padrão, prepare a retro seguinte pra falar sobre isso. Não tira nem coloca linha de código, mas ajuda o time a não recuar por reflexo.


ken imoto · WebRTC & Voice AI engineer · kenimoto.dev · TabNews