Pular para o conteúdo
Categoria: Fundamentos & Boas Práticas15 min de leitura

"KISS: o princípio Keep It Simple, Stupid no desenvolvimento de software"

Por Lucas Andrade ·

Entenda o princípio KISS, a diferença entre complexidade essencial e acidental, como evitar over-engineering e por que a IA pode induzir complexidade desnecessária.

"KISS: o princípio Keep It Simple, Stupid no desenvolvimento de software"

Poucas ideias atravessam tantas décadas de engenharia de software quanto a noção de que código simples vence código esperto. O princípio KISS — Keep It Simple, Stupid — é a formalização dessa intuição: na dúvida, escolha a solução mais simples que resolve o problema de verdade. Neste artigo vamos além do slogan e investigamos o que é simplicidade, por que ela é difícil de alcançar e como, na era da IA, ela se tornou ainda mais valiosa e ainda mais ameaçada.

A origem do KISS

A sigla KISS nasceu fora do mundo do software. A formulação mais citada é atribuída a Kelly Johnson, engenheiro-chefe da Lockheed Skunk Works nos anos 1960, responsável por aviões como o U-2 e o SR-71 Blackbird. A história conta que Johnson entregava à sua equipe um conjunto limitado de ferramentas e exigia que os aviões pudessem ser reparados por um mecânico de campo médio, sob condições de combate, com essas ferramentas. A mensagem era clara: um sistema só é robusto quando uma pessoa comum consegue entendê-lo e consertá-lo sob pressão.

Repare que o "stupid" não qualifica o engenheiro nem o usuário. Ele qualifica o resultado desejado: keep it simple, stupid significa "mantenha isso simples, [a ponto de ser à prova de] estúpido", ou ainda "mantenha isso estupidamente simples". A intenção nunca foi insultar quem projeta, e sim lembrar que sistemas que dependem da genialidade de quem os mantém são frágeis. No software, onde o "mecânico de campo" é o colega que vai ler seu código às duas da manhã durante um incidente em produção, o princípio se aplica com força redobrada.

A engenharia de software adotou o KISS porque descobriu, na prática, que a complexidade é o seu inimigo número um. Não a complexidade do problema — essa muitas vezes é dada —, mas a complexidade que nós mesmos adicionamos à solução.

O que é simplicidade, afinal

Aqui mora a primeira armadilha: simplicidade não é a mesma coisa que facilidade, nem que escassez de linhas de código. Um trecho denso de uma linha com três operadores ternários aninhados é curto, mas não é simples. Simplicidade tem a ver com a quantidade de coisas que você precisa manter na cabeça ao mesmo tempo para entender um pedaço do sistema.

Uma definição operacional útil: código simples é aquele cujo comportamento você consegue prever sem executá-lo. Você lê, entende, e a sua expectativa bate com a realidade. Quando você precisa rodar mentalmente um labirinto de condicionais, abstrações e indireções só para descobrir o que uma função faz, a simplicidade foi perdida — mesmo que cada peça, isolada, pareça elegante.

Complexidade essencial versus acidental

A distinção mais importante para discutir KISS de forma adulta vem de Frederick Brooks, no ensaio No Silver Bullet (Brooks, 1987). Brooks separa dois tipos de complexidade:

  • Complexidade essencial: é inerente ao problema. Um sistema de folha de pagamento precisa lidar com impostos, descontos, faltas, horas extras e regras trabalhistas. Nenhuma escolha de linguagem ou framework faz essas regras desaparecerem. Essa complexidade é a "essência" do trabalho.
  • Complexidade acidental: é a que nós introduzimos com nossas ferramentas, abstrações e decisões. Configuração frágil, camadas desnecessárias, frameworks mal escolhidos, código que existe "para o caso de". Essa parte é, em princípio, removível.

