Communitygithub.com

tunahanaliozturk/secure-dotnet-skills

Aegis — 12 habilidades de agente estilo avaliação para .NET seguro e de nível de produção no Azure (segurança, design, desempenho, concorrência, observabilidade). Funciona com Claude Code, Codex, Cursor, Gemini.

O que é secure-dotnet-skills?

Este skill transforma seu agente de IA em um revisor de design SOLID para código C#. Ele guia o agente através de cada princípio SOLID (SRP, OCP, LSP, ISP, DIP) usando lentes concretas do .NET, identificando cheiros de código específicos e violações de princípios, e propondo refatorações mínimas com mecanismos reais do C# (interfaces, injeção de dependência, estratégias). É ideal para classes que estão crescendo descontroladamente, PRs que adicionam novas variantes de tipo a cadeias switch/if-else ou construtores que instanciam dependências diretamente. O skill prioriza violações que causam acoplamento real ou retrabalho frequente, em vez de falhas teóricas, e inclui uma tabela de 'sinais vermelhos' para identificar rapidamente padrões como new HttpClient() dentro de serviços ou DateTime.Now em métodos de negócio. Funciona em qualquer plataforma de agente com suporte a habilidades, como Claude Code, Codex, Cursor e Gemini.

Funciona com✓Claude Code✓Codex CLI✓Cursor✓Gemini CLI
npx skills add tunahanaliozturk/secure-dotnet-skills

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Revisão de Design SOLID

Direciona o agente a percorrer uma classe ou módulo C# através de cada princípio SOLID usando lentes concretas do .NET, nomeando o cheiro de código, o princípio violado e a refatoração mínima — priorizando violações que causam acoplamento real ou retrabalho, não apenas falhas teóricas.

Quando usar

  • Uma classe está se tornando difícil de gerenciar — abrange E/S, lógica de negócios, mapeamento e efeitos colaterais em um único arquivo.
  • Um PR introduz uma nova variante de tipo e uma cadeia switch ou if-else existente deve ser editada para acomodá-la.
  • Construtores instanciam suas próprias dependências com new ou referenciam DateTime.Now / ajudantes static diretamente.
  • Uma interface acumulou membros para satisfazer vários chamadores não relacionados, forçando implementações vazias.

Processo

  1. Identifique a classe/módulo em revisão. Nomeie sua responsabilidade declarada. Se o nome exigir "e" ou "ou" para descrevê-la, isso já é um sinal.
  2. Percorra cada princípio SOLID em ordem usando as lentes C# abaixo. Trate cada um como uma passagem separada; não pare após a primeira descoberta.
  3. Nomeie o cheiro de código e o princípio violado. "Construtor pesado montando seu próprio HttpClient e SqlConnection" → DIP; "substituição de base lançando NotSupportedException" → LSP. Seja preciso.
  4. Proponha a refatoração mínima com o mecanismo C#. Nomeie o tipo, padrão ou alteração de registro de DI — não apenas "aplique o princípio." Conselhos genéricos como "siga SOLID" não são uma descoberta.
  5. Priorize pelo impacto real. Um serviço gigante que mistura persistência EF com envio de e-mail causa retrabalho a cada sprint; um IOrderService com 12 membros que nenhum chamador usa completamente é uma violação genuína de ISP. Uma classe sealed que atende a dois casos de uso fracamente relacionados não quebra SRP se tiver um único motivo para mudar. Distinga violações que prejudicam daquelas que são meramente impuras.

Verificações .NET / Azure

