Skip to content

🎯 代码质量标准

代码质量是 lzt 项目的核心要求,本文档定义了代码质量标准、检查工具和持续改进流程。

📊 质量指标

核心质量指标

质量基准

  • 测试覆盖率: pkg/ 包必须达到 85% 以上
  • 圈复杂度: 单个函数不超过 10
  • 函数长度: 不超过 50 行
  • 文件长度: 不超过 500 行
  • import 深度: 不超过 3 层

🔍 静态代码分析

Go 工具链

bash
# 格式化检查
go fmt ./...
goimports -w .
gofumpt -w .

# 代码检查
go vet ./...
golint ./...
golangci-lint run

# 安全检查
gosec ./...

# 依赖检查
go mod verify
go mod tidy

配置文件

yaml
# .golangci.yml
run:
  timeout: 5m
  tests: true
  modules-download-mode: readonly

linters-settings:
  gocyclo:
    min-complexity: 10
  
  goconst:
    min-len: 2
    min-occurrences: 2
    
  dupl:
    threshold: 100
    
  funlen:
    lines: 50
    statements: 40
    
  lll:
    line-length: 120

linters:
  enable:
    - gofmt
    - goimports
    - golint
    - govet
    - ineffassign
    - misspell
    - gocyclo
    - goconst
    - dupl
    - funlen
    - lll
    - unconvert
    - unparam
    - unused

📝 代码规范检查

命名规范验证

go
// ✅ 良好的命名
type TransactionService struct {
    repository TransactionRepository
    validator  TransactionValidator
    idGen      IDGenerator
}

func (s *TransactionService) CreateTransaction(ctx context.Context, req *CreateTransactionRequest) (*Transaction, error) {
    if err := s.validator.Validate(req); err != nil {
        return nil, fmt.Errorf("validation failed: %w", err)
    }
    
    tx := &Transaction{
        ID:          s.idGen.Generate(),
        LedgerID:    req.LedgerID,
        Type:        req.Type,
        Amount:      req.Amount,
        Description: req.Description,
        CreatedAt:   time.Now(),
    }
    
    if err := s.repository.Create(ctx, tx); err != nil {
        return nil, fmt.Errorf("create transaction failed: %w", err)
    }
    
    return tx, nil
}

// ❌ 需要改进的命名
type TxSvc struct {
    repo Repository  // 不够具体
    val  Validator   // 缩写不清晰
    gen  Generator   // 含义模糊
}

func (s *TxSvc) Create(ctx context.Context, req interface{}) (interface{}, error) {
    // 参数类型过于通用
    // 函数名不够描述性
}

复杂度控制

go
// ✅ 简单易懂的函数
func ValidateTransactionRequest(req *CreateTransactionRequest) error {
    if req == nil {
        return errors.New("request cannot be nil")
    }
    
    if req.LedgerID == "" {
        return errors.New("ledger_id is required")
    }
    
    if req.Amount.Amount <= 0 {
        return errors.New("amount must be positive")
    }
    
    if req.Amount.Currency == "" {
        return errors.New("currency is required")
    }
    
    return nil
}

// ❌ 复杂度过高的函数
func ProcessComplexTransaction(req *ComplexRequest) (*ComplexResponse, error) {
    // 大量的 if/else 嵌套
    // 多个循环和条件判断
    // 圈复杂度 > 10
    // 需要重构为多个小函数
}

🧪 测试质量标准

测试覆盖率要求

bash
# 检查覆盖率
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out

# pkg 包必须达到 85% 覆盖率
go test -cover ./pkg/...

# 生成详细报告
go tool cover -html=coverage.out -o coverage.html

测试质量评估

