Trilha de aprendizado · Nível 8 · Tutorial 9

Analisar estilo e possíveis defeitos com Ruff

Interpretar diagnósticos de análise estática, corrigir problemas simples e aplicar correções automáticas de maneira criteriosa.

  • Nível: Intermediário
  • Duração: 15 min
  • 8 passos
Analisar estilo e possíveis defeitos com Ruff

O que você vai percorrer

  1. Executar o linter e interpretar os diagnósticos Execute o Ruff sobre um arquivo local e aprenda a relacionar cada diagnóstico ao trecho correspondente do código. 2 min
  2. Corrigir importações sem uso e nomes indefinidos Decida como tratar F401 e F821 sem alterar a finalidade do código apenas para eliminar diagnósticos. 2 min
  3. Investigar atribuições locais sem uso Interprete o diagnóstico F841 e escolha uma correção que preserve a finalidade e os efeitos necessários da função. 2 min
  4. Entender quais regras foram verificadas Interprete o resultado do Ruff considerando o arquivo analisado e as regras habilitadas na execução. 1 min
  5. Revisar e aplicar correções automáticas Use a prévia do Ruff, revise as alterações propostas, aplique apenas correções adequadas e confirme o resultado com lint e testes. 3 min
  6. Usar noqa apenas quando houver justificativa Diferencie uma supressão pontual e documentada de tentativas de esconder defeitos reais. 1 min
  7. Reconhecer o que o lint não comprova Compare o alcance do lint, da verificação de tipos e dos testes para interpretar corretamente resultados positivos. 1 min
  8. Aplicar o ciclo completo de análise e revisão Conclua uma revisão local com Ruff, corrija os diagnósticos conforme a intenção do código e confirme o comportamento com pytest. 2 min

O que você vai aprender

  • Executar o linter Ruff e localizar os problemas indicados em seus diagnósticos.
  • Corrigir importações não utilizadas, nomes indefinidos e atribuições locais sem uso conforme a intenção do código.
  • Revisar correções automáticas antes de aceitá-las e executar os testes após as mudanças.
  • Explicar por que ausência de avisos não comprova que um programa esteja correto.

Antes de começar

  • Padronizar a formatação do código com Ruff
  • Controlar o escopo e o estado das funções
  • Separar código em módulos e usar importações explícitas

Passo 1 de 8

Executar o linter e interpretar os diagnósticos

Execute o Ruff sobre um arquivo local e aprenda a relacionar cada diagnóstico ao trecho correspondente do código.

Formatação e lint têm objetivos diferentes

Analisar sem executar

Você já usou o Ruff para padronizar a apresentação do código. O lint tem outro objetivo: examina o arquivo sem executar o programa e procura padrões de estilo ou indícios de possíveis problemas.

A formatação pode ajustar espaços, aspas e quebras de linha. O lint, por sua vez, produz diagnósticos que apontam onde um padrão foi encontrado. Nesta etapa, você apenas executará a análise e aprenderá a ler esses diagnósticos, sem corrigir o arquivo.

Dica

O arquivo permanece inalterado

O comando ruff check usado nesta prática não modifica nem executa analise.py. Ele apenas analisa o código-fonte.

Prepare e analise o arquivo

Crie uma pasta dedicada

No terminal, crie uma pasta para a prática, entre nela e abra essa pasta no editor de sua preferência. Use o mesmo ambiente Python em que o Ruff já está instalado.

Comandos para criar a pasta

shell
mkdir ruff_analise
cd ruff_analise

Conteúdo completo de analise.py

Crie o arquivo analise.py e salve exatamente este conteúdo:

python
import math


def calcular_total(preco, quantidade):
    subtotal = preco * quantidade
    desconto = subtotal * 0.1
    return total


print(calcular_total(50, 2))

Execute o linter

Com o terminal na mesma pasta do arquivo, execute:

shell
python -m ruff check analise.py

Leia cada parte do diagnóstico

Da mensagem ao código