O KISS é, no fundo, uma cruzada contra a complexidade acidental. Você não vai eliminar a complexidade essencial do domínio — e tentar isso gera abstrações fantasiosas que escondem regras de negócio reais. Mas você pode, e deve, recusar a complexidade acidental. Brooks alertava que não existe "bala de prata" que multiplique a produtividade da noite para o dia, justamente porque a parte difícil do software é a essencial, e essa não tem atalho. O que está ao nosso alcance é não piorar o problema com acidentes evitáveis.

Over-engineering: a doença que o KISS combate

Over-engineering é construir mais do que o problema pede. É a generalização prematura, a flexibilidade que ninguém solicitou, a arquitetura preparada para uma escala que talvez nunca chegue. Quase sempre nasce de boas intenções — "e se um dia precisarmos trocar o banco?", "e se aparecerem dez formas de pagamento?" — e quase sempre cobra um preço imediato em troca de um benefício hipotético.

Considere um caso clássico: uma função que precisa somar uma lista de números.

# Over-engineered: abstração e flexibilidade que ninguém pediu
from abc import ABC, abstractmethod

class AggregationStrategy(ABC):
    @abstractmethod
    def aggregate(self, values): ...

class SumStrategy(AggregationStrategy):
    def aggregate(self, values):
        return sum(values)

class Aggregator:
    def __init__(self, strategy: AggregationStrategy):
        self.strategy = strategy

    def run(self, values):
        return self.strategy.aggregate(values)

total = Aggregator(SumStrategy()).run([1, 2, 3])

Esse código antecipa um futuro em que existirão muitas estratégias de agregação. Mas hoje existe uma: somar. O Strategy Pattern aqui não resolve um problema real; ele cria três classes, uma hierarquia e um nome (Aggregator) que o leitor precisa decifrar para concluir que o objetivo era apenas somar.

# Simples: faz exatamente o que o problema pede
total = sum([1, 2, 3])

A versão simples não fecha portas. Se, mais tarde, surgir uma segunda forma de agregação de verdade, refatorar de sum() para uma abstração leva minutos e acontece com pleno conhecimento dos requisitos reais — não com base em adivinhação. Esse é o ponto central: o custo de adicionar uma abstração depois é quase sempre menor do que o custo de carregar uma abstração errada por meses.

O over-engineering também tem um custo psicológico. Cada camada extra é mais um lugar onde um bug pode se esconder, mais um trecho que precisa de teste, mais um conceito que um novo integrante da equipe precisa absorver antes de ser produtivo. A simplicidade é, antes de tudo, uma forma de respeito pelo tempo e pela atenção de quem vem depois.

YAGNI, KISS e DRY: aliados e tensões

KISS raramente atua sozinho. Ele convive com dois outros princípios fundamentais, e entender as tensões entre eles é o que separa o desenvolvedor maduro do que apenas decorou siglas.

YAGNI: o irmão temporal do KISS

YAGNI — You Aren't Gonna Need It — diz: não construa hoje algo que você só acha que vai precisar amanhã. Se KISS fala sobre a forma da solução (mantenha-a simples), YAGNI fala sobre o momento (não a construa antes da hora). Os dois empurram na mesma direção. Quando você resiste à tentação de adicionar um parâmetro de configuração "que pode ser útil", está praticando YAGNI; o resultado costuma ser um código mais KISS.

A sinergia é tão forte que muitos confundem os dois. A diferença prática: YAGNI te impede de começar a complexidade desnecessária; KISS te lembra de não introduzi-la mesmo nas partes que você realmente vai construir.

DRY: o aliado que às vezes briga com o KISS

DRY — Don't Repeat Yourself — pede que cada conhecimento tenha uma única representação no sistema. É um princípio excelente e profundamente ligado à manutenibilidade. Mas aqui aparece a tensão mais interessante deste artigo: DRY e KISS podem entrar em conflito.

Eliminar toda e qualquer repetição às vezes exige criar abstrações complicadas que tornam o código menos simples. Um exemplo comum:

# DRY levado ao extremo: uma função "genérica" para evitar duas linhas parecidas
def processar(entidade, tipo):
    config = {
        "usuario": {"tabela": "users", "campos": ["nome", "email"], "valida": valida_user},
        "produto": {"tabela": "products", "campos": ["titulo", "preco"], "valida": valida_prod},
    }[tipo]
    registro = {c: getattr(entidade, c) for c in config["campos"]}
    config["valida"](registro)
    salvar(config["tabela"], registro)

Essa função evita duplicação, mas para entender o que acontece ao salvar um usuário você precisa percorrer um dicionário, uma indireção por getattr e uma função de validação dinâmica. A "economia" de repetição custou clareza.

# Duas funções diretas: alguma repetição, muita clareza
def salvar_usuario(usuario):
    registro = {"nome": usuario.nome, "email": usuario.email}
    valida_user(registro)
    salvar("users", registro)

def salvar_produto(produto):
    registro = {"titulo": produto.titulo, "preco": produto.preco}
    valida_prod(registro)
    salvar("products", produto)

A versão "repetitiva" é mais longa, mas cada função é óbvia, testável isoladamente e fácil de evoluir quando as regras de usuário e produto divergirem — o que quase sempre acontece. A duplicação aparente aqui é o que Sandi Metz resumiu em uma frase célebre: "a duplicação é muito mais barata do que a abstração errada". O DRY foi pensado para eliminar a duplicação de conhecimento, não a coincidência de código que se parece. Dois trechos parecidos que representam regras de negócio diferentes não são, de fato, repetição.

A regra prática para conciliar os três: aplique DRY quando a repetição for de conhecimento genuíno e tiver provas de que ela existe (a famosa "regra de três" — abstraia na terceira ocorrência), e deixe o KISS desempatar quando a abstração proposta for mais difícil de entender do que a duplicação que ela remove.

Simplicidade na prática do dia a dia

Princípios viram hábito quando descem ao nível das pequenas decisões. Alguns padrões concretos de simplicidade que cabem em qualquer base de código:

  • Prefira retornos antecipados a aninhamento profundo. Uma cascata de if aninhados eleva a complexidade ciclomática e força o leitor a guardar muitas condições na memória. Guard clauses (cláusulas de guarda) achatam o fluxo.
// Aninhado: difícil de seguir
function descontoFrete(pedido) {
  if (pedido) {
    if (pedido.itens.length > 0) {
      if (pedido.total > 200) {
        return 0;
      } else {
        return 25;
      }
    }
  }
  return null;
}
// Plano: cada condição tratada e descartada cedo
function descontoFrete(pedido) {
  if (!pedido || pedido.itens.length === 0) return null;
  return pedido.total > 200 ? 0 : 25;
}
  • Nomeie pelo intento, não pela implementação. usuariosAtivos diz mais do que filtrarLista. Bons nomes reduzem a necessidade de comentários e de leitura do corpo da função, o que é um pilar do Clean Code.
  • Evite configuração que ninguém usa. Cada flag, variável de ambiente e parâmetro opcional é uma combinação de estados a mais para testar. Configuração é complexidade acidental disfarçada de flexibilidade.
  • Escolha estruturas de dados antes de algoritmos. Como nota The Pragmatic Programmer (Hunt e Thomas, 1999), boa parte da complexidade some quando os dados estão modelados corretamente. Um Map no lugar certo elimina dezenas de linhas de busca manual.

O fio condutor de todos esses hábitos é o mesmo: reduzir a carga cognitiva de quem lê. Simplicidade não é uma propriedade do computador — ele executa qualquer coisa —, é uma propriedade da relação entre o código e a mente humana que precisa compreendê-lo.

Simplicidade na arquitetura

KISS escala de uma função para o sistema inteiro. No nível arquitetural, o exemplo mais didático da última década é a corrida em direção aos microsserviços. Muitas equipes fragmentaram aplicações pequenas em dezenas de serviços independentes acreditando seguir "boas práticas", e acabaram trocando a complexidade de um código por uma complexidade muito pior: a de uma rede distribuída.

