@pilllesss/yorn 1.0.182 → 1.0.183

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @pilllesss/yorn might be problematic. Click here for more details.

Files changed (45) hide show
  1. package/README.md +1 -1
  2. package/dist/providers/data/.manifest.json +1 -1
  3. package/dist/skills/code-review/LICENSE +21 -0
  4. package/dist/skills/code-review/SKILL.md +233 -0
  5. package/dist/skills/code-review/assets/pr-review-template.md +137 -0
  6. package/dist/skills/code-review/assets/review-checklist.md +123 -0
  7. package/dist/skills/code-review/reference/angular.md +768 -0
  8. package/dist/skills/code-review/reference/architecture-review-guide.md +472 -0
  9. package/dist/skills/code-review/reference/c.md +890 -0
  10. package/dist/skills/code-review/reference/code-quality-universal.md +488 -0
  11. package/dist/skills/code-review/reference/code-review-best-practices.md +136 -0
  12. package/dist/skills/code-review/reference/common-bugs-checklist.md +302 -0
  13. package/dist/skills/code-review/reference/cpp.md +893 -0
  14. package/dist/skills/code-review/reference/cross-cutting/async-concurrency-patterns.md +515 -0
  15. package/dist/skills/code-review/reference/cross-cutting/error-handling-principles.md +492 -0
  16. package/dist/skills/code-review/reference/cross-cutting/n-plus-one-queries.md +309 -0
  17. package/dist/skills/code-review/reference/cross-cutting/sql-injection-prevention.md +308 -0
  18. package/dist/skills/code-review/reference/cross-cutting/xss-prevention.md +264 -0
  19. package/dist/skills/code-review/reference/csharp.md +525 -0
  20. package/dist/skills/code-review/reference/css-less-sass.md +661 -0
  21. package/dist/skills/code-review/reference/dart.md +670 -0
  22. package/dist/skills/code-review/reference/django.md +985 -0
  23. package/dist/skills/code-review/reference/fastapi.md +580 -0
  24. package/dist/skills/code-review/reference/go.md +993 -0
  25. package/dist/skills/code-review/reference/java.md +409 -0
  26. package/dist/skills/code-review/reference/java8.md +586 -0
  27. package/dist/skills/code-review/reference/kotlin.md +1018 -0
  28. package/dist/skills/code-review/reference/nestjs.md +593 -0
  29. package/dist/skills/code-review/reference/performance-review-guide.md +816 -0
  30. package/dist/skills/code-review/reference/php.md +684 -0
  31. package/dist/skills/code-review/reference/python.md +1073 -0
  32. package/dist/skills/code-review/reference/qt.md +757 -0
  33. package/dist/skills/code-review/reference/react.md +871 -0
  34. package/dist/skills/code-review/reference/ruby.md +964 -0
  35. package/dist/skills/code-review/reference/rust.md +846 -0
  36. package/dist/skills/code-review/reference/security-review-guide.md +494 -0
  37. package/dist/skills/code-review/reference/svelte.md +1064 -0
  38. package/dist/skills/code-review/reference/swift.md +936 -0
  39. package/dist/skills/code-review/reference/typescript.md +1016 -0
  40. package/dist/skills/code-review/reference/vue.md +924 -0
  41. package/dist/skills/code-review/reference/zig.md +440 -0
  42. package/dist/skills/code-review/scripts/pr-analyzer.py +435 -0
  43. package/dist/skills/code-review/scripts/test_pr_analyzer.py +380 -0
  44. package/dist/yorn.cjs +628 -628
  45. package/package.json +2 -2
