From 7a66dab1768f777b20e9893d0fc4007bdce958ae Mon Sep 17 00:00:00 2001 From: Tu Shaokun <2801884530@qq.com> Date: Sun, 30 Nov 2025 18:15:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20Go=20=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E4=BB=A3=E7=A0=81=E5=AE=A1=E6=9F=A5=E6=8C=87=E5=8D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 reference/go.md (~990 行),涵盖: - 错误处理(errors.Is/As, 包装, 自定义错误) - 并发(goroutine 泄漏, channel, sync.WaitGroup) - Context 使用(传播, 取消, 超时) - 接口设计(接受接口返回结构体, 小接口) - 接收器类型选择(值/指针) - 性能优化(预分配, sync.Pool, 逃逸分析) - 测试(表驱动, 并行, Mock) - 常见陷阱(nil slice, map 初始化, defer 循环) 更新 SKILL.md 和 README.md 添加 Go 语言支持 总行数:~8000 → ~9000 行 --- README.md | 10 +- SKILL.md | 1 + reference/go.md | 989 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 998 insertions(+), 2 deletions(-) create mode 100644 reference/go.md diff --git a/README.md b/README.md index 73909c6..ad61260 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ This is a Claude Code skill designed to help developers conduct effective code r | **Vue 3** | Composition API, reactivity system, defineProps/defineEmits, watch cleanup | | **Rust** | Ownership & borrowing, unsafe code review, async/await, error handling (thiserror vs anyhow) | | **TypeScript** | Type safety, async/await patterns, common pitfalls | +| **Go** | Error handling, goroutines/channels, context propagation, interface design, testing patterns | | **CSS/Less/Sass** | CSS variables, !important usage, performance optimization, responsive design, browser compatibility | | **TanStack Query** | v5 best practices, queryOptions, useSuspenseQuery, optimistic updates | | **Architecture** | SOLID principles, anti-patterns, coupling/cohesion, layered architecture | @@ -45,11 +46,12 @@ This is a Claude Code skill designed to help developers conduct effective code r | **reference/rust.md** | ~840 | Rust async/ownership/cancellation safety (on-demand) | | **reference/typescript.md** | ~540 | TypeScript generics/strict mode/ESLint (on-demand) | | **reference/python.md** | ~1070 | Python async/typing/pytest (on-demand) | +| **reference/go.md** | ~990 | Go goroutines/channels/context/interfaces (on-demand) | | **reference/css-less-sass.md** | ~660 | CSS/Less/Sass variables/performance/responsive (on-demand) | | **reference/architecture-review-guide.md** | ~470 | SOLID/anti-patterns/coupling analysis (on-demand) | | **reference/performance-review-guide.md** | ~750 | Core Web Vitals/N+1/memory/complexity (on-demand) | -**Total: ~8,000 lines** of review guidelines and code examples, loaded on-demand per language. +**Total: ~9,000 lines** of review guidelines and code examples, loaded on-demand per language. ### Installation @@ -96,6 +98,7 @@ code-review-skill/ │ ├── rust.md # Rust patterns (on-demand) │ ├── typescript.md # TypeScript/JS patterns (on-demand) │ ├── python.md # Python patterns (on-demand) +│ ├── go.md # Go patterns (on-demand) │ ├── css-less-sass.md # CSS/Less/Sass patterns (on-demand) │ ├── architecture-review-guide.md # Architecture design review (on-demand) │ ├── performance-review-guide.md # Performance review (on-demand) @@ -199,6 +202,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file | **Vue 3** | Composition API、响应性系统、defineProps/defineEmits、watch 清理 | | **Rust** | 所有权与借用、unsafe 代码审查、async/await、错误处理(thiserror vs anyhow) | | **TypeScript** | 类型安全、async/await 模式、常见陷阱 | +| **Go** | 错误处理、goroutine/channel、context 传播、接口设计、测试模式 | | **CSS/Less/Sass** | CSS 变量规范、!important 使用、性能优化、响应式设计、浏览器兼容性 | | **TanStack Query** | v5 最佳实践、queryOptions、useSuspenseQuery、乐观更新 | | **架构设计** | SOLID 原则、架构反模式、耦合度/内聚性、分层架构 | @@ -214,11 +218,12 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file | **reference/rust.md** | ~840 | Rust async/所有权/取消安全性(按需加载)| | **reference/typescript.md** | ~540 | TypeScript 泛型/strict 模式/ESLint(按需加载)| | **reference/python.md** | ~1070 | Python async/类型注解/pytest(按需加载)| +| **reference/go.md** | ~990 | Go goroutine/channel/context/接口(按需加载)| | **reference/css-less-sass.md** | ~660 | CSS/Less/Sass 变量/性能/响应式(按需加载)| | **reference/architecture-review-guide.md** | ~470 | SOLID/反模式/耦合度分析(按需加载)| | **reference/performance-review-guide.md** | ~750 | Core Web Vitals/N+1/内存/复杂度(按需加载)| -**总计:8,000 行**审查指南和代码示例,按语言按需加载。 +**总计:9,000 行**审查指南和代码示例,按语言按需加载。 ### 安装 @@ -265,6 +270,7 @@ code-review-skill/ │ ├── rust.md # Rust 模式(按需加载) │ ├── typescript.md # TypeScript/JS 模式(按需加载) │ ├── python.md # Python 模式(按需加载) +│ ├── go.md # Go 模式(按需加载) │ ├── css-less-sass.md # CSS/Less/Sass 模式(按需加载) │ ├── architecture-review-guide.md # 架构设计审查(按需加载) │ ├── performance-review-guide.md # 性能审查(按需加载) diff --git a/SKILL.md b/SKILL.md index dca634b..43c5c6d 100644 --- a/SKILL.md +++ b/SKILL.md @@ -180,6 +180,7 @@ Use labels to indicate priority: | **Rust** | [Rust Guide](reference/rust.md) | 所有权/借用, Unsafe 审查, 异步代码, 错误处理 | | **TypeScript** | [TypeScript Guide](reference/typescript.md) | 类型安全, async/await, 不可变性 | | **Python** | [Python Guide](reference/python.md) | 可变默认参数, 异常处理, 类属性 | +| **Go** | [Go Guide](reference/go.md) | 错误处理, goroutine/channel, context, 接口设计 | | **CSS/Less/Sass** | [CSS Guide](reference/css-less-sass.md) | 变量规范, !important, 性能优化, 响应式, 兼容性 | ## Additional Resources diff --git a/reference/go.md b/reference/go.md new file mode 100644 index 0000000..2438fea --- /dev/null +++ b/reference/go.md @@ -0,0 +1,989 @@ +# Go 代码审查指南 + +基于 Go 官方指南、Effective Go 和社区最佳实践的代码审查清单。 + +## 快速审查清单 + +### 必查项 +- [ ] 错误是否正确处理(不忽略、有上下文) +- [ ] goroutine 是否有退出机制(避免泄漏) +- [ ] context 是否正确传递和取消 +- [ ] 接收器类型选择是否合理(值/指针) +- [ ] 是否使用 `gofmt` 格式化代码 + +### 高频问题 +- [ ] 循环变量捕获问题(Go < 1.22) +- [ ] nil 检查是否完整 +- [ ] map 是否初始化后使用 +- [ ] defer 在循环中的使用 +- [ ] 变量遮蔽(shadowing) + +--- + +## 1. 错误处理 + +### 1.1 永远不要忽略错误 + +```go +// ❌ 错误:忽略错误 +result, _ := SomeFunction() + +// ✅ 正确:处理错误 +result, err := SomeFunction() +if err != nil { + return fmt.Errorf("some function failed: %w", err) +} +``` + +### 1.2 错误包装与上下文 + +```go +// ❌ 错误:丢失上下文 +if err != nil { + return err +} + +// ❌ 错误:使用 %v 丢失错误链 +if err != nil { + return fmt.Errorf("failed: %v", err) +} + +// ✅ 正确:使用 %w 保留错误链 +if err != nil { + return fmt.Errorf("failed to process user %d: %w", userID, err) +} +``` + +### 1.3 使用 errors.Is 和 errors.As + +```go +// ❌ 错误:直接比较(无法处理包装错误) +if err == sql.ErrNoRows { + // ... +} + +// ✅ 正确:使用 errors.Is(支持错误链) +if errors.Is(err, sql.ErrNoRows) { + return nil, ErrNotFound +} + +// ✅ 正确:使用 errors.As 提取特定类型 +var pathErr *os.PathError +if errors.As(err, &pathErr) { + log.Printf("path error: %s", pathErr.Path) +} +``` + +### 1.4 自定义错误类型 + +```go +// ✅ 推荐:定义 sentinel 错误 +var ( + ErrNotFound = errors.New("not found") + ErrUnauthorized = errors.New("unauthorized") +) + +// ✅ 推荐:带上下文的自定义错误 +type ValidationError struct { + Field string + Message string +} + +func (e *ValidationError) Error() string { + return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message) +} +``` + +### 1.5 错误处理只做一次 + +```go +// ❌ 错误:既记录又返回(重复处理) +if err != nil { + log.Printf("error: %v", err) + return err +} + +// ✅ 正确:只返回,让调用者决定 +if err != nil { + return fmt.Errorf("operation failed: %w", err) +} + +// ✅ 或者:只记录并处理(不返回) +if err != nil { + log.Printf("non-critical error: %v", err) + // 继续执行备用逻辑 +} +``` + +--- + +## 2. 并发与 Goroutine + +### 2.1 避免 Goroutine 泄漏 + +```go +// ❌ 错误:goroutine 永远无法退出 +func bad() { + ch := make(chan int) + go func() { + val := <-ch // 永远阻塞,无人发送 + fmt.Println(val) + }() + // 函数返回,goroutine 泄漏 +} + +// ✅ 正确:使用 context 或 done channel +func good(ctx context.Context) { + ch := make(chan int) + go func() { + select { + case val := <-ch: + fmt.Println(val) + case <-ctx.Done(): + return // 优雅退出 + } + }() +} +``` + +### 2.2 Channel 使用规范 + +```go +// ❌ 错误:向 nil channel 发送(永久阻塞) +var ch chan int +ch <- 1 // 永久阻塞 + +// ❌ 错误:向已关闭的 channel 发送(panic) +close(ch) +ch <- 1 // panic! + +// ✅ 正确:发送方关闭 channel +func producer(ch chan<- int) { + defer close(ch) // 发送方负责关闭 + for i := 0; i < 10; i++ { + ch <- i + } +} + +// ✅ 正确:接收方检测关闭 +for val := range ch { + process(val) +} +// 或者 +val, ok := <-ch +if !ok { + // channel 已关闭 +} +``` + +### 2.3 使用 sync.WaitGroup + +```go +// ❌ 错误:Add 在 goroutine 内部 +var wg sync.WaitGroup +for i := 0; i < 10; i++ { + go func() { + wg.Add(1) // 竞态条件! + defer wg.Done() + work() + }() +} +wg.Wait() + +// ✅ 正确:Add 在 goroutine 启动前 +var wg sync.WaitGroup +for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + work() + }() +} +wg.Wait() +``` + +### 2.4 避免在循环中捕获变量(Go < 1.22) + +```go +// ❌ 错误(Go < 1.22):捕获循环变量 +for _, item := range items { + go func() { + process(item) // 所有 goroutine 可能使用同一个 item + }() +} + +// ✅ 正确:传递参数 +for _, item := range items { + go func(it Item) { + process(it) + }(item) +} + +// ✅ Go 1.22+:默认行为已修复,每次迭代创建新变量 +``` + +### 2.5 Worker Pool 模式 + +```go +// ✅ 推荐:限制并发数量 +func processWithWorkerPool(ctx context.Context, items []Item, workers int) error { + jobs := make(chan Item, len(items)) + results := make(chan error, len(items)) + + // 启动 worker + for w := 0; w < workers; w++ { + go func() { + for item := range jobs { + results <- process(item) + } + }() + } + + // 发送任务 + for _, item := range items { + jobs <- item + } + close(jobs) + + // 收集结果 + for range items { + if err := <-results; err != nil { + return err + } + } + return nil +} +``` + +--- + +## 3. Context 使用 + +### 3.1 Context 作为第一个参数 + +```go +// ❌ 错误:context 不是第一个参数 +func Process(data []byte, ctx context.Context) error + +// ❌ 错误:context 存储在 struct 中 +type Service struct { + ctx context.Context // 不要这样做! +} + +// ✅ 正确:context 作为第一个参数,命名为 ctx +func Process(ctx context.Context, data []byte) error +``` + +### 3.2 传播而非创建新的根 Context + +```go +// ❌ 错误:在调用链中创建新的根 context +func middleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.Background() // 丢失了请求的 context! + process(ctx) + next.ServeHTTP(w, r) + }) +} + +// ✅ 正确:从请求中获取并传播 +func middleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + ctx = context.WithValue(ctx, key, value) + process(ctx) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} +``` + +### 3.3 始终调用 cancel 函数 + +```go +// ❌ 错误:未调用 cancel +ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second) +// 缺少 cancel() 调用,可能资源泄漏 + +// ✅ 正确:使用 defer 确保调用 +ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second) +defer cancel() // 即使超时也要调用 +``` + +### 3.4 响应 Context 取消 + +```go +// ✅ 推荐:在长时间操作中检查 context +func LongRunningTask(ctx context.Context) error { + for { + select { + case <-ctx.Done(): + return ctx.Err() // 返回 context.Canceled 或 context.DeadlineExceeded + default: + // 执行一小部分工作 + if err := doChunk(); err != nil { + return err + } + } + } +} +``` + +### 3.5 区分取消原因 + +```go +// ✅ 根据 ctx.Err() 区分取消原因 +if err := ctx.Err(); err != nil { + switch { + case errors.Is(err, context.Canceled): + log.Println("operation was canceled") + case errors.Is(err, context.DeadlineExceeded): + log.Println("operation timed out") + } + return err +} +``` + +--- + +## 4. 接口设计 + +### 4.1 接受接口,返回结构体 + +```go +// ❌ 不推荐:接受具体类型 +func SaveUser(db *sql.DB, user User) error + +// ✅ 推荐:接受接口(解耦、易测试) +type UserStore interface { + Save(ctx context.Context, user User) error +} + +func SaveUser(store UserStore, user User) error + +// ❌ 不推荐:返回接口 +func NewUserService() UserServiceInterface + +// ✅ 推荐:返回具体类型 +func NewUserService(store UserStore) *UserService +``` + +### 4.2 在消费者处定义接口 + +```go +// ❌ 不推荐:在实现包中定义接口 +// package database +type Database interface { + Query(ctx context.Context, query string) ([]Row, error) + // ... 20 个方法 +} + +// ✅ 推荐:在消费者包中定义所需的最小接口 +// package userservice +type UserQuerier interface { + QueryUsers(ctx context.Context, filter Filter) ([]User, error) +} +``` + +### 4.3 保持接口小而专注 + +```go +// ❌ 不推荐:大而全的接口 +type Repository interface { + GetUser(id int) (*User, error) + CreateUser(u *User) error + UpdateUser(u *User) error + DeleteUser(id int) error + GetOrder(id int) (*Order, error) + CreateOrder(o *Order) error + // ... 更多方法 +} + +// ✅ 推荐:小而专注的接口 +type UserReader interface { + GetUser(ctx context.Context, id int) (*User, error) +} + +type UserWriter interface { + CreateUser(ctx context.Context, u *User) error + UpdateUser(ctx context.Context, u *User) error +} + +// 组合接口 +type UserRepository interface { + UserReader + UserWriter +} +``` + +### 4.4 避免空接口滥用 + +```go +// ❌ 不推荐:过度使用 interface{} +func Process(data interface{}) interface{} + +// ✅ 推荐:使用泛型(Go 1.18+) +func Process[T any](data T) T + +// ✅ 推荐:定义具体接口 +type Processor interface { + Process() Result +} +``` + +--- + +## 5. 接收器类型选择 + +### 5.1 使用指针接收器的情况 + +```go +// ✅ 需要修改接收器时 +func (u *User) SetName(name string) { + u.Name = name +} + +// ✅ 接收器包含 sync.Mutex 等同步原语 +type SafeCounter struct { + mu sync.Mutex + count int +} + +func (c *SafeCounter) Inc() { + c.mu.Lock() + defer c.mu.Unlock() + c.count++ +} + +// ✅ 接收器是大型结构体(避免复制开销) +type LargeStruct struct { + Data [1024]byte + // ... +} + +func (l *LargeStruct) Process() { /* ... */ } +``` + +### 5.2 使用值接收器的情况 + +```go +// ✅ 接收器是小型不可变结构体 +type Point struct { + X, Y float64 +} + +func (p Point) Distance(other Point) float64 { + return math.Sqrt(math.Pow(p.X-other.X, 2) + math.Pow(p.Y-other.Y, 2)) +} + +// ✅ 接收器是基本类型的别名 +type Counter int + +func (c Counter) String() string { + return fmt.Sprintf("%d", c) +} + +// ✅ 接收器是 map、func、chan(本身是引用类型) +type StringSet map[string]struct{} + +func (s StringSet) Contains(key string) bool { + _, ok := s[key] + return ok +} +``` + +### 5.3 一致性原则 + +```go +// ❌ 不推荐:混合使用接收器类型 +func (u User) GetName() string // 值接收器 +func (u *User) SetName(n string) // 指针接收器 + +// ✅ 推荐:如果有任何方法需要指针接收器,全部使用指针 +func (u *User) GetName() string { return u.Name } +func (u *User) SetName(n string) { u.Name = n } +``` + +--- + +## 6. 性能优化 + +### 6.1 预分配 Slice + +```go +// ❌ 不推荐:动态增长 +var result []int +for i := 0; i < 10000; i++ { + result = append(result, i) // 多次分配和复制 +} + +// ✅ 推荐:预分配已知大小 +result := make([]int, 0, 10000) +for i := 0; i < 10000; i++ { + result = append(result, i) +} + +// ✅ 或者直接初始化 +result := make([]int, 10000) +for i := 0; i < 10000; i++ { + result[i] = i +} +``` + +### 6.2 避免不必要的堆分配 + +```go +// ❌ 可能逃逸到堆 +func NewUser() *User { + return &User{} // 逃逸到堆 +} + +// ✅ 考虑返回值(如果适用) +func NewUser() User { + return User{} // 可能在栈上分配 +} + +// 检查逃逸分析 +// go build -gcflags '-m -m' ./... +``` + +### 6.3 使用 sync.Pool 复用对象 + +```go +// ✅ 推荐:高频创建/销毁的对象使用 sync.Pool +var bufferPool = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + +func ProcessData(data []byte) string { + buf := bufferPool.Get().(*bytes.Buffer) + defer func() { + buf.Reset() + bufferPool.Put(buf) + }() + + buf.Write(data) + return buf.String() +} +``` + +### 6.4 字符串拼接优化 + +```go +// ❌ 不推荐:循环中使用 + 拼接 +var result string +for _, s := range strings { + result += s // 每次创建新字符串 +} + +// ✅ 推荐:使用 strings.Builder +var builder strings.Builder +for _, s := range strings { + builder.WriteString(s) +} +result := builder.String() + +// ✅ 或者使用 strings.Join +result := strings.Join(strings, "") +``` + +### 6.5 避免 interface{} 转换开销 + +```go +// ❌ 热路径中使用 interface{} +func process(data interface{}) { + switch v := data.(type) { // 类型断言有开销 + case int: + // ... + } +} + +// ✅ 热路径中使用泛型或具体类型 +func process[T int | int64 | float64](data T) { + // 编译时确定类型,无运行时开销 +} +``` + +--- + +## 7. 测试 + +### 7.1 表驱动测试 + +```go +// ✅ 推荐:表驱动测试 +func TestAdd(t *testing.T) { + tests := []struct { + name string + a, b int + expected int + }{ + {"positive numbers", 1, 2, 3}, + {"with zero", 0, 5, 5}, + {"negative numbers", -1, -2, -3}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Add(tt.a, tt.b) + if result != tt.expected { + t.Errorf("Add(%d, %d) = %d; want %d", + tt.a, tt.b, result, tt.expected) + } + }) + } +} +``` + +### 7.2 并行测试 + +```go +// ✅ 推荐:独立测试用例并行执行 +func TestParallel(t *testing.T) { + tests := []struct { + name string + input string + }{ + {"test1", "input1"}, + {"test2", "input2"}, + } + + for _, tt := range tests { + tt := tt // Go < 1.22 需要复制 + t.Run(tt.name, func(t *testing.T) { + t.Parallel() // 标记为可并行 + result := Process(tt.input) + // assertions... + }) + } +} +``` + +### 7.3 使用接口进行 Mock + +```go +// ✅ 定义接口以便测试 +type EmailSender interface { + Send(to, subject, body string) error +} + +// 生产实现 +type SMTPSender struct { /* ... */ } + +// 测试 Mock +type MockEmailSender struct { + SendFunc func(to, subject, body string) error +} + +func (m *MockEmailSender) Send(to, subject, body string) error { + return m.SendFunc(to, subject, body) +} + +func TestUserRegistration(t *testing.T) { + mock := &MockEmailSender{ + SendFunc: func(to, subject, body string) error { + if to != "test@example.com" { + t.Errorf("unexpected recipient: %s", to) + } + return nil + }, + } + + service := NewUserService(mock) + // test... +} +``` + +### 7.4 测试辅助函数 + +```go +// ✅ 使用 t.Helper() 标记辅助函数 +func assertEqual(t *testing.T, got, want interface{}) { + t.Helper() // 错误报告时显示调用者位置 + if got != want { + t.Errorf("got %v, want %v", got, want) + } +} + +// ✅ 使用 t.Cleanup() 清理资源 +func TestWithTempFile(t *testing.T) { + f, err := os.CreateTemp("", "test") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + os.Remove(f.Name()) + }) + // test... +} +``` + +--- + +## 8. 常见陷阱 + +### 8.1 Nil Slice vs Empty Slice + +```go +var nilSlice []int // nil, len=0, cap=0 +emptySlice := []int{} // not nil, len=0, cap=0 +made := make([]int, 0) // not nil, len=0, cap=0 + +// ✅ JSON 编码差异 +json.Marshal(nilSlice) // null +json.Marshal(emptySlice) // [] + +// ✅ 推荐:需要空数组 JSON 时显式初始化 +if slice == nil { + slice = []int{} +} +``` + +### 8.2 Map 初始化 + +```go +// ❌ 错误:未初始化的 map +var m map[string]int +m["key"] = 1 // panic: assignment to entry in nil map + +// ✅ 正确:使用 make 初始化 +m := make(map[string]int) +m["key"] = 1 + +// ✅ 或者使用字面量 +m := map[string]int{} +``` + +### 8.3 Defer 在循环中 + +```go +// ❌ 潜在问题:defer 在函数结束时才执行 +func processFiles(files []string) error { + for _, file := range files { + f, err := os.Open(file) + if err != nil { + return err + } + defer f.Close() // 所有文件在函数结束时才关闭! + // process... + } + return nil +} + +// ✅ 正确:使用闭包或提取函数 +func processFiles(files []string) error { + for _, file := range files { + if err := processFile(file); err != nil { + return err + } + } + return nil +} + +func processFile(file string) error { + f, err := os.Open(file) + if err != nil { + return err + } + defer f.Close() + // process... + return nil +} +``` + +### 8.4 Slice 底层数组共享 + +```go +// ❌ 潜在问题:切片共享底层数组 +original := []int{1, 2, 3, 4, 5} +slice := original[1:3] // [2, 3] +slice[0] = 100 // 修改了 original! +// original 变成 [1, 100, 3, 4, 5] + +// ✅ 正确:需要独立副本时显式复制 +slice := make([]int, 2) +copy(slice, original[1:3]) +slice[0] = 100 // 不影响 original +``` + +### 8.5 字符串子串内存泄漏 + +```go +// ❌ 潜在问题:子串持有整个底层数组 +func getPrefix(s string) string { + return s[:10] // 仍引用整个 s 的底层数组 +} + +// ✅ 正确:创建独立副本(Go 1.18+) +func getPrefix(s string) string { + return strings.Clone(s[:10]) +} + +// ✅ Go 1.18 之前 +func getPrefix(s string) string { + return string([]byte(s[:10])) +} +``` + +### 8.6 Interface Nil 陷阱 + +```go +// ❌ 陷阱:interface 的 nil 判断 +type MyError struct{} +func (e *MyError) Error() string { return "error" } + +func returnsError() error { + var e *MyError = nil + return e // 返回的 error 不是 nil! +} + +func main() { + err := returnsError() + if err != nil { // true! interface{type: *MyError, value: nil} + fmt.Println("error:", err) + } +} + +// ✅ 正确:显式返回 nil +func returnsError() error { + var e *MyError = nil + if e == nil { + return nil // 显式返回 nil + } + return e +} +``` + +### 8.7 Time 比较 + +```go +// ❌ 不推荐:直接使用 == 比较 time.Time +if t1 == t2 { // 可能因为单调时钟差异而失败 + // ... +} + +// ✅ 推荐:使用 Equal 方法 +if t1.Equal(t2) { + // ... +} + +// ✅ 比较时间范围 +if t1.Before(t2) || t1.After(t2) { + // ... +} +``` + +--- + +## 9. 代码组织 + +### 9.1 包命名 + +```go +// ❌ 不推荐 +package common // 过于宽泛 +package utils // 过于宽泛 +package helpers // 过于宽泛 +package models // 按类型分组 + +// ✅ 推荐:按功能命名 +package user // 用户相关功能 +package order // 订单相关功能 +package postgres // PostgreSQL 实现 +``` + +### 9.2 避免循环依赖 + +```go +// ❌ 循环依赖 +// package a imports package b +// package b imports package a + +// ✅ 解决方案1:提取共享类型到独立包 +// package types (共享类型) +// package a imports types +// package b imports types + +// ✅ 解决方案2:使用接口解耦 +// package a 定义接口 +// package b 实现接口 +``` + +### 9.3 导出标识符规范 + +```go +// ✅ 只导出必要的标识符 +type UserService struct { + db *sql.DB // 私有 +} + +func (s *UserService) GetUser(id int) (*User, error) // 公开 +func (s *UserService) validate(u *User) error // 私有 + +// ✅ 内部包限制访问 +// internal/database/... 只能被同项目代码导入 +``` + +--- + +## 10. 工具与检查 + +### 10.1 必须使用的工具 + +```bash +# 格式化(必须) +gofmt -w . +goimports -w . + +# 静态分析 +go vet ./... + +# 竞态检测 +go test -race ./... + +# 逃逸分析 +go build -gcflags '-m -m' ./... +``` + +### 10.2 推荐的 Linter + +```bash +# golangci-lint(集成多个 linter) +golangci-lint run + +# 常用检查项 +# - errcheck: 检查未处理的错误 +# - gosec: 安全检查 +# - ineffassign: 无效赋值 +# - staticcheck: 静态分析 +# - unused: 未使用的代码 +``` + +### 10.3 Benchmark 测试 + +```go +// ✅ 性能基准测试 +func BenchmarkProcess(b *testing.B) { + data := prepareData() + b.ResetTimer() // 重置计时器 + + for i := 0; i < b.N; i++ { + Process(data) + } +} + +// 运行 benchmark +// go test -bench=. -benchmem ./... +``` + +--- + +## 参考资源 + +- [Effective Go](https://go.dev/doc/effective_go) +- [Go Code Review Comments](https://go.dev/wiki/CodeReviewComments) +- [Go Common Mistakes](https://go.dev/wiki/CommonMistakes) +- [100 Go Mistakes](https://100go.co/) +- [Go Proverbs](https://go-proverbs.github.io/) +- [Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md)