@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,993 @@
1
+ # Go 代码审查指南
2
+
3
+ 基于 Go 官方指南、Effective Go 和社区最佳实践的代码审查清单。
4
+
5
+ ## 快速审查清单
6
+
7
+ ### 必查项
8
+ - [ ] 错误是否正确处理(不忽略、有上下文)
9
+ - [ ] goroutine 是否有退出机制(避免泄漏)
10
+ - [ ] context 是否正确传递和取消
11
+ - [ ] 接收器类型选择是否合理(值/指针)
12
+ - [ ] 是否使用 `gofmt` 格式化代码
13
+
14
+ ### 高频问题
15
+ - [ ] 循环变量捕获问题(Go < 1.22)
16
+ - [ ] nil 检查是否完整
17
+ - [ ] map 是否初始化后使用
18
+ - [ ] defer 在循环中的使用
19
+ - [ ] 变量遮蔽(shadowing)
20
+
21
+ ---
22
+
23
+ ## 1. 错误处理
24
+
25
+ > 📖 通用原则和跨语言示例详见 [错误处理跨语言指南](cross-cutting/error-handling-principles.md)
26
+
27
+ ### 1.1 永远不要忽略错误
28
+
29
+ ```go
30
+ // ❌ 错误:忽略错误
31
+ result, _ := SomeFunction()
32
+
33
+ // ✅ 正确:处理错误
34
+ result, err := SomeFunction()
35
+ if err != nil {
36
+ return fmt.Errorf("some function failed: %w", err)
37
+ }
38
+ ```
39
+
40
+ ### 1.2 错误包装与上下文
41
+
42
+ ```go
43
+ // ❌ 错误:丢失上下文
44
+ if err != nil {
45
+ return err
46
+ }
47
+
48
+ // ❌ 错误:使用 %v 丢失错误链
49
+ if err != nil {
50
+ return fmt.Errorf("failed: %v", err)
51
+ }
52
+
53
+ // ✅ 正确:使用 %w 保留错误链
54
+ if err != nil {
55
+ return fmt.Errorf("failed to process user %d: %w", userID, err)
56
+ }
57
+ ```
58
+
59
+ ### 1.3 使用 errors.Is 和 errors.As
60
+
61
+ ```go
62
+ // ❌ 错误:直接比较(无法处理包装错误)
63
+ if err == sql.ErrNoRows {
64
+ // ...
65
+ }
66
+
67
+ // ✅ 正确:使用 errors.Is(支持错误链)
68
+ if errors.Is(err, sql.ErrNoRows) {
69
+ return nil, ErrNotFound
70
+ }
71
+
72
+ // ✅ 正确:使用 errors.As 提取特定类型
73
+ var pathErr *os.PathError
74
+ if errors.As(err, &pathErr) {
75
+ log.Printf("path error: %s", pathErr.Path)
76
+ }
77
+ ```
78
+
79
+ ### 1.4 自定义错误类型
80
+
81
+ ```go
82
+ // ✅ 推荐:定义 sentinel 错误
83
+ var (
84
+ ErrNotFound = errors.New("not found")
85
+ ErrUnauthorized = errors.New("unauthorized")
86
+ )
87
+
88
+ // ✅ 推荐:带上下文的自定义错误
89
+ type ValidationError struct {
90
+ Field string
91
+ Message string
92
+ }
93
+
94
+ func (e *ValidationError) Error() string {
95
+ return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
96
+ }
97
+ ```
98
+
99
+ ### 1.5 错误处理只做一次
100
+
101
+ ```go
102
+ // ❌ 错误:既记录又返回(重复处理)
103
+ if err != nil {
104
+ log.Printf("error: %v", err)
105
+ return err
106
+ }
107
+
108
+ // ✅ 正确:只返回,让调用者决定
109
+ if err != nil {
110
+ return fmt.Errorf("operation failed: %w", err)
111
+ }
112
+
113
+ // ✅ 或者:只记录并处理(不返回)
114
+ if err != nil {
115
+ log.Printf("non-critical error: %v", err)
116
+ // 继续执行备用逻辑
117
+ }
118
+ ```
119
+
120
+ ---
121
+
122
+ ## 2. 并发与 Goroutine
123
+
124
+ > 📖 通用并发模式和跨语言示例详见 [异步与并发跨语言指南](cross-cutting/async-concurrency-patterns.md)
125
+
126
+ ### 2.1 避免 Goroutine 泄漏
127
+
128
+ ```go
129
+ // ❌ 错误:goroutine 永远无法退出
130
+ func bad() {
131
+ ch := make(chan int)
132
+ go func() {
133
+ val := <-ch // 永远阻塞,无人发送
134
+ fmt.Println(val)
135
+ }()
136
+ // 函数返回,goroutine 泄漏
137
+ }
138
+
139
+ // ✅ 正确:使用 context 或 done channel
140
+ func good(ctx context.Context) {
141
+ ch := make(chan int)
142
+ go func() {
143
+ select {
144
+ case val := <-ch:
145
+ fmt.Println(val)
146
+ case <-ctx.Done():
147
+ return // 优雅退出
148
+ }
149
+ }()
150
+ }
151
+ ```
152
+
153
+ ### 2.2 Channel 使用规范
154
+
155
+ ```go
156
+ // ❌ 错误:向 nil channel 发送(永久阻塞)
157
+ var ch chan int
158
+ ch <- 1 // 永久阻塞
159
+
160
+ // ❌ 错误:向已关闭的 channel 发送(panic)
161
+ close(ch)
162
+ ch <- 1 // panic!
163
+
164
+ // ✅ 正确:发送方关闭 channel
165
+ func producer(ch chan<- int) {
166
+ defer close(ch) // 发送方负责关闭
167
+ for i := 0; i < 10; i++ {
168
+ ch <- i
169
+ }
170
+ }
171
+
172
+ // ✅ 正确:接收方检测关闭
173
+ for val := range ch {
174
+ process(val)
175
+ }
176
+ // 或者
177
+ val, ok := <-ch
178
+ if !ok {
179
+ // channel 已关闭
180
+ }
181
+ ```
182
+
183
+ ### 2.3 使用 sync.WaitGroup
184
+
185
+ ```go
186
+ // ❌ 错误:Add 在 goroutine 内部
187
+ var wg sync.WaitGroup
188
+ for i := 0; i < 10; i++ {
189
+ go func() {
190
+ wg.Add(1) // 竞态条件!
191
+ defer wg.Done()
192
+ work()
193
+ }()
194
+ }
195
+ wg.Wait()
196
+
197
+ // ✅ 正确:Add 在 goroutine 启动前
198
+ var wg sync.WaitGroup
199
+ for i := 0; i < 10; i++ {
200
+ wg.Add(1)
201
+ go func() {
202
+ defer wg.Done()
203
+ work()
204
+ }()
205
+ }
206
+ wg.Wait()
207
+ ```
208
+
209
+ ### 2.4 避免在循环中捕获变量(Go < 1.22)
210
+
211
+ ```go
212
+ // ❌ 错误(Go < 1.22):捕获循环变量
213
+ for _, item := range items {
214
+ go func() {
215
+ process(item) // 所有 goroutine 可能使用同一个 item
216
+ }()
217
+ }
218
+
219
+ // ✅ 正确:传递参数
220
+ for _, item := range items {
221
+ go func(it Item) {
222
+ process(it)
223
+ }(item)
224
+ }
225
+
226
+ // ✅ Go 1.22+:默认行为已修复,每次迭代创建新变量
227
+ ```
228
+
229
+ ### 2.5 Worker Pool 模式
230
+
231
+ ```go
232
+ // ✅ 推荐:限制并发数量
233
+ func processWithWorkerPool(ctx context.Context, items []Item, workers int) error {
234
+ jobs := make(chan Item, len(items))
235
+ results := make(chan error, len(items))
236
+
237
+ // 启动 worker
238
+ for w := 0; w < workers; w++ {
239
+ go func() {
240
+ for item := range jobs {
241
+ results <- process(item)
242
+ }
243
+ }()
244
+ }
245
+
246
+ // 发送任务
247
+ for _, item := range items {
248
+ jobs <- item
249
+ }
250
+ close(jobs)
251
+
252
+ // 收集结果
253
+ for range items {
254
+ if err := <-results; err != nil {
255
+ return err
256
+ }
257
+ }
258
+ return nil
259
+ }
260
+ ```
261
+
262
+ ---
263
+
264
+ ## 3. Context 使用
265
+
266
+ ### 3.1 Context 作为第一个参数
267
+
268
+ ```go
269
+ // ❌ 错误:context 不是第一个参数
270
+ func Process(data []byte, ctx context.Context) error
271
+
272
+ // ❌ 错误:context 存储在 struct 中
273
+ type Service struct {
274
+ ctx context.Context // 不要这样做!
275
+ }
276
+
277
+ // ✅ 正确:context 作为第一个参数,命名为 ctx
278
+ func Process(ctx context.Context, data []byte) error
279
+ ```
280
+
281
+ ### 3.2 传播而非创建新的根 Context
282
+
283
+ ```go
284
+ // ❌ 错误:在调用链中创建新的根 context
285
+ func middleware(next http.Handler) http.Handler {
286
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
287
+ ctx := context.Background() // 丢失了请求的 context!
288
+ process(ctx)
289
+ next.ServeHTTP(w, r)
290
+ })
291
+ }
292
+
293
+ // ✅ 正确:从请求中获取并传播
294
+ func middleware(next http.Handler) http.Handler {
295
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
296
+ ctx := r.Context()
297
+ ctx = context.WithValue(ctx, key, value)
298
+ process(ctx)
299
+ next.ServeHTTP(w, r.WithContext(ctx))
300
+ })
301
+ }
302
+ ```
303
+
304
+ ### 3.3 始终调用 cancel 函数
305
+
306
+ ```go
307
+ // ❌ 错误:未调用 cancel
308
+ ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second)
309
+ // 缺少 cancel() 调用,可能资源泄漏
310
+
311
+ // ✅ 正确:使用 defer 确保调用
312
+ ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second)
313
+ defer cancel() // 即使超时也要调用
314
+ ```
315
+
316
+ ### 3.4 响应 Context 取消
317
+
318
+ ```go
319
+ // ✅ 推荐:在长时间操作中检查 context
320
+ func LongRunningTask(ctx context.Context) error {
321
+ for {
322
+ select {
323
+ case <-ctx.Done():
324
+ return ctx.Err() // 返回 context.Canceled 或 context.DeadlineExceeded
325
+ default:
326
+ // 执行一小部分工作
327
+ if err := doChunk(); err != nil {
328
+ return err
329
+ }
330
+ }
331
+ }
332
+ }
333
+ ```
334
+
335
+ ### 3.5 区分取消原因
336
+
337
+ ```go
338
+ // ✅ 根据 ctx.Err() 区分取消原因
339
+ if err := ctx.Err(); err != nil {
340
+ switch {
341
+ case errors.Is(err, context.Canceled):
342
+ log.Println("operation was canceled")
343
+ case errors.Is(err, context.DeadlineExceeded):
344
+ log.Println("operation timed out")
345
+ }
346
+ return err
347
+ }
348
+ ```
349
+
350
+ ---
351
+
352
+ ## 4. 接口设计
353
+
354
+ ### 4.1 接受接口,返回结构体
355
+
356
+ ```go
357
+ // ❌ 不推荐:接受具体类型
358
+ func SaveUser(db *sql.DB, user User) error
359
+
360
+ // ✅ 推荐:接受接口(解耦、易测试)
361
+ type UserStore interface {
362
+ Save(ctx context.Context, user User) error
363
+ }
364
+
365
+ func SaveUser(store UserStore, user User) error
366
+
367
+ // ❌ 不推荐:返回接口
368
+ func NewUserService() UserServiceInterface
369
+
370
+ // ✅ 推荐:返回具体类型
371
+ func NewUserService(store UserStore) *UserService
372
+ ```
373
+
374
+ ### 4.2 在消费者处定义接口
375
+
376
+ ```go
377
+ // ❌ 不推荐:在实现包中定义接口
378
+ // package database
379
+ type Database interface {
380
+ Query(ctx context.Context, query string) ([]Row, error)
381
+ // ... 20 个方法
382
+ }
383
+
384
+ // ✅ 推荐:在消费者包中定义所需的最小接口
385
+ // package userservice
386
+ type UserQuerier interface {
387
+ QueryUsers(ctx context.Context, filter Filter) ([]User, error)
388
+ }
389
+ ```
390
+
391
+ ### 4.3 保持接口小而专注
392
+
393
+ ```go
394
+ // ❌ 不推荐:大而全的接口
395
+ type Repository interface {
396
+ GetUser(id int) (*User, error)
397
+ CreateUser(u *User) error
398
+ UpdateUser(u *User) error
399
+ DeleteUser(id int) error
400
+ GetOrder(id int) (*Order, error)
401
+ CreateOrder(o *Order) error
402
+ // ... 更多方法
403
+ }
404
+
405
+ // ✅ 推荐:小而专注的接口
406
+ type UserReader interface {
407
+ GetUser(ctx context.Context, id int) (*User, error)
408
+ }
409
+
410
+ type UserWriter interface {
411
+ CreateUser(ctx context.Context, u *User) error
412
+ UpdateUser(ctx context.Context, u *User) error
413
+ }
414
+
415
+ // 组合接口
416
+ type UserRepository interface {
417
+ UserReader
418
+ UserWriter
419
+ }
420
+ ```
421
+
422
+ ### 4.4 避免空接口滥用
423
+
424
+ ```go
425
+ // ❌ 不推荐:过度使用 interface{}
426
+ func Process(data interface{}) interface{}
427
+
428
+ // ✅ 推荐:使用泛型(Go 1.18+)
429
+ func Process[T any](data T) T
430
+
431
+ // ✅ 推荐:定义具体接口
432
+ type Processor interface {
433
+ Process() Result
434
+ }
435
+ ```
436
+
437
+ ---
438
+
439
+ ## 5. 接收器类型选择
440
+
441
+ ### 5.1 使用指针接收器的情况
442
+
443
+ ```go
444
+ // ✅ 需要修改接收器时
445
+ func (u *User) SetName(name string) {
446
+ u.Name = name
447
+ }
448
+
449
+ // ✅ 接收器包含 sync.Mutex 等同步原语
450
+ type SafeCounter struct {
451
+ mu sync.Mutex
452
+ count int
453
+ }
454
+
455
+ func (c *SafeCounter) Inc() {
456
+ c.mu.Lock()
457
+ defer c.mu.Unlock()
458
+ c.count++
459
+ }
460
+
461
+ // ✅ 接收器是大型结构体(避免复制开销)
462
+ type LargeStruct struct {
463
+ Data [1024]byte
464
+ // ...
465
+ }
466
+
467
+ func (l *LargeStruct) Process() { /* ... */ }
468
+ ```
469
+
470
+ ### 5.2 使用值接收器的情况
471
+
472
+ ```go
473
+ // ✅ 接收器是小型不可变结构体
474
+ type Point struct {
475
+ X, Y float64
476
+ }
477
+
478
+ func (p Point) Distance(other Point) float64 {
479
+ return math.Sqrt(math.Pow(p.X-other.X, 2) + math.Pow(p.Y-other.Y, 2))
480
+ }
481
+
482
+ // ✅ 接收器是基本类型的别名
483
+ type Counter int
484
+
485
+ func (c Counter) String() string {
486
+ return fmt.Sprintf("%d", c)
487
+ }
488
+
489
+ // ✅ 接收器是 map、func、chan(本身是引用类型)
490
+ type StringSet map[string]struct{}
491
+
492
+ func (s StringSet) Contains(key string) bool {
493
+ _, ok := s[key]
494
+ return ok
495
+ }
496
+ ```
497
+
498
+ ### 5.3 一致性原则
499
+
500
+ ```go
501
+ // ❌ 不推荐:混合使用接收器类型
502
+ func (u User) GetName() string // 值接收器
503
+ func (u *User) SetName(n string) // 指针接收器
504
+
505
+ // ✅ 推荐:如果有任何方法需要指针接收器,全部使用指针
506
+ func (u *User) GetName() string { return u.Name }
507
+ func (u *User) SetName(n string) { u.Name = n }
508
+ ```
509
+
510
+ ---
511
+
512
+ ## 6. 性能优化
513
+
514
+ ### 6.1 预分配 Slice
515
+
516
+ ```go
517
+ // ❌ 不推荐:动态增长
518
+ var result []int
519
+ for i := 0; i < 10000; i++ {
520
+ result = append(result, i) // 多次分配和复制
521
+ }
522
+
523
+ // ✅ 推荐:预分配已知大小
524
+ result := make([]int, 0, 10000)
525
+ for i := 0; i < 10000; i++ {
526
+ result = append(result, i)
527
+ }
528
+
529
+ // ✅ 或者直接初始化
530
+ result := make([]int, 10000)
531
+ for i := 0; i < 10000; i++ {
532
+ result[i] = i
533
+ }
534
+ ```
535
+
536
+ ### 6.2 避免不必要的堆分配
537
+
538
+ ```go
539
+ // ❌ 可能逃逸到堆
540
+ func NewUser() *User {
541
+ return &User{} // 逃逸到堆
542
+ }
543
+
544
+ // ✅ 考虑返回值(如果适用)
545
+ func NewUser() User {
546
+ return User{} // 可能在栈上分配
547
+ }
548
+
549
+ // 检查逃逸分析
550
+ // go build -gcflags '-m -m' ./...
551
+ ```
552
+
553
+ ### 6.3 使用 sync.Pool 复用对象
554
+
555
+ ```go
556
+ // ✅ 推荐:高频创建/销毁的对象使用 sync.Pool
557
+ var bufferPool = sync.Pool{
558
+ New: func() interface{} {
559
+ return new(bytes.Buffer)
560
+ },
561
+ }
562
+
563
+ func ProcessData(data []byte) string {
564
+ buf := bufferPool.Get().(*bytes.Buffer)
565
+ defer func() {
566
+ buf.Reset()
567
+ bufferPool.Put(buf)
568
+ }()
569
+
570
+ buf.Write(data)
571
+ return buf.String()
572
+ }
573
+ ```
574
+
575
+ ### 6.4 字符串拼接优化
576
+
577
+ ```go
578
+ // ❌ 不推荐:循环中使用 + 拼接
579
+ var result string
580
+ for _, s := range strings {
581
+ result += s // 每次创建新字符串
582
+ }
583
+
584
+ // ✅ 推荐:使用 strings.Builder
585
+ var builder strings.Builder
586
+ for _, s := range strings {
587
+ builder.WriteString(s)
588
+ }
589
+ result := builder.String()
590
+
591
+ // ✅ 或者使用 strings.Join
592
+ result := strings.Join(strings, "")
593
+ ```
594
+
595
+ ### 6.5 避免 interface{} 转换开销
596
+
597
+ ```go
598
+ // ❌ 热路径中使用 interface{}
599
+ func process(data interface{}) {
600
+ switch v := data.(type) { // 类型断言有开销
601
+ case int:
602
+ // ...
603
+ }
604
+ }
605
+
606
+ // ✅ 热路径中使用泛型或具体类型
607
+ func process[T int | int64 | float64](data T) {
608
+ // 编译时确定类型,无运行时开销
609
+ }
610
+ ```
611
+
612
+ ---
613
+
614
+ ## 7. 测试
615
+
616
+ ### 7.1 表驱动测试
617
+
618
+ ```go
619
+ // ✅ 推荐:表驱动测试
620
+ func TestAdd(t *testing.T) {
621
+ tests := []struct {
622
+ name string
623
+ a, b int
624
+ expected int
625
+ }{
626
+ {"positive numbers", 1, 2, 3},
627
+ {"with zero", 0, 5, 5},
628
+ {"negative numbers", -1, -2, -3},
629
+ }
630
+
631
+ for _, tt := range tests {
632
+ t.Run(tt.name, func(t *testing.T) {
633
+ result := Add(tt.a, tt.b)
634
+ if result != tt.expected {
635
+ t.Errorf("Add(%d, %d) = %d; want %d",
636
+ tt.a, tt.b, result, tt.expected)
637
+ }
638
+ })
639
+ }
640
+ }
641
+ ```
642
+
643
+ ### 7.2 并行测试
644
+
645
+ ```go
646
+ // ✅ 推荐:独立测试用例并行执行
647
+ func TestParallel(t *testing.T) {
648
+ tests := []struct {
649
+ name string
650
+ input string
651
+ }{
652
+ {"test1", "input1"},
653
+ {"test2", "input2"},
654
+ }
655
+
656
+ for _, tt := range tests {
657
+ tt := tt // Go < 1.22 需要复制
658
+ t.Run(tt.name, func(t *testing.T) {
659
+ t.Parallel() // 标记为可并行
660
+ result := Process(tt.input)
661
+ // assertions...
662
+ })
663
+ }
664
+ }
665
+ ```
666
+
667
+ ### 7.3 使用接口进行 Mock
668
+
669
+ ```go
670
+ // ✅ 定义接口以便测试
671
+ type EmailSender interface {
672
+ Send(to, subject, body string) error
673
+ }
674
+
675
+ // 生产实现
676
+ type SMTPSender struct { /* ... */ }
677
+
678
+ // 测试 Mock
679
+ type MockEmailSender struct {
680
+ SendFunc func(to, subject, body string) error
681
+ }
682
+
683
+ func (m *MockEmailSender) Send(to, subject, body string) error {
684
+ return m.SendFunc(to, subject, body)
685
+ }
686
+
687
+ func TestUserRegistration(t *testing.T) {
688
+ mock := &MockEmailSender{
689
+ SendFunc: func(to, subject, body string) error {
690
+ if to != "test@example.com" {
691
+ t.Errorf("unexpected recipient: %s", to)
692
+ }
693
+ return nil
694
+ },
695
+ }
696
+
697
+ service := NewUserService(mock)
698
+ // test...
699
+ }
700
+ ```
701
+
702
+ ### 7.4 测试辅助函数
703
+
704
+ ```go
705
+ // ✅ 使用 t.Helper() 标记辅助函数
706
+ func assertEqual(t *testing.T, got, want interface{}) {
707
+ t.Helper() // 错误报告时显示调用者位置
708
+ if got != want {
709
+ t.Errorf("got %v, want %v", got, want)
710
+ }
711
+ }
712
+
713
+ // ✅ 使用 t.Cleanup() 清理资源
714
+ func TestWithTempFile(t *testing.T) {
715
+ f, err := os.CreateTemp("", "test")
716
+ if err != nil {
717
+ t.Fatal(err)
718
+ }
719
+ t.Cleanup(func() {
720
+ os.Remove(f.Name())
721
+ })
722
+ // test...
723
+ }
724
+ ```
725
+
726
+ ---
727
+
728
+ ## 8. 常见陷阱
729
+
730
+ ### 8.1 Nil Slice vs Empty Slice
731
+
732
+ ```go
733
+ var nilSlice []int // nil, len=0, cap=0
734
+ emptySlice := []int{} // not nil, len=0, cap=0
735
+ made := make([]int, 0) // not nil, len=0, cap=0
736
+
737
+ // ✅ JSON 编码差异
738
+ json.Marshal(nilSlice) // null
739
+ json.Marshal(emptySlice) // []
740
+
741
+ // ✅ 推荐:需要空数组 JSON 时显式初始化
742
+ if slice == nil {
743
+ slice = []int{}
744
+ }
745
+ ```
746
+
747
+ ### 8.2 Map 初始化
748
+
749
+ ```go
750
+ // ❌ 错误:未初始化的 map
751
+ var m map[string]int
752
+ m["key"] = 1 // panic: assignment to entry in nil map
753
+
754
+ // ✅ 正确:使用 make 初始化
755
+ m := make(map[string]int)
756
+ m["key"] = 1
757
+
758
+ // ✅ 或者使用字面量
759
+ m := map[string]int{}
760
+ ```
761
+
762
+ ### 8.3 Defer 在循环中
763
+
764
+ ```go
765
+ // ❌ 潜在问题:defer 在函数结束时才执行
766
+ func processFiles(files []string) error {
767
+ for _, file := range files {
768
+ f, err := os.Open(file)
769
+ if err != nil {
770
+ return err
771
+ }
772
+ defer f.Close() // 所有文件在函数结束时才关闭!
773
+ // process...
774
+ }
775
+ return nil
776
+ }
777
+
778
+ // ✅ 正确:使用闭包或提取函数
779
+ func processFiles(files []string) error {
780
+ for _, file := range files {
781
+ if err := processFile(file); err != nil {
782
+ return err
783
+ }
784
+ }
785
+ return nil
786
+ }
787
+
788
+ func processFile(file string) error {
789
+ f, err := os.Open(file)
790
+ if err != nil {
791
+ return err
792
+ }
793
+ defer f.Close()
794
+ // process...
795
+ return nil
796
+ }
797
+ ```
798
+
799
+ ### 8.4 Slice 底层数组共享
800
+
801
+ ```go
802
+ // ❌ 潜在问题:切片共享底层数组
803
+ original := []int{1, 2, 3, 4, 5}
804
+ slice := original[1:3] // [2, 3]
805
+ slice[0] = 100 // 修改了 original!
806
+ // original 变成 [1, 100, 3, 4, 5]
807
+
808
+ // ✅ 正确:需要独立副本时显式复制
809
+ slice := make([]int, 2)
810
+ copy(slice, original[1:3])
811
+ slice[0] = 100 // 不影响 original
812
+ ```
813
+
814
+ ### 8.5 字符串子串内存泄漏
815
+
816
+ ```go
817
+ // ❌ 潜在问题:子串持有整个底层数组
818
+ func getPrefix(s string) string {
819
+ return s[:10] // 仍引用整个 s 的底层数组
820
+ }
821
+
822
+ // ✅ 正确:创建独立副本(Go 1.18+)
823
+ func getPrefix(s string) string {
824
+ return strings.Clone(s[:10])
825
+ }
826
+
827
+ // ✅ Go 1.18 之前
828
+ func getPrefix(s string) string {
829
+ return string([]byte(s[:10]))
830
+ }
831
+ ```
832
+
833
+ ### 8.6 Interface Nil 陷阱
834
+
835
+ ```go
836
+ // ❌ 陷阱:interface 的 nil 判断
837
+ type MyError struct{}
838
+ func (e *MyError) Error() string { return "error" }
839
+
840
+ func returnsError() error {
841
+ var e *MyError = nil
842
+ return e // 返回的 error 不是 nil!
843
+ }
844
+
845
+ func main() {
846
+ err := returnsError()
847
+ if err != nil { // true! interface{type: *MyError, value: nil}
848
+ fmt.Println("error:", err)
849
+ }
850
+ }
851
+
852
+ // ✅ 正确:显式返回 nil
853
+ func returnsError() error {
854
+ var e *MyError = nil
855
+ if e == nil {
856
+ return nil // 显式返回 nil
857
+ }
858
+ return e
859
+ }
860
+ ```
861
+
862
+ ### 8.7 Time 比较
863
+
864
+ ```go
865
+ // ❌ 不推荐:直接使用 == 比较 time.Time
866
+ if t1 == t2 { // 可能因为单调时钟差异而失败
867
+ // ...
868
+ }
869
+
870
+ // ✅ 推荐:使用 Equal 方法
871
+ if t1.Equal(t2) {
872
+ // ...
873
+ }
874
+
875
+ // ✅ 比较时间范围
876
+ if t1.Before(t2) || t1.After(t2) {
877
+ // ...
878
+ }
879
+ ```
880
+
881
+ ---
882
+
883
+ ## 9. 代码组织
884
+
885
+ ### 9.1 包命名
886
+
887
+ ```go
888
+ // ❌ 不推荐
889
+ package common // 过于宽泛
890
+ package utils // 过于宽泛
891
+ package helpers // 过于宽泛
892
+ package models // 按类型分组
893
+
894
+ // ✅ 推荐:按功能命名
895
+ package user // 用户相关功能
896
+ package order // 订单相关功能
897
+ package postgres // PostgreSQL 实现
898
+ ```
899
+
900
+ ### 9.2 避免循环依赖
901
+
902
+ ```go
903
+ // ❌ 循环依赖
904
+ // package a imports package b
905
+ // package b imports package a
906
+
907
+ // ✅ 解决方案1:提取共享类型到独立包
908
+ // package types (共享类型)
909
+ // package a imports types
910
+ // package b imports types
911
+
912
+ // ✅ 解决方案2:使用接口解耦
913
+ // package a 定义接口
914
+ // package b 实现接口
915
+ ```
916
+
917
+ ### 9.3 导出标识符规范
918
+
919
+ ```go
920
+ // ✅ 只导出必要的标识符
921
+ type UserService struct {
922
+ db *sql.DB // 私有
923
+ }
924
+
925
+ func (s *UserService) GetUser(id int) (*User, error) // 公开
926
+ func (s *UserService) validate(u *User) error // 私有
927
+
928
+ // ✅ 内部包限制访问
929
+ // internal/database/... 只能被同项目代码导入
930
+ ```
931
+
932
+ ---
933
+
934
+ ## 10. 工具与检查
935
+
936
+ ### 10.1 必须使用的工具
937
+
938
+ ```bash
939
+ # 格式化(必须)
940
+ gofmt -w .
941
+ goimports -w .
942
+
943
+ # 静态分析
944
+ go vet ./...
945
+
946
+ # 竞态检测
947
+ go test -race ./...
948
+
949
+ # 逃逸分析
950
+ go build -gcflags '-m -m' ./...
951
+ ```
952
+
953
+ ### 10.2 推荐的 Linter
954
+
955
+ ```bash
956
+ # golangci-lint(集成多个 linter)
957
+ golangci-lint run
958
+
959
+ # 常用检查项
960
+ # - errcheck: 检查未处理的错误
961
+ # - gosec: 安全检查
962
+ # - ineffassign: 无效赋值
963
+ # - staticcheck: 静态分析
964
+ # - unused: 未使用的代码
965
+ ```
966
+
967
+ ### 10.3 Benchmark 测试
968
+
969
+ ```go
970
+ // ✅ 性能基准测试
971
+ func BenchmarkProcess(b *testing.B) {
972
+ data := prepareData()
973
+ b.ResetTimer() // 重置计时器
974
+
975
+ for i := 0; i < b.N; i++ {
976
+ Process(data)
977
+ }
978
+ }
979
+
980
+ // 运行 benchmark
981
+ // go test -bench=. -benchmem ./...
982
+ ```
983
+
984
+ ---
985
+
986
+ ## 参考资源
987
+
988
+ - [Effective Go](https://go.dev/doc/effective_go)
989
+ - [Go Code Review Comments](https://go.dev/wiki/CodeReviewComments)
990
+ - [Go Common Mistakes](https://go.dev/wiki/CommonMistakes)
991
+ - [100 Go Mistakes](https://100go.co/)
992
+ - [Go Proverbs](https://go-proverbs.github.io/)
993
+ - [Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md)