SRP — Princípio da Responsabilidade Única

  • Serviços gigantes / classes gigantes. Uma única classe que toca EF Core (_context.SaveChangesAsync), envia e-mail (_emailSender.SendAsync), valida regras de negócio e mapeia para DTOs tem múltiplas razões para mudar. Extraia um IOrderValidator, um IOrderRepository (ou use o DbContext diretamente em uma classe de persistência dedicada) e um IOrderNotifier como colaboradores separados.
  • Controladores pesados. Um controlador ASP.NET Core que contém lógica if/validação, chama o banco de dados diretamente e formata payloads de resposta está fazendo três tarefas. Controladores devem orquestrar: chamar um serviço, mapear o resultado e retornar IActionResult. Regras de negócio pertencem à camada de aplicação/domínio.
  • "E" no nome do tipo ou responsabilidade. OrderValidationAndPersistenceService é um cheiro de nomenclatura que sinaliza violação de SRP. Se você só consegue descrever a classe usando "e", divida-a.
  • Múltiplas razões para mudar. Pergunte: "Esta classe precisaria mudar se trocássemos de provedor de e-mail? Se alterássemos o esquema do banco de dados? Se as regras de negócio mudassem?" Mais de um "sim" significa mais de uma responsabilidade.

OCP — Princípio Aberto/Fechado

  • switch / if-else crescente em um discriminador de tipo. Um bloco switch(order.Type) dentro de um serviço que deve ser reaberto para cada novo valor de enum OrderType viola OCP. Substitua por uma estratégia: defina IOrderHandler (ou IOrderProcessor) e registre uma implementação por tipo; resolva via IEnumerable<IOrderHandler> injetado no coordenador, despachando em handler.Handles(order.Type).
  • Expansão de comportamento codificada. Se adicionar uma nova regra de desconto, canal de notificação ou formato de exportação exige editar uma classe existente, em vez de adicionar um novo IDiscountRule ou IExporter, a classe não está fechada para modificação. Introduza a abstração antes do segundo caso chegar.
  • Anti-padrão de discriminador de tipo. if (order is PremiumOrder) ou if (typeof(T) == typeof(FooOrder)) dentro de uma classe de processamento é um cheiro de código equivalente ao switch acima. Polimorfismo ou um registro de estratégia eliminam isso.

LSP — Princípio da Substituição de Liskov

  • Substituições que lançam throw new NotSupportedException() / NotImplementedException(). Um subtipo que não pode honrar um contrato base (ex.: ReadOnlyCollection<T> lançando em Add) viola LSP. Se a substituição não pode ser implementada de forma significativa, a hierarquia está errada — prefira composição ou interfaces separadas.
  • Subtipos enfraquecendo pós-condições. Se a classe base garante que GetOrders() nunca retorna null, uma substituição que retorna null viola o contrato que os chamadores usam. Documente invariantes (comentários XML ou contratos) e os aplique nas substituições.
  • Subtipos fortalecendo pré-condições. Uma substituição que adiciona uma verificação de nulo não presente na base, ou que rejeita uma faixa de parâmetros válida que a base aceitava, quebra o código que passa entrada válida do contrato base. O subtipo deve aceitar tudo que a base aceitava.
  • Downcasts generalizados com is/as no código chamador. if (handler is SpecialHandler sh) sh.SpecialMethod() no chamador significa que a abstração está quebrada — ou o método pertence à interface base, ou os tipos não deveriam compartilhar uma hierarquia. Downcasts frequentes são um sinal de falha de LSP / abstração.

ISP — Princípio da Segregação de Interfaces

  • Interfaces pesadas (10+ membros) com preocupações não relacionadas. Um IOrderService expondo CreateOrder, CancelOrder, GetInvoice, SendConfirmation, ExportToCsv, CalculateTax, ValidateAddress e GetShippingRates força cada implementador e duplo de teste a implementar métodos que não usa. Divida em interfaces de função: IOrderWriter, IOrderReader, IOrderNotifier, IOrderExporter.
  • Implementações vazias / stubs com NotImplementedException. Se uma implementação mock ou de teste retorna throw new NotImplementedException() para metade dos membros da interface, esses membros pertencem a uma interface de função diferente. A presença de operações vazias é evidência direta de violação de ISP.
  • Clientes importando métodos que nunca chamam. Se PaymentService depende de IOrderService mas só chama GetOrder, deveria depender de IOrderReader. Estreitar a dependência torna o acoplamento explícito e o teste mais barato.

