Ruff: lint e formatação para escrever Python com mais consistência

Em um projeto Python, parte do tempo é consumida por problemas que não estão na ideia principal da aplicação. Imports que sobraram, variáveis que nunca são usadas, condições difíceis de entender e diferenças de formatação aparecem aos poucos e tornam a revisão mais cansativa.
O Ruff reúne lint e formatação em uma ferramenta rápida. Ele analisa o código sem executá-lo, aponta problemas que podem esconder bugs e aplica um padrão visual consistente. Quando habilito as regras adequadas, também consigo antecipar alguns padrões de código que representam risco de segurança.
Linter e formatter não são a mesma coisa
O linter examina o código em busca de regras que indicam erro, construção suspeita, import desnecessário ou padrão difícil de manter. Ele não garante que a aplicação esteja correta, mas encontra problemas antes que cheguem ao teste manual ou à produção.
O formatter cuida principalmente da apresentação: indentação, espaços, quebras de linha e outras decisões mecânicas. A padronização reduz discussões sobre estilo durante a revisão e faz com que o diff mostre melhor a mudança de comportamento.
Essa diferença é importante. O comando de lint pode sugerir uma correção lógica ou de qualidade, enquanto o formatador reorganiza a aparência do código. Mesmo quando a ferramenta corrige automaticamente, ainda reviso o diff antes de aceitar a alteração.
Começando com os comandos básicos
ruff check .
ruff format --check .
ruff format .O ruff check . apenas verifica os arquivos e retorna erro quando encontra uma regra que precisa de atenção. Já ruff format --check . confirma se os arquivos estão formatados, mas não os modifica. Essa forma de verificar é adequada para uma etapa de CI.
Quando decido aplicar as correções de lint que o Ruff considera seguras, uso:
ruff check --fix .Para formatar os arquivos localmente, uso ruff format .. Não trato --fix como uma autorização para aceitar qualquer mudança sem revisão. Algumas correções alteram imports ou trechos de código e precisam ser avaliadas junto com os testes.
A contribuição para a segurança
Um linter ajuda na segurança porque encontra padrões que frequentemente aparecem em código vulnerável ou difícil de revisar. No Ruff, posso habilitar as regras do conjunto S, derivado do flake8-bandit, para procurar situações como senhas escritas diretamente no código, uso inseguro de subprocessos, geradores aleatórios inadequados e outras construções suspeitas.
Uma verificação específica pode ser executada assim:
ruff check --select S .Em um projeto real, prefiro registrar as regras no pyproject.toml em vez de depender de comandos diferentes na memória de cada pessoa. O Ruff ajuda a apontar o problema, mas não decide sozinho se um uso é aceitável. Uma chamada de subprocesso pode ser necessária em um caso controlado, por exemplo, e ainda assim exigir validação de argumentos e revisão de permissões.
Também é importante não confundir lint de segurança com uma auditoria completa. O Ruff não substitui análise de dependências, revisão de segredos, testes, atualização de bibliotecas ou análise do ambiente de execução. Ele acrescenta uma barreira rápida no momento em que o código está sendo escrito.
Configuração no pyproject.toml
Uma configuração inicial pode centralizar as regras mais úteis para o projeto:
[tool.ruff]
line-length = 88
target-version = "py312"
[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "I", "B", "S"]
[tool.ruff.format]
quote-style = "double"
indent-style = "space"Os grupos E e F cobrem regras de estilo e problemas encontrados pelo Pyflakes, I organiza imports, B encontra padrões problemáticos e S acrescenta verificações relacionadas à segurança. Não habilito conjuntos apenas para aumentar a quantidade de avisos; escolho regras que consigo entender, corrigir e manter.
O target-version deve corresponder à versão mínima de Python do projeto. Se ele estiver configurado de forma incorreta, o Ruff pode aceitar sintaxe que não funciona no ambiente de produção ou deixar de sinalizar uma incompatibilidade.
Também excluo diretórios gerados quando necessário. Código gerado, ambientes virtuais e artefatos de build não deveriam produzir uma lista de avisos que esconde os problemas dos arquivos que realmente mantenho.
Formatação padronizada
O formatador é útil porque tira do desenvolvedor uma série de decisões repetitivas. Em vez de discutir aspas, espaços e quebras de linha em cada pull request, o projeto adota uma regra e a ferramenta aplica o mesmo resultado sempre que é executada.
Antes de enviar uma alteração, posso verificar a formatação sem modificar arquivos:
ruff format --check .Se a verificação falhar, executo ruff format ., reviso o diff e separo mudanças puramente mecânicas de alterações de comportamento. Essa separação evita misturar uma grande reformatação com uma correção funcional difícil de revisar.
O formatter não substitui o linter. Um arquivo pode estar perfeitamente formatado e ainda conter uma variável não definida, uma exceção ignorada ou uma chamada insegura. Por isso mantenho as duas verificações no fluxo.
No commit e na integração contínua
No desenvolvimento local, costumo executar as verificações antes do commit:
ruff check .
ruff format --check .Na integração contínua, repito os mesmos comandos. A CI deve falhar quando o código não atende às regras, em vez de alterar os arquivos silenciosamente. A correção fica explícita no commit seguinte e o histórico continua fácil de entender.
Também posso configurar o editor para mostrar os avisos enquanto digito. Isso reduz o tempo entre introduzir um problema e percebê-lo, mas não elimina a verificação no CI. O editor é uma ajuda para o fluxo individual; a automação compartilhada é o que garante o padrão do projeto.
Ruff junto com uv
O Ruff combina bem com o uv. Posso adicionar a ferramenta como dependência de desenvolvimento e executá-la pelo ambiente do próprio projeto:
uv add --dev ruff
uv run ruff check .
uv run ruff format --check .Assim, a versão usada pelo projeto fica registrada no pyproject.toml e no uv.lock, em vez de depender de uma instalação global diferente em cada máquina. Isso ajuda a reproduzir o resultado localmente e na CI.
Minha conclusão
O Ruff reduz a quantidade de tarefas mecânicas que preciso manter separadas em um projeto Python. O linter encontra problemas de estilo, possíveis bugs e, com as regras adequadas, padrões que merecem uma revisão de segurança. O formatter mantém a apresentação consistente e deixa as revisões mais concentradas no comportamento.
Ainda preciso entender os avisos, revisar correções automáticas e executar testes. O Ruff não substitui uma auditoria nem transforma código inseguro em código seguro, mas coloca verificações rápidas no caminho normal do desenvolvimento. Para mim, esse é o principal benefício: encontrar problemas cedo e padronizar o projeto sem criar uma coleção difícil de operar.