From zenbu-powers
C# Integration Test 程式碼品質規範合集。包含 SOLID 設計原則、Step Definition 組織規範、 Meta 註記清理、日誌實踐、程式架構、程式碼品質等規範。供 refactor 階段嚴格遵守。
npx claudepluginhub zenbuapps/zenbu-powers --plugin zenbu-powersThis skill uses the workspace's default tool permissions.
供 `/zenbu-powers:aibdd.auto.csharp.it.refactor` 重構階段嚴格遵守。涵蓋 SOLID、Step Definition 組織、Meta 清理、日誌實踐、程式架構、程式碼品質。
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
Guides code writing, review, and refactoring with Karpathy-inspired rules to avoid overcomplication, ensure simplicity, surgical changes, and verifiable success criteria.
Share bugs, ideas, or general feedback.
供 /zenbu-powers:aibdd.auto.csharp.it.refactor 重構階段嚴格遵守。涵蓋 SOLID、Step Definition 組織、Meta 清理、日誌實踐、程式架構、程式碼品質。
每個類別/方法只負責一件事。
// ❌ Service 做太多事
public class AssignmentService
{
public void SubmitAssignment(string userId, string content)
{
CheckPermission(userId);
_repository.Save(new Assignment(userId, content));
SendEmail(userId);
}
}
// ✅ 職責分離(constructor DI)
public class AssignmentService
{
private readonly IAssignmentRepository _repository;
private readonly IPermissionValidator _validator;
private readonly INotificationService _notifier;
public AssignmentService(
IAssignmentRepository repository,
IPermissionValidator validator,
INotificationService notifier)
{
_repository = repository;
_validator = validator;
_notifier = notifier;
}
public void SubmitAssignment(string userId, string content)
{
_validator.Validate(userId);
_repository.Save(new Assignment(userId, content));
_notifier.Notify(userId);
}
}
透過 interface + DI 擴展。
public interface IPaymentStrategy
{
void Pay(Order order);
}
public class CreditCardPayment : IPaymentStrategy { /* ... */ }
public class LinePayPayment : IPaymentStrategy { /* ... */ }
// Program.cs
builder.Services.AddScoped<IPaymentStrategy, CreditCardPayment>();
子類別可安全替換父類別,不改變契約。
// ❌ 過大介面
public interface IUserService
{
void CreateUser(); void UpdateUser(); void SendEmail(); void GenerateReport();
}
// ✅ 分離
public interface IUserCrudService { void CreateUser(); void UpdateUser(); }
public interface IEmailService { void SendEmail(); }
public interface IReportService { void GenerateReport(); }
Constructor injection,依賴抽象(interface),不依賴具體實作。
public class OrderService
{
private readonly IOrderRepository _orders;
private readonly IProductRepository _products;
public OrderService(IOrderRepository orders, IProductRepository products)
{
_orders = orders;
_products = products;
}
}
// Program.cs
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<OrderService>();
[Binding] 標記ScenarioContexttests/${ProjectName}.IntegrationTests/Steps/
├── Lesson/ # {Subdomain}
│ ├── AggregateGiven/
│ │ └── LessonProgressGivenSteps.cs
│ ├── Commands/
│ │ └── UpdateVideoProgressSteps.cs
│ ├── Query/
│ │ └── GetLessonProgressSteps.cs
│ ├── AggregateThen/
│ │ └── LessonProgressThenSteps.cs
│ └── ReadModelThen/
│ └── ProgressResultSteps.cs
├── Order/ # 另一個 subdomain
│ └── ...
├── CommonThen/ # 跨 feature 共用
│ ├── SuccessSteps.cs
│ └── FailureSteps.cs
└── Helpers/
├── StatusMapper.cs
└── ScenarioContextExtensions.cs
public static class StatusMapper
{
private static readonly Dictionary<string, string> Mapping = new()
{
["進行中"] = "IN_PROGRESS",
["已完成"] = "COMPLETED",
["未開始"] = "NOT_STARTED",
["已付款"] = "PAID",
["待付款"] = "PENDING"
};
public static string Map(string chineseStatus) =>
Mapping.GetValueOrDefault(chineseStatus, chineseStatus);
}
// TODO: [事件風暴部位: ...]// TODO: 參考 /zenbu-powers:aibdd.auto.csharp.it.handlers.xxx 實作/// <summary> TODO: ... </summary>// 重構前
[Given(@"...")]
public void GivenUserHasProgress(...)
{
// TODO: [事件風暴部位: Aggregate - LessonProgress]
// TODO: 參考 /zenbu-powers:aibdd.auto.csharp.it.handlers.aggregate-given 實作
var userId = Ids[userName].ToString()!;
// ...
}
// 重構後(移除 TODO,保留業務註解)
[Given(@"...")]
public void GivenUserHasProgress(...)
{
var userId = Ids[userName].ToString()!;
var mappedStatus = StatusMapper.Map(status); // 中文狀態 → enum
// ...
}
使用 ILogger<T>(Microsoft.Extensions.Logging)+ structured logging。
// ❌ Console.WriteLine
public class OrderService
{
public void CreateOrder() => Console.WriteLine("Order created");
}
// ✅ ILogger<T> + structured logging
public class OrderService
{
private readonly ILogger<OrderService> _logger;
public OrderService(ILogger<OrderService> logger) => _logger = logger;
public void CreateOrder(string orderNumber, string userId)
{
_logger.LogInformation(
"Order created: OrderNumber={OrderNumber}, UserId={UserId}",
orderNumber, userId);
}
}
| 等級 | 用途 | 範例 |
|---|---|---|
| Error | 未預期異常,含 stack trace | _logger.LogError(ex, "Unexpected: {Message}", ex.Message) |
| Warning | 認證失敗、權限不足 | _logger.LogWarning("Expired JWT for {Method} {Uri}", method, uri) |
| Information | 業務關鍵操作完成 | _logger.LogInformation("Order created: OrderNumber={N}", orderNumber) |
| Debug | 詳細流程、查詢數量 | _logger.LogDebug("Fetching order={Id}", orderId) |
LogInformation 記錄請求進入LogInformation 寫入完成;LogDebug 查詢結果LogWarning 認證失敗LogErrorConsole.WriteLine / Console.Error$"msg {var}" 作為 log message)// ❌ 字串拼接(無法結構化查詢)
_logger.LogInformation($"Order {orderNumber} created by {userId}");
// ✅ 結構化佔位符
_logger.LogInformation(
"Order created: OrderNumber={OrderNumber}, UserId={UserId}",
orderNumber, userId);
src/${ProjectName}/
├── Controllers/ # [ApiController] + [Route] + [HttpPost/Get/Put/Delete]
├── Services/ # 業務邏輯,constructor DI
├── Repositories/ # (可選) 資料存取抽象;或直接使用 DbContext
├── Models/ # EF Core entities
├── DTOs/ # Request/Response records
├── Data/ # AppDbContext
├── Helpers/ # Middleware, utilities
└── Program.cs # DI 註冊 + middleware pipeline
| 層 | 負責 | 不負責 |
|---|---|---|
| Controller | 路由、解析 Request、構建 Response | 業務邏輯、資料存取 |
| Service | 業務規則、協調 Repository、拋業務異常 | HTTP 處理、直接操作 SQL |
| Repository | LINQ/EF Core CRUD、自訂查詢 | 業務規則 |
| Model | EF Core Entity 定義 | 驗證、業務邏輯 |
| DTO | Request/Response 資料容器(record) | 行為、方法 |
// Program.cs
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddControllers();
builder.Services.AddDbContext<AppDbContext>(/* ... */);
DbContext(繞過 Repository 抽象;除非整個專案選擇「不使用 Repository 模式」的一致作法)所有透過 constructor DI 注入的欄位宣告為 readonly。
// ✅
private readonly IOrderRepository _repository;
private readonly ILogger<OrderService> _logger;
不可變 DTO 使用 record:
public record UpdateVideoProgressRequest(int LessonId, int Progress);
public record LessonProgressResponse(int LessonId, int Progress, string Status);
// ❌ if-else
public string MapStatus(string status)
{
if (status == "進行中") return "IN_PROGRESS";
else if (status == "已完成") return "COMPLETED";
else return status;
}
// ✅ switch expression
public string MapStatus(string status) => status switch
{
"進行中" => "IN_PROGRESS",
"已完成" => "COMPLETED",
"未開始" => "NOT_STARTED",
_ => status
};
專案必須開啟 <Nullable>enable</Nullable>。
// 明確標示可為 null
public string? Description { get; set; }
// Null-conditional + null-coalescing
var name = user?.Name ?? "匿名";
// ❌ 深層巢狀
public void Process(Data? data)
{
if (data != null)
{
if (data.IsValid)
{
DoWork(data);
}
}
}
// ✅ Guard clauses
public void Process(Data? data)
{
if (data is null) throw new ArgumentNullException(nameof(data));
if (!data.IsValid) throw new ValidationException("Invalid data");
DoWork(data);
}
// ❌ 每次方法呼叫都建立
public string Map(string status)
{
var mapping = new Dictionary<string, string> { ["A"] = "狀態A" };
return mapping[status];
}
// ✅ static readonly
private static readonly Dictionary<string, string> StatusMapping = new()
{
["A"] = "狀態A"
};
// ❌ 直接取值(可能 NullReferenceException)
var order = _context.Orders.First(o => o.Id == orderId);
// ✅ FirstOrDefault + 明確拋出
var order = _context.Orders.FirstOrDefault(o => o.Id == orderId)
?? throw new OrderNotFoundException(orderId);
// ✅ 全程 async
public async Task<Order> GetOrderAsync(int id)
{
return await _context.Orders.FirstOrDefaultAsync(o => o.Id == id)
?? throw new OrderNotFoundException(id);
}
// ❌
throw new ArgumentNullException("userId");
// ✅
throw new ArgumentNullException(nameof(userId));
UpdateVideoProgress)_camelCase(private readonly)MaxRetryCount)is/has/can 開頭ILogger<T> + structured logging(不用 Console.WriteLine){PropertyName} 佔位符,不用字串拼接readonlyrecordswitch expression<Nullable>enable</Nullable>static readonly 常數FirstOrDefault() ?? throw 處理可能為空的查詢nameof() 取代硬編碼名稱字串