Como escrever código limpo e manutenível

1. Fundamentos do código limpo

1.1 O que define "código limpo" e seus benefícios a longo prazo

Código limpo é aquele que pode ser lido e compreendido por outro desenvolvedor (incluindo você mesmo seis meses depois) com o mínimo esforço cognitivo. Não se trata de escrever código inteligente, mas de escrever código claro. Os benefícios são tangíveis: redução do tempo de onboarding de novos membros, menor incidência de bugs durante alterações e custos de manutenção drasticamente reduzidos.

// Código obscuro
def calc(a, b, c):
    return a * b + c / 2

// Código limpo
def calcular_preco_final(preco_base, quantidade, desconto_percentual):
    return preco_base * quantidade + desconto_percentual / 2

1.2 Princípios SOLID aplicados à manutenibilidade

Os princípios SOLID são fundamentais para criar sistemas que resistam ao tempo:

  • Single Responsibility: Cada classe ou função deve ter exatamente uma razão para mudar
  • Open/Closed: Entidades devem estar abertas para extensão, mas fechadas para modificação
  • Liskov Substitution: Subtipos devem ser substituíveis por seus tipos base
  • Interface Segregation: Interfaces específicas são melhores que interfaces genéricas
  • Dependency Inversion: Dependa de abstrações, não de implementações concretas
// Violação do Princípio da Responsabilidade Única
class UsuarioService:
    def salvar_usuario(self, usuario):
        # Valida dados
        # Conecta ao banco
        # Envia email de boas-vindas
        # Gera relatório
        pass

// Correção aplicando SRP
class UsuarioValidator:
    def validar(self, usuario): pass

class UsuarioRepository:
    def salvar(self, usuario): pass

class EmailService:
    def enviar_boas_vindas(self, usuario): pass

1.3 A importância da legibilidade sobre a esperteza técnica

Desenvolvedores experientes sabem que código legível é mais valioso que código "esperto". Expressões complexas em uma única linha podem impressionar em entrevistas, mas causam dor de cabeça na manutenção.

// "Esperto" mas ilegível
resultado = [x**2 for x in range(20) if x % 2 == 0 and x > 5]

// Legível e manutenível
numeros_pares = [x for x in range(20) if x % 2 == 0]
numeros_maiores_que_cinco = [x for x in numeros_pares if x > 5]
resultado = [x**2 for x in numeros_maiores_que_cinco]

2. Nomenclatura e convenções

2.1 Nomes significativos para variáveis, funções e classes

Nomes devem revelar a intenção. Um nome bom elimina a necessidade de comentários explicativos.

// Ruim
def p(d):
    return d * 0.1

// Bom
def calcular_desconto_padrao(valor_compra):
    TAXA_DESCONTO = 0.1
    return valor_compra * TAXA_DESCONTO

2.2 Padrões de nomenclatura consistentes

A consistência é mais importante que a escolha individual do padrão:

  • camelCase: variáveis e funções em JavaScript/Java (calcularTotal)
  • snake_case: variáveis e funções em Python (calcular_total)
  • PascalCase: classes em todas as linguagens (ClientePremium)
  • SCREAMING_SNAKE_CASE: constantes (TAXA_JUROS_MAXIMA)

2.3 Evitando abreviações obscuras e nomes genéricos

// Evite
tmp = get_data()
proc(tmp)

// Prefira
dados_processamento = obter_dados_do_sistema()
processar_dados(dados_processamento)

Nomes como data, info, manager, helper são genéricos demais e não agregam significado.

3. Estrutura e organização do código

3.1 Funções pequenas e com responsabilidade única

Funções devem fazer apenas uma coisa e fazer bem. O limite ideal é entre 10 e 20 linhas.

// Função que faz muitas coisas
def processar_pedido(pedido):
    validar_estoque(pedido)
    calcular_frete(pedido)
    aplicar_descontos(pedido)
    gerar_nota_fiscal(pedido)
    enviar_email_confirmacao(pedido)
    atualizar_estoque(pedido)

// Cada chamada encapsula uma responsabilidade única

3.2 Separação clara entre lógica de negócio e infraestrutura

A lógica de negócio deve ser independente de frameworks, bancos de dados e APIs externas.

// Camada de domínio (pura lógica de negócio)
class CalculadoraImposto:
    def calcular(self, valor_base, aliquota):
        return valor_base * (aliquota / 100)

// Camada de infraestrutura
class RepositorioFiscal:
    def __init__(self, banco_conexao):
        self.conexao = banco_conexao

    def salvar_imposto(self, imposto):
        self.conexao.executar("INSERT INTO impostos ...")

3.3 Organização de arquivos e módulos por domínio

Agrupe arquivos por funcionalidade, não por tipo técnico:

projeto/
  dominio/
    clientes/
      Cliente.py
      ClienteValidator.py
      ClienteRepository.py
    pedidos/
      Pedido.py
      CalculadoraFrete.py
  infraestrutura/
    banco/
      ConexaoBanco.py
    email/
      ServicoEmail.py

4. Tratamento de erros e exceções

4.1 Uso adequado de exceções versus códigos de erro

Exceções são para situações excepcionais, não para fluxo de controle normal.

// Ruim: usando exceção para fluxo normal
try:
    usuario = buscar_usuario_por_id(id)
except UsuarioNaoEncontrado:
    usuario = criar_usuario_padrao()

// Bom: verificando antes
usuario = buscar_usuario_por_id(id)
if usuario is None:
    usuario = criar_usuario_padrao()

4.2 Mensagens de erro descritivas e acionáveis

// Ruim
raise Exception("Erro 42")

// Bom
raise ValidationError(
    "CPF inválido: o formato esperado é XXX.XXX.XXX-XX, "
    "mas foi recebido '123456789'"
)

4.3 Estratégias para fallback e degradação graciosa

def processar_pagamento(pedido):
    try:
        return gateway_principal.cobrar(pedido)
    except GatewayIndisponivel:
        logger.warning("Gateway principal falhou, tentando fallback")
        return gateway_fallback.cobrar(pedido)
    except Exception as erro:
        logger.error(f"Falha crítica no pagamento: {erro}")
        return ResultadoFalha("Pagamento temporariamente indisponível")

5. Comentários e documentação no código

5.1 Quando comentar (e quando não comentar)

Comente o "porquê", não o "como". Código bem escrito já explica o "como".

// Comentário desnecessário
soma = a + b  # Soma a com b

// Comentário útil
# Usamos algoritmo de ordenação estável para preservar a ordem
# de inserção original em caso de empate
resultados = sorted(dados, key=lambda x: x.prioridade, stable=True)

5.2 Documentação de APIs e interfaces públicas

def calcular_juros_compostos(
    valor_principal: float,
    taxa_anual: float,
    periodo_meses: int
) -> float:
    """
    Calcula o montante final com juros compostos.

    Args:
        valor_principal: Valor inicial do investimento
        taxa_anual: Taxa de juros anual em percentual (ex: 12.5 para 12.5%)
        periodo_meses: Período em meses

    Returns:
        Montante final após o período

    Raises:
        ValueError: Se valor_principal for negativo
    """

5.3 Manutenção de comentários sincronizados com o código

Comentários desatualizados são piores que nenhum comentário. Durante refatorações, atualize ou remova comentários obsoletos.

6. Testes como parte do código limpo

6.1 Escrevendo testes legíveis e focados em comportamento

def test_calcular_desconto_cliente_vip():
    cliente = Cliente(tipo="VIP", tempo_cadastro=2)
    pedido = Pedido(valor_total=1000.0, cliente=cliente)

    desconto = calcular_desconto(pedido)

    assert desconto == 150.0  # 15% para VIP com 2+ anos

6.2 Cobertura de bordas e casos de erro

def test_calcular_desconto_valor_zero():
    pedido = Pedido(valor_total=0.0, cliente=cliente_comum)
    assert calcular_desconto(pedido) == 0.0

def test_calcular_desconto_cliente_inativo():
    cliente = Cliente(tipo="comum", status="inativo")
    pedido = Pedido(valor_total=500.0, cliente=cliente)
    assert calcular_desconto(pedido) == 0.0

6.3 Refatoração guiada por testes para manter a qualidade

Testes fornecem a rede de segurança necessária para refatorar com confiança. Sempre que identificar um code smell, escreva testes primeiro, depois refatore.

7. Refatoração contínua e dívida técnica

7.1 Identificando sinais de código "cheiroso" (code smells)

  • Métodos longos: mais de 20 linhas
  • Classe grande: mais de 200 linhas ou muitas responsabilidades
  • Parâmetros em excesso: mais de 3-4 parâmetros
  • Duplicação: código idêntico ou muito similar em múltiplos lugares
  • Comentários em excesso: indicam que o código não é autoexplicativo

7.2 Estratégias para refatoração incremental sem quebrar funcionalidades

# Passo 1: Extrair método
def processar_pedido_completo(pedido):
    validar_pedido(pedido)
    processar_pagamento(pedido)
    gerar_confirmacao(pedido)

# Passo 2: Mover método para classe apropriada
class ProcessadorPedido:
    def executar(self, pedido):
        self.validador.validar(pedido)
        self.pagamento.processar(pedido)
        self.notificador.enviar_confirmacao(pedido)

7.3 Gerenciamento consciente da dívida técnica

Dívida técnica não é inerentemente ruim — é uma ferramenta estratégica. O problema é quando ela é acumulada sem consciência. Mantenha um backlog técnico e reserve 20% do tempo de cada sprint para pagar dívidas.

// Documente a dívida técnica conscientemente
# TODO: Refatorar esta função quando o módulo de relatórios for
# reescrito. Atualmente funciona, mas viola SRP ao validar
# e gerar relatório simultaneamente.
def validar_e_gerar_relatorio(dados):
    # implementação atual
    pass

Código limpo não é um destino, mas uma prática contínua. Cada commit é uma oportunidade de deixar o código um pouco melhor do que você encontrou. A manutenibilidade não acontece por acaso — ela é construída deliberadamente, linha por linha, decisão por decisão.

Referências