go
// ✅ 高质量测试
func TestCreateTransaction_ValidRequest_Success(t *testing.T) {
    // Arrange
    mockRepo := &MockTransactionRepository{}
    service := NewTransactionService(mockRepo)
    
    req := &CreateTransactionRequest{
        LedgerID: "ledger-123",
        Type:     TransactionTypeExpense,
        Amount:   Money{Amount: 1000, Currency: "CNY"},
    }
    
    mockRepo.On("Create", mock.Anything, mock.AnythingOfType("*Transaction")).Return(nil)
    
    // Act
    result, err := service.CreateTransaction(context.Background(), req)
    
    // Assert
    assert.NoError(t, err)
    assert.NotNil(t, result)
    assert.Equal(t, req.LedgerID, result.LedgerID)
    assert.Equal(t, req.Type, result.Type)
    assert.Equal(t, req.Amount, result.Amount)
    assert.NotEmpty(t, result.ID)
    
    mockRepo.AssertExpectations(t)
}

// 测试边界条件
func TestCreateTransaction_InvalidAmount_ReturnsError(t *testing.T) {
    service := NewTransactionService(nil)
    
    req := &CreateTransactionRequest{
        LedgerID: "ledger-123",
        Type:     TransactionTypeExpense,
        Amount:   Money{Amount: -1000, Currency: "CNY"}, // 负数金额
    }
    
    result, err := service.CreateTransaction(context.Background(), req)
    
    assert.Error(t, err)
    assert.Nil(t, result)
    assert.Contains(t, err.Error(), "amount must be positive")
}

📚 文档质量标准

代码注释规范

go
// ✅ 良好的包级注释
// Package ledger 提供账本管理的核心功能。
//
// 主要功能包括:
//   - 交易记录管理
//   - 预算控制
//   - 数据统计分析
//
// 使用示例:
//   service := ledger.NewTransactionService(repo)
//   tx, err := service.CreateTransaction(ctx, req)
package ledger

// ✅ 良好的函数注释
// CreateTransaction 创建新的交易记录。
//
// 该方法验证请求参数,检查账本存在性,然后创建并保存交易记录。
// 如果验证失败或账本不存在,将返回详细的错误信息。
//
// 参数:
//   ctx: 请求上下文,用于超时控制和取消
//   req: 创建交易的请求参数,包含账本ID、类型、金额等
//
// 返回值:
//   *Transaction: 创建成功的交易记录
//   error: 创建失败时的错误信息
func (s *TransactionService) CreateTransaction(ctx context.Context, req *CreateTransactionRequest) (*Transaction, error) {
    // 实现细节...
}

// ✅ 良好的类型注释
// Transaction 表示一笔财务交易记录。
//
// 交易一旦创建,核心字段(ID、金额、类型)不可修改,
// 只能修改描述、标签等辅助信息。
type Transaction struct {
    ID          string    `json:"id" gorm:"primarykey"`           // 交易唯一标识
    LedgerID    string    `json:"ledger_id" gorm:"index"`         // 所属账本ID
    Type        string    `json:"type" gorm:"not null"`           // 交易类型
    Amount      Money     `json:"amount" gorm:"embedded"`          // 交易金额
    Description string    `json:"description"`                     // 交易描述
    CreatedAt   time.Time `json:"created_at" gorm:"autoCreateTime"` // 创建时间
}

API 文档标准

go
// swagger:route POST /api/v1/transactions transactions createTransaction
//
// 创建新交易
//
// 创建一笔新的财务交易记录。支持收入、支出和转账三种类型。
//
//     Consumes:
//     - application/json
//
//     Produces:
//     - application/json
//
//     Schemes: http, https
//
//     Security:
//       api_key:
//
//     Responses:
//       201: transactionResponse
//       400: errorResponse
//       401: errorResponse
//       500: errorResponse
func (h *Handler) CreateTransaction(w http.ResponseWriter, r *http.Request) {
    // 实现...
}

🚀 性能质量标准

性能基准

go
// 基准测试要求
func BenchmarkCreateTransaction(b *testing.B) {
    service := setupBenchmarkService()
    req := &CreateTransactionRequest{
        LedgerID: "bench-ledger",
        Type:     TransactionTypeExpense,
        Amount:   Money{Amount: 1000, Currency: "CNY"},
    }
    
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        _, err := service.CreateTransaction(context.Background(), req)
        if err != nil {
            b.Fatal(err)
        }
    }
}

// 性能目标
const (
    MaxCreateTransactionLatency = 50 * time.Millisecond  // 最大延迟
    MaxMemoryPerOperation      = 1 * 1024               // 最大内存使用 1KB
    MaxAllocationsPerOperation = 10                      // 最大分配次数
)

内存管理

go
// ✅ 良好的内存管理
func ProcessLargeDataset(items []string) []string {
    const batchSize = 1000
    results := make([]string, 0, len(items)) // 预分配容量
    
    for i := 0; i < len(items); i += batchSize {
        end := i + batchSize
        if end > len(items) {
            end = len(items)
        }
        
        batch := items[i:end]
        processed := processBatch(batch)
        results = append(results, processed...)
        
        // 显式回收大对象
        if len(batch) > 500 {
            runtime.GC()
        }
    }
    
    return results
}

// ❌ 内存使用不当
func ProcessLargeDatasetBadly(items []string) []string {
    var results []string // 没有预分配
    
    for _, item := range items {
        // 每次都创建新的临时对象
        temp := make([]string, 1000)
        temp[0] = processItem(item)
        results = append(results, temp[0])
        // temp 会在每次迭代中被丢弃,造成GC压力
    }
    
    return results
}

🔄 持续改进流程

代码审查清单

markdown
## 代码审查清单

### 功能性
- [ ] 功能是否按需求正确实现
- [ ] 边界条件是否正确处理
- [ ] 错误处理是否完善
- [ ] 并发安全性是否考虑

### 可读性
- [ ] 命名是否清晰表达意图
- [ ] 代码结构是否逻辑清晰
- [ ] 注释是否必要且准确
- [ ] 复杂逻辑是否有解释

### 可维护性
- [ ] 函数是否单一职责
- [ ] 依赖是否合理
- [ ] 代码是否遵循现有模式
- [ ] 是否容易扩展

### 测试
- [ ] 单元测试是否充分
- [ ] 测试用例是否覆盖主要场景
- [ ] 测试是否独立且可重复
- [ ] 测试名称是否描述性

### 性能
- [ ] 算法复杂度是否合理
- [ ] 内存使用是否优化
- [ ] 是否有明显的性能瓶颈
- [ ] 并发处理是否高效

质量门禁

yaml
# .github/workflows/quality-gate.yml
name: Quality Gate

on:
  pull_request:
    branches: [ main ]

jobs:
  quality-check:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Go
      uses: actions/setup-go@v3
      with:
        go-version: '1.24.3'
    
    - name: Run tests with coverage
      run: |
        go test -coverprofile=coverage.out ./...
        COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
        echo "Coverage: $COVERAGE%"
        if (( $(echo "$COVERAGE < 85" | bc -l) )); then
          echo "Coverage $COVERAGE% is below required 85%"
          exit 1
        fi
    
    - name: Run linter
      uses: golangci/golangci-lint-action@v3
      with:
        version: latest
        args: --timeout=5m
    
    - name: Security scan
      uses: securecodewarrior/github-action-add-sarif@v1
      with:
        sarif-file: 'gosec-report.sarif'

📊 质量监控

质量指标仪表板

go
// 质量指标收集
type QualityMetrics struct {
    TestCoverage    float64 `json:"test_coverage"`
    CyclomaticComplexity float64 `json:"cyclomatic_complexity"`
    LinesOfCode     int     `json:"lines_of_code"`
    TechnicalDebt   int     `json:"technical_debt_hours"`
    BugDensity      float64 `json:"bugs_per_kloc"`
}

func CollectQualityMetrics() *QualityMetrics {
    return &QualityMetrics{
        TestCoverage:         calculateTestCoverage(),
        CyclomaticComplexity: calculateAverageComplexity(),
        LinesOfCode:          countLinesOfCode(),
        TechnicalDebt:        estimateTechnicalDebt(),
        BugDensity:           calculateBugDensity(),
    }
}

📚 相关资源

项目标准文档

外部参考


💡 质量建议: 代码质量不是一次性达成的,而是需要持续改进的过程。建立质量门禁,定期进行代码审查,关注质量指标变化趋势。

基于 MIT 许可证发布