go-idioms
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGo Idioms
Go语言惯用写法
Boring, explicit, race-safe Go. Accept interfaces, return structs.
遵循简洁明确、无竞态的Go风格:依赖接口,返回结构体。
Error Handling
错误处理
Always wrap with context at package boundaries:
go
// Use %w to preserve error chain
return fmt.Errorf("fetching user %s: %w", userID, err)
// Check wrapped errors
if errors.Is(err, sql.ErrNoRows) { ... }
var paymentErr *PaymentError
if errors.As(err, &paymentErr) { ... }Never: (loses type), raw errors from exported functions, generic context.
%v在包的边界处始终携带上下文包装错误:
go
// Use %w to preserve error chain
return fmt.Errorf("fetching user %s: %w", userID, err)
// Check wrapped errors
if errors.Is(err, sql.ErrNoRows) { ... }
var paymentErr *PaymentError
if errors.As(err, &paymentErr) { ... }切勿:使用(会丢失类型信息)、对外暴露的函数返回原始错误、使用通用上下文。
%vInterface Design
接口设计
Define interfaces in consuming package, not provider:
go
// notification/sender.go (consumer defines interface)
type EmailSender interface {
Send(ctx context.Context, to, subject, body string) error
}
// email/client.go (provider implements)
type Client struct { ... }
func (c *Client) Send(ctx context.Context, to, subject, body string) error { ... }Small interfaces (1-3 methods). Compose larger from smaller:
go
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }
type ReadWriter interface { Reader; Writer }Compile-time verification:
go
var _ EmailSender = (*Client)(nil)在消费包中定义接口,而非提供方:
go
// notification/sender.go (consumer defines interface)
type EmailSender interface {
Send(ctx context.Context, to, subject, body string) error
}
// email/client.go (provider implements)
type Client struct { ... }
func (c *Client) Send(ctx context.Context, to, subject, body string) error { ... }接口要小(1-3个方法),通过组合小接口形成大接口:
go
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }
type ReadWriter interface { Reader; Writer }编译时验证:
go
var _ EmailSender = (*Client)(nil)Concurrency
并发处理
Always propagate context:
go
func FetchData(ctx context.Context) ([]byte, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
case result := <-dataChan:
return result, nil
}
}Bounded concurrency (semaphore):
go
sem := make(chan struct{}, 10)
for _, item := range items {
sem <- struct{}{}
go func(item Item) {
defer func() { <-sem }()
process(item)
}(item)
}Race safety: Always run
go test -race ./...始终传递上下文:
go
func FetchData(ctx context.Context) ([]byte, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
case result := <-dataChan:
return result, nil
}
}有界并发(信号量实现):
go
sem := make(chan struct{}, 10)
for _, item := range items {
sem <- struct{}{}
go func(item Item) {
defer func() { <-sem }()
process(item)
}(item)
}竞态安全: 始终运行命令检测竞态条件。
go test -race ./...Package Design
包设计
internal/
user/ # Domain: single purpose
user.go
service.go
repository.go
order/ # Another domain
app/ # Dependency wiring
cmd/
api/ # Entry pointsRules:
- Single purpose per package
- No generic names (utils, helpers, common)
- No circular dependencies
- Export only what's necessary
internal/
user/ # Domain: single purpose
user.go
service.go
repository.go
order/ # Another domain
app/ # Dependency wiring
cmd/
api/ # Entry points规则:
- 每个包单一职责
- 避免使用通用名称(如utils、helpers、common)
- 禁止循环依赖
- 仅导出必要内容
Dependency Injection
依赖注入
go
// Constructor accepts interfaces
func NewUserService(repo UserRepository, mailer EmailSender) *UserService {
return &UserService{repo: repo, mailer: mailer}
}
// Wire in app/ package
func NewApp() *App {
repo := postgres.NewUserRepo(db)
mailer := sendgrid.NewClient(apiKey)
userSvc := user.NewUserService(repo, mailer)
return &App{UserService: userSvc}
}go
// Constructor accepts interfaces
func NewUserService(repo UserRepository, mailer EmailSender) *UserService {
return &UserService{repo: repo, mailer: mailer}
}
// Wire in app/ package
func NewApp() *App {
repo := postgres.NewUserRepo(db)
mailer := sendgrid.NewClient(apiKey)
userSvc := user.NewUserService(repo, mailer)
return &App{UserService: userSvc}
}Deps Struct Pattern (5+ Parameters)
依赖结构体模式(参数≥5个时使用)
When constructor takes 5+ parameters, use a deps struct:
go
// Group dependencies into named struct
type ServiceDeps struct {
Repo Repository
Mailer EmailSender
Logger Logger
Config *Config
Cache Cache
}
// Panic on nil - catches programming errors at construction
func NewService(deps ServiceDeps) *Service {
if deps.Repo == nil {
panic("NewService: Repo cannot be nil")
}
if deps.Mailer == nil {
panic("NewService: Mailer cannot be nil")
}
// ... check all required deps
return &Service{...}
}Benefits:
- Named fields = self-documenting call sites
- Adding deps doesn't change signature
- Nil panics catch bugs at construction, not at runtime
When changing old constructor to deps struct:
- Update ALL call sites to struct literal pattern
- Check for tests expecting old nil-tolerance behavior
当构造函数参数超过5个时,使用依赖结构体:
go
// Group dependencies into named struct
type ServiceDeps struct {
Repo Repository
Mailer EmailSender
Logger Logger
Config *Config
Cache Cache
}
// Panic on nil - catches programming errors at construction
func NewService(deps ServiceDeps) *Service {
if deps.Repo == nil {
panic("NewService: Repo cannot be nil")
}
if deps.Mailer == nil {
panic("NewService: Mailer cannot be nil")
}
// ... check all required deps
return &Service{...}
}优势:
- 命名字段让调用处自文档化
- 添加依赖无需修改函数签名
- 空指针 panic 在构造阶段捕获 bug,而非运行时
将旧构造函数改为依赖结构体时:
- 更新所有调用处为结构体字面量写法
- 检查测试是否依赖旧的空值兼容行为
Test Cleanup
测试清理
Use for teardown, not with error suppression:
t.Cleanup()defergo
// BAD - Error suppression hides failures
defer func() { _ = os.Chdir(orig) }() // SA4017 warning
// GOOD - Proper cleanup with error handling
t.Cleanup(func() {
if err := os.Chdir(orig); err != nil {
t.Errorf("cleanup failed: %v", err)
}
})t.Cleanup使用进行清理,而非带错误抑制的:
t.Cleanup()defergo
// BAD - Error suppression hides failures
defer func() { _ = os.Chdir(orig) }() // SA4017 warning
// GOOD - Proper cleanup with error handling
t.Cleanup(func() {
if err := os.Chdir(orig); err != nil {
t.Errorf("cleanup failed: %v", err)
}
})t.CleanupAnti-Patterns
反模式
- Goroutines without cancellation path (leaks)
- Monolithic interfaces (10+ methods)
- Framework-like inheritance patterns
- Reflection when explicit types work
- Global singletons for dependencies
- Generic everything (overuse of generics)
- /
interface{}without justificationany - with error suppression in tests
defer
- 没有取消路径的Goroutine(会导致泄漏)
- 单体式接口(包含10+个方法)
- 类框架的继承模式
- 能用显式类型却使用反射
- 依赖全局单例
- 过度使用泛型
- 无正当理由使用/
interface{}any - 测试中使用并抑制错误
defer
Embrace Boring
拥抱简洁
- Explicit error handling at each step
- Standard library first (,
map,[]T)sort.Slice - Table-driven tests
- Struct composition, not inheritance
- Clear, verbose code over clever code
- 每一步都显式处理错误
- 优先使用标准库(如、
map、[]T)sort.Slice - 表驱动测试
- 结构体组合而非继承
- 清晰 verbose 的代码优于巧妙的代码