Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

03 - KISS (Keep It Simple, Stupid)

#kiss#manutencao#over-engineering#simplicidade
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione video: https://youtu.be/... no frontmatter da nota.

KISS (Keep It Simple, Stupid)

KISS é o princípio de design que manda escolher, entre as soluções que resolvem o problema,
a mais simples que atende os requisitos atuais. A origem é a indústria aeronáutica: Kelly
Johnson, do Skunk Works da Lockheed, exigia projetos tão simples que um técnico de campo com
ferramentas básicas conseguisse reparar a aeronave. Em software de produção, a tradução é
direta: o sistema precisa ser operável por quem está de plantão às três da manhã, com o
runbook em mãos e sem depender do conhecimento tribal do autor original. Complexidade não é
incômodo estético; ela se paga em incidentes mais longos, onboarding mais lento e mudanças
mais arriscadas. O princípio não pede código trivial, pede que cada unidade de complexidade
adicionada compre um benefício correspondente e visível.

Complexidade essencial versus acidental

Fred Brooks separou a complexidade em duas espécies, e essa distinção é a base de qualquer
conversa honesta sobre simplificação. Complexidade essencial é a que pertence ao problema:
cálculo de impostos, regras de câmbio, concorrência real entre transações. Complexidade
acidental é a que o time escolhe introduzir: camadas extras, abstrações genéricas,
frameworks adotados sem necessidade e configurabilidade que ninguém usa. KISS ataca apenas a
segunda, porque a primeira não pode ser eliminada, apenas organizada. O erro comum é tratar
a essencial como se fosse acidental e tentar escondê-la atrás de abstrações, o que
normalmente aumenta o problema. Antes de simplificar, classifique a complexidade em jogo;
simplificar o essencial produz sistema incompleto, e tolerar o acidental produz sistema
caro.

Um exemplo concreto ajuda a calibrar o olhar. Um serviço de assinaturas com regras fiscais
por estado carrega complexidade essencial: as regras existem fora do software e não há como
removê-las. O time, porém, acrescentou estratégias registradas por arquivo de configuração,
um motor de regras genérico e três níveis de mapeamento entre entidades; nenhuma dessas
peças responde a requisito fiscal, todas respondem a uma hipótese de flexibilidade. Seis
meses depois, corrigir uma alíquota exigia tocar em quatro arquivos e reexecutar a suíte
inteira. A regra essencial continuou complexa; a acidental dobrou o preço de lidar com ela.
Separar os dois tipos nesse diagnóstico é o que permite simplificar sem cortar requisitos.

O custo da complexidade em produção

Cada componente adicional traz modos de falha próprios, e o custo aparece em três contas
mensuráveis. A primeira é o tempo médio de restauração: quanto mais peças móveis, mais tempo
para localizar a origem de um incidente e mais superfícies para inspecionar. A segunda é o
custo de entrada: um time que leva semanas para entender o fluxo de um pedido gera custo
salarial em cada contratação e risco em cada troca de equipe. A terceira é o custo de
mudança: complexidade composta se multiplica, porque cada nova feature precisa atravessar
todas as camadas e respeitar todas as exceções acumuladas.

Times maduros monitoram proxies de complexidade e tratam crescimento sem justificativa como
problema técnico de verdade. Tamanho médio de pull request, número de dependências diretas,
quantidade de configurações ativas e número de componentes no caminho crítico são métricas
simples de coletar. Quando esses indicadores sobem sem aumento correspondente de capacidade
entregue, a curva de custo da complexidade já está dominando a curva de valor.

Over-engineering e seus formatos mais frequentes

O over-engineering tem formatos previsíveis, e reconhecê-los acelera a revisão de
arquitetura. O primeiro é a solução genérica para um caso único: framework de plugins quando
existe um único plugin possível. O segundo é a arquitetura antecipada: microserviços, filas
e cache para um produto com trinta usuários e um requisito de consistência simples. O
terceiro é a configurabilidade infinita: sistemas que permitem ajustar o que nunca foi
ajustado, pagando o custo de testar todos os caminhos. O quarto é a indireção em cascata:
camadas que apenas repassam chamadas, DTOs espelhando entidades e mapeadores sem
transformação. O quinto é a adoção de tecnologia sofisticada por prestígio, como event
sourcing para um domínio sem auditoria relevante. Todos têm algo em comum: cobram juros
diários e pagam retorno incerto.

Como simplificar na prática

Simplificação é trabalho de engenharia, e não apenas de intenção. Comece pela pergunta de
escopo: qual requisito real este componente atende hoje, e qual atenderia apenas
hipoteticamente? Remova o que só tem resposta hipotética, com registro em ADR para permitir
reversão segura. Prefira tecnologia chata e consolidada, com boa documentação e comunidade
grande, porque o custo de operar com pessoas disponíveis no mercado é menor. Escolha a
ferramenta menos poderosa que resolve o problema: um cron com SQL resolve lote noturno de
dez mil linhas sem cluster de streaming.

