Aprenda a manter código gerado manutenível com a regra da arquitetura sem firulas: limites de pastas claros, nomes consistentes e defaults simples que reduzem retrabalho.

Código gerado altera o trabalho do dia a dia. Você não está apenas construindo funcionalidades; está guiando um sistema que pode criar muitos arquivos rapidamente. A velocidade é real, mas pequenas inconsistências se multiplicam rápido.
O resultado gerado costuma parecer OK isoladamente. O custo aparece na segunda ou terceira mudança: você não sabe onde uma parte pertence, corrige o mesmo comportamento em dois lugares, ou evita mexer em um arquivo porque não sabe o que mais ele afeta.
Estruturas “espertas” ficam caras porque são difíceis de prever. Padrões personalizados, mágicas ocultas e abstrações pesadas fazem sentido no primeiro dia. Na semana seis, a próxima mudança desacelera porque é preciso reaprender o truque antes de atualizar com segurança. Com geração assistida por IA, essa esperteza também pode confundir gerações futuras e levar a lógica duplicada ou camadas novas empilhadas por cima.
Arquitetura sem firulas é o oposto: limites claros, nomes óbvios e padrões simples. Não se trata de perfeição. Trata-se de escolher um layout que um colega cansado (ou você no futuro) consiga entender em 30 segundos.
Um objetivo simples: facilitar a próxima mudança, não impressionar. Normalmente isso significa um lugar claro para cada tipo de código (UI, API, dados, utilitários compartilhados), nomes previsíveis que descrevem o que o arquivo faz e o mínimo de “mágica” como auto-wiring, globais ocultos ou metaprogramação.
Exemplo: se você pedir ao Koder.ai para adicionar “convites de equipe”, quer que a UI vá para a área de UI, que haja uma rota de API na área de API e que os dados de convite fiquem na camada de dados, sem inventar uma pasta ou padrão novo só para essa funcionalidade. Essa consistência sem firulas é o que mantém edições futuras baratas.
Código gerado fica caro quando oferece muitas maneiras de fazer a mesma coisa. A regra da arquitetura sem firulas é simples: torne a próxima mudança previsível, mesmo que a primeira versão pareça menos engenhosa.
Você deve conseguir responder isso rapidamente:
Escolha uma estrutura simples e siga-a em todo lugar. Quando uma ferramenta (ou colega) sugerir um padrão chique, a resposta padrão é “não”, a menos que isso remova uma dor real.
Defaults práticos que se sustentam com o tempo:
Imagine que um desenvolvedor novo abre seu repositório e precisa adicionar um botão “Cancelar assinatura”. Ele não deveria ter que aprender uma arquitetura customizada primeiro. Deve encontrar uma área de funcionalidade clara, um componente de UI evidente, um único local de cliente de API e um único caminho de acesso a dados.
Essa regra funciona especialmente bem com ferramentas de vibe-coding como o Koder.ai: você pode gerar rápido, mas ainda direciona o resultado para os mesmos limites sem firulas sempre.
Código gerado tende a crescer rápido. A maneira mais segura de mantê-lo manutenível é um mapa de pastas sem firulas, onde qualquer um pode adivinhar onde uma mudança pertence.
Um layout pequeno no nível superior que serve para muitos apps web:
app/ telas, roteamento e estado em nível de páginacomponents/ peças de UI reaproveitáveisfeatures/ uma pasta por funcionalidade (billing, projects, settings)api/ código do cliente de API e helpers de requisiçãoserver/ handlers de backend, serviços e regras de negócioIsso torna os limites óbvios: UI vive em app/ e components/, chamadas de API vivem em api/ e lógica de backend vive em server/.
O acesso a dados também deve ser sem firulas. Mantenha queries SQL e código de repositório perto do backend, não espalhados por arquivos de UI. Em uma stack Go + PostgreSQL, uma regra simples é: handlers HTTP chamam serviços, serviços chamam repositórios, repositórios falam com o banco.
Tipos e utilitários compartilhados merecem um lar claro, mas mantenha pequeno. Coloque tipos cross-cutting em types/ (DTOs, enums, interfaces compartilhadas) e pequenos helpers em utils/ (formatação de datas, validadores simples). Se utils/ começar a parecer um segundo app, o código provavelmente pertence a uma pasta de feature.
Trate pastas geradas como substituíveis.
generated/ (ou gen/) e evite editá-lo diretamente.features/ ou server/ para que a regeneração não a sobrescreva.Exemplo: se o Koder.ai gerar um cliente de API, armazene-o em generated/api/, depois escreva wrappers finos em api/ onde você possa adicionar retries, logs ou mensagens de erro mais claras sem tocar nos arquivos gerados.
Código gerado é fácil de criar e fácil de acumular. Nomear é o que o mantém legível um mês depois.
Escolha um estilo de nomeação e não misture:
kebab-case (user-profile-card.tsx, billing-settings)PascalCase (UserProfileCard)camelCase (getUserProfile)SCREAMING_SNAKE_CASE (MAX_RETRY_COUNT)Nomeie pela função, não por como funciona hoje. user-repository.ts é um papel. postgres-user-repository.ts é um detalhe de implementação que pode mudar. Use sufixos de implementação só quando realmente houver múltiplas implementações.
Evite gavetas de lixo como misc, helpers ou um utils gigante. Se uma função é usada por apenas uma feature, mantenha-a perto dessa feature. Se for compartilhada, faça o nome descrever a capacidade (date-format.ts, money-format.ts, id-generator.ts) e mantenha o módulo pequeno.
Quando rotas, handlers e componentes seguem um padrão, você encontra coisas sem procurar:
routes/users.ts com paths como /users/:userIdhandlers/users.get.ts, handlers/users.update.tsservices/user-profile-service.tsrepositories/user-repository.tscomponents/user/UserProfileCard.tsxSe você usa Koder.ai (ou qualquer gerador), coloque essas regras no prompt e mantenha-as consistentes durante as edições. O objetivo é previsibilidade: se você consegue adivinhar o nome do arquivo, mudanças futuras continuam mais baratas.
Código gerado pode parecer impressionante no primeiro dia e doloroso no trigésimo. Escolha defaults que tornem o código óbvio, mesmo que um pouco repetitivo.
Comece reduzindo a mágica. Evite carregamento dinâmico, truques de reflexão e auto-wiring a menos que haja uma necessidade medida. Esses recursos escondem de onde as coisas vêm, o que torna debugging e refatoração mais lentos.
Prefira imports explícitos e dependências claras. Se um arquivo precisa de algo, importe diretamente. Se módulos precisam ser ligados, faça isso em um lugar visível (por exemplo, um único arquivo de composição). Quem lê não deve ter que adivinhar o que roda primeiro.
Mantenha configuração entediante e centralizada. Coloque variáveis de ambiente, flags de recurso e configurações globais em um módulo com um esquema de nomes único. Não espalhe config por arquivos aleatórios porque parece conveniente.
Regras práticas que mantêm a equipe consistente:
Tratamento de erros é onde a esperteza mais atrapalha. Escolha um padrão e use-o em todo lugar: retorne erros estruturados da camada de dados, mapeie-os para respostas HTTP em um lugar só e traduza-os em mensagens para o usuário na borda da UI. Não lance três tipos diferentes de erro dependendo do arquivo.
Se você gerar um app com Koder.ai, peça esses defaults desde o início: wiring explícito de módulos, config centralizada e um padrão único de erros.
Linhas claras entre UI, API e dados mantêm mudanças contidas. A maioria dos bugs misteriosos acontece quando uma camada começa a fazer o trabalho de outra.
Trate a UI (frequentemente React) como um lugar para renderizar telas e gerenciar estado só da UI: qual aba está aberta, erros de formulário, spinners de carregamento e manipulação básica de inputs.
Mantenha estado do servidor separado: listas buscadas, perfis em cache e tudo que deve bater com o backend. Quando componentes de UI começam a calcular totais, validar regras complexas ou decidir permissões, a lógica se espalha pelas telas e fica caro mudar.
Mantenha a camada de API previsível. Deve traduzir requisições HTTP em chamadas ao código de negócio e traduzir resultados de volta em formas de request/response estáveis. Evite enviar modelos de banco de dados diretamente pela rede. Respostas estáveis permitem refatorar internals sem quebrar a UI.
Um caminho simples que funciona bem:
Coloque SQL (ou lógica ORM) atrás de um limite de repositório para que o resto do app não “saiba” como os dados são armazenados. Em Go + PostgreSQL, isso geralmente significa repositórios tipo UserRepo ou InvoiceRepo com métodos pequenos e claros (GetByID, ListByAccount, Save).
Exemplo concreto: adicionar códigos de desconto. A UI renderiza um campo e mostra o preço atualizado. A API aceita code e retorna {total, discount}. O serviço decide se o código é válido e como descontos se combinam. O repositório busca e persiste as linhas necessárias.
Apps gerados podem parecer “prontos” rapidamente, mas é a estrutura que mantém mudanças baratas depois. Decida regras sem firulas primeiro, então gere só código suficiente para prová-las.
Comece com uma breve passagem de planejamento. Se usar o Koder.ai, o Planning Mode é um bom lugar para escrever um mapa de pastas e algumas regras de nomes antes de gerar qualquer coisa.
Depois siga esta sequência:
ui/, api/, data/, features/) e um punhado de regras de nomeação.CONVENTIONS.md e trate-o como um contrato. Quando a base cresce, mudar nomes e padrões de pastas fica caro.Checagem de realidade: se uma pessoa nova não consegue adivinhar onde colocar “editar contato” sem perguntar, a arquitetura ainda não é sem firulas o suficiente.
Pense em um CRM simples: página de lista de contatos e um formulário de edição de contato. Você constrói a primeira versão rápido e, uma semana depois, precisa adicionar “tags” aos contatos.
Trate o app como três caixas sem firulas: UI, API e dados. Cada caixa tem limites claros e nomes literais para que a mudança de “tags” permaneça pequena.
Um layout limpo poderia ser assim:
web/src/pages/ContactsPage.tsx e web/src/components/ContactForm.tsxserver/internal/http/contacts_handlers.goserver/internal/service/contacts_service.goserver/internal/repo/contacts_repo.goserver/migrations/Agora “tags” fica previsível. Atualize o esquema (nova tabela contact_tags ou uma coluna tags), depois toque uma camada de cada vez: repo lê/grava tags, service valida, handler expõe o campo, UI renderiza e edita. Não coloque SQL em handlers nem regras de negócio em componentes React.
Se o produto depois pedir “filtrar por tag”, você vai trabalhar principalmente em ContactsPage.tsx (estado da UI e query params) e no handler HTTP (parsing da requisição), enquanto o repo cuida da query.
Para testes e fixtures, mantenha as coisas pequenas e próximas ao código:
server/internal/service/contacts_service_test.go para regras como “nomes de tag devem ser únicos por contato”server/internal/repo/testdata/ para fixtures mínimasweb/src/components/__tests__/ContactForm.test.tsx para comportamento do formulárioSe você está gerando isso com Koder.ai, a mesma regra vale após a exportação: mantenha pastas sem firulas, nomes literais e as edições param de parecer arqueologia.
Código gerado pode parecer limpo no primeiro dia e ainda assim custar caro depois. O culpado usual não é “código ruim”, é inconsistência.
Um hábito caro é deixar o gerador inventar estrutura a cada vez. Uma feature chega com suas próprias pastas, estilo de nomes e helper functions, e você acaba com três maneiras de fazer a mesma coisa. Escolha um padrão, escreva-o e trate qualquer novo padrão como uma mudança consciente, não como default.
Outra armadilha é misturar camadas. Quando um componente UI fala direto com o banco, ou um handler de API constrói SQL, pequenas mudanças viram edições arriscadas por toda a app. Mantenha o limite: UI chama API, API chama serviço, serviço chama acesso a dados.
Abstrações genéricas usadas cedo demais também adicionam custo. Um “BaseService” ou “Repository” universal parece elegante, mas abstrações iniciais são palpites. Quando a realidade muda, você briga com seu próprio framework em vez de entregar.
Renomeações e reorganizações constantes são uma forma mais silenciosa de dívida. Se arquivos se movem toda semana, as pessoas deixam de confiar no layout e correções rápidas caem em lugares aleatórios. Estabilize o mapa de pastas primeiro, depois refatore em blocos planejados.
Por fim, cuidado com “código de plataforma” que não tem usuário real. Bibliotecas compartilhadas e ferramentas internas só valem a pena quando há uma necessidade repetida comprovada. Até lá, mantenha defaults diretos.
Se alguém novo abrir o repo, deve conseguir responder uma pergunta rápido: “Onde eu adiciono isso?”
Entregue o projeto a um colega (ou a você no futuro) e peça para adicionar uma pequena feature, como “adicionar um campo no formulário de signup”. Se ele não achar o lugar certo rapidamente, a estrutura não está cumprindo seu papel.
Cheque três lares claros:
Se sua plataforma suportar, mantenha um caminho de rollback. Snapshots e rollback são especialmente úteis quando você experimenta com estrutura e quer um jeito seguro de voltar.
A manutenibilidade melhora mais rápido quando você para de debater estilo e começa a tomar algumas decisões que permanecem. Escreva um pequeno conjunto de convenções que removam hesitação diária: onde os arquivos vão, como são nomeados e como erros e config são tratados. Mantenha curto o suficiente para ler em um minuto.
Depois faça uma única passada de limpeza para alinhar o código a essas regras e pare de reorganizar toda semana. Reorganizações frequentes tornam a próxima mudança mais lenta, mesmo que o código pareça melhor.
Se você está construindo com Koder.ai (koder.ai), vale a pena salvar essas convenções como prompt inicial para que cada nova geração caia na mesma estrutura. A ferramenta pode ser rápida, mas são os limites sem firulas que mantêm o código fácil de mudar.