A aparência pode variar um pouco conforme a versão do Ruff, mas cada diagnóstico informa os mesmos elementos essenciais:

  • arquivo: qual arquivo contém o trecho;
  • linha e coluna: a posição inicial do trecho destacado;
  • código da regra: um identificador como F821;
  • mensagem: uma descrição do padrão encontrado.

Por exemplo, a localização analise.py:7:12 aponta para a linha 7, coluna 12. Nessa posição começa o nome total da instrução return total. O código e a mensagem explicam qual diagnóstico foi associado a esse ponto.

Conexão entre diagnóstico e localização

Diagrama que conecta as partes de um diagnóstico do terminal à linha e à coluna correspondentes em um arquivo Python.

A localização conduz ao trecho do arquivo; o código identifica a regra e a mensagem descreve o que foi detectado.

Exemplo

Saída esperada

Sua saída deve indicar três pontos do arquivo, embora o formato visual possa variar:

  • analise.py:1:8 — código F401;
  • analise.py:6:5 — código F841;
  • analise.py:7:12 — código F821.

Por enquanto, não altere o arquivo. O objetivo é localizar e interpretar as informações apresentadas.

Registre o que você observou

Localize um diagnóstico

Escolha um dos diagnósticos exibidos no seu terminal. Informe o arquivo, a linha, a coluna, o código da regra e o trecho ao qual ele aponta. Se sua saída diferir do exemplo, registre o resultado que você realmente observou.

Escreva pelo menos 40 caracteres (0/40).

Passo 2 de 8

Corrigir importações sem uso e nomes indefinidos

Decida como tratar F401 e F821 sem alterar a finalidade do código apenas para eliminar diagnósticos.

F401: a importação não participa do código

Remover ou usar?

O diagnóstico F401 indica que uma importação não é utilizada no código analisado. Isso não determina sozinho a correção.

  • Se o programa não precisa da importação, remova-a.
  • Se a operação deveria usar o recurso importado, recupere esse uso conforme a finalidade do programa.

No exemplo, math não participa do cálculo. Se a intenção é manter a expressão atual, a importação é dispensável. Se a intenção é usar explicitamente a função de raiz quadrada da biblioteca, o retorno pode chamar math.sqrt(...).

Importação diagnosticada com F401

Analise a finalidade antes de escolher entre remover a primeira linha e usar math.sqrt.

python
import math


def hipotenusa(cateto_a, cateto_b):
    return (cateto_a**2 + cateto_b**2) ** 0.5

Dica

O diagnóstico não define a intenção

Não acrescente um uso artificial para justificar uma importação. A correção deve ser explicável pelo comportamento esperado do programa.

F821: o nome não está definido

Investigue a origem da referência

O diagnóstico F821 aponta uma referência a um nome que não está definido naquele contexto. Antes de corrigir, verifique se houve:

  • erro de digitação;
  • ausência de uma importação necessária;
  • referência ao nome errado.

Atribuir qualquer valor ao nome elimina a mensagem, mas pode esconder o defeito. No exemplo, a função recebeu taxa; portanto, usar taxa no cálculo é mais coerente do que inventar um valor para desconto. De modo semelhante, se Path(...) for necessário, a correção pode ser importar Path de pathlib, e não criar um nome arbitrário chamado Path.

Referência diagnosticada e correção coerente

Comparação didática entre o trecho original e a correção orientada pelo parâmetro da função.

python
# Antes: "desconto" não foi definido.
def aplicar_desconto(preco, taxa):
    return preco * (1 - desconto)  # F821


# Depois: o cálculo usa o parâmetro recebido.
def aplicar_desconto_corrigido(preco, taxa):
    return preco * (1 - taxa)

Da referência quebrada ao nome pretendido

Comparação entre uma função que referencia um nome indefinido e a versão corrigida que conecta o cálculo ao parâmetro taxa.

A correção reconecta a expressão a um nome que já faz parte do contrato da função, em vez de criar um valor arbitrário.

Escolha pela finalidade do código

Associe o cenário à correção

Relacione cada situação à correção mais coerente.

