Introdução
A restrição já está posta pelo artigo: a Lei da Mudança Contínua de Lehman (1974) — todo sistema em uso real será solicitado a mudar, indefinidamente, e a única pergunta em aberto é se essa mudança sai barata (alteração localizada) ou cara (reescrita). Os três princípios desse cluster não impedem a mudança — não tem como impedir que ela chegue —, eles atacam a fonte de onde ela vem, cada um numa direção diferente: #8 blinda contra mudança que vem de fora do sistema (o ecossistema de frameworks), #11 blinda contra mudança que vem das pessoas ao redor do sistema (atores com pedidos concorrentes), e #9 blinda contra mudança que vem do próprio domínio crescendo além do que foi validado como seguro.
#8 Distância do Framework
A base conceitual é o que Robert C. Martin ("Uncle Bob") formaliza em Clean Architecture (2017): frameworks pertencem ao anel mais externo da arquitetura — são um detalhe de entrega, não o centro do sistema. A regra de negócio não deveria importar nada do framework; é o framework que deveria se adaptar pra rodar a regra de negócio (a Inversão de Dependência aplicada em escala arquitetural, não só entre duas classes). Martin propõe um teste simples pra isso, conhecido como "screaming architecture": olhando só a estrutura de pastas de um projeto, ela deveria gritar o domínio do negócio — "isso é um sistema de compliance fiscal" — e não gritar o framework — "isso é um projeto ASP.NET" ou "isso é um projeto Angular".
O caso do AngularJS, é essa falha na prática: equipes que escreveram regra de negócio usando construtos específicos do AngularJS ($scope, watchers, diretivas com lógica de domínio embutida) não enfrentaram só um problema de "trocar de framework" quando o suporte acabou em 2022 — tiveram que reescrever a regra de negócio em si, porque ela nunca existiu separada do framework pra começo de conversa. A mudança que a Lei de Lehman garante chegou pela porta do ecossistema, não por um pedido de negócio, e o custo foi o de uma reescrita completa.
// Ruim — regra de negócio vive dentro do Controller, acoplada ao ASP.NET
public class PedidoController : ControllerBase
{
[HttpPost]
public IActionResult CriarPedido([FromBody] PedidoDto dto)
{
if (dto.Itens.Count == 0)
return BadRequest("Pedido sem itens"); // regra de negócio dentro do framework web
if (dto.ValorTotal > 10000 && dto.Cliente.Tipo == "PF")
return BadRequest("Limite excedido para pessoa física");
return Ok();
}
}
// Bom — regra de negócio isolada, sem qualquer referência ao ASP.NET
public class ValidadorPedido
{
public ResultadoValidacao Validar(Pedido pedido)
{
if (pedido.Itens.Count == 0)
return ResultadoValidacao.Falha("Pedido sem itens");
if (pedido.ValorTotal > 10000 && pedido.Cliente.Tipo == TipoCliente.PessoaFisica)
return ResultadoValidacao.Falha("Limite excedido para pessoa física");
return ResultadoValidacao.Sucesso();
}
}
// O Controller vira uma casca fina que só traduz HTTP <-> domínio
public class PedidoController : ControllerBase
{
private readonly ValidadorPedido _validador;
public PedidoController(ValidadorPedido validador) => _validador = validador;
[HttpPost]
public IActionResult CriarPedido([FromBody] PedidoDto dto)
{
var resultado = _validador.Validar(dto.ParaPedido());
return resultado.Valido ? Ok() : BadRequest(resultado.Erro);
}
}
Se o ASP.NET Core for substituído amanhã — por minimal APIs, por outro framework inteiro —, só a casca (PedidoController) muda. ValidadorPedido nunca soube que o ASP.NET existia.
#11 Organizar por Ator
Martin também reformula aqui o "S" do SOLID. A formulação clássica do Single Responsibility Principle — "uma classe deve ter só uma razão pra mudar" — é vaga: razão pra mudar, segundo quem? Em Clean Architecture, ele resolve a ambiguidade com uma definição operacional: um módulo deve ser responsável perante um, e apenas um, ator — sendo ator o grupo de pessoas (ou papel de negócio) que pode solicitar uma mudança naquele módulo especificamente. Se dois atores diferentes têm poder de pedir mudanças na mesma classe, essa classe tem duas razões concorrentes pra mudar — e uma mudança pedida por um ator arrisca quebrar, silenciosamente, o comportamento que o outro ator depende, sem que ninguém tenha pedido isso.
O exemplo canônico é uma classe Funcionario que atende três atores ao mesmo tempo:
// Ruim — uma classe atende três atores diferentes: Financeiro, RH e o time de dados
public class Funcionario
{
public decimal CalcularSalario() { /* regra que só o Financeiro pode pedir pra mudar */ }
public string GerarRelatorioHoras() { /* regra que só o RH pode pedir pra mudar */ }
public void Salvar() { /* regra que só o time de dados/infra pode pedir pra mudar */ }
}
Se CalcularSalario e GerarRelatorioHoras compartilham um método privado auxiliar — por exemplo, um cálculo de dias trabalhados no mês —, uma mudança pedida pelo Financeiro na fórmula de hora extra pode alterar silenciosamente o número que aparece no relatório do RH. O RH nunca pediu nada, nunca revisou nada, só um dia percebe que o número mudou sem aviso.
// Bom — cada ator tem sua própria classe; a mudança de um nunca atravessa pro código do outro
public class CalculadoraSalario
{
public decimal Calcular(Funcionario funcionario) { /* só o Financeiro mexe aqui */ }
}
public class RelatorioHoras
{
public string Gerar(Funcionario funcionario) { /* só o RH mexe aqui */ }
}
public class FuncionarioRepository
{
public void Salvar(Funcionario funcionario) { /* só o time de dados mexe aqui */ }
}
Segundo exemplo, pra mostrar que o princípio não vale só entre classes — vale entre pastas/módulos também:
// Ruim — organizado por camada técnica; atores diferentes convivem no mesmo diretório
/Services
FuncionarioService.cs <- muda quando Financeiro OU RH pedem algo
RelatorioService.cs <- muda quando RH OU Diretoria pedem algo
NotificacaoService.cs <- muda por qualquer motivo, de qualquer ator
// Bom — organizado por ator/domínio; a mudança de um ator fica fisicamente isolada
/Financeiro
CalculadoraSalario.cs
/RH
RelatorioHoras.cs
GestaoFerias.cs
/Diretoria
RelatorioExecutivo.cs
Na versão "por camada técnica", um Pull Request do RH e um Pull Request do Financeiro competem pelo mesmo arquivo (FuncionarioService.cs) sem relação nenhuma entre os dois pedidos — terreno fértil pra conflito de merge e, pior, pra mudança de um ator vazar pro comportamento que o outro depende, sem ninguém perceber no code review.
#9 Constructs Corretos
Structs são blocos fundamentais de controle de fluxo e organização de código: for, while, if/else, switch, funções, recursão, estruturas de dados (arrays, listas, dicionários, etc.). "Usar constructs corretos" significa escolher a ferramenta certa pra cada problema, em vez de forçar uma solução genérica ou "na moda" onde ela não se encaixa.
1. Escolha a ferramenta certa pro problema — não a que você "sabe usar melhor"
O exemplo clássico: iterar um array com for em vez de recursão.
// Desnecessariamente complexo — recursão para algo simples
public decimal SomarValores(List<decimal> valores, int index = 0)
{
if (index >= valores.Count) return 0;
return valores[index] + SomarValores(valores, index + 1);
}
// Correto — for/LINQ é direto e sem risco de stack overflow
public decimal SomarValores(List<decimal> valores)
{
return valores.Sum();
}
Recursão tem seu lugar (árvores, backtracking, algoritmos naturalmente recursivos), mas usar recursão pra percorrer uma lista simples é usar o construct errado — mais difícil de ler, mais arriscado (stack overflow em listas grandes), sem ganho nenhum.
2. Evite variáveis globais — passe dados via parâmetros e retornos
// Ruim — estado global, difícil de rastrear quem altera o quê
public static class ContextoAtual
{
public static string TenantId;
public static DateTime PeriodoReferencia;
}
public void ProcessarArquivo()
{
// usa ContextoAtual.TenantId direto — de onde veio? quem garante que está certo?
}
// Bom — dependência explícita via parâmetro
public void ProcessarArquivo(string tenantId, DateTime periodoReferencia)
{
// fica óbvio de onde vêm os dados, e é testável isoladamente
}
3. Evite complexidade desnecessária
// Complexo demais para o que faz
public bool EhValido(string cnpj)
{
return !(string.IsNullOrEmpty(cnpj) == false ? false : true) && cnpj.Length == 14;
}
// Direto
public bool EhValido(string cnpj)
{
return !string.IsNullOrEmpty(cnpj) && cnpj.Length == 14;
}
Aninhamentos de if desnecessários, negações duplas, ternários encadeados — tudo isso é "construct errado" mesmo quando funciona, porque exige esforço mental extra de quem lê.
4. Control flow lógico e intuitivo
// Cadeia de if — funciona, mas escala mal
public string ObterDescricaoStatus(StatusArquivo status)
{
if (status == StatusArquivo.Pendente) return "Pendente";
else if (status == StatusArquivo.Processando) return "Processando";
else if (status == StatusArquivo.Concluido) return "Concluído";
else if (status == StatusArquivo.Erro) return "Erro";
else return "Desconhecido";
}
// Switch expression — mais legível pra múltiplos casos fixos
public string ObterDescricaoStatus(StatusArquivo status) => status switch
{
StatusArquivo.Pendente => "Pendente",
StatusArquivo.Processando => "Processando",
StatusArquivo.Concluido => "Concluído",
StatusArquivo.Erro => "Erro",
_ => "Desconhecido"
};
Top comments (0)