Communitygithub.com

tunahanaliozturk/secure-dotnet-skills

Aegis — 보안, 설계, 성능, 동시성, 관측 가능성 등 프로덕션 등급의 .NET on Azure를 위한 12가지 판단형 에이전트 스킬입니다. Claude Code, Codex, Cursor, Gemini에서 사용할 수 있습니다.

secure-dotnet-skills란 무엇인가요?

이 스킬은 SOLID 원칙을 .NET 및 Azure 맥락에서 구체적으로 점검할 수 있는 에이전트 가이드입니다. C# 클래스나 모듈을 평가할 때 단순히 원칙 이름을 나열하는 대신, 'Fat Controller', 'switch 문 타입 분기', 'NotSupportedException을 던지는 오버라이드' 등 구체적인 코드 스멜을 식별하고 어떤 SOLID 원칙이 위반되었는지 정확히 지목합니다. 또한 'IHttpClientFactory 주입', '전략 패턴 도입', 'TimeProvider 사용' 등 .NET 메커니즘을 활용한 최소한의 리팩터링 방안을 제시합니다. 이 스킬은 실제 변경 비용이 많이 드는 위반(예: 신 서비스, switch 문 체인)을 이론적으로만 틀린 위반(예: 우연히 두 사용 사례를 만족하는 sealed 클래스)과 구분하도록 설계되었습니다. Claude Code, Cursor, Codex, Gemini 등 주요 AI 코딩 에이전트 플랫폼에서 사용할 수 있습니다.

지원 대상✓Claude Code✓Codex CLI✓Cursor✓Gemini CLI
npx skills add tunahanaliozturk/secure-dotnet-skills

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

SOLID 설계 리뷰

에이전트가 C# 클래스 또는 모듈을 각 SOLID 원칙에 따라 구체적인 .NET 관점으로 살펴보고, 스멜, 위반된 원칙 및 최소한의 리팩터링 방안을 제시하도록 안내합니다 — 교과서적인 지적이 아닌 실제 결합도나 변경 비용을 유발하는 위반을 우선시합니다.

사용 시점

  • 클래스가 다루기 힘들어질 때 — 하나의 파일에 I/O, 비즈니스 로직, 매핑, 부수 효과가 모두 포함된 경우.
  • PR에서 새로운 타입 변형이 도입되고 기존 switch 또는 if-else 체인을 수정해야 할 때.
  • 생성자가 직접 의존성을 new로 생성하거나 DateTime.Now / static 헬퍼를 직접 참조할 때.
  • 인터페이스에 여러 관련 없는 호출자를 만족시키기 위해 멤버가 누적되어 no-op 구현이 강제될 때.

프로세스

  1. 검토할 클래스/모듈을 식별합니다. 그 책임을 명명합니다. 이름을 설명하는 데 "그리고" 또는 "또는"이 필요하다면 이미 신호입니다.
  2. 아래 C# 렌즈를 사용하여 각 SOLID 원칙을 순서대로 살펴봅니다. 각각을 별도의 패스로 취급하고, 첫 번째 발견 후 멈추지 않습니다.
  3. 스멜과 위반된 원칙을 명명합니다. "자체 HttpClient와 SqlConnection을 조립하는 뚱뚱한 생성자" → DIP; "NotSupportedException을 던지는 기본 재정의" → LSP. 정확해야 합니다.
  4. C# 메커니즘을 사용한 최소한의 리팩터링을 제안합니다. 타입, 패턴, 또는 DI 등록 변경 사항을 명명합니다 — 단순히 "원칙을 적용하라"가 아닙니다. 일반적인 "SOLID 따르기" 조언은 발견 사항이 아닙니다.
  5. 실제 영향에 따라 우선순위를 정합니다. EF 지속성과 이메일 발송을 혼합한 갓 서비스는 매 스프린트마다 변경을 유발합니다. 12개의 멤버를 가졌지만 어떤 호출자도 완전히 사용하지 않는 IOrderService는 진정한 ISP 위반입니다. 우연히 두 개의 느슨하게 관련된 사용 사례를 만족하는 sealed 클래스는 변경 이유가 하나라면 SRP를 위반하지 않습니다. 해를 끼치는 위반과 단순히 불순한 위반을 구분하십시오.

.NET / Azure 점검 사항