Toque em um item e depois no par correspondente.

Passo 3 de 8

Investigar atribuições locais sem uso

Interprete o diagnóstico F841 e escolha uma correção que preserve a finalidade e os efeitos necessários da função.

Quando um valor fica sem destino

O que o F841 indica

O diagnóstico F841 aparece quando uma variável local recebe um valor, mas não é utilizada depois. Isso pode revelar um cálculo abandonado, uma atribuição desnecessária ou um resultado que deveria participar de uma operação ou do retorno.

No exemplo, o contrato da função exige o preço com desconto. O valor total é calculado, mas o retorno usa subtotal.

Resultado calculado, mas esquecido

O Ruff aponta F841 na atribuição a total.

python
def calcular_preco_final(valores, desconto):
    subtotal = sum(valores)
    total = subtotal - desconto
    return subtotal

O caminho esperado do valor

Diagrama comparando um valor calculado que termina em um caminho interrompido com outro que segue até a saída da função.

Um cálculo necessário deve chegar ao uso previsto pelo contrato, como uma operação posterior ou o retorno.

Remover a atribuição ou usar o resultado?

A correção depende da intenção

No primeiro exemplo, apagar total apenas eliminaria o aviso e manteria o retorno incorreto. A correção coerente é return total.

Em outros casos, o valor devolvido por uma chamada pode ser dispensável, mas a própria chamada pode produzir um efeito necessário. Nesse caso, remova somente a atribuição, não a chamada inteira.

Preservando um efeito necessário

O contrato exige salvar o pedido, mas não exige usar o protocolo devolvido.

python
# Antes: protocolo recebe um valor que não é usado.
def registrar_envio(pedido, repositorio):
    protocolo = repositorio.salvar(pedido)
    return "Envio registrado"


# Depois: a gravação continua acontecendo.
def registrar_envio(pedido, repositorio):
    repositorio.salvar(pedido)
    return "Envio registrado"

Dica

Investigue antes de apagar

Pergunte: esse valor deveria ser retornado ou usado depois? A expressão produz algum efeito necessário? Corrija conforme o contrato da função, em vez de apenas renomear ou apagar a variável para silenciar o diagnóstico.

Decida com base no contrato

Dois diagnósticos F841

Considere o contrato descrito nos comentários de cada função.

python
# Deve devolver a soma dos itens incluindo o frete.
def calcular_total(itens, frete):
    total = sum(itens) + frete
    return sum(itens)


# Deve gravar o acesso e informar que a operação terminou.
# O valor devolvido por gravar não é necessário.
def registrar_acesso(usuario, auditoria):
    registro = auditoria.gravar(usuario)
    return True

Proponha as duas correções

Para cada função, proponha uma alteração que resolva o F841. Explique também por que remover toda a instrução diagnosticada seria adequado ou inadequado.

Escreva pelo menos 80 caracteres (0/80).

Passo 4 de 8

Entender quais regras foram verificadas

Interprete o resultado do Ruff considerando o arquivo analisado e as regras habilitadas na execução.

O resultado depende do alcance da execução

Disponível não significa verificado

O Ruff possui um catálogo amplo de regras, mas cada execução aplica somente as regras habilitadas aos arquivos indicados. Sem uma configuração que altere a seleção, o Ruff habilita por padrão as famílias E4, E7, E9 e F.

Uma configuração existente pode mudar esse conjunto. Portanto, “nenhum diagnóstico encontrado” significa apenas que nenhum problema foi identificado dentro dos arquivos e das regras considerados naquela execução.

Do catálogo aos diagnósticos

A seleção de regras funciona como um filtro: primeiro existem as regras disponíveis; depois vêm as habilitadas; por fim aparecem apenas os diagnósticos encontrados no arquivo analisado.

Diagrama em três etapas mostrando um catálogo amplo de regras, três regras selecionadas e os diagnósticos correspondentes encontrados em um arquivo Python.

Regras disponíveis → regras selecionadas → diagnósticos encontrados no arquivo.