O restante é disciplina operacional. Colapse camadas que não fazem transformação, unifique
configurações duplicadas e delete código morto com a mesma seriedade com que se escreve
código novo. Mantenha o caminho feliz simples e isole a exceção, em vez de espalhar
condicionais pelo fluxo principal. Por fim, trate simplificação como item de backlog com
prioridade: "podemos remover isso?" é pergunta legítima em revisão de arquitetura, e o
trabalho de remoção compete por capacidade como qualquer outro.

Exemplo prático: a mesma necessidade, dois custos

O cenário: processar diariamente um arquivo de conciliação com dez mil linhas. A primeira
versão, montada para escalar, tinha produtor na fila, três consumidores com controle de
concorrência, reprocessador de mensagens mortas e dashboards dedicados; o custo de operação
e o tempo de diagnóstico eram maiores que o próprio processamento. A versão simplificada
mantém o mesmo contrato de saída:

# Versão simples: job noturno com retry idempotente e log estruturado.
# O lote de 10 mil linhas roda em minutos; não há necessidade de streaming.
def conciliar_dia(data: date) -> RelatorioConciliacao:
    linhas = baixar_arquivo(data)              # fonte oficial do dia
    falhas: list[ErroConciliacao] = []
    for linha in linhas:
        try:
            registrar_lancamento(linha)        # idempotente por chave única
        except ErroNegocio as e:
            falhas.append(e)                   # fila morta vira lista com dono
    return RelatorioConciliacao(data, falhas)  # alerta só quando há falha

A versão simples tem menos peças para falhar, diagnóstico direto e capacidade de retorno
clara quando o volume crescer dez vezes. Nesse momento, a troca por processamento
distribuído é uma decisão com dados de produção, e não uma aposta antecipada. O mesmo
raciocínio vale para cache, fila e particionamento: cada um entra quando a medição pedir, e
o desenho simples de hoje não deve dificultar essa entrada.

Simplicidade como decisão arquitetural

KISS precisa estar institucionalizado nas decisões, ou ele perde para o entusiasmo de cada
sprint. Registre em ADRs a razão de cada componente existir, com o requisito que justifica o
custo; requisito que sumiu vira candidato à remoção. Inclua simplicidade como critério
explícito em revisão de arquitetura e em code review, com perguntas do tipo: existe caminho
mais curto que atende o requisito atual? Cada peça nova traz qual modo de falha adicional? O
time de plantão consegue operar isso sem o autor?

Orçamentar complexidade completa o processo. Limite o número de tecnologias por domínio,
exija justificativa formal para adicionar mais uma e revise a lista periodicamente.
Simplicidade não é ausência de sofisticação; é sofisticação suficiente para o problema, e
nada além disso. A frase "make it work, make it right, make it fast" resume a ordem correta:
funcionar primeiro, com estrutura limpa, e acelerar só o que a medição apontar.

Erros comuns e boas práticas

  • Confundir simples com fácil: a solução simples pode exigir esforço intelectual maior agora, e é exatamente esse investimento que reduz o custo futuro.
  • Simplificar a complexidade essencial: cortar regra de negócio ou tratamento de concorrência real produz sistema barato e errado.
  • Medir simplicidade por linhas de código: menos linhas com regex densa pode ser menos simples; métrica melhor é número de conceitos e de peças móveis.
  • Refatorar para simplificar sem testes: mudanças estruturais amplas exigem rede de proteção; comece pelos testes de caracterização.
  • Ignorar o custo de migração: trocar componentes simples por outros igualmente simples tem custo; simplifique primeiro o que dói na operação.
  • Tratar KISS como veto a tecnologia nova: a adoção é legítima quando o benefício compensa a complexidade; a exigência é justificar o acréscimo.

Antipadrões e alternativas

A tabela reúne os antipadrões de complexidade acidental mais comuns, o custo que geram em
produção e a alternativa simples correspondente.

Antipadrão Custo em produção Alternativa simples
Fila de mensagens para lote noturno Alertas, DLQ e dashboards extras Cron com processamento idempotente
Framework de plugins com um plugin Superfície de API morta Chamada direta e ponto de troca futuro
Microsserviços por entidade Latência, orquestração e custo de ops Módulos internos em monólito bem traçado
Configuração ajustável sem consumidor Matriz de combinações não testadas Constantes em código e ADR para mudar
Camadas de repasse sem transformação Rastro de depuração mais longo Menos camadas, com responsabilidade real