SRP — 단일 책임 원칙

  • 갓 서비스 / 갓 클래스. EF Core(_context.SaveChangesAsync), 이메일 전송(_emailSender.SendAsync), 비즈니스 규칙 검증, DTO 매핑을 모두 수행하는 단일 클래스는 여러 변경 이유를 가집니다. IOrderValidator, IOrderRepository(또는 전용 지속성 클래스에서 DbContext 직접 사용), IOrderNotifier를 별도 협력자로 추출하십시오.
  • 뚱뚱한 컨트롤러. if/검증 로직을 포함하고, 데이터베이스를 직접 호출하며, 응답 페이로드를 형식화하는 ASP.NET Core 컨트롤러는 세 가지 작업을 수행합니다. 컨트롤러는 조정만 해야 합니다: 서비스 호출, 결과 매핑, IActionResult 반환. 비즈니스 규칙은 애플리케이션/도메인 계층에 속합니다.
  • 타입 이름이나 책임에 "And"가 포함된 경우. OrderValidationAndPersistenceService는 SRP 위반을 알리는 명명 스멜입니다. 클래스를 "그리고"로만 설명할 수 있다면 분할하십시오.
  • 여러 변경 이유. 질문: "이메일 공급자를 전환하면 이 클래스가 변경되어야 합니까? 데이터베이스 스키마를 변경하면? 비즈니스 규칙이 변경되면?" "예"가 두 개 이상이면 책임이 두 개 이상입니다.

OCP — 개방-폐쇄 원칙

  • 타입 판별자에 대한 switch / if-else 증가. 서비스 내부의 switch(order.Type) 블록은 새로운 OrderType 열거형 값마다 다시 열려야 하므로 OCP를 위반합니다. 전략으로 대체하십시오: IOrderHandler(또는 IOrderProcessor)를 정의하고 타입당 하나의 구현을 등록합니다. 조정자에 주입된 IEnumerable<IOrderHandler>를 통해 해결하고 handler.Handles(order.Type)으로 디스패치합니다.
  • 하드 코딩된 동작 확장. 새 할인 규칙, 알림 채널, 또는 내보내기 형식을 추가하려면 새 IDiscountRule 또는 IExporter를 도입하는 대신 기존 클래스를 편집해야 한다면, 해당 클래스는 수정에 대해 닫혀 있지 않은 것입니다. 두 번째 사례가 발생하기 전에 추상화를 도입하십시오.
  • 타입 판별자 안티패턴. 처리 클래스 내부의 if (order is PremiumOrder) 또는 if (typeof(T) == typeof(FooOrder))는 위 switch와 동등한 코드 스멜입니다. 다형성 또는 전략 레지스트리가 이를 제거합니다.

LSP — 리스코프 치환 원칙

  • throw new NotSupportedException() / NotImplementedException()을 던지는 재정의. 기본 계약을 준수할 수 없는 하위 타입(예: Add에서 예외를 던지는 ReadOnlyCollection<T>)은 LSP를 위반합니다. 재정의를 의미 있게 구현할 수 없다면 계층 구조가 잘못된 것입니다 — 합성 또는 별도 인터페이스를 선호하십시오.
  • 사후 조건을 약화시키는 하위 타입. 기본 클래스가 GetOrders()가 절대 null을 반환하지 않음을 보장한다면, null을 반환하는 재정의는 호출자가 의존하는 계약을 위반합니다. 불변 조건(XML 문서 또는 계약)을 문서화하고 재정의에서 이를 강제하십시오.
  • 사전 조건을 강화하는 하위 타입. 기본 클래스에 없는 null 검사를 추가하거나 기본 클래스가 허용한 유효한 매개변수 범위를 거부하는 재정의는 기본 계약 입력을 전달하는 코드를 손상시킵니다. 하위 타입은 기본 클래스가 허용한 모든 것을 수용해야 합니다.
  • 호출 코드에서 만연한 is/as 다운캐스트. 호출자의 if (handler is SpecialHandler sh) sh.SpecialMethod()는 추상화가 깨졌음을 의미합니다 — 메서드가 기본 인터페이스에 속하거나 타입이 동일한 계층 구조를 공유하지 않아야 합니다. 빈번한 다운캐스트는 LSP/추상화 실패 신호입니다.

ISP — 인터페이스 분리 원칙

  • 관련 없는 관심사를 가진 뚱뚱한 인터페이스(10개 이상의 멤버). IOrderService가 CreateOrder, CancelOrder, GetInvoice, SendConfirmation, ExportToCsv, CalculateTax, ValidateAddress, GetShippingRates를 노출한다면 모든 구현자와 테스트 더블이 사용하지 않는 메서드를 스터브하도록 강제합니다. 역할 인터페이스로 분할하십시오: IOrderWriter, IOrderReader, IOrderNotifier, IOrderExporter.
  • No-op 구현 / NotImplementedException 스터브. 목 또는 테스트 구현이 인터페이스 멤버의 절반에 대해 throw new NotImplementedException()을 반환한다면 해당 멤버는 다른 역할 인터페이스에 속합니다. no-op의 존재는 ISP 위반의 직접적인 증거입니다.
  • 호출하지 않는 메서드를 임포트하는 클라이언트. PaymentService가 IOrderService에 의존하지만 GetOrder만 호출한다면 IOrderReader에 의존해야 합니다. 의존성을 좁히면 결합도가 명확해지고 테스트 비용이 낮아집니다.

DIP — 의존성 역전 원칙

  • 클래스 내부에서 구체적인 의존성을 new로 생성. 서비스 내부에서 new HttpClient()를 생성하면 숨겨진 의존성이 생기고, DI 컨테이너를 우회하며, 소켓 누수(연결 풀링 없음)가 발생하고, 리플렉션 트릭 없이는 클래스를 테스트할 수 없게 만듭니다. IHttpClientFactory(타입화된 클라이언트) 또는 명명된 클라이언트를 생성자를 통해 주입하십시오. new SqlConnection(connectionString)도 마찬가지입니다 — IDbConnectionFactory를 주입하거나 EF Core / Dapper가 등록된 DbContext를 통해 연결을 관리하도록 하십시오.
  • 추상화 대신 구체적인 타입에 의존. IOrderRepository 대신 SqlOrderRepository로 타입화된 생성자는 클래스를 스토리지 기술에 결합시킵니다. 재사용 가능하거나 격리되어 테스트 가능해야 하는 모든 클래스는 인터페이스 또는 추상 타입에만 의존해야 합니다.
  • DateTime.Now / 정적 상태에 대한 숨겨진 결합. 비즈니스 메서드 내부의 var now = DateTime.Now는 클래스를 비결정적으로 만들고 시간 여행 해킹 없이는 테스트할 수 없게 만듭니다. ISystemClock(또는 .NET 8+의 TimeProvider)을 도입하고 주입하십시오. Environment.MachineName, Random.Shared 또는 기타 앰비언트 정적에도 동일하게 적용됩니다.
  • 정적 가변 싱글톤. 서비스 클래스의 static Dictionary<string, Order> _cache는 DI 수명 관리를 우회하고 동시 사용에 안전하지 않은 숨겨진 공유 상태입니다. 적절한 범위 지정 및 제거를 지원하는 IMemoryCache / IDistributedCache로 대체하십시오.

판단 메모: 모든 new가 DIP 위반은 아닙니다. new OrderDto(order)(일반 데이터 백) 또는 new List<T>()(표준 컬렉션)은 괜찮습니다. DIP는 구성된 객체가 동작, 외부 I/O 또는 구성을 가질 때 — 즉, 테스트 더블이나 대체 구현으로 교체하는 것이 가치 있을 때 중요합니다.

위험 신호

신호중요한 이유
서비스 메서드 또는 생성자 내부에 new HttpClient() 생성IHttpClientFactory 연결 풀링을 우회(부하 시 소켓 고갈), 의존성을 숨기고, 프로덕션 I/O 없이는 클래스를 테스트할 수 없게 만듦.
조정자 클래스의 switch(order.Type) 또는 if (x is ConcreteType)새로운 변형마다 다시 열려야 함. 호출자가 타입 계층 구조에 대해 너무 많이 알고 있음. DI에서 확인된 IEnumerable<IHandler>로 대체.
추상이 아닌 재정의에서 throw new NotImplementedException() 또는 throw new NotSupportedException()하위 타입이 기본 계약을 준수할 수 없음 — LSP 위반. 메서드를 올바르게 구현하거나 계층 구조를 재구성.
호출자가 2-3개만 사용하는 10개 이상의 멤버를 가진 인터페이스큰 테스트 더블을 강제. 여러 역할 인터페이스가 하나로 축소되었음을 나타냄.
new AppDbContext(options)를 통해 자체 DbContext를 생성하는 클래스DI 수명 관리(Scoped vs Transient vs Singleton)를 우회하고, 연결 풀링을 방지하며, 테스트성을 저하시킴. AppDbContext 또는 IDbContextFactory<AppDbContext>를 주입.
비즈니스 메서드 내부에서 직접 호출된 DateTime.Now테스트에서 비결정적; 시스템 시계에 결합되어 논리적 의존성을 숨김. TimeProvider(.NET 8+) 또는 ISystemClock을 주입.
if/switch 비즈니스 로직 또는 EF 쿼리를 포함하는 컨트롤러 액션컨트롤러가 애플리케이션/도메인 계층에 속하는 책임을 맡음. 비즈니스 규칙의 단일 변경이 컨트롤러를 건드려야 함.
인프로세스 캐시로 사용되는 static 가변 필드명시적 잠금 없이 스레드 안전하지 않음. DI 범위 지정을 우회. 제거 또는 관찰 불가. IMemoryCache 사용.

예시

examples/solid-review/를 참조하십시오.

관련 스킬