Communitygithub.com

tunahanaliozturk/secure-dotnet-skills

Aegis — 12 個判斷式風格的代理技能,用於在 Azure 上建構安全、生產級的 .NET 應用程式(涵蓋安全性、設計、效能、並行性、可觀測性)。支援 Claude Code、Codex、Cursor、Gemini。

secure-dotnet-skills 是什麼?

這個技能提供了一個結構化的「SOLID 設計審查」流程,讓 AI 代理能夠系統性地評估 C# 類別或模組,並逐一檢驗每個 SOLID 原則(SRP、OCP、LSP、ISP、DIP)。它不僅僅是給出泛泛的「遵循 SOLID」建議,而是精確指出程式碼異味、違反的原則,並提出具體的 .NET 重構方案,例如提取介面、使用策略模式、注入依賴項等。此技能特別針對在 Azure 上運行的 .NET 應用程式進行了優化,能幫助開發者或代理在程式碼審查或重構時,識別出真正會導致耦合、維護困難或效能問題的關鍵違規,而非純粹的理論問題。它支援多種 AI 代理平台,如 Claude Code、Codex、Cursor 和 Gemini,使其能夠在開發工作流程中一致地應用這些設計準則。

相容平台✓Claude Code✓Codex CLI✓Cursor✓Gemini CLI
npx skills add tunahanaliozturk/secure-dotnet-skills

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

SOLID 設計審查

引導代理以具體的 .NET 視角,逐一檢視 C# 類別或模組是否符合每個 SOLID 原則,指出程式碼異味、違反的原則以及最小的重構方案 — 優先處理會導致實際耦合或頻繁修改的違規,而非純粹理論上的吹毛求疵。

使用時機

  • 類別變得難以管理 — 一個檔案中同時包含了 IO、商業邏輯、對映和副作用。
  • 一個 PR 引入了新的型別變體,必須編輯現有的 switch 或 if-else 鏈才能容納它。
  • 建構函式直接 new 出自己的依賴項,或直接參考 DateTime.Now / static 輔助方法。
  • 介面為了滿足多個不相關的呼叫者而累積了許多成員,導致必須實作空操作的方法。

流程

  1. 識別待審查的類別/模組。 說明其陳述的職責。如果描述其職責時需要用到「和」或「或」,這本身就是一個訊號。
  2. 依序檢視每個 SOLID 原則,使用下面的 C# 透鏡。將每個原則視為單獨的檢查,不要只找到一個問題就停止。
  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。商業規則屬於應用程式/領域層。
  • 型別名稱或職責描述中包含「和」。 OrderValidationAndPersistenceService 是一個命名異味,表示違反 SRP。如果你只能用「和」來描述這個類別,就應該拆分它。
  • 多個變更原因。 問:「如果我們更換電子郵件供應商,這個類別需要變更嗎?如果我們改變資料庫結構?如果商業規則改變了?」超過一個「是」就表示超過一個職責。

OCP — 開放/封閉原則

  • 基於型別鑑別的 switch / if-else 增長。 服務內部針對每個新的 OrderType 列舉值都必須重新開啟的 switch(order.Type) 區塊,違反了 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()。 一個無法履行基底合約的子型別(例如,ReadOnlyCollection<T> 在 Add 時拋出例外)違反了 LSP。如果無法有意義地實作該覆寫,則表示繼承體系有問題 — 應優先考慮組合或分離的介面。
  • 子型別弱化後置條件。 如果基底類別保證 GetOrders() 永遠不回傳 null,那麼回傳 null 的覆寫就違反了呼叫者依賴的合約。應記錄不變式(XML 文件或契約)並在覆寫中強制執行。
  • 子型別強化前置條件。 一個覆寫新增了基底中沒有的 null 檢查,或者拒絕了基底接受的合法參數範圍,這會破壞傳遞有效基底合約輸入的程式碼。子型別必須接受基底接受的所有輸入。
  • 呼叫端程式碼中普遍存在 is/as 下行轉型。 呼叫者中的 if (handler is SpecialHandler sh) sh.SpecialMethod() 表示抽象已破裂 — 該方法應屬於基底介面,或者這些型別不應共用同一個繼承體系。頻繁的下行轉型是 LSP / 抽象失敗的訊號。

ISP — 介面隔離原則

  • 肥大介面(10 個以上成員)且包含不相關的關注點。 一個公開了 CreateOrder、CancelOrder、GetInvoice、SendConfirmation、ExportToCsv、CalculateTax、ValidateAddress 和 GetShippingRates 的 IOrderService,會強迫每個實作者和測試替身去實作它們不用的方法。應拆分為角色介面:IOrderWriter、IOrderReader、IOrderNotifier、IOrderExporter。
  • 空操作實作 / NotImplementedException 樁。 如果一個模擬或測試實作為一半的介面成員回傳 throw new NotImplementedException(),則這些成員屬於不同的角色介面。空操作的存在是 ISP 違規的直接證據。
  • 用戶匯入他們從不呼叫的方法。 如果 PaymentService 依賴 IOrderService 但只呼叫 GetOrder,則它應該依賴 IOrderReader。縮小依賴範圍使耦合更明確,並使測試更便宜。

DIP — 依賴反轉原則

  • 在類別內部 new 出具體依賴項。 在服務內部建立的 new HttpClient() 會產生隱藏的依賴,繞過 DI 容器,洩漏 Socket(無連線池),並使類別無法在不使用反射技巧的情況下進行測試。應注入 IHttpClientFactory(具型別用戶端)或透過建構函式注入具名用戶端。同樣的模式也適用於 new SqlConnection(connectionString) — 應注入 IDbConnectionFactory,或讓 EF Core / Dapper 透過註冊的 DbContext 管理連線。
  • 依賴具體實作而非抽象。 建構函式型別為 SqlOrderRepository 而非 IOrderRepository,會將類別與儲存技術耦合。任何應該可重用或可隔離測試的類別,都必須僅依賴介面或抽象型別。
  • 對 DateTime.Now / 靜態狀態的隱藏耦合。 商業方法內部的 var now = DateTime.Now 會使類別不確定且無法在不使用時間旅行技巧的情況下進行測試。應引入 ISystemClock(或使用 .NET 8+ 中的 TimeProvider)並注入它。同樣的原則適用於 Environment.MachineName、Random.Shared 或任何其他環境靜態變數。
  • 可變的靜態單例。 服務類別中的 static Dictionary<string, Order> _cache 是繞過 DI 生命週期管理且非執行緒安全的隱藏共享狀態。應替換為透過 DI 注入的 IMemoryCache / IDistributedCache,這支援適當的範圍和逐出策略。

判斷備註: 並非每個 new 都是 DIP 違規。new OrderDto(order)(一個純資料包)或 new List<T>()(一個標準集合)是沒問題的。DIP 關注的是當被建構的物件具有行為、外部 I/O 或配置時 — 也就是說,當將其替換為測試替身或替代實作會很有價值時。

紅旗訊號

訊號重要性
在服務方法或建構函式內部建構 new HttpClient()繞過 IHttpClientFactory 連線池(負載下會導致 Socket 耗盡),隱藏了依賴項,並使類別無法在沒有生產 I/O 的情況下進行測試。
協調器類別中的 switch(order.Type) 或 if (x is ConcreteType)每當有新的變體加入時就必須重新開啟;呼叫者對型別階層了解過多。應替換為從 DI 解析的 IEnumerable<IHandler>。
在非抽象的覆寫中拋出 throw new NotImplementedException() 或 throw new NotSupportedException()子型別無法履行基底合約 — LSP 違規。應正確實作該方法或重組階層。
一個有 10 個以上成員的介面,但呼叫者只使用其中 2-3 個強迫產生大型測試替身;表示多個角色介面已被合併為一個。
一個透過 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/。

相關技能