DIP — Princípio da Inversão de Dependência

  • Instanciar dependências concretas com new dentro de uma classe. new HttpClient() construído dentro de um serviço cria uma dependência oculta, ignora o contêiner de DI, vaza soquetes (sem pooling de conexão) e torna a classe não testável sem truques de reflexão. Injete IHttpClientFactory (cliente tipado) ou um cliente nomeado via construtor. Mesmo padrão para new SqlConnection(connectionString) — injete IDbConnectionFactory ou deixe EF Core / Dapper gerenciarem a conexão via DbContext registrado.
  • Depender de concretos em vez de abstrações. Um construtor tipado para SqlOrderRepository em vez de IOrderRepository acopla a classe à tecnologia de armazenamento. Qualquer classe que deva ser reutilizável ou testável isoladamente deve depender apenas de interfaces ou tipos abstratos.
  • Acoplamento oculto a DateTime.Now / estado estático. var now = DateTime.Now dentro de um método de negócio torna a classe não determinística e não testável sem truques de viagem no tempo. Introduza ISystemClock (ou use TimeProvider do .NET 8+) e o injete. O mesmo se aplica a Environment.MachineName, Random.Shared ou qualquer outro estático ambiente.
  • Singletons mutáveis estáticos. Um static Dictionary<string, Order> _cache em uma classe de serviço é estado compartilhado oculto que ignora o gerenciamento de tempo de vida da DI e não é seguro para uso concorrente. Substitua por IMemoryCache / IDistributedCache injetados via DI, que suportam escopo adequado e expurgo.

Nota de julgamento: nem todo new é uma violação de DIP. new OrderDto(order) (um simples recipiente de dados) ou new List<T>() (uma coleção padrão) são aceitáveis. DIP importa quando o objeto construído tem comportamento, E/S externa ou configuração — ou seja, quando trocá-lo por um duplo de teste ou implementação alternativa seria valioso.

Sinais vermelhos

SinalPor que é importante
new HttpClient() construído dentro de um método ou construtor de serviçoIgnora o pooling de conexão de IHttpClientFactory (exaustão de soquetes sob carga), esconde a dependência e torna a classe não testável sem E/S de produção.
switch(order.Type) ou if (x is ConcreteType) em uma classe coordenadoraDeve ser reaberto para cada nova variante; chamadores sabem demais sobre a hierarquia de tipos. Substitua por IEnumerable<IHandler> resolvido da DI.
throw new NotImplementedException() ou throw new NotSupportedException() em uma substituição não abstrataO subtipo não pode honrar o contrato base — violação de LSP. Implemente o método corretamente ou reestruture a hierarquia.
Uma interface com 10+ membros onde os chamadores usam 2–3 delesForça grandes duplos de teste; sinaliza que múltiplas interfaces de função foram colapsadas em uma.
Uma classe construindo seu próprio DbContext via new AppDbContext(options)Ignora o gerenciamento de tempo de vida da DI (Scoped vs Transient vs Singleton), impede o pooling de conexões e quebra a testabilidade. Injete AppDbContext ou IDbContextFactory<AppDbContext>.
DateTime.Now chamado diretamente dentro de um método de negócioNão determinístico em testes; acoplamento ao relógio do sistema esconde uma dependência lógica. Injete TimeProvider (.NET 8+) ou ISystemClock.
Uma ação de controlador contendo lógica de negócio if/switch ou consultas EFO controlador assumiu responsabilidades que pertencem à camada de aplicação/domínio; uma única mudança nas regras de negócio exige tocar no controlador.
Campo mutável static usado como cache em processoNão seguro para threads sem bloqueio explícito; ignora escopo da DI; não pode ser expurgado ou observado. Use IMemoryCache.

Exemplo

Veja examples/solid-review/.

Habilidades Relacionadas