Um monólito bem organizado tem chamadas de função; um sistema de microsserviços tem chamadas de rede, que falham, têm latência, exigem versionamento de contratos, observabilidade distribuída, consistência eventual e orquestração de deploy. Para uma equipe de cinco pessoas servindo alguns milhares de usuários, isso é complexidade acidental em estado puro — exatamente o que o KISS condena.

# Over-engineered para a escala atual:
[ API Gateway ] -> [ Serviço de Usuários ] -> [ Fila ] -> [ Serviço de Notificação ]
                 -> [ Serviço de Pedidos ]  -> [ Fila ] -> [ Serviço de Estoque ]
                 -> [ Serviço de Pagamentos ] (cada um com seu banco e seu deploy)

# Simples e adequado para começar:
[ Aplicação monolítica modular ]  ->  [ Um banco de dados ]
   (módulos: usuarios, pedidos, pagamentos, notificacoes)

O monólito modular não é "menos profissional". Ele mantém fronteiras claras entre módulos dentro do mesmo processo, o que preserva quase todos os benefícios de organização sem pagar o imposto operacional da distribuição. Quando — e se — um módulo específico precisar escalar de forma independente, ele já tem fronteiras bem definidas e pode ser extraído. Essa abordagem dialoga diretamente com os princípios de SOLID: coesão alta e acoplamento baixo entre módulos é o que torna uma futura extração barata, sem exigir que você pague antecipadamente pela infraestrutura distribuída.

A lição arquitetural do KISS é evolutiva: comece com a estrutura mais simples que comporta o problema de hoje e mantenha as costuras nos lugares certos para crescer depois. Brooks já dizia em The Mythical Man-Month (Brooks, 1975) que adicionar pessoas e estruturas a um sistema atrasado o torna mais lento, porque o custo de comunicação e coordenação cresce de forma não linear. O mesmo vale para serviços: cada peça nova multiplica os caminhos de integração.

Como a IA pode induzir complexidade desnecessária

Aqui chegamos ao ponto mais atual — e mais relevante para quem aprende a construir aplicações com assistentes de IA. Modelos de linguagem são extraordinariamente úteis para gerar código, mas têm um viés que conspira contra o KISS, e reconhecê-lo é parte essencial da habilidade de programar com IA.

Assistentes de IA tendem a produzir código abrangente, não código mínimo. Quando você pede "uma função para validar um e-mail", é comum receber uma classe com tratamento de exceções customizadas, suporte a internacionalização, logging, parâmetros de configuração e comentários extensos — uma resposta que parece completa e impressiona, mas que entrega muito mais do que o pedido. O modelo foi treinado em milhões de exemplos, muitos deles de bibliotecas robustas e propósito geral, e sua média estatística pende para a abrangência. Ele não conhece o seu contexto, a sua escala nem o seu prazo, então, na dúvida, ele adiciona.

# Resposta típica de IA para "valide um e-mail" — abrangente demais para o caso
import re
import logging
from dataclasses import dataclass

class EmailValidationError(Exception): ...

@dataclass
class EmailValidatorConfig:
    allow_international: bool = True
    max_length: int = 254
    log_failures: bool = True

class EmailValidator:
    def __init__(self, config: EmailValidatorConfig = EmailValidatorConfig()):
        self.config = config
        self.logger = logging.getLogger(__name__)
    # ... dezenas de linhas ...
# O que o problema realmente pedia
import re

def email_valido(email: str) -> bool:
    return bool(re.fullmatch(r"[^@\s]+@[^@\s]+\.[^@\s]+", email))

