A Arte das Aplicações Sem Design: Um Guia de Campo para Arquitetura de Software Orgânica
Como construir sistemas que futuros desenvolvedores estudarão com a mesma reverência que arqueólogos dão a ruínas antigas
Introdução
Existe uma certa beleza em software que não foi projetado. Não foi arquitetado. Simplesmente… emergiu. Como um recife de coral, ou um engarrafamento, ou aquela gaveta na sua cozinha com as pilhas e os cardápios de delivery.
Hoje, exploramos os princípios por trás das Aplicações Sem Design — sistemas que corajosamente rejeitam a tirania do planejamento e abraçam a liberdade do “a gente resolve depois”.
Princípio #1: A Dualidade de Colunas
Considere este elegante padrão de schema:
failure_reason jsonb -- Singular
failure_reasons jsonb -- Plural
Um engenheiro inferior perguntaria: “Por que não apenas uma coluna?”
Mas essa pergunta revela uma incompreensão fundamental. Veja, failure_reason foi adicionada em 2019 quando antecipávamos apenas falhas únicas. Então veio 2020, e bem… precisamos de failure_reasons.
Poderíamos ter migrado? Claro. Mas migrações são para pessoas que acreditam que o futuro é previsível.
Dica profissional: Sempre verifique ambas as colunas. Em produção, envolva-as em uma função auxiliar chamada get_motivo_real_da_falha_ou_motivos_sei_la().
Princípio #2: As Camadas Arqueológicas
Grandes aplicações sem design têm estratos — camadas visíveis de história, como formações geológicas.
# NÃO REMOVA - quebra produção (2018)
# Ok, provavelmente podemos remover agora (2020)
# DEFINITIVAMENTE NÃO REMOVA - EU ESTAVA ERRADO (2020)
# TODO: investigar por que isso ainda está aqui (2022)
def coisa_legada
# Autor original saiu da empresa
end
Cada camada conta uma história. Removê-la seria como passar um trator em Pompéia.
Como o XKCD 1205 nos lembra, o tempo que você gastaria limpando isso nunca vale a pena. Apenas adicione outra camada.
Princípio #3: O Status de Schrödinger
Um sistema robusto sem design mantém campos de status que existem em superposição quântica:
{
"status": "active",
"state": "pending",
"is_active": false,
"active": true,
"enabled": null,
"disabled_at": "2024-03-15T10:00:00Z"
}
Este registro está ativo? A resposta é sim e não até você verificar os logs de produção.
Melhor prática: Ao adicionar uma nova funcionalidade, sempre adicione um NOVO campo de status em vez de reutilizar os existentes. Isso preserva o registro histórico e garante emprego para futuros desenvolvedores tentando entender QUE DIABOS está acontecendo.
Como Wally do Dilbert diria: “Otimizei meu fluxo de trabalho tornando meu código impossível de entender. Assim, só eu posso dar manutenção.”
Princípio #4: As Convenções de Nomenclatura (Plural)
Consistência é o duende das mentes pequenas. Um codebase maduro celebra a diversidade:
user_id
userId
UserID
id_of_user
user
usr_id
the_user_identifier
Todos estes devem se referir ao mesmo conceito, mas vêm de diferentes microsserviços, cada um com sua própria personalidade convenção de nomenclatura.
Princípio #5: O Booleano Que Não Era
is_deleted: str # Valores: "yes", "no", "Y", "N", "1", "0", "true", "mais ou menos"
Por que usar um booleano quando você pode usar uma string? E por que restringir essa string a dois valores quando o negócio pode precisar de “soft deleted” ou “deletado mas recuperável” ou “deletado na UE mas não nos EUA devido ao GDPR”?
Você não está apenas construindo software. Você está construindo software flexível.
Princípio #6: A Hidra de Configuração
Para cada valor de configuração, deve haver pelo menos três lugares onde ele pode ser definido:
- Variável de ambiente
- Tabela de config no banco
- Constante hardcoded (com um comentário dizendo “mover para config depois”)
- Arquivo YAML que às vezes é lido
- Feature flag que sobrescreve tudo exceto quando não sobrescreve
Qual vence? A ordem de prioridade varia por ambiente de deploy.
Veja XKCD 927 — a solução para 14 fontes de configuração concorrentes é, claro, uma 15ª fonte de configuração que unifica todas.
Princípio #7: A Resposta de API Que Já Viu Coisas
{
"data": {
"result": {
"response": {
"payload": {
"actual_data": { ... },
"actual_data_v2": { ... }
}
}
}
},
"error": null,
"errors": [],
"success": true,
"succeeded": 1,
"failed": false,
"message": "Success",
"messages": ["Success", "Warning: deprecated field used"]
}
Esta estrutura de resposta não foi projetada. Ela evoluiu. Cada campo é uma cicatriz de batalha de um incidente em produção.
| Campo | Por Que Existe |
|---|---|
error |
Design original (2017) |
errors |
Cliente queria arrays (2018) |
success |
QA não conseguia parsear errors nulos (2019) |
succeeded |
Inteiro para operações em lote (2020) |
failed |
Alguém perguntou “e se falhar parcialmente?” (2021) |
message |
Suporte ao cliente precisava de texto legível (2022) |
messages |
Múltiplos warnings por request (2023) |
Conclusão: Abraçando o Caos
Da próxima vez que alguém perguntar “por que é assim?”, lembre-se: eles não estão vendo bugs. Estão vendo história.
Toda inconsistência é uma história. Toda coluna duplicada é uma lição aprendida. Todo conflito de convenção de nomenclatura é evidência de que seu software sobreviveu tempo suficiente para ter múltiplos autores com múltiplas opiniões.
Aplicações sem design não são falhas de planejamento. São triunfos de persistência.
E aquela dualidade failure_reason / failure_reasons? Isso não é dívida técnica.
Isso é patrimônio.
O autor produz bugs em massa há 47 anos. Este artigo foi encontrado em uma coluna legacy_content E legacy_contents.