Revisão SOLID
Autor: Guerth Castro (github.com/GuerthCastro). Licenciado sob MIT.
Processo de Revisão
Quando o código é fornecido para revisão:
- Identifique a linguagem e a camada (entidade de domínio C#, handler de aplicação, serviço Angular, componente React, etc.).
- Avalie cada um dos cinco princípios de forma independente.
- Produza um relatório estruturado usando o formato abaixo.
- Classifique as violações por severidade: Crítico (quebra a arquitetura) sobre Maior (degrada a manutenibilidade) sobre Menor (preferência de estilo).
- Forneça uma versão refatorada concreta para toda violação Crítica ou Maior.
Não encha o relatório. Se um princípio for genuinamente satisfeito, diga PASS e prossiga.
Os cinco princípios
S: Princípio da Responsabilidade Única
Uma classe, uma razão para mudar.
Violações a serem procuradas:
- Uma classe faz tanto acesso a dados quanto lógica de negócios.
- Um serviço envia e-mails, valida dados e escreve no banco de dados.
- Um componente busca dados, formata-os e gerencia o estado da UI.
- Um handler orquestra várias operações não relacionadas.
- Classes god com dez ou mais métodos abrangendo múltiplos domínios.
VIOLAÇÃO. OrderService valida, persiste e notifica. A chamada de notificação é a que não pertence.
public class OrderService(IOrderRepository repository, IEmailService email)
{
public async Task<bool> AddItem(long orderId, long productId)
{
Order order = await repository.GetById(orderId);
if (order.ItemCount >= order.MaxItems)
{
return false;
}
await repository.AddItem(orderId, productId);
await email.SendOrderUpdated(orderId);
return true;
}
}
Correção: extrair um serviço de notificação e deixar o handler coordenar as duas chamadas.
O: Princípio Aberto/Fechado
Aberto para extensão, fechado para modificação.
Violações a serem procuradas:
- Cadeias de
if/else ifou blocosswitchem strings de tipo ou enums que crescem a cada funcionalidade. - Adicionar um novo tipo de relatório requer editar o serviço de relatório existente.
- Adicionar um novo método de pagamento requer editar o handler de checkout.
- Dependências concretas onde uma abstração pertence.
VIOLAÇÃO. Um novo formato de exportação significa editar este método.
public byte[] ExportInvoice(string format, IEnumerable<InvoiceLine> lines)
{
if (format == "pdf")
{
return GeneratePdf(lines);
}
if (format == "excel")
{
return GenerateExcel(lines);
}
throw new NotSupportedException(format);
}
Correção: uma abstração IInvoiceExporter com implementações PdfInvoiceExporter e ExcelInvoiceExporter, resolvidas por chave a partir do container.
L: Princípio da Substituição de Liskov
Subtipo devem ser substituíveis pelos seus tipos base.
Violações a serem procuradas:
- Uma classe derivada lança
NotImplementedExceptionpara um membro herdado. - Uma sobrescrita altera o comportamento esperado: um no-op silencioso, uma forma de retorno diferente, um novo tipo de exceção.
- Uma interface declara membros que algumas implementações não podem implementar de forma significativa.
- Casting para um tipo concreto para alcançar comportamento:
if (repository is SqlOrderRepository sql).
I: Princípio da Segregação de Interfaces
Nenhuma classe deve ser forçada a implementar membros que não usa.
Violações a serem procuradas:
- Interfaces gordas com quinze ou mais membros onde implementações usam três.
- Um
IRepository<T>genérico forçandoGetAll,GetById,Create,Update,Delete,Paginate,SearcheBulkInsertem toda entidade. - Uma interface de serviço misturando leituras e escritas quando consumidores precisam apenas de um lado.
VIOLAÇÃO. Um consumidor de relatório somente leitura é forçado a ver Create, Update e Delete.
public interface IOrderRepository
{
Task<Order> GetById(long id);
Task<IEnumerable<Order>> GetAll();
Task Create(Order order);
Task Update(Order order);
Task Delete(long id);
}
Correção: dividir em IOrderReader e IOrderWriter. Compor onde ambos são necessários.
D: Princípio da Inversão de Dependência
Dependa de abstrações, não de concreções.
Violações a serem procuradas:
newinstanciando um serviço ou repositório dentro da lógica de negócios.- Uma dependência direta de
SqlConnectionouNpgsqlConnectiondentro de um serviço. - A camada de Aplicação importando tipos de Infraestrutura.
- Classes de serviço estáticas mantendo estado mutável.
VIOLAÇÃO. O handler constrói seu próprio repositório concreto e conexão.
public class GetOrderHandler
{
public async Task<OrderDto> Handle(long id)
{
OrderRepository repository = new(new SqlConnection("..."));
Order order = await repository.GetById(id);
return order.MapToDto();
}
}
Correção: injetar IOrderRepository através do construtor primário e registrá-lo no container na raiz de composição.
Formato do Relatório
Produza esta estrutura para cada revisão:
## Revisão SOLID: [NomeClasse ou nome do arquivo]
**Linguagem/Camada:** Handler de Aplicação C#, Serviço Angular, etc.
### S: Responsabilidade Única
Status: PASS / VIOLAÇÃO (Crítico, Maior, Menor)
Descoberta: [o que a classe faz além de sua responsabilidade única]
Correção: [ação concreta]
### O: Aberto/Fechado
Status: PASS / VIOLAÇÃO
Descoberta: ...
Correção: ...
### L: Substituição de Liskov
Status: PASS / VIOLAÇÃO / N/A (sem herança)
Descoberta: ...
### I: Segregação de Interfaces
Status: PASS / VIOLAÇÃO / N/A (sem interfaces)
Descoberta: ...
### D: Inversão de Dependência
Status: PASS / VIOLAÇÃO
Descoberta: ...
Correção: ...
### Violações prioritárias
1. [Crítico] DIP: handler instancia um repositório concreto
2. [Maior] SRP: serviço também envia o e-mail de notificação
### Versão refatorada
[bloco de código completo corrigido]
Violações de Camadas da Arquitetura Limpa
Além do SOLID, sinalize estes problemas transversais:
- Uma entidade de Domínio importa tipos de Aplicação ou Infraestrutura (violação de direção de dependência).
- Um controlador contém lógica de negócios que pertence a um handler.
- Um repositório retorna DTOs em vez de entidades de domínio. Mapeamento pertence à Aplicação.
- Infraestrutura chama uma API externa diretamente em vez de implementar uma interface declarada em Aplicação ou Domínio.
- Um handler toca
HttpContext, vazando uma preocupação de infraestrutura para a camada de aplicação. - Um registro de DI no assembly errado, fazendo com que uma camada tenha que referenciar para baixo para compilar.