Há três armadilhas específicas da IA que merecem atenção consciente:

  1. Abstração prematura por padrão. A IA reproduz padrões "de produção" mesmo em scripts simples. Cabe a você pedir explicitamente a versão mínima e questionar cada camada gerada: "isso resolve um problema que eu tenho agora?".
  2. Acúmulo silencioso. Em sessões longas de geração incremental, cada resposta adiciona um pouco. Sem revisão crítica, você termina com várias abstrações que se sobrepõem, dependências que ninguém escolheu de propósito e um código que ninguém entende por inteiro — incluindo você. A IA não enxerga o sistema todo; quem mantém a visão de conjunto é o desenvolvedor.
  3. Falsa autoridade. Código gerado parece confiante e bem-estruturado, o que desencoraja a pergunta "isso é simples demais ou complexo demais para o meu caso?". O polimento da forma esconde o excesso de substância.

A defesa é tratar a IA como um colega muito produtivo e muito entusiasmado, que precisa de um revisor cético. Especifique a escala ("é um script de uso único", "é um protótipo", "ainda não temos usuários"), peça a solução mais simples explicitamente, e remova sem dó tudo o que não corresponde a um requisito real. O KISS deixa de ser apenas um princípio de escrita e vira um princípio de revisão: a sua principal contribuição ao programar com IA é frequentemente subtrair, não adicionar.

Vale notar que a IA também pode ajudar a simplificar — peça a ela para reduzir a complexidade ciclomática de uma função, identificar abstrações redundantes ou propor a versão mínima de um trecho. A ferramenta é neutra; o viés está na média do que ela gera por padrão, e o antídoto é o julgamento humano informado pelos princípios deste artigo.

Quando a simplicidade vira simplismo

Para fechar o raciocínio com honestidade, é preciso reconhecer o limite do KISS. Existe um ponto em que perseguir a simplicidade vira simplismo — e aí o princípio se volta contra si mesmo. Ignorar a complexidade essencial do domínio não é manter as coisas simples; é varrer o problema para baixo do tapete, de onde ele volta como bug em produção.

Um sistema de pagamentos precisa lidar com reembolsos parciais, estornos, idempotência e reconciliação. Fingir que não precisa, em nome da simplicidade, não produz código simples — produz código incompleto que vai falhar de formas caras. O KISS pede que você seja simples no acidental e honesto com o essencial. A frase de Einstein costuma resumir bem o equilíbrio: tudo deve ser feito tão simples quanto possível, mas não mais simples do que isso.

A maturidade está em distinguir os dois lados. Recuse a camada de abstração que ninguém pediu; abrace a regra de negócio que o domínio exige. Simplicidade verdadeira não é a ausência de complexidade — é a ausência de complexidade desnecessária.

Conclusão

O KISS sobreviveu a décadas de modas tecnológicas porque aponta para uma verdade que não muda: o recurso mais escasso na engenharia de software é a capacidade humana de compreender sistemas. Cada linha, cada abstração e cada serviço que adicionamos consome um pouco desse recurso, e a simplicidade é a disciplina de gastá-lo bem.

Vimos que simplicidade não é facilidade nem brevidade, mas previsibilidade e baixa carga cognitiva; que a distinção entre complexidade essencial e acidental é o que torna o KISS aplicável sem virar dogma; que ele caminha lado a lado com YAGNI e às vezes precisa moderar o DRY; e que, na arquitetura, começar simples e evoluir vence quase sempre a sofisticação prematura. E vimos que a IA, por seu viés natural para a abrangência, torna o KISS mais necessário do que nunca — transformando-o de princípio de escrita em princípio de revisão.

No fim, manter as coisas simples é um ato contínuo de coragem: a coragem de não adicionar, de remover o que não serve e de confiar que a clareza de hoje é o melhor presente que você pode deixar para quem mantém o código amanhã — inclusive você mesmo.

Referências

  • Brooks, F. P. (1975). The Mythical Man-Month: Essays on Software Engineering. Addison-Wesley.
  • Brooks, F. P. (1987). No Silver Bullet: Essence and Accidents of Software Engineering. IEEE Computer.
  • Hunt, A., & Thomas, D. (1999). The Pragmatic Programmer: From Journeyman to Master. Addison-Wesley.

Leituras relacionadas

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly