Skip to content

📋 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 {
    // 保存逻辑
}

📚 相关资源

官方指南

项目相关


💡 编码建议: 好的代码是写给人看的,机器只是恰好能执行。始终考虑代码的可读性和可维护性,而不仅仅是功能实现。

基于 MIT 许可证发布