Selecionar somente as regras estudadas

Seleção explícita

Para verificar explicitamente F401, F821 e F841 em analise.py, execute o comando abaixo na pasta em que o arquivo está salvo.

Executar três regras específicas

shell
python -m ruff check analise.py --select F401,F821,F841

Dica

Como interpretar --select

Nesse comando, --select define a seleção como essas três regras. Ele não habilita todo o catálogo e não significa simplesmente acrescentá-las ao conjunto padrão. O resultado também continua limitado a analise.py.

Verifique a conclusão possível

Verdadeiro ou falso?

Se python -m ruff check analise.py --select F401,F821,F841 não produzir diagnósticos, isso comprova que o Ruff executou todas as regras disponíveis sobre o projeto inteiro.

Passo 5 de 8

Revisar e aplicar correções automáticas

Use a prévia do Ruff, revise as alterações propostas, aplique apenas correções adequadas e confirme o resultado com lint e testes.

Automático não significa irrefletido

Entenda antes de aplicar

Alguns diagnósticos do Ruff têm correção automática; outros exigem uma decisão manual baseada na intenção do programa. A disponibilidade de uma correção não elimina a revisão.

O comando python -m ruff check analise.py --diff apresenta uma prévia das correções aplicáveis sem gravar o arquivo. Em um diff, linhas com - seriam removidas e linhas com + seriam acrescentadas. Confirme se essas mudanças preservam o comportamento esperado.

Da prévia à verificação

A correção automática deve passar por uma etapa de revisão antes de ser aplicada.

Fluxo visual no qual um arquivo Python passa pela análise, pela inspeção de uma linha removida, pela correção e pela verificação final.

Primeiro visualize a diferença; depois decida, aplique e verifique.

Atenção

Cuidado com correções inseguras

Por padrão, --fix aplica correções classificadas como seguras pelo Ruff. A opção --unsafe-fixes também permite correções classificadas como inseguras e exige cautela adicional; não a use nesta prática. Mesmo uma correção classificada como segura não garante todos os requisitos do programa, e nem todo diagnóstico pode ser resolvido automaticamente.

Prepare um caso local

Crie os dois arquivos

Em uma pasta dedicada, salve os arquivos abaixo como analise.py e test_analise.py. O exemplo contém uma importação sem uso e um nome indefinido. O teste registra o comportamento esperado para o desconto.

analise.py

python
import math


def calcular_total(precos, aplicar_desconto=False):
    subtotal = sum(precos)
    desconto = subtotal * 0.10

    if aplicar_desconto:
        return total - desconto

    return subtotal

test_analise.py

python
from analise import calcular_total


def test_total_sem_desconto():
    assert calcular_total([40.0, 60.0]) == 100.0


def test_total_com_desconto():
    assert calcular_total([40.0, 60.0], aplicar_desconto=True) == 90.0

Liste todos os diagnósticos primeiro

Execute na pasta que contém os dois arquivos. Essa execução normal mostra tanto o F401 quanto o F821.

shell
python -m ruff check analise.py

Visualize, revise e aplique

Use a prévia antes da escrita

Execute --diff e procure a remoção de import math. Ela é coerente porque o módulo não participa do cálculo. A prévia mostra somente correções aplicáveis e não substitui a execução normal usada para listar os diagnósticos restantes.

Depois da revisão, aplique as correções disponíveis com --fix e abra novamente analise.py para conferir o resultado.

Prévia e aplicação

shell
python -m ruff check analise.py --diff
python -m ruff check analise.py --fix

Ordene o fluxo controlado

Coloque as ações na ordem adequada.

  1. Executar o lint normalmente para conhecer os diagnósticos.
  2. Revisar as linhas removidas e acrescentadas.
  3. Aplicar as correções aprovadas com `--fix`.
  4. Revisar o arquivo resultante.
  5. Gerar a prévia das correções com `--diff`.
  6. Executar novamente o lint e os testes.

Resolva o que restou e verifique

A correção automática é apenas uma etapa