@@ -0,0 +1,525 @@
1
+ # C# / .NET Code Review Guide
2
+
3
+ > C# / .NET 8 代码审查指南,覆盖 C# 12 新特性、异步编程、EF Core 性能、ASP.NET Core 最佳实践、依赖注入、LINQ 等核心主题。
4
+
5
+ ## 目录
6
+
7
+ - [C# 12 新特性](#c-12-新特性)
8
+ - [异步编程](#异步编程)
9
+ - [EF Core 性能](#ef-core-性能)
10
+ - [ASP.NET Core 最佳实践](#aspnet-core-最佳实践)
11
+ - [依赖注入](#依赖注入)
12
+ - [LINQ 最佳实践](#linq-最佳实践)
13
+ - [Review Checklist](#review-checklist)
14
+
15
+ ---
16
+
17
+ ## C# 12 新特性
18
+
19
+ ### Primary Constructors(非 record 类型)
20
+
21
+ ```csharp
22
+ // ❌ 样板代码过多的传统构造函数
23
+ public class ProductService
24
+ {
25
+ private readonly ProductDbContext _db;
26
+ private readonly ILogger<ProductService> _logger;
27
+
28
+ public ProductService(ProductDbContext db, ILogger<ProductService> logger)
29
+ {
30
+ _db = db;
31
+ _logger = logger;
32
+ }
33
+ }
34
+
35
+ // ✅ Primary Constructor——简洁的依赖注入
36
+ public class ProductService(ProductDbContext db, ILogger<ProductService> logger)
37
+ {
38
+ public async Task<Product?> GetAsync(int id)
39
+ => await db.Products.FindAsync(id);
40
+ }
41
+
42
+ // ⚠️ 注意:primary constructor 参数不是属性,不能被重新赋值
43
+ // ⚠️ 如果需要长期存储,显式声明字段
44
+ public class OrderService(OrderDbContext db)
45
+ {
46
+ private readonly OrderDbContext _db = db; // 显式捕获
47
+ }
48
+ ```
49
+
50
+ ### Collection Expressions
51
+
52
+ ```csharp
53
+ // ❌ 传统集合初始化
54
+ int[] nums = new int[] { 1, 2, 3 };
55
+ List<string> names = new List<string> { "alice", "bob" };
56
+
57
+ // ✅ 集合表达式
58
+ int[] nums = [1, 2, 3];
59
+ List<string> names = ["alice", "bob"];
60
+ Span<char> span = ['a', 'b'];
61
+
62
+ // ✅ 展开运算符
63
+ int[] merged = [..nums, 4, 5];
64
+ ```
65
+
66
+ ### Default Lambda Parameters
67
+
68
+ ```csharp
69
+ // ❌ 重载 lambda
70
+ var add = (int a, int b) => a + b;
71
+ var addDefault = (int a) => a + 1;
72
+
73
+ // ✅ 默认参数
74
+ var add = (int a, int b = 1) => a + b;
75
+ ```
76
+
77
+ ---
78
+
79
+ ## 异步编程
80
+
81
+ > 📖 通用并发模式和跨语言示例详见 [异步与并发跨语言指南](cross-cutting/async-concurrency-patterns.md)
82
+
83
+ ### Task.Wait() / .Result / async void 是严重反模式
84
+
85
+ ```csharp
86
+ // ❌ Task.Wait() —— 死锁风险(同步阻塞异步操作)
87
+ public ActionResult<Data> Get(int id)
88
+ {
89
+ var data = _service.GetDataAsync(id).Result; // 死锁!
90
+ return Ok(data);
91
+ }
92
+
93
+ // ❌ async void —— 异常无法捕获,会崩溃进程
94
+ public async void HandleEvent()
95
+ {
96
+ await _service.ProcessAsync(); // 异常直接崩溃
97
+ }
98
+
99
+ // ✅ async Task —— 全链路异步
100
+ public async Task<ActionResult<Data>> Get(int id)
101
+ {
102
+ var data = await _service.GetDataAsync(id);
103
+ return Ok(data);
104
+ }
105
+ ```
106
+
107
+ ### ConfigureAwait(false) 用于库代码
108
+
109
+ ```csharp
110
+ // ❌ 库代码不必要地捕获 SynchronizationContext
111
+ public class LibraryService
112
+ {
113
+ public async Task<string> GetDataAsync()
114
+ {
115
+ var response = await _httpClient.GetAsync("/api/data");
116
+ return await response.Content.ReadAsStringAsync();
117
+ }
118
+ }
119
+
120
+ // ✅ 库代码使用 ConfigureAwait(false) 避免死锁
121
+ public class LibraryService
122
+ {
123
+ public async Task<string> GetDataAsync()
124
+ {
125
+ var response = await _httpClient.GetAsync("/api/data").ConfigureAwait(false);
126
+ return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
127
+ }
128
+ }
129
+ ```
130
+
131
+ ### CancellationToken 传播
132
+
133
+ ```csharp
134
+ // ❌ 丢弃 CancellationToken
135
+ public async Task<List<User>> SearchAsync(string query)
136
+ {
137
+ return await _db.Users.Where(u => u.Name.Contains(query)).ToListAsync();
138
+ }
139
+
140
+ // ✅ 全链路传递 CancellationToken
141
+ public async Task<List<User>> SearchAsync(string query, CancellationToken ct = default)
142
+ {
143
+ return await _db.Users
144
+ .Where(u => u.Name.Contains(query))
145
+ .ToListAsync(ct);
146
+ }
147
+ ```
148
+
149
+ ### Async Disposal
150
+
151
+ ```csharp
152
+ // ❌ 同步 dispose 异步资源
153
+ public class DataClient : IDisposable
154
+ {
155
+ public void Dispose()
156
+ {
157
+ _httpClient.Dispose(); // 可能丢弃正在进行的请求
158
+ }
159
+ }
160
+
161
+ // ✅ IAsyncDisposable
162
+ public class DataClient : IAsyncDisposable
163
+ {
164
+ public async ValueTask DisposeAsync()
165
+ {
166
+ await _stream.DisposeAsync();
167
+ }
168
+ }
169
+
170
+ // ✅ 调用方使用 await using
171
+ await using var client = new DataClient();
172
+ ```
173
+
174
+ ---
175
+
176
+ ## EF Core 性能
177
+
178
+ ### N+1 查询问题
179
+
180
+ > 📖 通用原理和跨语言方案详见 [N+1 查询跨语言指南](cross-cutting/n-plus-one-queries.md)
181
+
182
+ ```csharp
183
+ // ❌ 经典 N+1——每个 Blog 触发一次查询获取 Posts
184
+ foreach (var blog in await context.Blogs.ToListAsync())
185
+ {
186
+ foreach (var post in blog.Posts) // 每次循环都查询数据库!
187
+ {
188
+ Console.WriteLine(post.Title);
189
+ }
190
+ }
191
+
192
+ // ✅ Eager Loading + 投影
193
+ await foreach (var blog in context.Blogs
194
+ .Select(b => new { b.Url, b.Posts })
195
+ .AsAsyncEnumerable())
196
+ {
197
+ foreach (var post in blog.Posts)
198
+ Console.WriteLine(post.Title);
199
+ }
200
+ ```
201
+
202
+ ### 过度获取(不投影)
203
+
204
+ ```csharp
205
+ // ❌ 加载所有列——只需要 Url 时加载了全部字段
206
+ var urls = await context.Blogs.ToListAsync();
207
+
208
+ // ✅ 只投影需要的字段
209
+ var urls = await context.Blogs
210
+ .Select(b => b.Url)
211
+ .ToListAsync();
212
+ ```
213
+
214
+ ### 缺少分页
215
+
216
+ ```csharp
217
+ // ❌ 无界结果集
218
+ var posts = await context.Posts
219
+ .Where(p => p.Title.StartsWith("A"))
220
+ .ToListAsync(); // 可能有百万条记录!
221
+
222
+ // ✅ 限制结果数量
223
+ var posts = await context.Posts
224
+ .Where(p => p.Title.StartsWith("A"))
225
+ .OrderBy(p => p.Id)
226
+ .Skip((page - 1) * pageSize)
227
+ .Take(pageSize)
228
+ .ToListAsync();
229
+ ```
230
+
231
+ ### Cartesian Explosion(JOIN 笛卡尔爆炸)
232
+
233
+ ```csharp
234
+ // ❌ 多个 Include 创建大量重复数据
235
+ var blogs = await context.Blogs
236
+ .Include(b => b.Posts)
237
+ .Include(b => b.Tags)
238
+ .ToListAsync(); // 每行重复 Blog 数据
239
+
240
+ // ✅ 使用 AsSplitQuery 拆分查询
241
+ var blogs = await context.Blogs
242
+ .Include(b => b.Posts)
243
+ .Include(b => b.Tags)
244
+ .AsSplitQuery()
245
+ .ToListAsync();
246
+ ```
247
+
248
+ ### 只读场景缺少 AsNoTracking
249
+
250
+ ```csharp
251
+ // ❌ 默认跟踪——只读查询也付出跟踪开销
252
+ var products = await context.Products.ToListAsync();
253
+
254
+ // ✅ AsNoTracking——跳过变更跟踪,更快且更省内存
255
+ var products = await context.Products
256
+ .AsNoTracking()
257
+ .ToListAsync();
258
+ ```
259
+
260
+ ### 列上函数阻止索引使用
261
+
262
+ ```csharp
263
+ // ✅ 可以使用索引——sargable
264
+ var posts1 = await context.Posts
265
+ .Where(p => p.Title.StartsWith("A"))
266
+ .ToListAsync();
267
+
268
+ // ❌ 无法使用索引——全表扫描
269
+ var posts2 = await context.Posts
270
+ .Where(p => p.Title.EndsWith("A"))
271
+ .ToListAsync();
272
+
273
+ // ❌ 列上套函数——全表扫描
274
+ var posts3 = await context.Posts
275
+ .Where(p => p.Title.ToLower() == "foo")
276
+ .ToListAsync();
277
+ ```
278
+
279
+ ### 同步 vs 异步数据库访问
280
+
281
+ ```csharp
282
+ // ❌ 同步数据库调用——阻塞线程
283
+ var products = context.Products.ToList();
284
+ context.SaveChanges();
285
+
286
+ // ✅ 异步数据库调用
287
+ var products = await context.Products.ToListAsync();
288
+ await context.SaveChangesAsync();
289
+ ```
290
+
291
+ ---
292
+
293
+ ## ASP.NET Core 最佳实践
294
+
295
+ ### HttpClient 误用
296
+
297
+ ```csharp
298
+ // ❌ 每次请求创建新的 HttpClient——socket 耗尽
299
+ using var client = new HttpClient();
300
+ var response = await client.GetAsync("https://api.example.com/data");
301
+
302
+ // ✅ IHttpClientFactory 注入
303
+ public class MyService
304
+ {
305
+ private readonly HttpClient _client;
306
+ public MyService(HttpClient client) => _client = client; // 从工厂注入
307
+ }
308
+ ```
309
+
310
+ ### HttpContext 在后台线程中使用
311
+
312
+ ```csharp
313
+ // ❌ 在后台任务中捕获 scoped 服务——请求结束后已释放
314
+ _ = Task.Run(async () =>
315
+ {
316
+ await context.SaveChangesAsync(); // ObjectDisposedException!
317
+ });
318
+
319
+ // ✅ 创建新的 scope
320
+ _ = Task.Run(async () =>
321
+ {
322
+ await using var scope = serviceScopeFactory.CreateAsyncScope();
323
+ var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
324
+ await db.SaveChangesAsync();
325
+ });
326
+ ```
327
+
328
+ ### Request.Form 同步访问
329
+
330
+ ```csharp
331
+ // ❌ 同步读取 Form——sync over async
332
+ var form = HttpContext.Request.Form;
333
+
334
+ // ✅ 异步读取
335
+ var form = await HttpContext.Request.ReadFormAsync();
336
+ ```
337
+
338
+ ### 异常用于控制流
339
+
340
+ ```csharp
341
+ // ❌ 用异常判断是否存在——异常开销大,比直接检查慢得多
342
+ try
343
+ {
344
+ var user = await _db.Users.FirstAsync(u => u.Id == id);
345
+ }
346
+ catch (InvalidOperationException)
347
+ {
348
+ return NotFound();
349
+ }
350
+
351
+ // ✅ 使用检查而非异常
352
+ var user = await _db.Users.FirstOrDefaultAsync(u => u.Id == id);
353
+ if (user is null) return NotFound();
354
+ ```
355
+
356
+ ### 响应头在 Body 之后设置
357
+
358
+ ```csharp
359
+ // ❌ body 已发送后再设置 header——抛异常
360
+ await next(context);
361
+ context.Response.Headers["X-Custom"] = "value"; // 可能抛异常!
362
+
363
+ // ✅ 使用 OnStarting 回调
364
+ context.Response.OnStarting(() =>
365
+ {
366
+ context.Response.Headers["X-Custom"] = "value";
367
+ return Task.CompletedTask;
368
+ });
369
+ await next(context);
370
+ ```
371
+
372
+ ---
373
+
374
+ ## 依赖注入
375
+
376
+ ### Scoped 服务注入 Singleton
377
+
378
+ ```csharp
379
+ // ❌ Scoped 服务注入 Singleton——生命周期不匹配
380
+ services.AddSingleton<BackgroundWorker>();
381
+ services.AddScoped<IUserRepository, UserRepository>();
382
+
383
+ // BackgroundWorker 是 Singleton,UserRepository 是 Scoped
384
+ // → UserRepository 在多个请求间共享或已释放
385
+
386
+ // ✅ 在 Singleton 中通过 IServiceProvider 创建 scope
387
+ public class BackgroundWorker : BackgroundService
388
+ {
389
+ private readonly IServiceScopeFactory _scopeFactory;
390
+
391
+ public BackgroundWorker(IServiceScopeFactory scopeFactory)
392
+ => _scopeFactory = scopeFactory;
393
+
394
+ protected override async Task ExecuteAsync(CancellationToken ct)
395
+ {
396
+ await using var scope = _scopeFactory.CreateAsyncScope();
397
+ var repo = scope.ServiceProvider.GetRequiredService<IUserRepository>();
398
+ }
399
+ }
400
+ ```
401
+
402
+ ---
403
+
404
+ ## LINQ 最佳实践
405
+
406
+ ### ToList 之后再 LINQ
407
+
408
+ ```csharp
409
+ // ❌ 先 ToList 再过滤——全表加载到内存
410
+ var results = context.Posts
411
+ .Where(p => p.Title.StartsWith("A"))
412
+ .ToList()
413
+ .Where(p => SomeClientFilter(p)); // 客户端过滤,已加载全部行
414
+
415
+ // ✅ 尽可能让数据库执行过滤
416
+ var results = await context.Posts
417
+ .Where(p => p.Title.StartsWith("A") && SomeDbFilter(p))
418
+ .AsAsyncEnumerable()
419
+ .Where(p => SomeClientFilter(p)) // 只过滤数据库返回的行
420
+ .ToListAsync();
421
+ ```
422
+
423
+ ### Count() vs Any()
424
+
425
+ ```csharp
426
+ // ❌ Count() 执行完整查询
427
+ if (context.Users.Count() > 0) { /* ... */ }
428
+
429
+ // ✅ Any() 更高效——遇到第一条记录就返回
430
+ if (await context.Users.AnyAsync()) { /* ... */ }
431
+ ```
432
+
433
+ ### 多次枚举 IEnumerable
434
+
435
+ ```csharp
436
+ // ❌ IEnumerable 被枚举两次
437
+ public void Process(IEnumerable<int> numbers)
438
+ {
439
+ if (numbers.Any()) // 第一次枚举
440
+ {
441
+ foreach (var n in numbers) // 第二次枚举(可能是重新查询)
442
+ {
443
+ Console.WriteLine(n);
444
+ }
445
+ }
446
+ }
447
+
448
+ // ✅ 如果需要多次使用,先物化
449
+ public void Process(IEnumerable<int> numbers)
450
+ {
451
+ var list = numbers.ToList(); // 只枚举一次
452
+ if (list.Any())
453
+ {
454
+ foreach (var n in list)
455
+ {
456
+ Console.WriteLine(n);
457
+ }
458
+ }
459
+ }
460
+ ```
461
+
462
+ ### Select 中的副作用
463
+
464
+ ```csharp
465
+ // ❌ Select 中执行副作用——不可预测的执行时机
466
+ var results = users.Select(u =>
467
+ {
468
+ _logger.LogInformation($"Processing {u.Name}"); // 副作用!
469
+ return u.Email;
470
+ }).ToList();
471
+
472
+ // ✅ 副作用放在 foreach 中
473
+ foreach (var user in users)
474
+ {
475
+ _logger.LogInformation("Processing {Name}", user.Name);
476
+ }
477
+ var results = users.Select(u => u.Email).ToList();
478
+ ```
479
+
480
+ ---
481
+
482
+ ## Review Checklist
483
+
484
+ ### C# 12 新特性
485
+
486
+ - [ ] Primary constructor 参数不被重新赋值
487
+ - [ ] 集合表达式语法一致(不混用新旧风格)
488
+
489
+ ### 异步编程
490
+
491
+ - [ ] 无 `Task.Wait()`、`.Result`、`async void`
492
+ - [ ] 库代码使用 `ConfigureAwait(false)`
493
+ - [ ] `CancellationToken` 全链路传递
494
+ - [ ] 异步资源使用 `IAsyncDisposable` / `await using`
495
+ - [ ] 不混用同步和异步数据访问
496
+
497
+ ### EF Core
498
+
499
+ - [ ] 无 N+1 查询(导航属性在循环中访问)
500
+ - [ ] 投影 `Select()` 避免过度获取
501
+ - [ ] 分页:`ToListAsync()` 前有 `Take()`/`Skip()`
502
+ - [ ] 多个 `Include()` 使用 `AsSplitQuery()`
503
+ - [ ] 只读查询使用 `AsNoTracking()`
504
+ - [ ] 列上无函数调用阻止索引使用
505
+ - [ ] 数据库调用全部异步
506
+
507
+ ### ASP.NET Core
508
+
509
+ - [ ] HttpClient 通过 `IHttpClientFactory` 获取
510
+ - [ ] 后台任务中不直接使用 scoped 服务
511
+ - [ ] 使用 `ReadFormAsync` 代替 `Request.Form`
512
+ - [ ] 异常不用于控制流
513
+ - [ ] 响应头通过 `OnStarting` 设置
514
+
515
+ ### 依赖注入
516
+
517
+ - [ ] Scoped 服务不注入 Singleton
518
+ - [ ] 后台任务创建新 scope
519
+
520
+ ### LINQ
521
+
522
+ - [ ] 无不必要的 `ToList()` 后再 LINQ
523
+ - [ ] `Any()` 代替 `Count() > 0`
524
+ - [ ] IEnumerable 不被多次枚举(或先物化)
525
+ - [ ] Select 中无副作用