Um guia consolidado de princípios, processos, arquitetura e boas práticas — da concepção do problema até o código em produção. Escrito para ser consultado, não decorado.
Baseado em anotações de estudo acumuladas ao longo da carreira (originalmente com foco em PHP/Laravel), reorganizadas e generalizadas para programação em geral.
Nota sobre a natureza deste documento: o que segue são escolhas pragmáticas de um autor específico, não verdades absolutas nem consenso universal da indústria. Em vários pontos existem escolas de pensamento divergentes igualmente válidas (ex.: quando aplicar SOLID, qual ordem seguir ao construir um sistema). Onde isso for especialmente relevante, o texto sinaliza explicitamente — mas a regra geral vale para o documento inteiro: trate como um ponto de partida sólido para pensar, não como dogma a seguir cegamente.
A ordem das seções segue duas regras:
- Dependência — se um conceito B usa um conceito A, então A aparece antes.
- Fundamento — quanto mais básico/atemporal, mais cedo aparece. Detalhes de framework específico vêm por último.
Este documento está dividido em múltiplas partes menores, por assunto, para facilitar consulta e edição:
- Fundamentos — Fase 0, Princípios fundamentais
- Resolução processo — Técnicas de resolução de problemas, Processo de desenvolvimento
- Arquitetura de dados — Arquitetura de software, Modelagem de dados
- SOLID e POO — Orientação a Objetos e SOLID
- Patterns e Cleancode — Design Patterns, Clean Code
- Contratos e testes — Contratos e tratamento de erros, Testes
- Web API — Web/framework, API/REST/CRUD
- Debug infra — Debug, Ferramentas de infraestrutura
- Antipatterns e postura — Anti-patterns, Postura profissional
- Resumo final — Super-resumo/colinha
Não existe uma norma universal obrigatória para isso — nem a ABNT a define (ela apenas exige consistência, deixando a escolha a critério de cada autor). Este documento adota a seguinte hierarquia própria, da mais forte para a mais fraca:
| Nível | Estilo | Uso |
|---|---|---|
| 1 (máximo) | MAIÚSCULAS + negrito | Regra crítica, risco de erro grave ou perda de dados |
| 2 | Negrito | Termo-chave sendo definido, ou ênfase de conceito importante |
| 3 | Itálico | Termo técnico/estrangeirismo na primeira menção, nomes de arquivo genéricos |
| 4 | Código |
Nomes literais: comandos, variáveis, trechos de código, paths |
| 5 (mínimo) | Sublinhado | Raro (baixo contraste em Markdown); reservado a casos raros de destaque secundário dentro de uma citação |
Racional: MAIÚSCULAS interrompe a leitura mais que qualquer outro recurso — por isso fica reservada a avisos onde o erro custa caro (ex.: NUNCA faça X). Negrito marca a "palavra que você buscaria com Ctrl+F". Itálico é mais suave, bom para nuance ou termo emprestado. Sublinhado tem baixo contraste em telas e é o recurso menos usado aqui, ao contrário do uso comum em documentos acadêmicos impressos.
NUNCA comece mexendo em código sem antes entender muito bem o quê precisa ser feito — e, acima de tudo, por quê (qual missão/objetivo cumprir). Na dúvida, pergunte antes de escrever a primeira linha.
Esse entendimento prévio também é o que permite estimar prazos com realismo. Se o tempo disponível parecer curto demais para a tarefa entendida, é hora de renegociar — não de cortar caminho silenciosamente.
Talvez a tarefa nem seja necessária, ou não valha o custo/benefício. Vale se perguntar isso antes de tudo.
O ciclo PDCA (Plan, Do, Check, Act) descreve como uma equipe distribui esforço entre planejar e executar. Vale conhecer as duas versões — a "correta" e a paródia comum — porque ilustram bem o erro mais frequente:
- PDCA (correto): Plan (planeja bastante) → Do (executa rápido) → Check (verifica eficácia) → Act (age para melhorar).
- "PDCA" apagando incêndio (armadilha comum, humor de equipe — não é um framework de verdade): Para que tanto planejamento? → Demora mais para executar → Dá uma conferida se está andando → Apaga incêndios e não sobra tempo pra pensar.
A segunda versão é o que acontece quando o planejamento é pulado — o "apagar incêndio" vira rotina. É contra isso que a Fase 0 existe.
Aproximadamente 80% do resultado vem de 20% do esforço. Antes de planejar como fazer, identifique qual fração do trabalho é essa — é nela que o foco deve estar.
Só então se define: linguagem, frameworks, versões, pessoas, ferramentas.
Organize-se com rascunhos, arquivos .todo/.task, mockups de tela, fluxogramas. Um algoritmo pode ser desenhado como diagrama de blocos antes mesmo de virar código:
Início → Entrada → Processamento → Saída → Fim
Ou, com decisão:
Início → Entrada → Processamento → Decisão? ─┬─ Sim → Saída A ─┐
│ ├→Fim
└─ Não → Saída B ─┘
Esta seção reúne os princípios que orientam toda decisão de código, na ordem em que devem ser aplicados: primeiro os que cortam o desnecessário, depois os que refinam a estrutura. Na prática, essas lentes se aplicam em paralelo, repetidamente, a cada decisão — a ordem abaixo é uma heurística de "por onde pensar primeiro", não um passo único a se dar uma vez por projeto.
"Você não vai precisar disso."
Valide a necessidade real antes de escrever qualquer código. Pergunte-se: preciso mesmo disso agora? Se a resposta for não, descarte a ideia — não implemente "para o futuro".
A solução mais simples que resolve o problema é, quase sempre, a melhor. Simplicidade não é preguiça — é a versão mais fácil de manter, testar e entender por outra pessoa (ou por você mesmo, em 6 meses).
SINE — Simple Is Not Easy: simples não quer dizer fácil de alcançar. Chegar a uma solução simples costuma exigir mais reflexão do que empilhar complexidade.
Repetiu um bloco de lógica? Unifique em uma função/módulo. Mas não é uma regra absoluta — veja a "Regra das Três Repetições", no arquivo de Design Patterns/Clean Code, para saber quando de fato vale a pena.
Prefira uma biblioteca madura e testada pela comunidade a escrever sua própria versão de algo já resolvido (parser de datas, validação de e-mail, criptografia, etc.) — exceto quando o objetivo explícito for aprender o funcionamento interno daquilo.
"Premature optimization is the root of all evil" — Donald Knuth, 1974.
A frase completa é: "We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%." — ou seja, o próprio Knuth já reconhecia que existe um 3% de casos (hot path conhecido, requisito de performance explícito desde o design) onde otimizar cedo é, sim, o certo a fazer. A versão curta virou clichê e costuma ser citada fora desse contexto.
Não otimize o que ainda não é um problema real de performance. A sequência correta (associada a Kent Beck, na tradição do Extreme Programming):
Make it work → make it right → make it fast. (Faça funcionar → faça certo → faça rápido — nessa ordem, nunca invertida.)
Guia prático de otimização, só quando já existir um problema de performance medido:
- Tenha um problema real de performance (não uma suposição).
- Meça antes de mexer em qualquer coisa.
- Ataque primeiro os 20% que geram 80% do ganho (estruturas de dados). Meça de novo.
- Faça profiling e corrija os pontos quentes (hot spots). Meça de novo.
- Analise memória e comportamento em baixo nível, se ainda necessário. Meça de novo.
O triângulo de trade-off a ter em mente: Performance, Velocidade (de entrega) e Adaptabilidade raramente são maximizados ao mesmo tempo — escolha conscientemente onde o projeto precisa estar.
Segurança de dados (validação de entrada, tratamento de exceção, controle de acesso) não é opcional nem para MVP. Ver arquivo 06 (Contratos e tratamento de erros).
SOLID entra depois que YAGNI/KISS/DRY já cortaram o supérfluo, e apenas quando o código já existe e mostra sinais reais de responsabilidades emaranhadas (tipicamente: 2+ classes/módulos já brigando por responsabilidade). Aplicado cedo demais ou com rigor excessivo em projeto pequeno gera abstração prematura (interfaces e injeções de dependência onde não fazem falta) — exatamente o oposto do que YAGNI tenta evitar.
Essa é uma posição pedagógica pragmática (boa para dev solo/times pequenos), não consenso universal — em times grandes, domínios regulados, ou TDD rigoroso desde o design, há quem defenda pensar em abstrações corretas desde cedo. Ambas as escolas são defensáveis; esta é a que serve melhor ao público deste documento.
Detalhes completos no arquivo 04 (Orientação a Objetos e SOLID).
1. YAGNI ..........: não precisa agora? não faz.
2. KISS ...........: mais simples que resolve.
3. DRY ............: repetiu 2x? considere unificar (mas veja "regra das 3x").
4. No-Reinvent ....: lib confiável > reescrever.
5. No-Premature-Opt: funciona → certo → rápido (nessa ordem).
6. SOLID ..........: só quando já há 2+ módulos reais brigando por responsabilidade.
7. Safety-First ...: sempre, em paralelo a tudo isso.
Ferramentas mentais para destravar quando um problema parece grande, confuso ou "impossível" — usadas antes ou durante a escrita de código, não depois.
O maior obstáculo costuma não ser falta de conhecimento técnico, mas o ponto de vista adotado. É fácil focar demais no "código pelo código" (visão concreta/específica) em vez de partir do par Problema → Solução (visão abstrata/ampla), usando código apenas como ferramenta para resolver algo maior.
Código é texto. O objetivo real é a mensagem/comportamento que ele produz — a linguagem, o framework e as palavras usadas são só o meio. Por isso: não "programe" — desenvolva uma solução, que por acaso é expressa em forma de código.
Sequência prática de abordagem a qualquer problema novo:
- Simplificar — leia e entenda a descrição por completo antes de tocar em código. Divida em partes menores e identifique os elementos-chave.
- Pesquisar — busque problemas e soluções semelhantes já documentados.
- Planejar — escreva pseudocódigo, esboce diagramas, faça um brainstorm de ideias.
- Implementar — escreva o código e teste com dados de exemplo.
- Refatorar — revise e simplifique depois que a solução já funciona.
- Documentar — registre decisões e mudanças para você mesmo (ou outros) no futuro.
Princípio da Iúca (do livro Design para quem não é Designer): dar um nome a um problema é o primeiro passo para enxergá-lo com clareza — depois de conscientizar-se da existência de um padrão, você passa a identificá-lo em todos os lugares. É assim que o cérebro funciona: por reconhecimento de padrões.
Ao travar em um problema, explique-o em voz alta (ou por escrito) para alguém — ou algo — que não tenha contexto nenhum sobre o assunto: um pato de borracha, um objeto inanimado, uma pessoa leiga. O ato de destrinchar o problema em partes mínimas, para que alguém sem contexto entenda, frequentemente revela a solução sozinho.
Funciona melhor por escrito. Um modelo simples:
RUBBER DUCK:
1. Descrição sucinta do problema
2. Itens suspeitos
3. Observações e pistas
4. Possíveis soluções (brainstorm)
Problemas grandes/complexos devem ser quebrados em partes menores e analisados do fim para o começo:
- Situação atual (problema) ......: "A"
- Situação ideal (objetivo) ......: "Z"
- Passo imediatamente antes de "Z": "Y"
- Passo imediatamente antes de "Y": "X"
- ... e assim por diante, até voltar em "A"
- Inverta a ordem: A → ... → X → Y → Z
Consequência prática: prefira várias funções pequenas, cada uma com uma única missão e um retorno claro (mesmo que seja só true/false), a poucas funções grandes fazendo tudo.
Não é um método formalmente reconhecido (mistura GTD, task breakdown e spec-first), mas é uma sequência útil para quem não vai criar suítes de teste automatizado para cada tarefa pequena:
1. Brainstorm : jogue todas as ideias, sem filtro.
2. Filtro ....: mantenha só o que realmente presta (aplique YAGNI aqui).
3. Checklist .: organize o que sobrou em itens acionáveis.
4. Priorização: ordene por dependência e/ou importância.
5. Execução ..: siga a checklist item a item, sem se perder no caminho.
Útil especialmente para desenvolvedor solo ou times pequenos, em MVPs, ou como "TDD leve" quando o overhead de testes formais não se justifica ainda. Um item da checklist deve "graduar-se" para teste automatizado real quando: a lógica envolvida é uma regra de negócio crítica, o código será tocado com frequência por várias pessoas, ou um bug ali já custou caro uma vez.
Prefira ciclos curtos de feedback (rodar, ver o resultado, ajustar) a longos períodos escrevendo "às cegas". Refatore ao longo do caminho — mas veja a ordem certa disso no arquivo 05 (Clean Code): refatorar durante a criação inicial tende a atrapalhar, não ajudar.
Como as técnicas acima se encaixam em um fluxo de construção de sistema completo, do zero à entrega.
1. Planeje O QUÊ fazer, depois COMO fazer (Fase 0).
2. Construa a estrutura do Frontend (sem estética ainda).
3. A partir do Frontend + Regras de Negócio, use BDD para definir o comportamento esperado e desenhar o Banco de Dados.
4. Integre tudo usando TDD para construir o Backend (especialmente os Models/regras de negócio).
5. Revise tudo: aplique a estética do Frontend, garanta Contratos (Interfaces, try/catch/finally) em todas as entradas/saídas.
6. Otimize — só agora, e só se necessário (ver No-Premature-Opt).
Esta é uma sequência válida para certos tipos de projeto (CRUD tradicional, aplicações internas, MVPs) — não é universal. Para arquiteturas API-first (comum quando há múltiplos consumidores: app mobile, SPA, integrações de terceiros), a ordem costuma ser inversa: modelar o domínio/banco primeiro, construir a API, e só depois o frontend consumir.
Esses passos podem ser aplicados ao sistema inteiro de uma vez, ou fatiados por domínio (ex.: todo o fluxo de "Usuário", depois "Financeiro", depois "Estoque").
Guia a criação do software a partir do comportamento esperado pelo usuário, e não da implementação técnica.
- Cria-se primeiro a interface visual (formulários, campos, botões, rótulos) — ainda sem funcionalidade real.
- A partir das regras de negócio, define-se o comportamento esperado: quais campos são obrigatórios, quais validações se aplicam (ex.: telefone só aceita números), máscaras, limites de caracteres.
- Implementa-se o mínimo de código necessário para que as validações funcionem, os dados cheguem ao backend, e o backend trate e registre esses dados no banco.
O uso do termo "BDD" aqui é uma adaptação livre. O BDD formal da literatura (Dan North, ferramentas como Cucumber/Gherkin) é sobre especificar comportamento em linguagem natural compartilhada entre negócio e dev antes de codificar (
Given/When/Then), não sobre "construir a UI primeiro". O que está descrito acima é mais próximo de um fluxo outside-in/UI-first. Vale a diferenciação para não confundir se um dia esbarrar no BDD formal.
Em ambiente de desenvolvimento web tradicional, uma técnica prática desse fluxo "UI-first": acesse diretamente a URL/rota que renderiza a tela que você quer trabalhar, pulando os menus e fluxos de navegação intermediários — apenas tenha em mente que isso pode deixar a página fora de seu contexto normal (fora de frames/layouts esperados), então alguns comportamentos podem não funcionar 100% como em produção.
Metodologia de desenvolvimento guiado por testes, onde o teste é escrito antes do código de produção. Ciclo clássico Red → Green → Refactor:
- Escreva o teste que verifica o comportamento esperado de um método.
- Rode o teste — ele falhará (o método/classe/arquivo ainda não existe).
- Crie o arquivo, a classe e o método (vazio).
- Rode de novo — falhará porque o método não retorna nada ainda.
- Faça o método retornar exatamente o que o teste espera (ainda sem lógica real — só para provar que o teste alcança o método corretamente).
- Rode de novo — deve passar (esse passo só confirma a comunicação teste↔método, não a lógica).
- Refatore: implemente a lógica real, com o mínimo de código necessário.
- Rode de novo. É esperado que falhe nessa hora, por erros de implementação.
- Corrija e repita os passos 7–8 até passar de verdade.
Atenção: o próprio teste pode conter erros — não assuma que "o teste passou" é sinônimo de "está certo". TDD deixa a escrita mais lenta no curto prazo, mas garante um código limpo e funcional no médio/longo prazo.
Não confie cegamente em métricas de cobertura de código: um teste pode "passar pelo" código sem de fato validar nada relevante, ou validar a coisa errada. Cobertura serve como guia geral (para não esquecer de testar algo), não como prova de qualidade.
Em ambientes sem suíte de testes automatizados, uma versão informal/temporária do TDD é usar um arquivo descartável (ex.:
teste.php,scratch.py) para instanciar a classe/função em construção e avaliar manualmente se o retorno é o esperado — sempre um pedaço de cada vez.
O CDD (visto na seção anterior) não substitui TDD/BDD — ele é o que preenche o espaço quando o custo de escrever testes formais para cada pequena tarefa não compensa (ex.: scripts pessoais, protótipos, tarefas de configuração). Trate-o como o degrau anterior ao TDD, não como concorrente dele.
O foco deve estar nas Entities (regras de negócio) antes de qualquer decisão técnica — linguagem, framework, banco de dados e servidor vêm depois, não antes.
┌────────────────────────────────────────────────┐
│ Frameworks & Drivers (Web, DB, Devices) │
│ ┌──────────────────────────────────────────┐ │
│ │ Interface Adapters (Controllers, │ │
│ │ Presenters, Gateways) │ │
│ │ ┌────────────────────────────────────┐ │ │
│ │ │ Use Cases (regras de aplicação) │ │ │
│ │ │ ┌────────────────────────┐ │ │ │
│ │ │ │ Entities (regras de │ │ │ │
│ │ │ │ negócio centrais) │ │ │ │
│ │ │ └────────────────────────┘ │ │ │
│ │ └────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────┘ │
└────────────────────────────────────────────────┘
As camadas externas dependem das internas — nunca o contrário. Um Use Case é um fluxo de nível de aplicação que utiliza as Entities e acessa interfaces externas para completar uma ação. Exemplo:
Realizar uma venda
1. Verificar disponibilidade no estoque (Entity: Produto)
2. Gerar Ordem de Pedido (regra de negócio)
3. Processar pagamento (regra: valor não pode ser < 1)
4. Emitir Nota Fiscal (regra: exige CPF)
5. Acionar envio (integração externa: Correios/transportadora)
Para a maioria dos projetos solo ou de time pequeno, um monolito modular (um único deploy, mas com módulos internos bem separados por domínio/responsabilidade) costuma ser a escolha mais pragmática — evita a complexidade operacional de microsserviços sem cair no problema do "monolito espaguete". Migrar módulos para serviços independentes depois, se e quando a escala exigir, é mais barato do que desfazer uma decisão prematura de microsserviços.
Isso é uma decisão de arquitetura por projeto, não uma regra universal — avalie escala esperada, tamanho do time e necessidade real de deploys independentes antes de escolher.
Mais uma filosofia/metodologia de organização do domínio do negócio do que uma técnica de programação em si. Foca em linguagem, modelagem e processos — não em código técnico. As Regras de Negócio devem ser encapsuladas nas Entidades (não em getters/setters "burros" — ver arquivo 05, Clean Code).
Os 3 pilares do DDD (esta é a "porta de entrada" do DDD, não o quadro completo — para aprofundar, ver Eric Evans ou Vaughn Vernon):
- Linguagem Ubíqua — vocabulário compartilhado entre devs e especialistas do domínio, evitando o "efeito Torre de Babel" (cada lado usando termos diferentes para a mesma coisa).
- Bounded Contexts — áreas específicas e delimitadas do sistema, cada uma com sua própria responsabilidade e, se necessário, seu próprio glossário de termos.
- Context Maps — representam como diferentes bounded contexts se relacionam entre si.
Linguagem do Negócio Linguagem dos Desenvolvedores
┌───────────────────┐ ┌──────────────────────┐
│ termos do domínio │◄─────────►│ termos técnicos que │
│ irrelevantes ao │ ZONA │ só os devs precisam │
│ sistema │ COMUM: │ saber │
│ │ Linguagem │ │
│ │ Ubíqua │ │
└───────────────────┘ └──────────────────────┘
Recomenda-se criar um glossário de termos do negócio — cada bounded context pode ter o seu próprio.
Padrão de arquitetura que isola camadas de um programa por responsabilidade, melhorando organização e segurança dos dados.
Nota sobre este arquivo: os exemplos de MVC abaixo usam a analogia de restaurante (papéis que já eram usados aqui). O restante do documento — especialmente SOLID — foi convertido para um universo único de loja física, unificando as analogias que antes misturavam obra (Pedreiro/Pintor) com restaurante. Ver arquivo 04.
Analogia de restaurante:
| Papel no restaurante | Camada MVC | Função |
|---|---|---|
| Cliente | User | Realiza solicitações (quer consumir algo) |
| Cardápio | View | Exibe as opções de ação disponíveis |
| Garçom | Controller | Recebe o pedido, intermedia, retorna a resposta |
| Cozinheiro | Model | Lida com os dados (prepara os pratos) |
| Dispensa | Banco de Dados | Armazena os ingredientes (dados) usados no processo |
| Receitas | Regras de Negócio | Determinam como as ações devem ser executadas |
Regras invioláveis do padrão:
- A View NUNCA executa uma ação — apenas exibe opções.
- O User NUNCA fala diretamente com o Model, nem vice-versa.
- O Controller NUNCA "cozinha" (não deve conter lógica de acesso a dados).
- O Model NUNCA trabalha sem seguir as regras de negócio.
Detalhes de implementação (Model, View, Controller, Migration, etc., como aparecem em frameworks reais) estão no arquivo 07 (Web: conceitos de framework).
TUDO, no fim das contas, gira em torno do Banco de Dados — entender bem o modelo de dados facilita muito saber "quem deve receber qual dado".
Antes de tocar em código, se possível, encontre e interprete o MER do projeto para compreender a relação entre tabelas e colunas envolvidas.
Cardinalidades comuns (nas pontas das linhas de relacionamento):
one to one ───┼───
one to many (obrig.) ───┼──◇
many ──────◇
one or more (obrig.) ───◇┼──
one and only one ───┼┼──
zero or one (opcional) ───○┼──
zero or many (opcional) ───○◇──
Exemplo de leitura: Company ──┼◇── Employee ──┼○── Projects descreve que uma empresa tem um-ou-mais funcionários (obrigatório), e um funcionário pode estar em zero-ou-mais projetos (opcional).
Evite criar funcionalidade de DELETE direto no código de aplicação (ou isole bem o seu uso a casos muito específicos e controlados). Prefira:
- Criar duas colunas em cada tabela relevante:
deleted_atedeleted_by(ou, com semântica mais clara,archived_at/archived_by). - Ao "deletar", faça um
UPDATEnessas colunas, marcando quando e quem arquivou o registro. - Em todos os
SELECTs da aplicação, adicione uma condição para não retornar registros com essas colunas preenchidas. - O registro físico continua existindo — pode ser consultado manualmente, restaurado (limpando as colunas), ou eliminado de vez (Hard Delete) apenas em casos muito específicos e deliberados (ex.: conformidade legal, LGPD/GDPR — dados pessoais às vezes precisam ser removidos fisicamente por lei, então "nunca fazer hard delete" tem essa exceção real).
Universo dos exemplos: esta seção usa uma loja física como analogia única para todos os princípios de SOLID — Cliente, Catálogo, Caixa, Estoquista, Depósito, Produto, meios de pagamento. Isso substitui a mistura anterior de "obra" (Pedreiro/Pintor/Encanador) com "restaurante" (usado só no MVC, arquivo 03), unificando o fio condutor mental do documento.
Um tipo de contrato que especifica quais métodos (ações) uma classe obrigatoriamente deve implementar — a descrição de quais ações um objeto deve ser capaz de realizar, sem se importar como ele as realiza por dentro.
interface MeioDePagamento {
// Interfaces só declaram assinaturas de método, nunca variáveis.
function cobrar(float $valor);
}
class Dinheiro implements MeioDePagamento {
function cobrar(float $valor) {
// abre a gaveta, calcula troco...
}
}
class CartaoDeCredito implements MeioDePagamento {
function cobrar(float $valor) {
// aciona a maquininha, aguarda aprovação...
}
}Quem chama cobrar() não precisa saber se é Dinheiro ou CartaoDeCredito — só precisa saber que, sendo um MeioDePagamento, o método existe e cobra.
Acrônimo criado por Michael Feathers para os 5 princípios de orientação a objetos e design de código formulados por Robert C. Martin ("Uncle Bob").
Uma classe deve ter uma única responsabilidade. Se houver mais de uma necessidade, crie classes específicas para cada uma.
// ERRADO — um único funcionário acumula funções que não deveriam ser dele:
class Caixa {
function receberPagamento() { /* ... */ }
function reporPrateleira() { /* ... */ } // isso é trabalho do Repositor
function fazerInventario() { /* ... */ } // isso é trabalho do Estoquista
}
// CERTO — cada um com sua responsabilidade:
class Caixa {
function receberPagamento() { /* ... */ }
function emitirNotaFiscal() { /* ... */ }
}
class Repositor {
function reporPrateleira() { /* ... */ }
function organizarGondola() { /* ... */ }
}
class Estoquista {
function fazerInventario() { /* ... */ }
function registrarEntradaMercadoria() { /* ... */ }
}Classes devem ser abertas para extensão, mas fechadas para modificação: deve ser possível adicionar comportamento novo sem editar o código-fonte já existente e testado. Isso normalmente se consegue com uma abstração (interface/classe abstrata) que o código cliente já depende, permitindo plugar novas implementações sem tocar em nada que já funciona.
// ERRADO — toda vez que surge um tipo novo de desconto, alguém precisa
// abrir esta função já existente e adicionar mais um "if", arriscando
// quebrar os cálculos que já estavam funcionando:
function calcularDesconto($tipo, $valor) {
if ($tipo === 'natal') {
return $valor * 0.90;
} elseif ($tipo === 'blackfriday') {
return $valor * 0.70;
}
// amanhã vem "aniversario", "primeira-compra"... e esta função
// só cresce, sendo reeditada e re-testada a cada novo caso.
return $valor;
}
// CERTO — a função/classe que orquestra o cálculo nunca precisa mudar.
// Cada novo tipo de desconto é uma CLASSE NOVA implementando o contrato,
// sem tocar em nenhum código já existente:
interface Desconto {
function aplicar(float $valor): float;
}
class DescontoNatal implements Desconto {
function aplicar(float $valor): float { return $valor * 0.90; }
}
class DescontoBlackFriday implements Desconto {
function aplicar(float $valor): float { return $valor * 0.70; }
}
// Adicionar um desconto novo no futuro = criar mais uma classe assim,
// sem editar CalculadoraDePreco nem as classes de desconto já existentes:
class DescontoAniversario implements Desconto {
function aplicar(float $valor): float { return $valor * 0.85; }
}
class CalculadoraDePreco {
function calcular(float $valor, Desconto $desconto): float {
return $desconto->aplicar($valor);
}
}Repare que CalculadoraDePreco está fechada para modificação (nunca precisa ser editada de novo) e aberta para extensão (novos descontos entram como classes novas). O exemplo do "S" (acima) mostrava várias classes cada uma com sua responsabilidade — aqui a ideia é outra: uma única abstração (Desconto) que absorve variação futura sem exigir edição do código já pronto.
Definição original de Barbara Liskov: se q(x) é uma propriedade provável do objeto x do tipo T, então q(y) também deve ser provável para o objeto y do tipo S, sendo S um subtipo de T.
Em outras palavras: uma classe filha nunca deve infringir os comportamentos ou contratos definidos pela classe base/interface que estende. Um objeto da subclasse deve poder substituir um objeto da classe pai em qualquer lugar do sistema, sem quebrar nada.
- Pré-condição: a subclasse não pode exigir mais do que a classe base exigia.
- Pós-condição: a subclasse não pode reduzir as garantias que a classe base oferecia após a execução do método.
- Invariância: a subclasse não pode alterar condições internas que a classe base mantinha constantes.
class Repositor {
function reporPrateleira() { /* ... */ }
function organizarGondola() { /* ... */ }
}
class RepositorSenior extends Repositor {
// Herda tudo de Repositor, sem alterar o que já existe,
// e acrescenta novas capacidades:
function conferirValidade() { /* ... */ }
function treinarNovoRepositor() { /* ... */ }
}Em qualquer lugar do sistema que espera um Repositor, um RepositorSenior pode entrar no lugar sem quebrar nada — ele nunca perde nenhuma capacidade do pai, só ganha novas.
Classes clientes não devem ser forçadas a depender de métodos que não usam. Prefira várias interfaces pequenas e específicas a uma única interface genérica e "gorda".
// ERRADO — obriga TODO Funcionario a operarCaixa(),
// mesmo quem nunca vai trabalhar no caixa:
interface Funcionario {
function baterPonto();
function operarCaixa();
}
class Estoquista implements Funcionario {
function baterPonto() { /* ... */ }
function operarCaixa() { /* ... */ } // forçado a implementar algo que não usa
}
// CERTO — segrega em interfaces menores e mais específicas:
interface Funcionario {
function baterPonto();
}
interface FuncionarioDeCaixa extends Funcionario {
function operarCaixa();
}
class Caixa implements FuncionarioDeCaixa {
function baterPonto() { /* ... */ }
function operarCaixa() { /* ... */ }
}
class Estoquista implements Funcionario {
function baterPonto() { /* ... */ } // só implementa o que de fato usa
}O "pulo do gato" aqui é focar sempre na abstração certa para o contexto, em vez de generalizar demais.
Classes de alto nível (que executam ações usando ferramentas) não devem depender diretamente de classes de baixo nível (a ferramenta específica) — ambas devem depender de uma abstração (interface) que as conecta. Abstrações não devem depender de detalhes de implementação; os detalhes é que devem depender das abstrações.
// ERRADO — o Caixa está amarrado a uma forma de pagamento concreta:
class Caixa {
private $maquininha;
function __construct() {
$this->maquininha = new MaquinaDeCartao(); // acoplamento direto e rígido
}
function finalizarVenda() {
$this->maquininha->cobrar();
}
}
// CERTO — depende da abstração, não da implementação concreta:
interface MeioDePagamento {
function cobrar(float $valor);
}
class Dinheiro implements MeioDePagamento {
function cobrar(float $valor) { /* ... */ }
}
class CartaoDeCredito implements MeioDePagamento {
function cobrar(float $valor) { /* ... */ }
}
class Pix implements MeioDePagamento {
function cobrar(float $valor) { /* ... */ }
}
class Caixa {
private $pagamento;
function __construct(MeioDePagamento $pagamento) {
$this->pagamento = $pagamento; // injeção de dependência via interface
}
function finalizarVenda(float $valor) {
$this->pagamento->cobrar($valor);
}
}Isso é a base tanto da Injeção de Dependência quanto do padrão Strategy (ver arquivo 05): passar uma interface para o construtor permite que a classe aceite qualquer implementação daquele contrato, com flexibilidade máxima.
Um conjunto de exercícios/regras (Jeff Bay, no livro The ThoughtWorks Anthology, 2008) que, seguidas, tendem a produzir naturalmente um código alinhado com SOLID:
- Apenas um nível de indentação por método.
- Nunca use
else(prefira early return/cláusulas de guarda). - Encapsule tipos primitivos e strings que tenham comportamento próprio em objetos dedicados (ver nota abaixo).
- Envolva coleções em classes próprias, em vez de manipular arrays "nus" espalhados pelo código.
- Use no máximo um ponto por linha (Lei de Demeter) — evite encadeamentos longos tipo
$a->b->c->d. - Não abrevie nomes de variáveis e métodos.
- Mantenha entidades pequenas (idealmente até ~50 linhas; impraticável como regra rígida, mas passar de 100 linhas já é sinal de alerta).
- Evite classes com mais de duas variáveis de instância (regra ideal, raramente seguida à risca — usar como referência de "está inchando", não como lei).
- Evite getters/setters "burros" e indiscriminados (ver abaixo).
Sobre o item 3 — CPF, CNPJ, e-mail, taxas e preços não são tipos primitivos "de verdade" porque carregam comportamento e regras próprias (é essencialmente o que DDD chama de Value Object):
- CPF/CNPJ/e-mail precisam seguir um formato (regex), com validação específica.
- Taxas e preços precisam ser não-negativos, sofrer arredondamento correto, respeitar limites (ex.: desconto não pode superar o valor do produto).
- Usar
floatpara valores monetários é arriscado: quase toda linguagem tem imprecisão de ponto flutuante em casas decimais — prefira tipos decimais exatos ou inteiros representando centavos.
Getters/Setters sem nenhuma lógica de validação deixam a classe anêmica — ela vira um mero "saco de dados" sem regras, o que é o oposto do que DDD e SOLID pregam.
// ERRADO — setter "burro", sem nenhuma proteção:
class ProdutoErrado {
private $preco;
function getPreco() { return $this->preco; }
function setPreco($preco) { $this->preco = $preco; } // aceita qualquer coisa
}
$produto = new ProdutoErrado();
$produto->setPreco(-50); // permite valor inválido!
echo $produto->getPreco(); // saída: -50
// CERTO — setter que protege a integridade dos dados:
class ProdutoCerto {
private $preco;
function getPreco() { return $this->preco; }
function setPreco($preco) {
if ($preco < 0) {
throw new \InvalidArgumentException("O preço não pode ser negativo.");
}
$this->preco = $preco;
}
function aplicarDesconto($percentual) {
if ($percentual < 0 || $percentual > 100) {
throw new \InvalidArgumentException("Percentual de desconto inválido.");
}
$this->setPreco($this->getPreco() - ($this->getPreco() * ($percentual / 100)));
}
}Soluções nomeadas para problemas recorrentes de design — facilitam manutenção, reuso e comunicação entre desenvolvedores (todos sabem o que "é um Factory" ou "é um Observer" sem precisar reexplicar).
Esta é uma curadoria dos padrões mais usados no dia a dia — não a lista completa. A obra de referência é "Design Patterns: Elements of Reusable Object-Oriented Software" (Gamma, Helm, Johnson, Vlissides — "Gang of Four", 1994), com 23 padrões catalogados. Outros muito comuns em código real que não entram aqui: Decorator, Command, Template Method, State, Chain of Responsibility.
- Singleton — garante uma única instância global de algo que não deveria ter mais de uma (ex.: a conexão com o sistema de caixa central da loja). Cuidado: dificulta testes automatizados por criar estado global oculto.
- Builder — para objetos complexos com parâmetros opcionais e construção em etapas sequenciais (ex.: montar um
PedidoDeCompracom item, quantidade, desconto opcional, forma de entrega opcional). Na prática, encadeia chamadas de setters de forma organizada, com valores padrão sensatos. - Factory — uma "fábrica" que decide qual subtipo concreto instanciar (ex.: uma
FabricaDeDescontoque recebe o tipo e devolveDescontoNatal,DescontoBlackFriday, etc. — reaproveitando o exemplo do Open-Closed, arquivo 04), substituindo umswitch/if-elseespalhado pelo código chamador por uma única função responsável por essa decisão.
- Facade — expõe uma interface simples ("Finalizar Venda") escondendo toda a orquestração complexa por trás (verificar estoque, calcular desconto, processar pagamento, emitir nota fiscal), sem que o Caixa precise conhecer os detalhes de cada etapa.
- Adapter — intermediário que traduz entre duas interfaces incompatíveis. Exemplo: a loja troca de fornecedor de maquininha de cartão, e a API nova devolve os dados em um formato diferente da antiga — um
AdapterMaquininhatraduz a resposta nova para o formato que oCaixajá espera, sem precisar alterarCaixa.
- Strategy ⭐ — provavelmente o mais versátil de todos. Assim como no princípio da Inversão de Dependência (arquivo 04), o construtor recebe uma interface, permitindo que a classe funcione com qualquer implementação daquele contrato, escolhida em tempo de execução — é exatamente o que
MeioDePagamentojá demonstrou. Evita mais umswitch/if-elsegigante — dessa vez sobre comportamento, não sobre tipo de objeto. - Observer — uma classe emite uma notificação, e apenas os "observadores" cadastrados para aquele evento são acionados. Exemplo: quando o
Estoquistaregistra que um produto bateu o estoque mínimo, umObserveravisa automaticamente o setor de compras — sem que o código de baixa de estoque precise saber quem mais depende desse evento. Cuidado: notificações que disparam outras notificações podem gerar um loop exponencial se não houver controle.
As dependências de uma classe são "injetadas" nela via construtor (ou, às vezes, via métodos setter), em vez de serem instanciadas internamente. Consequência direta do princípio "D" do SOLID — ver exemplo de código no arquivo 04 (Caixa recebendo MeioDePagamento).
Cuidado com as técnicas de "Clean Code": não exagere, e não as aplique onde não fizer diferença real. É fácil cair na otimização prematura de legibilidade — fragmentar e abstrair tanto que o código fica mais difícil de acompanhar do que antes.
O objetivo é tornar o código legível e compreensível, não apenas "limpo" no sentido de estar excessivamente fatiado ou abstrato. Um código claro permite que você e outras pessoas entendam rapidamente o que ele faz e como se comporta.
Código "sujo":
- Excesso de
if/elseaninhado ("código hadouken" — cada nível de indentação parecendo um soco em uma sequência de luta). - Blocos de código repetidos em várias partes do sistema.
- Lógica difícil de identificar por misturar várias responsabilidades no mesmo lugar.
Código excessivamente abstrato:
- Interfaces, classes e funções fatiadas em partes tão pequenas que o contexto geral se perde.
- Necessidade de "lembrar de cabeça" como todas as peças se conectam, dificultando manutenção.
Siga a lógica do TDD: deixe o código "sujo" surgir primeiro, resolvendo o problema. Só depois de funcionar é que fica claro o que realmente precisa de refino.
- Não tenha medo do rascunho inicial imperfeito — é o que permite ajustar e refinar depois, como um desenho a lápis sendo corrigido com a borracha.
- "Seja como a água" (Bruce Lee) — a água se adapta às condições, mas só depois que existe um caminho por onde fluir. No código, a adaptação (refino) só faz sentido depois que existe algo escrito para adaptar.
- Regra das três repetições (variação do "Rule of Three", associada a Martin Fowler/Don Roberts): se um bloco de código se repete três vezes, é hora de transformá-lo em função. Duas vezes ainda pode ser cedo demais — abstrair algo que ainda está em evolução tende a gerar a abstração errada.
- Evite refatorar durante a criação inicial: enquanto ainda está resolvendo o problema, foque em resolvê-lo. Refatorar antes da hora cria complexidade desnecessária.
Na Programação Orientada a Objetos, um Contrato é o uso de normas pré-determinadas para garantir que uma classe/função/método:
- Recebe os parâmetros necessários para funcionar.
- Realiza o que se propõe a fazer.
- Retorna a resposta esperada, no formato esperado.
Isso é literalmente um contrato — um acordo entre duas partes: o usuário se compromete a fornecer dados válidos, e o programa se compromete a realizar o trabalho esperado e devolver os dados de resposta corretos.
Exemplos comuns de contrato: Interfaces (ver arquivo 04) e try/catch/finally, além de cláusulas de guarda (condicionais no início de uma função que interrompem a execução com um early return caso alguma entrada esteja vazia ou inválida).
O uso de "contrato" aqui é informal/didático. Na literatura formal existe Design by Contract, termo técnico cunhado por Bertrand Meyer (linguagem Eiffel) com pré-condições, pós-condições e invariantes rigorosamente especificados — bem mais formal do que o uso deste documento. Vale a diferenciação para não confundir se um dia esbarrar no termo técnico.
Estrutura que lida com erros que podem ocorrer durante a execução de um bloco de código — sejam eles erros de codificação ou causados por entrada inválida do usuário.
try→ define o bloco de código a ser monitorado quanto a erros durante a execução.catch→ define o que fazer se um erro ocorrer dentro dotry.finally→ executa sempre, independentemente do resultado dos blocos anteriores.
catchefinallysão individualmente opcionais, mas ao menos um dos dois é obrigatório ao usartry.
Isso vale para PHP, Java, C# e JavaScript — mas o mecanismo de tratamento de erros não é universal entre linguagens. Python aceita
try/finallysemexcept, e ainda tem a cláusula extraelse. Go não tem try/catch: usa retorno de erro explícito como segundo valor de retorno. Rust usa o tipoResult<T, E>em vez de exceções. O conceito de "contrato" por trás continua valendo, mas a sintaxe e a filosofia mudam bastante de ecossistema para ecossistema.
try {
$caixa->abrirGaveta();
$resultado = $pagamento->cobrar($valor);
} catch (\PagamentoRecusadoException $e) {
registrarErro($e->getMessage());
throw new \RuntimeException("Falha ao processar o pagamento.", 0, $e);
} finally {
$caixa->liberarParaProximoCliente(); // executa sempre, mesmo se o catch relançou a exceção
}Fundamentais (unitários ou não) para garantir o bom funcionamento contínuo de um sistema — especialmente ao evoluí-lo depois de já estar em produção.
- Unitários — testam uma unidade isolada (função/método), sem dependências externas reais.
- Integração — testam a comunicação entre unidades/módulos reais (ex.: código + banco de dados real).
- HTTP/API — simulam requisições reais contra os endpoints da aplicação.
- Browser/E2E — automatizam um navegador real, simulando um usuário navegando pela interface.
- Mocking — substitui dependências reais por versões falsas/controladas durante o teste, sem afetar dados reais.
- Mock — simula uma dependência e verifica se ela foi chamada corretamente (foco na interação/entrada).
- Stub — simula uma dependência apenas para retornar um valor pré-definido (foco na saída).
- Spy — combina os dois: registra chamadas e também pode retornar valores controlados.
- AAA (Arrange, Act, Assert) — organize cada teste em três blocos claros: prepare o cenário, execute a ação, verifique o resultado.
- SUT (System Under Test) — o "objeto" (classe/função) que está sendo especificamente testado naquele teste, para deixar claro o que é foco e o que é apoio/dependência.
- Small Commits — commits pequenos e frequentes facilitam revisão e reversão, e casam bem com o ciclo Red-Green-Refactor do TDD.
Esta seção descreve conceitos genéricos que aparecem, com nomes quase idênticos, em praticamente qualquer framework web maduro (Laravel, Spring Boot, ASP.NET Core, Django, Rails etc.); o foco é entender o conceito, independente de qual framework o implementa.
Camada responsável por interagir com o banco de dados (CRUD) e conter as regras de negócio. Convenção comum: cada tabela do banco tem um Model correspondente (ex.: tabela pessoas → Model Pessoa, singular). Em arquiteturas mais rígidas (Clean Architecture/DDD), o Model puro de banco é separado da Entity que carrega as regras de negócio.
Camada de exibição — o que o usuário final vê. Não deveria conter nenhuma lógica de ação (função/método); apenas invoca o Controller responsável quando o usuário interage. A maioria dos frameworks fornece uma engine de templates própria (ex.: Blade no Laravel, Razor no ASP.NET, Thymeleaf no Spring) para misturar HTML com dados dinâmicos de forma mais segura e legível do que concatenação manual de strings.
Camada intermediária: recebe a requisição do usuário, decide o quê deve ser feito (chamando o Model necessário) e para qual View redirecionar em cada caso — incluindo verificar se a ação é permitida (ver Middleware, Authorization abaixo).
Mecanismo que intercepta e filtra requisições antes de chegarem ao Controller (ou depois, antes da resposta sair), útil para questões transversais: autenticação, autorização, logging, limitação de taxa, etc. Roda "no meio do caminho" — daí o nome.
Mapeamento entre um caminho de URL (e método HTTP) e o comportamento que deve ser executado. Exemplo conceitual: acessar /login aciona o Controller de autenticação, que pode, por exemplo, forçar o encerramento de qualquer sessão ativa antes de exibir a tela de login.
Toda rota/Controller deveria retornar uma resposta clara ao cliente: um objeto (geralmente JSON, em APIs), uma View renderizada, ou um redirecionamento.
Mecanismo para armazenar informações do usuário entre requisições HTTP (que são, por natureza, sem estado/stateless). Pode ser persistida em arquivo, cookie, banco de dados, cache (Redis) ou até em memória/array, dependendo da necessidade de escala e persistência.
Conjunto de regras pré-definidas para validar dados recebidos em um formulário/requisição antes de processá-los — ex.: garantir que um campo "telefone" contenha apenas números.
Um dos recursos mais importantes de qualquer stack madura: funciona como um controle de versão do schema do banco de dados, permitindo salvar, restaurar e compartilhar definições de tabelas/colunas entre membros da equipe (e até versionar isso em Git). Essencial, por exemplo, ao adicionar uma nova coluna e precisar propagar essa mudança para todos os ambientes.
Recurso para popular o banco com dados fictícios/aleatórios em formato controlado — útil para desenvolvimento e testes.
Definem modelos/padrões de dados para "fabricar" registros falsos em massa, tipicamente usados junto com Seeding e testes automatizados. (Não confundir com o Factory Design Pattern, visto no arquivo 05 — o nome é coincidente, mas o propósito é diferente.)
Classe que organiza a lógica de autorização referente a um Model/recurso específico — ex.: uma PostPolicy decidindo quem pode editar ou apagar uma postagem de blog.
- Authentication (Autenticação) — confirma quem é o usuário (login).
- Authorization (Autorização) — define o que esse usuário tem permissão de fazer, mesmo já autenticado. Exemplo: o usuário está logado e pode comprar, mas não pode alterar preços de produtos.
Camada que mapeia tabelas do banco para objetos/classes na linguagem de programação, evitando escrever SQL manual repetidamente e reduzindo risco de SQL Injection por usar parâmetros preparados internamente. Geralmente inclui suporte nativo a relacionamentos entre tabelas (1:1, 1:N, N:N).
Interface fluente para montar consultas ao banco programaticamente (em vez de strings SQL cruas), geralmente construída sobre um driver nativo de acesso a dados da linguagem, o que ajuda a prevenir injeção de SQL.
Recurso para dividir um resultado de consulta muito grande em "páginas" menores, evitando sobrecarregar tempo de resposta, memória do servidor e do navegador.
Redis é um exemplo popular de banco de dados em memória (volátil por padrão, mas pode ser configurado para persistir em disco), usado tipicamente para cache, filas e dados de sessão que precisam de leitura/escrita extremamente rápida.
Mecanismo para enfileirar tarefas ("jobs") que serão processadas de forma assíncrona — em um horário definido, após certo tempo, ou disparadas por um evento — sem bloquear a resposta imediata ao usuário. Exemplo clássico: enviar um e-mail de confirmação sem fazer o usuário esperar o envio terminar.
Combinação com cache que limita a quantidade de ações de um usuário dentro de um período. Exemplo: bloquear envio de mais de 5 mensagens por minuto, evitando abuso ou "clique nervoso" repetido.
Permitem desacoplar partes do código: um evento (ex.: "pagamento recebido") pode ter um ou mais listeners que reagem a ele (ex.: exibir um alerta, disparar um e-mail). Conceitualmente equivalente ao padrão Observer (arquivo 05), aplicado em nível de framework.
Uso de WebSockets (ou tecnologia equivalente) para atualizar a interface do usuário em tempo real, sem exigir que ele recarregue a página. Exemplo: alertar automaticamente que um pagamento foi confirmado, mesmo que a confirmação bancária demore alguns minutos.
Suporte a múltiplos idiomas para os textos exibidos ao usuário, tipicamente armazenados em arquivos de tradução organizados por idioma/chave.
- Hashing — transformação unidirecional (o valor original não é recuperável, apenas comparável). Esta descrição se refere especificamente a hashing para senhas/segurança (bcrypt, Argon2). Hashing em sentido amplo é um conceito mais abrangente da ciência da computação — usado também em hash tables, checksums, indexação — sem essa propriedade de irreversibilidade como objetivo.
- Encryption — transformação bidirecional (cifra/decifra) usada para proteger dados que precisam ser recuperados depois, geralmente sobre uma biblioteca criptográfica madura do sistema operacional/linguagem (nunca implementada do zero — ver princípio No-Reinvent, arquivo 01).
Camada de transformação entre os Models/Entities internos e o formato de resposta da API (tipicamente JSON), controlando exatamente quais campos são expostos externamente — evitando vazar campos internos sensíveis sem querer.
A maioria dos frameworks modernos oferece uma ferramenta de linha de comando para gerar automaticamente o conjunto de arquivos relacionados a um recurso (Model, Migration, Controller, testes, etc.) de uma vez, poupando trabalho repetitivo de "boilerplate".
CRUD é a sigla para as quatro operações básicas de manipulação de dados: Create, Read, Update, Delete.
REST (REpresentational State Transfer) é um estilo arquitetural onde essas operações são mapeadas de forma padronizada sobre os métodos HTTP:
| Método | Operação | Idempotente? | Descrição | Exemplo |
|---|---|---|---|---|
POST |
Create | Não | Cria um registro novo, do zero | POST /users |
GET |
Read | Sim | Busca todos os registros, ou um específico por ID. Não altera estado (método seguro) | GET /users/123 |
PUT |
Update (completo) | Sim | Substitui todos os campos de um registro; campos omitidos costumam ser apagados/resetados | PUT /users/123 |
PATCH |
Update (parcial) | Não* | Altera apenas os campos enviados, mantendo o resto intacto | PATCH /users/123 com {"email": "novo@x.com"} |
DELETE |
Delete | Sim | Remove o registro; requisições futuras ao mesmo ID tendem a retornar 404 |
DELETE /users/123 |
*PATCH normalmente é tratado como não-idempotente na prática, embora a especificação HTTP não exija isso estritamente — depende de como o servidor implementa a operação parcial.
"Método seguro" (GET não altera estado) é a especificação REST, não uma garantia técnica automática — nada impede alguém de escrever um endpoint GET que grava dados no banco (má prática, mas acontece em sistemas mal projetados). Cabe ao desenvolvedor honrar essa convenção; o protocolo HTTP não a impõe.
Idempotente significa: repetir a mesma requisição várias vezes produz o mesmo resultado final da primeira vez (não gera efeitos colaterais cumulativos).
Sobre DELETE: ver a técnica de Soft Delete já descrita no arquivo 03 (Modelagem de dados) — evite implementar remoção física direta como ação padrão.
Uma conexão padronizada entre programas/computadores. Segue o mesmo princípio de contrato do Model no MVC, mas aplicado entre cliente e servidor, com regras rígidas sobre o que é permitido enviar/receber.
Analogia simples: o cliente faz um pedido (Client), o garçom (API) leva o pedido até a cozinha (Servidor) e traz de volta a resposta — sem que o cliente precise saber como a cozinha funciona por dentro. (Esta analogia reaproveita o universo do restaurante, usado no MVC — arquivo 03 — por já estar consolidada e ser amplamente reconhecida nesse contexto específico de API.)
Sequência recomendada ao investigar um problema em uma aplicação web:
- Abra as DevTools do navegador na tela em questão e verifique:
- Console → há algum erro relevante reportado?
- Network → clique na requisição desejada e confira:
- Headers: URL, método (GET/POST/...), código de status:
200 OK— sucesso.404 Not Found— verifique se o caminho/rota está correto.500 Server Error— verifique o log de erros do servidor.
- Request Headers: o
Referermostra qual URL "pai" chamou essa requisição. - Payload: os parâmetros e valores enviados são os esperados?
- Headers: URL, método (GET/POST/...), código de status:
- Sources → use breakpoints (via
debuggerno JavaScript) para caminhar pela execução passo a passo.
- Ative o log de queries do banco (extrato de versão/query log), copie a consulta gerada e rode-a diretamente no gerenciador de banco de dados para confirmar o resultado retornado.
- Use o debugger da IDE (ex.: Xdebug para PHP) com breakpoints no backend, acompanhando se a sequência de execução e os valores das variáveis batem com o esperado.
- Se ainda não resolver:
- Tente a técnica do Rubber Duck (ver arquivo 02).
- Pesquise por uma situação semelhante já documentada.
- Peça ajuda a um colega ou responsável técnico.
Boa prática: ative todos os debuggers relevantes antes de começar a alterar o código, em todos os pontos que vão participar da investigação (frontend, backend, banco), em vez de ativá-los um de cada vez conforme o problema aparece.
Não fique preso a assistir tutoriais em excesso — teoria sem prática não avança conhecimento real; só a prática consolida. Feito é melhor que perfeito. A pergunta a se fazer é: você quer se tornar um pesquisador ou um criador de soluções?
- Sempre informe qualquer possível melhoria identificada, mesmo fora do escopo da tarefa atual.
- Se encontrar um bug que impede o progresso da tarefa atual, resolva-o dentro da mesma tarefa. Se for um bug não relacionado, registre uma observação para investigação futura, sem se desviar do foco.
- Se um teste reportar um problema que você não conseguiu confirmar, documente sua análise na própria tarefa e avance — evite insistir repetidamente com quem testou, para não consumir tempo de ambos desnecessariamente.
Sistema gerenciador de containers, alternativa mais leve e ágil a máquinas virtuais tradicionais.
- Imagem — "planta" imutável que serve de base para criar um container (equivalente à planta de uma casa, em relação à construção física).
- Container — processo individual (podendo conter um SO, linguagem, banco, etc.), que pode ser totalmente independente de outros containers ou se comunicar com eles.
Dockerfile— arquivo "manifesto" com as definições de como construir a imagem.docker-compose.yaml— arquivo que define e orquestra vários containers de uma vez (a partir de seus respectivos Dockerfiles), incluindo a comunicação entre eles.
Comandos essenciais:
docker ps -a ...................................................................... # lista todos os containers (ativos ou não)
docker build ...................................................................... # constrói uma imagem a partir do Dockerfile
docker run --rm --name <nome> -dp <porta_local>:<porta_container> <imagem> tail -f
docker exec -it <container> bash .................................................. # entra em um container ativo, em modo interativo
docker rm -f $(docker ps -a -q) ................................................... # remove TODOS os containers, rodando ou nãoConvenção comum: nos comandos, o que fica à esquerda dos dois-pontos (
:) se refere à sua máquina; o que fica à direita se refere ao container.
Ferramentas de CI/CD (ex.: GitHub Actions, GitLab CI, Jenkins) automatizam a construção, teste e implantação do código a cada mudança, trazendo benefícios como:
- Reduzir "bugs inocentes" e conflitos entre uploads de diferentes desenvolvedores.
- Diminuir a chance de erro humano ao subir uma nova versão para produção.
- Permitir acompanhamento de métricas e histórico de builds.
- Rodar checkout automático, testes e relatórios a cada push, garantindo que o código permaneça sempre em estado "implantável".
Sinais recorrentes de problema estrutural no código — vale reconhecer o nome para reconhecer o padrão (ver Princípio da Iúca, arquivo 02):
| Anti-pattern | Descrição rápida |
|---|---|
| God Class | Uma única classe acumula responsabilidades demais (viola SRP) |
| Speculative Generality | Abstração criada "para o futuro", sem necessidade real (viola YAGNI) |
| Shotgun Surgery | Uma mudança pequena exige alterar muitos arquivos/módulos diferentes |
| Divergent Change | Uma única classe precisa mudar por vários motivos não relacionados |
| Duplicate Code | Lógica repetida em múltiplos lugares (viola DRY) |
| High Coupling | Módulos excessivamente dependentes uns dos outros |
| Long Parameter List | Função com parâmetros demais, difícil de chamar e entender |
| Primitive Obsession | Uso de tipos primitivos onde um objeto com comportamento seria mais apropriado |
| Improper Instantiation | Objetos criados no lugar errado da arquitetura, sem passar pelas camadas devidas |
| Test code in production | Código de teste/mocking vazando para o ambiente de produção |
| Bad naming | Nomes de variáveis/funções que não comunicam sua real intenção |
| Blank Lines (excesso) | Espaçamento inconsistente ou exagerado, prejudicando a leitura |
Complementos práticos ao dia a dia de trabalho em equipe, além da técnica pura:
- Aumente a atenção a detalhes, especialmente os básicos e os padrões já estabelecidos do sistema — controle a ansiedade e a velocidade de leitura das tarefas para não pular pontos importantes.
- Acompanhe indicadores/KPIs relevantes ao seu trabalho, para saber onde focar a melhoria.
- Ao registrar decisões ou mudanças em uma wiki/documentação compartilhada, resuma com suas próprias palavras e peça revisão de quem definiu a tarefa, para confirmar entendimento mútuo.
- Consulte a documentação do sistema/projeto antes de assumir como algo funciona.
Se restar só um minuto para relembrar tudo, é isto:
- PENSE → Fase 0: entenda a missão antes de tocar em código.
- ANTES → Princípios de corte (YAGNI/KISS/DRY) vêm antes de refino (SOLID).
- TESTE → BDD define comportamento, TDD garante que funciona, CDD cobre o resto.
- SEMPRE → Safety-First e Contratos (try/catch, validação) não são opcionais.
═════════════════════════════════════════════════════════════════════════════════════════════
MISSÃO:
1. Entenda o QUÊ e o PORQUÊ antes do COMO.
2. Teve dúvida? Pergunte!
3. Não estimou direito? Renegocie o prazo.
---------------------------------------------------------------------------------------------
PROCESSO (nessa ordem geral de execução):
1. Frontend (estrutura, sem estética)
2. BDD (comportamento esperado)
3. Banco de Dados
4. TDD (Red-Green-Refactor)
5. Backend/Models
6. Revisão + Estética + Contratos
7. Otimização (só se necessário e medido)
CDD (substitui TDD só quando o formal não compensa):
1. brainstorm
2. filtro
3. checklist
4. ordem
5. executa
LOOP: feedback curto, refatora por ÚLTIMO, não durante
---------------------------------------------------------------------------------------------
PRINCÍPIOS (ordem de aplicação — corte primeiro, refino depois):
YAGNI ..........: não precisa AGORA? não faz.
KISS ...........: mais simples que resolve (SINE: simples ≠ fácil)
DRY ............: repetiu 3x? aí sim, unifica.
No-Reinvent ....: lib confiável > reescrever.
No-Prematuro-Opt: funciona → certo → rápido (nessa ordem)
SOLID ..........: se 2+ módulos disputarem responsabilidade (senão = abstração prematura)
Safety-First ...: sempre, em paralelo a tudo.
---------------------------------------------------------------------------------------------
SOLID (refino estrutural):
S: 1 classe = 1 responsabilidade
O: aberta a estender, fechada a modificar
L: filha nunca quebra o contrato da mãe
I: interfaces pequenas > 1 interface gorda
D: dependa de abstração (interface), não de implementação
---------------------------------------------------------------------------------------------
QUANDO TRAVAR:
Rubber Duck ........: explique em voz alta/por escrito
Engenharia Reversa .: Z (objetivo) até A (hoje), depois inverta
Dar nome ao problema: nomear = começar a enxergar o padrão
---------------------------------------------------------------------------------------------
SEMPRE ATIVO:
Contratos .......: recebe certo, faz certo, retorna certo
try/catch/finally: finally SEMPRE roda (ex: liberar caixa)
Soft Delete .....: nunca DELETE físico direto; marque e filtre
MVC .............: View nunca age; User nunca fala com Model direto
═════════════════════════════════════════════════════════════════════════════════════════════
MAIÚSCULAS+negrito > negrito > itálico > `código` > sublinhado
(crítico) (chave) (nuance) (literal) (raro)
Fim do manifesto. Documento vivo — revisite e ajuste conforme a experiência acumular. As anotações originais que deram origem a este documento contêm, por si, o próprio conselho mais importante: feito é melhor que perfeito — não deixe este manifesto virar, ele mesmo, uma forma de procrastinação.