Depois de --fix, execute novamente o Ruff. O F401 deve ter desaparecido, mas o F821 referente a total permanece. Execute também os testes: o cenário com desconto falha porque esse nome não existe.

A intenção registrada pelo teste indica que o desconto deve ser subtraído de subtotal. Corrija manualmente a linha para return subtotal - desconto e repita as duas verificações.

Correção manual e verificações finais

shell
# Em analise.py, troque:
# return total - desconto
# por:
# return subtotal - desconto

python -m ruff check analise.py
python -m pytest

Registre o que você observou

Qual alteração o Ruff aplicou automaticamente? O que permaneceu para correção manual e qual foi o resultado final de python -m pytest?

Escreva pelo menos 80 caracteres (0/80).

Passo 6 de 8

Usar noqa apenas quando houver justificativa

Diferencie uma supressão pontual e documentada de tentativas de esconder defeitos reais.

Suprimir não é corrigir

Uma supressão instrui o Ruff a não apresentar um diagnóstico, mas não altera o comportamento do programa. Ela só é adequada quando o código é intencional e existe uma justificativa clara para manter a situação indicada.

Importação intencional

Aqui, a importação serve somente para verificar se o módulo está disponível. O comentário limita a supressão a F401, e a linha anterior registra a intenção.

python
def tem_tomllib():
    try:
        # Importação intencional: verifica a disponibilidade do módulo.
        import tomllib  # noqa: F401
    except ImportError:
        return False
    return True

Limite a exceção

Prefira # noqa: F401 na própria linha diagnosticada. Um # noqa sem código pode ocultar outros problemas presentes na mesma linha, dificultando revisões futuras.

Supressão específica e supressão ampla

Comparação entre um filtro que silencia somente um aviso escolhido e outro que esconde vários avisos diferentes.

A supressão específica afeta apenas a regra indicada; uma supressão ampla pode esconder diagnósticos não relacionados.

Atenção

O defeito continua existindo

Uma supressão não define um nome que não existe nem faz um resultado esquecido participar do retorno. Usar noqa para esconder F821 ou F841 sem uma justificativa real apenas remove o aviso; o problema do código permanece.

Decida pela intenção

Qual situação apresenta o uso mais criterioso de noqa?

Passo 7 de 8

Reconhecer o que o lint não comprova

Compare o alcance do lint, da verificação de tipos e dos testes para interpretar corretamente resultados positivos.

Cada verificação tem um alcance

Quando o Ruff não apresenta diagnósticos, a conclusão correta é limitada: nenhuma das regras habilitadas encontrou um problema nos arquivos analisados, considerando também eventuais supressões. Isso não comprova que todos os resultados do programa estejam corretos.

As ferramentas observam aspectos diferentes:

  • Lint: padrões estáticos abrangidos pelas regras habilitadas.
  • Verificação de tipos: compatibilidade de tipos descrita no código.
  • Testes: resultados observáveis nos cenários executados.

Elas se complementam, mas nenhuma fornece, sozinha, uma prova geral de correção.

Três perspectivas sobre o mesmo programa

Comparação visual entre uma lente que inspeciona padrões do código, outra que compara tipos e uma terceira que confronta entrada, resultado obtido e resultado esperado.

Lint, verificação de tipos e testes examinam propriedades diferentes e têm alcances limitados.

Lint aprovado, comportamento reprovado

Crie os dois arquivos abaixo na mesma pasta. A função não contém F401, F821 nem F841, mas sua operação está errada: ela aumenta o preço quando deveria aplicar um desconto.

analise.py

python
def aplicar_desconto(preco, percentual):
    return preco + preco * percentual / 100

test_analise.py

python
from analise import aplicar_desconto


def test_aplica_desconto_de_dez_por_cento():
    assert aplicar_desconto(100, 10) == 90

Execute as duas verificações

O Ruff não deve encontrar as três regras selecionadas em analise.py. Já o pytest deve mostrar que o resultado obtido é 110.0, e não 90.

shell
python -m ruff check analise.py --select F401,F821,F841
python -m pytest

Interprete a ausência de diagnósticos

Se o Ruff não encontra F401, F821 nem F841 em um arquivo, isso comprova que as regras selecionadas não detectaram esses padrões nesse arquivo — mas não que o programa produz resultados corretos.

Explique as evidências

Como um lint sem diagnósticos pode coexistir com um teste que falha neste exemplo? Explique a responsabilidade de cada verificação.

Escreva pelo menos 80 caracteres (0/80).

Passo 8 de 8

Aplicar o ciclo completo de análise e revisão

Conclua uma revisão local com Ruff, corrija os diagnósticos conforme a intenção do código e confirme o comportamento com pytest.

Prepare o caso final

Um módulo com três diagnósticos

Crie uma pasta dedicada e salve nela os dois arquivos abaixo. O contrato é simples: calcular_total deve devolver a soma dos preços menos o desconto. Primeiro execute o Ruff sem modificar o código e classifique os diagnósticos encontrados.

pedidos.py

Salve este conteúdo no arquivo pedidos.py.

python
import json


def calcular_total(precos, desconto):
    subtotal = sum(precos)
    total = subtotal - desconto
    mensagem = f"Total: {total:.2f}"
    return tota

test_pedidos.py

Salve o teste na mesma pasta.

python
from pedidos import calcular_total


def test_calcular_total_aplica_desconto():
    resultado = calcular_total([10.0, 20.0], 5.0)

    assert resultado == 25.0

Analise, revise e verifique

Siga o ciclo completo

Execute os comandos na pasta dos arquivos. Relacione cada F401, F821 e F841 à linha correspondente. Use --diff para examinar as correções automáticas disponíveis sem gravar o arquivo. Se as alterações respeitarem o contrato, aplique-as com --fix; depois faça manualmente as correções restantes. Neste caso, uma supressão só seria adequada se houvesse uma intenção real e documentável — não use noqa apenas para obter uma saída limpa.

Comandos da revisão

Revise o arquivo depois de --diff e novamente depois de --fix. A disponibilidade exata de correções pode variar conforme a versão do Ruff.

shell
python -m ruff check pedidos.py
python -m pytest

python -m ruff check pedidos.py --diff
python -m ruff check pedidos.py --fix

# Faça no editor as correções manuais ainda necessárias.
python -m ruff check pedidos.py
python -m pytest

Fluxo de revisão

Uma saída limpa só ganha significado quando você conhece o arquivo analisado, as regras executadas e as mudanças realizadas.

Fluxo circular em cinco etapas: analisar o arquivo, interpretar diagnósticos, revisar alterações, corrigir e verificar com testes.

O ciclo termina com uma nova execução do lint e dos testes, não com a simples remoção das mensagens.

Registre sua decisão

Relatório curto da revisão

Relate os diagnósticos encontrados, justifique cada alteração e informe os resultados da última execução do Ruff e do pytest. Termine explicando por que esses resultados não provam que o programa esteja correto em todos os casos.

Escreva pelo menos 180 caracteres (0/180).

Conclusão

Resumo

O ciclo de análise e revisão

A revisão combina diagnóstico estático, decisão humana e verificação de comportamento.

  • Execute o lint e localize arquivo, linha, coluna e regra de cada diagnóstico.
  • Corrija F401, F821 e F841 de acordo com a finalidade do código, não apenas para silenciar mensagens.
  • Examine a prévia antes de aplicar correções automáticas e revise o arquivo resultante.
  • Use supressões somente quando forem pontuais, intencionais e justificáveis.
  • Execute novamente o Ruff e os testes depois das mudanças.
  • Lint sem avisos e testes aprovados aumentam a confiança apenas dentro do alcance das regras e dos cenários verificados.

Tutorial concluído

Parabéns! Você concluiu: Analisar estilo e possíveis defeitos com Ruff

Revisão concluída! Agora você pode usar o Ruff para investigar possíveis problemas sem confundir ausência de avisos com garantia de correção.

100 XP

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso