golang-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGo Development Patterns
Go 开发设计模式
Idiomatic Go patterns and best practices for building robust, efficient, and maintainable applications.
本文介绍地道的Go语言设计模式与最佳实践,帮助你构建健壮、高效且可维护的应用程序。
When to Activate
适用场景
- Writing new Go code
- Reviewing Go code
- Refactoring existing Go code
- Designing Go packages/modules
- 编写新的Go代码
- 评审Go代码
- 重构现有Go代码
- 设计Go包/模块
Core Principles
核心原则
1. Simplicity and Clarity
1. 简洁清晰
Go favors simplicity over cleverness. Code should be obvious and easy to read.
go
// Good: Clear and direct
func GetUser(id string) (*User, error) {
user, err := db.FindUser(id)
if err != nil {
return nil, fmt.Errorf("get user %s: %w", id, err)
}
return user, nil
}
// Bad: Overly clever
func GetUser(id string) (*User, error) {
return func() (*User, error) {
if u, e := db.FindUser(id); e == nil {
return u, nil
} else {
return nil, e
}
}()
}Go语言推崇简洁而非花哨,代码应当直观易懂。
go
// 推荐写法:清晰直接
func GetUser(id string) (*User, error) {
user, err := db.FindUser(id)
if err != nil {
return nil, fmt.Errorf("get user %s: %w", id, err)
}
return user, nil
}
// 不推荐写法:过于花哨
func GetUser(id string) (*User, error) {
return func() (*User, error) {
if u, e := db.FindUser(id); e == nil {
return u, nil
} else {
return nil, e
}
}()
}2. Make the Zero Value Useful
2. 让零值可用
Design types so their zero value is immediately usable without initialization.
go
// Good: Zero value is useful
type Counter struct {
mu sync.Mutex
count int // zero value is 0, ready to use
}
func (c *Counter) Inc() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
// Good: bytes.Buffer works with zero value
var buf bytes.Buffer
buf.WriteString("hello")
// Bad: Requires initialization
type BadCounter struct {
counts map[string]int // nil map will panic
}设计类型时,确保其零值无需初始化即可直接使用。
go
// 推荐写法:零值可用
type Counter struct {
mu sync.Mutex
count int // 零值为0,可直接使用
}
func (c *Counter) Inc() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
// 推荐写法:bytes.Buffer的零值可直接使用
var buf bytes.Buffer
buf.WriteString("hello")
// 不推荐写法:需要初始化
type BadCounter struct {
counts map[string]int // nil map会导致panic
}3. Accept Interfaces, Return Structs
3. 接受接口,返回结构体
Functions should accept interface parameters and return concrete types.
go
// Good: Accepts interface, returns concrete type
func ProcessData(r io.Reader) (*Result, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
return &Result{Data: data}, nil
}
// Bad: Returns interface (hides implementation details unnecessarily)
func ProcessData(r io.Reader) (io.Reader, error) {
// ...
}函数应接受接口类型参数,返回具体结构体类型。
go
// 推荐写法:接受接口,返回具体类型
func ProcessData(r io.Reader) (*Result, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
return &Result{Data: data}, nil
}
// 不推荐写法:返回接口(不必要地隐藏实现细节)
func ProcessData(r io.Reader) (io.Reader, error) {
// ...
}Error Handling Patterns
错误处理模式
Error Wrapping with Context
带上下文的错误包装
go
// Good: Wrap errors with context
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("load config %s: %w", path, err)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parse config %s: %w", path, err)
}
return &cfg, nil
}go
// 推荐写法:为错误添加上下文信息
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("加载配置文件 %s: %w", path, err)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("解析配置文件 %s: %w", path, err)
}
return &cfg, nil
}Custom Error Types
自定义错误类型
go
// Define domain-specific errors
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
// Sentinel errors for common cases
var (
ErrNotFound = errors.New("resource not found")
ErrUnauthorized = errors.New("unauthorized")
ErrInvalidInput = errors.New("invalid input")
)go
// 定义领域特定错误
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("字段 %s 验证失败: %s", e.Field, e.Message)
}
// 为常见错误定义哨兵错误
var (
ErrNotFound = errors.New("资源未找到")
ErrUnauthorized = errors.New("未授权访问")
ErrInvalidInput = errors.New("输入无效")
)Error Checking with errors.Is and errors.As
使用errors.Is和errors.As检查错误
go
func HandleError(err error) {
// Check for specific error
if errors.Is(err, sql.ErrNoRows) {
log.Println("No records found")
return
}
// Check for error type
var validationErr *ValidationError
if errors.As(err, &validationErr) {
log.Printf("Validation error on field %s: %s",
validationErr.Field, validationErr.Message)
return
}
// Unknown error
log.Printf("Unexpected error: %v", err)
}go
func HandleError(err error) {
// 检查特定错误
if errors.Is(err, sql.ErrNoRows) {
log.Println("未找到记录")
return
}
// 检查错误类型
var validationErr *ValidationError
if errors.As(err, &validationErr) {
log.Printf("字段 %s 验证错误: %s",
validationErr.Field, validationErr.Message)
return
}
// 未知错误
log.Printf("意外错误: %v", err)
}Never Ignore Errors
切勿忽略错误
go
// Bad: Ignoring error with blank identifier
result, _ := doSomething()
// Good: Handle or explicitly document why it's safe to ignore
result, err := doSomething()
if err != nil {
return err
}
// Acceptable: When error truly doesn't matter (rare)
_ = writer.Close() // Best-effort cleanup, error logged elsewherego
// 不推荐写法:用空白标识符忽略错误
result, _ := doSomething()
// 推荐写法:处理错误,或明确说明忽略的原因
result, err := doSomething()
if err != nil {
return err
}
// 可接受的情况:错误确实无关紧要(极少出现)
_ = writer.Close() // 尽力清理,错误已在其他地方记录Concurrency Patterns
并发模式
Worker Pool
工作池
go
func WorkerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- process(job)
}
}()
}
wg.Wait()
close(results)
}go
func WorkerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- process(job)
}
}()
}
wg.Wait()
close(results)
}Context for Cancellation and Timeouts
使用Context进行取消和超时控制
go
func FetchWithTimeout(ctx context.Context, url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("fetch %s: %w", url, err)
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}go
func FetchWithTimeout(ctx context.Context, url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("创建请求: %w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("请求 %s: %w", url, err)
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}Graceful Shutdown
优雅关闭
go
func GracefulShutdown(server *http.Server) {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}
log.Println("Server exited")
}go
func GracefulShutdown(server *http.Server) {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("正在关闭服务器...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("服务器被迫关闭: %v", err)
}
log.Println("服务器已退出")
}errgroup for Coordinated Goroutines
使用errgroup协调协程
go
import "golang.org/x/sync/errgroup"
func FetchAll(ctx context.Context, urls []string) ([][]byte, error) {
g, ctx := errgroup.WithContext(ctx)
results := make([][]byte, len(urls))
for i, url := range urls {
i, url := i, url // Capture loop variables
g.Go(func() error {
data, err := FetchWithTimeout(ctx, url)
if err != nil {
return err
}
results[i] = data
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}go
import "golang.org/x/sync/errgroup"
func FetchAll(ctx context.Context, urls []string) ([][]byte, error) {
g, ctx := errgroup.WithContext(ctx)
results := make([][]byte, len(urls))
for i, url := range urls {
i, url := i, url // 捕获循环变量
g.Go(func() error {
data, err := FetchWithTimeout(ctx, url)
if err != nil {
return err
}
results[i] = data
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}Avoiding Goroutine Leaks
避免协程泄漏
go
// Bad: Goroutine leak if context is cancelled
func leakyFetch(ctx context.Context, url string) <-chan []byte {
ch := make(chan []byte)
go func() {
data, _ := fetch(url)
ch <- data // Blocks forever if no receiver
}()
return ch
}
// Good: Properly handles cancellation
func safeFetch(ctx context.Context, url string) <-chan []byte {
ch := make(chan []byte, 1) // Buffered channel
go func() {
data, err := fetch(url)
if err != nil {
return
}
select {
case ch <- data:
case <-ctx.Done():
}
}()
return ch
}go
// 不推荐写法:如果context被取消,协程会泄漏
func leakyFetch(ctx context.Context, url string) <-chan []byte {
ch := make(chan []byte)
go func() {
data, _ := fetch(url)
ch <- data // 如果没有接收者,会永远阻塞
}()
return ch
}
// 推荐写法:正确处理取消
func safeFetch(ctx context.Context, url string) <-chan []byte {
ch := make(chan []byte, 1) // 带缓冲的通道
go func() {
data, err := fetch(url)
if err != nil {
return
}
select {
case ch <- data:
case <-ctx.Done():
}
}()
return ch
}Interface Design
接口设计
Small, Focused Interfaces
小巧、聚焦的接口
go
// Good: Single-method interfaces
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// Compose interfaces as needed
type ReadWriteCloser interface {
Reader
Writer
Closer
}go
// 推荐写法:单一方法接口
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// 根据需要组合接口
type ReadWriteCloser interface {
Reader
Writer
Closer
}Define Interfaces Where They're Used
在使用处定义接口
go
// In the consumer package, not the provider
package service
// UserStore defines what this service needs
type UserStore interface {
GetUser(id string) (*User, error)
SaveUser(user *User) error
}
type Service struct {
store UserStore
}
// Concrete implementation can be in another package
// It doesn't need to know about this interfacego
// 在消费端包中定义,而非提供端
package service
// UserStore定义了本服务所需的方法
type UserStore interface {
GetUser(id string) (*User, error)
SaveUser(user *User) error
}
type Service struct {
store UserStore
}
// 具体实现可以在其他包中
// 它不需要知道这个接口的存在Optional Behavior with Type Assertions
使用类型断言实现可选行为
go
type Flusher interface {
Flush() error
}
func WriteAndFlush(w io.Writer, data []byte) error {
if _, err := w.Write(data); err != nil {
return err
}
// Flush if supported
if f, ok := w.(Flusher); ok {
return f.Flush()
}
return nil
}go
type Flusher interface {
Flush() error
}
func WriteAndFlush(w io.Writer, data []byte) error {
if _, err := w.Write(data); err != nil {
return err
}
// 如果支持则执行Flush
if f, ok := w.(Flusher); ok {
return f.Flush()
}
return nil
}Package Organization
包结构组织
Standard Project Layout
标准项目布局
text
myproject/
├── cmd/
│ └── myapp/
│ └── main.go # Entry point
├── internal/
│ ├── handler/ # HTTP handlers
│ ├── service/ # Business logic
│ ├── repository/ # Data access
│ └── config/ # Configuration
├── pkg/
│ └── client/ # Public API client
├── api/
│ └── v1/ # API definitions (proto, OpenAPI)
├── testdata/ # Test fixtures
├── go.mod
├── go.sum
└── Makefiletext
myproject/
├── cmd/
│ └── myapp/
│ └── main.go # 程序入口
├── internal/
│ ├── handler/ # HTTP处理器
│ ├── service/ # 业务逻辑
│ ├── repository/ # 数据访问层
│ └── config/ # 配置管理
├── pkg/
│ └── client/ # 公开API客户端
├── api/
│ └── v1/ # API定义(proto、OpenAPI)
├── testdata/ # 测试用例数据
├── go.mod
├── go.sum
└── MakefilePackage Naming
包命名规范
go
// Good: Short, lowercase, no underscores
package http
package json
package user
// Bad: Verbose, mixed case, or redundant
package httpHandler
package json_parser
package userService // Redundant 'Service' suffixgo
// 推荐写法:简短、小写、无下划线
package http
package json
package user
// 不推荐写法:冗长、大小写混合或冗余
package httpHandler
package json_parser
package userService // 冗余的'Service'后缀Avoid Package-Level State
避免包级别的状态
go
// Bad: Global mutable state
var db *sql.DB
func init() {
db, _ = sql.Open("postgres", os.Getenv("DATABASE_URL"))
}
// Good: Dependency injection
type Server struct {
db *sql.DB
}
func NewServer(db *sql.DB) *Server {
return &Server{db: db}
}go
// 不推荐写法:全局可变状态
var db *sql.DB
func init() {
db, _ = sql.Open("postgres", os.Getenv("DATABASE_URL"))
}
// 推荐写法:依赖注入
type Server struct {
db *sql.DB
}
func NewServer(db *sql.DB) *Server {
return &Server{db: db}
}Struct Design
结构体设计
Functional Options Pattern
函数式选项模式
go
type Server struct {
addr string
timeout time.Duration
logger *log.Logger
}
type Option func(*Server)
func WithTimeout(d time.Duration) Option {
return func(s *Server) {
s.timeout = d
}
}
func WithLogger(l *log.Logger) Option {
return func(s *Server) {
s.logger = l
}
}
func NewServer(addr string, opts ...Option) *Server {
s := &Server{
addr: addr,
timeout: 30 * time.Second, // default
logger: log.Default(), // default
}
for _, opt := range opts {
opt(s)
}
return s
}
// Usage
server := NewServer(":8080",
WithTimeout(60*time.Second),
WithLogger(customLogger),
)go
type Server struct {
addr string
timeout time.Duration
logger *log.Logger
}
type Option func(*Server)
func WithTimeout(d time.Duration) Option {
return func(s *Server) {
s.timeout = d
}
}
func WithLogger(l *log.Logger) Option {
return func(s *Server) {
s.logger = l
}
}
func NewServer(addr string, opts ...Option) *Server {
s := &Server{
addr: addr,
timeout: 30 * time.Second, // 默认值
logger: log.Default(), // 默认值
}
for _, opt := range opts {
opt(s)
}
return s
}
// 使用示例
server := NewServer(":8080",
WithTimeout(60*time.Second),
WithLogger(customLogger),
)Embedding for Composition
通过嵌入实现组合
go
type Logger struct {
prefix string
}
func (l *Logger) Log(msg string) {
fmt.Printf("[%s] %s\n", l.prefix, msg)
}
type Server struct {
*Logger // Embedding - Server gets Log method
addr string
}
func NewServer(addr string) *Server {
return &Server{
Logger: &Logger{prefix: "SERVER"},
addr: addr,
}
}
// Usage
s := NewServer(":8080")
s.Log("Starting...") // Calls embedded Logger.Loggo
type Logger struct {
prefix string
}
func (l *Logger) Log(msg string) {
fmt.Printf("[%s] %s\n", l.prefix, msg)
}
type Server struct {
*Logger // 嵌入Logger,Server自动拥有Log方法
addr string
}
func NewServer(addr string) *Server {
return &Server{
Logger: &Logger{prefix: "SERVER"},
addr: addr,
}
}
// 使用示例
s := NewServer(":8080")
s.Log("启动中...") // 调用嵌入的Logger.Log方法Memory and Performance
内存与性能优化
Preallocate Slices When Size is Known
已知大小时预分配切片
go
// Bad: Grows slice multiple times
func processItems(items []Item) []Result {
var results []Result
for _, item := range items {
results = append(results, process(item))
}
return results
}
// Good: Single allocation
func processItems(items []Item) []Result {
results := make([]Result, 0, len(items))
for _, item := range items {
results = append(results, process(item))
}
return results
}go
// 不推荐写法:切片会多次扩容
func processItems(items []Item) []Result {
var results []Result
for _, item := range items {
results = append(results, process(item))
}
return results
}
// 推荐写法:一次分配足够内存
func processItems(items []Item) []Result {
results := make([]Result, 0, len(items))
for _, item := range items {
results = append(results, process(item))
}
return results
}Use sync.Pool for Frequent Allocations
使用sync.Pool处理频繁分配的对象
go
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func ProcessRequest(data []byte) []byte {
buf := bufferPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufferPool.Put(buf)
}()
buf.Write(data)
// Process...
return buf.Bytes()
}go
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func ProcessRequest(data []byte) []byte {
buf := bufferPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufferPool.Put(buf)
}()
buf.Write(data)
// 处理数据...
return buf.Bytes()
}Avoid String Concatenation in Loops
避免在循环中拼接字符串
go
// Bad: Creates many string allocations
func join(parts []string) string {
var result string
for _, p := range parts {
result += p + ","
}
return result
}
// Good: Single allocation with strings.Builder
func join(parts []string) string {
var sb strings.Builder
for i, p := range parts {
if i > 0 {
sb.WriteString(",")
}
sb.WriteString(p)
}
return sb.String()
}
// Best: Use standard library
func join(parts []string) string {
return strings.Join(parts, ",")
}go
// 不推荐写法:会创建大量字符串对象
func join(parts []string) string {
var result string
for _, p := range parts {
result += p + ","
}
return result
}
// 推荐写法:使用strings.Builder一次分配
func join(parts []string) string {
var sb strings.Builder
for i, p := range parts {
if i > 0 {
sb.WriteString(",")
}
sb.WriteString(p)
}
return sb.String()
}
// 最优写法:使用标准库函数
func join(parts []string) string {
return strings.Join(parts, ",")
}Go Tooling Integration
Go工具链集成
Essential Commands
核心命令
bash
undefinedbash
undefinedBuild and run
构建并运行
go build ./...
go run ./cmd/myapp
go build ./...
go run ./cmd/myapp
Testing
测试
go test ./...
go test -race ./...
go test -cover ./...
go test ./...
go test -race ./...
go test -cover ./...
Static analysis
静态代码分析
go vet ./...
staticcheck ./...
golangci-lint run
go vet ./...
staticcheck ./...
golangci-lint run
Module management
模块管理
go mod tidy
go mod verify
go mod tidy
go mod verify
Formatting
代码格式化
gofmt -w .
goimports -w .
undefinedgofmt -w .
goimports -w .
undefinedRecommended Linter Configuration (.golangci.yml)
推荐的Linter配置(.golangci.yml)
yaml
linters:
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- gofmt
- goimports
- misspell
- unconvert
- unparam
linters-settings:
errcheck:
check-type-assertions: true
govet:
check-shadowing: true
issues:
exclude-use-default: falseyaml
linters:
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- gofmt
- goimports
- misspell
- unconvert
- unparam
linters-settings:
errcheck:
check-type-assertions: true
govet:
check-shadowing: true
issues:
exclude-use-default: falseQuick Reference: Go Idioms
速查:Go语言惯用写法
| Idiom | Description |
|---|---|
| Accept interfaces, return structs | Functions accept interface params, return concrete types |
| Errors are values | Treat errors as first-class values, not exceptions |
| Don't communicate by sharing memory | Use channels for coordination between goroutines |
| Make the zero value useful | Types should work without explicit initialization |
| A little copying is better than a little dependency | Avoid unnecessary external dependencies |
| Clear is better than clever | Prioritize readability over cleverness |
| gofmt is no one's favorite but everyone's friend | Always format with gofmt/goimports |
| Return early | Handle errors first, keep happy path unindented |
| 惯用写法 | 说明 |
|---|---|
| 接受接口,返回结构体 | 函数接受接口类型参数,返回具体结构体类型 |
| 错误是值 | 将错误作为一等公民对待,而非异常 |
| 不要通过共享内存通信,要通过通信共享内存 | 使用通道协调协程之间的操作 |
| 让零值可用 | 类型无需显式初始化即可使用 |
| 少量复制优于少量依赖 | 避免不必要的外部依赖 |
| 清晰优于花哨 | 优先保证可读性,而非追求技巧 |
| gofmt虽非人人最爱,但却是所有人的朋友 | 始终使用gofmt/goimports格式化代码 |
| 提前返回 | 优先处理错误,让主逻辑保持无缩进 |
Anti-Patterns to Avoid
需要避免的反模式
go
// Bad: Naked returns in long functions
func process() (result int, err error) {
// ... 50 lines ...
return // What is being returned?
}
// Bad: Using panic for control flow
func GetUser(id string) *User {
user, err := db.Find(id)
if err != nil {
panic(err) // Don't do this
}
return user
}
// Bad: Passing context in struct
type Request struct {
ctx context.Context // Context should be first param
ID string
}
// Good: Context as first parameter
func ProcessRequest(ctx context.Context, id string) error {
// ...
}
// Bad: Mixing value and pointer receivers
type Counter struct{ n int }
func (c Counter) Value() int { return c.n } // Value receiver
func (c *Counter) Increment() { c.n++ } // Pointer receiver
// Pick one style and be consistentRemember: Go code should be boring in the best way - predictable, consistent, and easy to understand. When in doubt, keep it simple.
go
// 不推荐写法:长函数中使用裸返回
func process() (result int, err error) {
// ... 50行代码 ...
return // 不清楚返回的是什么值
}
// 不推荐写法:用panic控制流程
func GetUser(id string) *User {
user, err := db.Find(id)
if err != nil {
panic(err) // 切勿这样做
}
return user
}
// 不推荐写法:将Context放在结构体中
type Request struct {
ctx context.Context // Context应当作为第一个参数
ID string
}
// 推荐写法:将Context作为第一个参数
func ProcessRequest(ctx context.Context, id string) error {
// ...
}
// 不推荐写法:混合使用值接收器和指针接收器
type Counter struct{ n int }
func (c Counter) Value() int { return c.n } // 值接收器
func (c *Counter) Increment() { c.n++ } // 指针接收器
// 应选择一种风格并保持一致记住:优秀的Go代码应当是“乏味”的——可预测、一致且易于理解。拿不准的时候,保持简单就好。