最佳实践
这是Go社区总结的最佳实践,让你的代码更加专业!这些都是我在实际工作中一点一滴积累下来的经验。刚开始写Go的时候,我常常不按这些规范来,后来维护代码的时候苦不堆言。现在把这些分享给你,希望能让你少走些弯路!
代码风格
使用gofmt
# 格式化代码
gofmt -w .
# 或使用
go fmt ./...
命名规范
// 包名:小写,简短,无下划线
package userservice // ✅
package UserService // ❌
package user_service // ❌
// 变量名:驼峰命名
userName := "张三" // ✅
user_name := "张三" // ❌
// 常量:驼峰或全大写
const MaxRetries = 3
const maxRetries = 3
const MAX_RETRIES = 3 // 也可以
// 接口名:动词+er
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }
// 缩写保持一致大写
userID, httpClient, xmlParser // ✅
userId, httpClient, xmlparser // ❌(不一致)
函数设计
// 参数数量不超过5个,太多用结构体
// ❌
func CreateUser(name, email, password, phone, address string, age int) {}
// ✅
type CreateUserParams struct {
Name string
Email string
Password string
Phone string
Address string
Age int
}
func CreateUser(params CreateUserParams) {}
// 返回值不超过3个
func Parse(s string) (value int, err error) {} // ✅
func Parse(s string) (value int, remaining string, lineNum int, err error) {} // ❌
// ✅ 使用结构体
type ParseResult struct {
Value int
Remaining string
LineNum int
}
func Parse(s string) (ParseResult, error) {}
错误处理
立即处理错误
// ❌ 错误处理在很远的地方
result, err := doSomething()
// ... 很多代码
if err != nil { }
// ✅ 立即处理
result, err := doSomething()
if err != nil {
return err
}
添加上下文
// ❌ 直接返回
return err
// ✅ 添加上下文
return fmt.Errorf("读取配置文件失败: %w", err)
// ✅✅ 更多上下文
return fmt.Errorf("初始化服务 %s 失败: %w", serviceName, err)
不要忽略错误
// ❌ 忽略错误
file.Close()
json.Unmarshal(data, &obj)
// ✅ 处理错误
if err := file.Close(); err != nil {
log.Printf("关闭文件失败: %v", err)
}
// 或使用defer处理
defer func() {
if err := file.Close(); err != nil {
log.Printf("关闭文件失败: %v", err)
}
}()
并发安全
避免数据竞争
// ❌ 数据竞争
var counter int
go func() { counter++ }()
go func() { counter++ }()
// ✅ 使用锁
var (
counter int
mu sync.Mutex
)
go func() {
mu.Lock()
counter++
mu.Unlock()
}()
// ✅ 使用原子操作
var counter int64
go func() { atomic.AddInt64(&counter, 1) }()
// ✅ 使用channel
counter := make(chan int, 1)
counter <- 0
go func() {
v := <-counter
counter <- v + 1
}()
使用race检测
go run -race main.go
go test -race ./...
goroutine生命周期
// ❌ goroutine泄漏
func process() {
go func() {
for {
// 永远运行...
}
}()
}
// ✅ 可控制的goroutine
func process(ctx context.Context) {
go func() {
for {
select {
case <-ctx.Done():
return // 优雅退出
default:
// 工作
}
}
}()
}
资源管理
使用defer
// ✅ 确保资源释放
func readFile(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close() // 确保关闭
return io.ReadAll(f)
}
// 注意defer的参数在声明时求值
func trace(name string) func() {
start := time.Now()
fmt.Printf("entering %s\n", name)
return func() {
fmt.Printf("leaving %s (%v)\n", name, time.Since(start))
}
}
func foo() {
defer trace("foo")() // 注意这里的()
// ...
}
连接池
// ✅ 数据库连接池
db, err := sql.Open("mysql", dsn)
if err != nil {
return err
}
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5 * time.Minute)
// ✅ HTTP客户端复用
var httpClient = &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
},
}
包设计
避免循环依赖
// ❌ 循环依赖
// package a imports package b
// package b imports package a
// ✅ 使用接口解耦
// package a
type UserRepository interface {
GetUser(id int) (*User, error)
}
// package b
type UserService struct {
repo a.UserRepository
}
最小化导出
// ❌ 导出太多
type User struct {
ID int
Name string
Password string // 不应该导出
salt string
}
// ✅ 只导出必要的
type User struct {
ID int
Name string
}
type user struct {
User
password string
salt string
}
接口设计
// ❌ 大接口
type Repository interface {
Create(item *Item) error
Update(item *Item) error
Delete(id int) error
Get(id int) (*Item, error)
List() ([]*Item, error)
Search(query string) ([]*Item, error)
// ... 更多方法
}
// ✅ 小接口
type Reader interface {
Get(id int) (*Item, error)
}
type Writer interface {
Create(item *Item) error
Update(item *Item) error
}
type Deleter interface {
Delete(id int) error
}
type ReadWriter interface {
Reader
Writer
}
测试
表格驱动测试
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"正数", 1, 2, 3},
{"负数", -1, -2, -3},
{"零", 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.expected {
t.Errorf("Add(%d, %d) = %d; want %d",
tt.a, tt.b, got, tt.expected)
}
})
}
}
使用testify
import "github.com/stretchr/testify/assert"
func TestUser(t *testing.T) {
user := NewUser("张三", 25)
assert.NotNil(t, user)
assert.Equal(t, "张三", user.Name)
assert.Equal(t, 25, user.Age)
assert.True(t, user.IsAdult())
}
Mock接口
// 使用接口便于测试
type UserRepository interface {
GetByID(id int) (*User, error)
}
// 真实实现
type MySQLUserRepository struct {
db *sql.DB
}
// Mock实现
type MockUserRepository struct {
users map[int]*User
}
func (m *MockUserRepository) GetByID(id int) (*User, error) {
if user, ok := m.users[id]; ok {
return user, nil
}
return nil, errors.New("not found")
}
日志
结构化日志
// ❌ 拼接字符串
log.Printf("用户 %s 登录失败: %v", username, err)
// ✅ 结构化日志
logger.Error("登录失败",
zap.String("username", username),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
日志级别
// 使用适当的日志级别
logger.Debug("详细调试信息")
logger.Info("一般信息")
logger.Warn("警告信息")
logger.Error("错误信息")
logger.Fatal("致命错误,程序退出")
配置管理
使用环境变量
// 从环境变量读取配置
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
// 使用库简化
import "github.com/kelseyhightower/envconfig"
type Config struct {
Port int `envconfig:"PORT" default:"8080"`
Database string `envconfig:"DATABASE_URL" required:"true"`
Debug bool `envconfig:"DEBUG" default:"false"`
}
var cfg Config
envconfig.Process("", &cfg)
配置文件
// config.yaml
type Config struct {
Server struct {
Port int `yaml:"port"`
Host string `yaml:"host"`
} `yaml:"server"`
Database struct {
DSN string `yaml:"dsn"`
} `yaml:"database"`
}
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var cfg Config
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, err
}
return &cfg, nil
}
项目结构
myproject/
├── cmd/ # 可执行程序
│ └── myapp/
│ └── main.go
├── internal/ # 私有代码
│ ├── config/
│ ├── handler/
│ ├── service/
│ └── repository/
├── pkg/ # 公开库
│ └── utils/
├── api/ # API定义
│ └── proto/
├── web/ # 静态文件
├── scripts/ # 脚本
├── deployments/ # 部署配置
├── docs/ # 文档
├── go.mod
├── go.sum
├── Makefile
└── README.md
代码审查清单
功能性
- [ ] 代码实现了需求
- [ ] 边界条件处理正确
- [ ] 错误处理完善
可读性
- [ ] 命名清晰有意义
- [ ] 代码结构清晰
- [ ] 有必要的注释
安全性
- [ ] 没有SQL注入
- [ ] 没有敏感信息硬编码
- [ ] 输入有验证
性能
- [ ] 没有明显的性能问题
- [ ] 资源正确释放
- [ ] 没有goroutine泄漏
测试
- [ ] 有单元测试
- [ ] 测试覆盖率足够
- [ ] 测试可读性好
推荐工具
开发工具
# 格式化
gofmt, goimports
# 静态分析
go vet
golangci-lint
# 依赖管理
go mod
# 测试
go test
CI/CD配置
# .github/workflows/go.yml
name: Go
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.21'
- run: go build ./...
- run: go test ./...
- run: go vet ./...
总结
Go之禅
简单胜于复杂
明确胜于隐晦
少即是多
可读性很重要
错误要处理
并发要安全
接口要小
测试要充分
学习资源
🎉 恭喜你完成了整个Go语言教程!
你已经从基础到高阶系统地学习了Go语言。现在:
- 动手实践 - 用Go写一个自己的项目
- 阅读源码 - 看看优秀的开源项目
- 持续学习 - Go社区在不断发展
祝你Go程愉快! 🚀
