📋 Go 编程规范
本文档详细阐述 lzt 项目的 Go 语言编程规范,确保代码的一致性、可读性和可维护性。
🎯 核心原则
Go 语言哲学
- 简洁性 - 简单胜于复杂
- 可读性 - 代码是写给人看的
- 一致性 - 保持风格统一
- 实用性 - 解决实际问题
📝 命名规范
包命名
go
// ✅ 好的包名
package bubble // 简洁、描述性
package ledger // 表达业务领域
package errors // 通用工具包
// ❌ 避免的包名
package bubbleteaui // 过长
package utils // 过于通用
package helpers // 意义不明确
package common // 含义模糊变量命名
go
// ✅ 清晰的变量命名
var maxRetryCount = 3
var userRepository UserRepository
var httpClient *http.Client
// 短变量名适用于短作用域
for i, item := range items {
// i 和 item 在短循环中是可接受的
}
// ❌ 不清晰的命名
var max = 3 // 不明确最大值是什么
var ur UserRepository // 过度缩写
var c *http.Client // 含义不明函数命名
go
// ✅ 动词开头,表达行为
func CreateTransaction(req *CreateTransactionRequest) (*Transaction, error)
func ValidateAmount(amount Money) error
func ProcessFiles(paths []string, processor func(string) error) error
// ✅ 布尔函数以 Is、Has、Can 开头
func IsValidCurrency(currency string) bool
func HasPermission(user User, action string) bool
func CanAccess(resource Resource) bool
// ❌ 不清晰的函数命名
func Process(data interface{}) error // 太通用
func Check(input string) bool // 检查什么?
func Handle(ctx context.Context) // 处理什么?常量命名
go
// ✅ 使用有意义的常量名
const (
DefaultTimeout = 30 * time.Second
MaxRetryAttempts = 3
TransactionTypeExpense = "expense"
TransactionTypeIncome = "income"
)
// ✅ 枚举类型使用 iota
type Status int
const (
StatusPending Status = iota
StatusProcessing
StatusCompleted
StatusFailed
)🏗️ 代码结构
包组织
internal/app/ledger/
├── domain/ # 领域层
│ ├── entities/ # 实体
│ ├── repositories/ # 仓储接口
│ └── services/ # 领域服务
├── application/ # 应用层
│ └── services/ # 应用服务
├── infrastructure/ # 基础设施层(在 internal/pkg/)
└── interfaces/ # 接口层(在 cmd/)文件组织
go
// service.go - 主要服务实现
type TransactionService struct {
repo TransactionRepository
validator Validator
idGen IDGenerator
}
// service_test.go - 测试文件
func TestTransactionService_Create(t *testing.T) {
// 测试实现
}
// service_budget.go - 相关功能分组
func (s *TransactionService) CreateBudget(ctx context.Context, req *CreateBudgetRequest) (*Budget, error) {
// 预算相关功能
}接口设计
go
// ✅ 小而专注的接口
type Reader interface {
Read([]byte) (int, error)
}
type Writer interface {
Write([]byte) (int, error)
}
type ReadWriter interface {
Reader
Writer
}
// ✅ 业务领域接口
type TransactionRepository interface {
Create(ctx context.Context, tx *Transaction) error
GetByID(ctx context.Context, id string) (*Transaction, error)
List(ctx context.Context, filter ListFilter) ([]*Transaction, error)
Update(ctx context.Context, tx *Transaction) error
Delete(ctx context.Context, id string) error
}
// ❌ 过大的接口
type TransactionManager interface {
// 太多职责混合在一起
Create(ctx context.Context, tx *Transaction) error
Validate(tx *Transaction) error
Process(tx *Transaction) error
Notify(tx *Transaction) error
Archive(tx *Transaction) error
Export(format string) ([]byte, error)
}🚨 错误处理
错误类型设计
go
// ✅ 自定义错误类型
type ValidationError struct {
Field string `json:"field"`
Message string `json:"message"`
Code string `json:"code"`
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation error on field '%s': %s", e.Field, e.Message)
}
// ✅ 哨兵错误
var (
ErrTransactionNotFound = errors.New("transaction not found")
ErrInvalidAmount = errors.New("invalid amount")
ErrLedgerNotFound = errors.New("ledger not found")
)
// ✅ 错误包装
func (s *Service) CreateTransaction(ctx context.Context, req *CreateTransactionRequest) (*Transaction, error) {
if err := s.validate(req); err != nil {
return nil, fmt.Errorf("validation failed: %w", err)
}
tx := s.buildTransaction(req)
if err := s.repo.Create(ctx, tx); err != nil {
return nil, fmt.Errorf("failed to create transaction: %w", err)
}
return tx, nil
}错误处理模式
go
// ✅ 明确的错误检查
result, err := someOperation()
if err != nil {
return fmt.Errorf("operation failed: %w", err)
}
// ✅ 早期返回
func ProcessTransaction(tx *Transaction) error {
if tx == nil {
return errors.New("transaction cannot be nil")
}
if tx.Amount.Amount <= 0 {
return errors.New("amount must be positive")
}
// 继续处理...
return nil
}
// ❌ 忽略错误
result, _ := someOperation() // 危险!
// ❌ 过度嵌套
if result, err := someOperation(); err == nil {
if processed, err := processResult(result); err == nil {
if saved, err := saveProcessed(processed); err == nil {
return saved, nil
} else {
return nil, err
}
} else {
return nil, err
}
} else {
return nil, err
}📊 并发编程
Goroutine 使用
go
// ✅ 有限的 goroutine 数量
func ProcessTasksConcurrently(tasks []Task, maxWorkers int) error {
taskChan := make(chan Task, len(tasks))
resultChan := make(chan error, len(tasks))
// 启动固定数量的工作协程
var wg sync.WaitGroup
for i := 0; i < maxWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for task := range taskChan {
resultChan <- processTask(task)
}
}()
}
// 发送任务
for _, task := range tasks {
taskChan <- task
}
close(taskChan)
// 等待完成
go func() {
wg.Wait()
close(resultChan)
}()
// 收集结果
for err := range resultChan {
if err != nil {
return err
}
}
return nil
}
// ❌ 无限制的 goroutine
func ProcessTasksBadly(tasks []Task) {
for _, task := range tasks {
go func(t Task) {
// 可能创建成千上万个 goroutine
processTask(t)
}(task)
}
}Context 使用
go
// ✅ 正确的 Context 传递
func (s *Service) CreateTransaction(ctx context.Context, req *CreateTransactionRequest) (*Transaction, error) {
// 验证超时控制
validateCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := s.validator.Validate(validateCtx, req); err != nil {
return nil, fmt.Errorf("validation failed: %w", err)
}
// 数据库操作继承原始 context
tx := s.buildTransaction(req)
if err := s.repo.Create(ctx, tx); err != nil {
return nil, fmt.Errorf("create failed: %w", err)
}
return tx, nil
}
// ✅ Context 取消检查
func LongRunningOperation(ctx context.Context) error {
for i := 0; i < 1000; i++ {
select {
case <-ctx.Done():
return ctx.Err()
default:
// 继续处理
if err := processItem(i); err != nil {
return err
}
}
}
return nil
}🧪 测试规范
测试命名
go
// ✅ 描述性的测试名称
func TestCreateTransaction_ValidRequest_ReturnsTransaction(t *testing.T) {}
func TestCreateTransaction_InvalidLedgerID_ReturnsError(t *testing.T) {}
func TestCreateTransaction_NilRequest_ReturnsValidationError(t *testing.T) {}
// ✅ 表驱动测试
func TestValidateAmount(t *testing.T) {
tests := []struct {
name string
amount Money
wantErr bool
errMsg string
}{
{
name: "valid positive amount",
amount: Money{Amount: 1000, Currency: "CNY"},
wantErr: false,
},
{
name: "zero amount should fail",
amount: Money{Amount: 0, Currency: "CNY"},
wantErr: true,
errMsg: "amount must be positive",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateAmount(tt.amount)
if tt.wantErr {
assert.Error(t, err)
if tt.errMsg != "" {
assert.Contains(t, err.Error(), tt.errMsg)
}
} else {
assert.NoError(t, err)
}
})
}
}模拟和依赖
go
// ✅ 接口模拟
type MockTransactionRepository struct {
mock.Mock
}
func (m *MockTransactionRepository) Create(ctx context.Context, tx *Transaction) error {
args := m.Called(ctx, tx)
return args.Error(0)
}
// ✅ 测试中使用
func TestServiceWithMock(t *testing.T) {
mockRepo := new(MockTransactionRepository)
service := NewTransactionService(mockRepo)
// 设置期望
mockRepo.On("Create", mock.Anything, mock.AnythingOfType("*Transaction")).Return(nil)
// 执行测试
_, err := service.CreateTransaction(context.Background(), validRequest)
// 验证
assert.NoError(t, err)
mockRepo.AssertExpectations(t)
}📝 注释和文档
注释规范
go
// ✅ 包级别注释
// Package ledger 提供账本管理的核心功能,包括交易记录、预算管理和统计分析。
//
// 主要组件:
// - TransactionService: 交易管理服务
// - BudgetService: 预算管理服务
// - AnalyticsService: 数据分析服务
//
// 使用示例:
// service := ledger.NewTransactionService(repo)
// tx, err := service.CreateTransaction(ctx, req)
package ledger
// ✅ 函数注释
// CreateTransaction 创建新的交易记录。
//
// 该方法会验证请求参数,检查账本是否存在,然后创建并保存交易记录。
// 如果验证失败或账本不存在,将返回相应的错误。
//
// 参数:
// ctx: 请求上下文,用于超时控制和取消
// req: 创建交易的请求参数
//
// 返回值:
// *Transaction: 创建的交易记录
// error: 如果创建失败则返回错误
func (s *TransactionService) CreateTransaction(ctx context.Context, req *CreateTransactionRequest) (*Transaction, error) {
// 实现...
}
// ✅ 类型注释
// Transaction 表示一笔财务交易记录。
//
// 包含交易的基本信息如金额、类型、描述等,以及元数据如创建时间、更新时间等。
// 交易一旦创建不可修改金额和类型,只能修改描述和标签。
type Transaction struct {
ID string `json:"id" gorm:"primarykey"` // 交易唯一标识
LedgerID string `json:"ledger_id" gorm:"index"` // 所属账本ID
Type string `json:"type" gorm:"not null"` // 交易类型:income/expense/transfer
Amount Money `json:"amount" gorm:"embedded"` // 交易金额和货币
Description string `json:"description"` // 交易描述
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` // 创建时间
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"` // 更新时间
}🔧 代码格式化
格式化工具
bash
# 使用 gofmt 格式化代码
go fmt ./...
# 使用 goimports 处理导入
goimports -w .
# 使用 gofumpt 更严格的格式化
gofumpt -w .Import 组织
go
// ✅ 正确的 import 顺序
package main
import (
// 标准库
"context"
"fmt"
"time"
// 第三方库
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"gorm.io/gorm"
// 项目内部包
"lzt/internal/app/ledger"
"lzt/pkg/errors"
)📊 代码度量
复杂度控制
go
// ✅ 简单的函数(圈复杂度 < 10)
func ValidateTransaction(tx *Transaction) error {
if tx == nil {
return errors.New("transaction cannot be nil")
}
if tx.Amount.Amount <= 0 {
return errors.New("amount must be positive")
}
if tx.LedgerID == "" {
return errors.New("ledger ID is required")
}
return nil
}
// ❌ 复杂的函数需要重构
func ComplexFunction(input *Input) (*Output, error) {
// 大量的 if/else 嵌套
// 多个循环和条件判断
// 圈复杂度过高
}函数长度
go
// ✅ 适中的函数长度(<50行)
func CreateTransaction(req *CreateTransactionRequest) (*Transaction, error) {
if err := validateRequest(req); err != nil {
return nil, err
}
tx := buildTransaction(req)
if err := saveTransaction(tx); err != nil {
return nil, err
}
return tx, nil
}
// 将复杂逻辑分解为小函数
func validateRequest(req *CreateTransactionRequest) error {
// 验证逻辑
}
func buildTransaction(req *CreateTransactionRequest) *Transaction {
// 构建逻辑
}
func saveTransaction(tx *Transaction) error {
// 保存逻辑
}📚 相关资源
官方指南
项目相关
💡 编码建议: 好的代码是写给人看的,机器只是恰好能执行。始终考虑代码的可读性和可维护性,而不仅仅是功能实现。