Toda migração grande produz uma pilha de "não faça X".
Não misture os dois padrões no mesmo arquivo. Não crie mais arquivo no formato antigo. Não coloque componente
novo dentro de tela velha. Não use valor de cor direto, use o token.
Escrevemos essas regras num documento, com o motivo de cada uma, numeradas e datadas. O documento ficou bom.
E eu sei exatamente o que acontece com ele: alguém lê na primeira semana, ninguém lê na terceira, e em dois
meses uma pessoa nova entra no time e viola três regras no primeiro dia. Sem má vontade, sem descuido, só
por não saber que existiam.
Documento não escala. Ferramenta escala.
Isto é sobre o que aconteceu quando paramos de escrever regras e começamos a fazer as ferramentas recusarem.
A regra que virou verificação de build
A regra mais importante da migração era: os dois design systems não se misturam no mesmo arquivo.
O motivo é real. Os dois geram classes CSS com a mesma especificidade, e quando duas regras empatam quem
vence é decidido por ordem de injeção. Que muda entre ambiente de desenvolvimento e build de produção. O
resultado é aquele bug que funciona na sua máquina e quebra em produção, e que ninguém reproduz.
Isso ficou escrito no documento por semanas. Dependia de todo mundo lembrar.
Aí, corrigindo um problema completamente diferente, restringimos onde a ferramenta de CSS pode procurar
classes. Ela passou a olhar só o diretório do código novo, em vez do projeto inteiro.
O efeito colateral foi melhor que a correção: classe do padrão novo escrita dentro de um arquivo do padrão
antigo simplesmente não é gerada. O componente aparece sem estilo nenhum. A pessoa percebe na hora, antes
de commitar, sem ter lido regra nenhuma.
A regra saiu do documento e virou comportamento de build.
A regra que virou recusa da ferramenta
A segunda regra: migração acontece por bloco completo. Uma tela inteira por vez, nunca um componente
solto dentro de uma tela antiga.
O motivo é o mesmo de antes. Um componente novo dentro de um container antigo é exatamente a mistura que a
primeira regra proíbe.
O projeto tem um gerador de componentes: você roda um comando, responde onde quer criar, e ele monta a
estrutura de arquivos. Todo mundo usa, porque é mais rápido que criar à mão.
Colocamos a regra ali dentro. Hoje, se você tenta criar um componente dentro de uma tela que ainda não foi
migrada, o gerador para antes de criar qualquer arquivo:
A tela "x" ainda nao foi migrada para o design system novo.
Criar componente novo dentro de tela legada viola a regra de migracao
por bloco completo. Ver o documento de migracao.
Repare no que mudou. Antes, a regra dependia de a pessoa conhecer o documento. Agora, a ferramenta a
apresenta no exato momento em que ela é relevante e explica o porquê, para não parecer arbitrária.
E tem um ganho que não estava planejado: como a ferramenta precisa saber quais telas foram migradas, essa
lista virou um dado real no projeto, em vez de conhecimento na cabeça de alguém. A lista de configuração
passou a ser o registro do que já mudou.
O contra-exemplo: a regra que continuou só escrita
Uma das regras não tem nenhuma ferramenta atrás: nenhum arquivo novo no formato antigo.
O indicador da migração é a contagem desses arquivos. Ele só deveria descer.
Fui conferir depois de algumas semanas. Tinha subido: de 437 para 449.
Não era vazamento da fronteira. Eram doze arquivos nascidos em funcionalidades que já estavam na esteira de
produção, começadas antes de a fundação existir. Dez deles numa tela inteiramente nova, construída em
paralelo enquanto eu preparava a base.
Nada disso é erro de ninguém. É a consequência de migrar um sistema sem parar a entrega. De um lado
alguém prepara a fundação, do outro o produto continua saindo. Foi uma decisão, não um acidente.
Mas o número deixou clara a diferença entre as duas categorias de regra:
| Regra | Estado |
|---|---|
| Não misture os padrões | ferramenta recusa |
| Não crie componente em tela não migrada | ferramenta recusa |
| Não crie arquivo no formato antigo | só documento |
Adivinha qual foi violada.
A regra escrita que funcionou, porque tinha prazo
Não é que documento nunca sirva. Serve como base.
O menu novo tem 21 itens, e por semanas alguns não tinham destino definido. Em vez de deixar o link vazio,
criamos uma constante com nome próprio:
export const ROTA_PENDENTE = '#'
O comentário dela dizia o que fazer com ela: grep ROTA_PENDENTE src lista o que ainda falta, e essa é a
lista de trabalho da task de rotas.
Convenção pura. Nenhuma ferramenta atrás, dependendo de alguém rodar o grep. E funcionou, por um motivo que
as outras regras escritas não tinham: ela era autoliquidante. Cada item que ganhava destino saía da lista.
Quando o último saiu, a constante não tinha mais razão de existir, e apaguei ela junto com o ramo de código
que ela sustentava.
A vigilância não desapareceu. Mudou de forma:
todosOsItens.forEach((item) =>
expect(item.rota).toMatch(/^(\/app|https:\/\/)/)
)
Item novo que entre com atalho, string vazia ou qualquer marcador de pendência quebra esse teste. Mesma
proteção, agora sem depender de ninguém lembrar de nada.
A diferença entre esta regra e a que foi violada é o prazo. Regra escrita para valer enquanto a migração
durar é aposta na memória do time, e essa aposta perde. Regra escrita para valer até uma lista zerar é
combinado com data, e data se cumpre.
Quando não transformar a regra em bloqueio
Aqui está a parte que eu quase errei.
Bloquear a terceira regra é trivial. Cinco linhas num hook de commit resolvem:
git diff --cached --name-only --diff-filter=A | grep '\.styles\.ts$' && exit 1
Não fiz. E o motivo vale mais que a técnica.
A primeira regra diz que componente novo não pode viver dentro de tela antiga. Então, se alguém precisa
acrescentar um componente a uma tela legada e o formato antigo estiver bloqueado, sobram duas opções:
migrar a tela inteira antes ou ficar travado.
Migrar uma tela inteira pode ser semanas de trabalho não planejado, no meio de uma entrega com data.
Ou seja: o bloqueio técnico transferiria um custo grande para pessoas que não participaram da decisão. Isso
não é ajuste de ferramenta, é mudança de política de entrega. Política se acorda, não se impõe por um
hook que aparece um dia sem aviso.
O critério que eu extraí disso: transforme a regra em bloqueio quando cumprir a regra custa quase nada.
Quando cumprir custa caro, o bloqueio precisa vir depois do acordo, não no lugar dele.
Guardrail imposto sobre custo alto não produz conformidade. Produz gente procurando como contornar. E quem
contorna uma vez contorna sempre.
O dia em que o guardrail me barrou
Vale contar a vez em que ele me pegou, porque minha primeira reação estava errada.
O pre-commit barrou um commit meu pelo mínimo de cobertura: 66,66% de linhas contra os 70% exigidos.
Achei que era formalidade. Eu tinha mexido em três drawers de uma tela, e os testes que já existiam
renderizavam todos eles. Fui olhar o relatório inteiro: cobertura de funções em 40%.
Os testes montavam os drawers e paravam ali. Nenhum clicava em fechar. Então os três callbacks que avisam
qual drawer foi fechado nunca tinham sido executados uma única vez. Se alguém trocasse as chaves entre si,
fechar um drawer fecharia outro, e a suíte continuaria verde.
Escrevi cinco testes. A cobertura foi para 85,71%, e um deles hoje afirma exatamente aquilo: cada botão de
fechar avisa a chave certa.
O hook não me barrou por formalidade. Me barrou porque faltava teste de verdade, e a porcentagem era o único
sintoma visível de uma falha que eu não tinha percebido lendo o código.
Foi também o caso em que cumprir a regra custava trinta minutos, porque o que faltava era teste do que eu
mesmo tinha acabado de tocar. É exatamente por isso que ela deve ser bloqueio.
A lição aprendida
Documentação é para explicar o porquê. Ferramenta é para garantir o quê. Documento explica bem e garante
mal. Quando eu escrevo uma regra num documento, o que estou realmente fazendo é apostar na memória coletiva do
time. E essa aposta perde no longo prazo, sempre.
A melhor mensagem de erro é a que chega no momento certo. A mesma frase, lida num documento em janeiro,
não impede nada. Impressa pela ferramenta no segundo em que você tenta fazer a coisa errada, impede tudo.
Guardrail é gentileza, não desconfiança. Ele não existe porque as pessoas são descuidadas. Existe porque
ninguém deveria precisar carregar na cabeça o motivo de cada decisão tomada há três meses por outra pessoa.
Terceiro de três textos sobre migrar um design system em produção sem parar a entrega. Os outros são sobre um
botão que quase desapareceu ao instalar uma biblioteca de CSS, e sobre conviver com duas identidades visuais
ao mesmo tempo.
Top comments (0)