创建与发布模块

32.1 模块设计原则

1. 单一职责

每个模块应该只做一件事,并把它做好:

// 好的设计:职责单一
github.com/example/validator    // 只负责验证
github.com/example/logger       // 只负责日志
github.com/example/cache        // 只负责缓存

// 不好的设计:职责混乱
github.com/example/utils        // 包含各种不相关的功能

2. 最小化 API

只暴露必要的接口,隐藏实现细节:

package cache

// Cache 公开的接口
type Cache interface {
    Get(key string) (interface{}, bool)
    Set(key string, value interface{})
    Delete(key string)
}

// NewCache 公开的构造函数
func NewCache() Cache {
    return &memoryCache{
        data: make(map[string]interface{}),
    }
}

// memoryCache 私有的实现
type memoryCache struct {
    data map[string]interface{}
    mu   sync.RWMutex
}

func (c *memoryCache) Get(key string) (interface{}, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    val, ok := c.data[key]
    return val, ok
}

func (c *memoryCache) Set(key string, value interface{}) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.data[key] = value
}

func (c *memoryCache) Delete(key string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    delete(c.data, key)
}

3. 可配置性

提供灵活的配置选项:

package cache

type Config struct {
    MaxSize     int
    DefaultTTL  time.Duration
    OnEvict     func(key string, value interface{})
}

type Option func(*Config)

func WithMaxSize(size int) Option {
    return func(c *Config) {
        c.MaxSize = size
    }
}

func WithDefaultTTL(ttl time.Duration) Option {
    return func(c *Config) {
        c.DefaultTTL = ttl
    }
}

func WithOnEvict(fn func(key string, value interface{})) Option {
    return func(c *Config) {
        c.OnEvict = fn
    }
}

func NewCache(opts ...Option) Cache {
    config := &Config{
        MaxSize:    1000,
        DefaultTTL: 0,
    }
    for _, opt := range opts {
        opt(config)
    }
    return &memoryCache{
        config: config,
        data:   make(map[string]interface{}),
    }
}

32.2 项目结构

标准项目结构

myproject/
├── cmd/                    # 主应用程序
│   └── myapp/
│       └── main.go
├── internal/               # 私有代码
│   └── service/
│       └── user.go
├── pkg/                    # 公开代码
│   └── utils/
│       └── string.go
├── api/                    # API 定义
│   └── v1/
│       └── user.proto
├── configs/                # 配置文件
│   └── config.yaml
├── docs/                   # 文档
│   └── README.md
├── examples/               # 示例代码
│   └── example.go
├── go.mod
├── go.sum
├── Makefile
└── README.md

库项目结构

mylib/
├── cache.go                # 主要功能
├── cache_test.go           # 测试文件
├── options.go              # 配置选项
├── internal/               # 私有代码
│   └── store/
│       └── memory.go
├── examples/               # 示例
│   └── basic/
│       └── main.go
├── go.mod
├── go.sum
├── LICENSE
└── README.md

32.3 编写文档

README.md

# MyCache

一个简单高效的 Go 内存缓存库。

## 特性

- 支持过期时间
- 支持最大容量限制
- 线程安全
- 支持淘汰回调

## 安装

```bash
go get github.com/example/mycache

快速开始

package main

import (
    "fmt"
    "github.com/example/mycache"
)

func main() {
    cache := mycache.NewCache(
        mycache.WithMaxSize(100),
        mycache.WithDefaultTTL(5 * time.Minute),
    )

    cache.Set("key", "value")
    if val, ok := cache.Get("key"); ok {
        fmt.Println(val)
    }
}

配置选项

选项说明默认值
WithMaxSize最大容量1000
WithDefaultTTL默认过期时间0(永不过期)
WithOnEvict淘汰回调nil

文档

完整文档请访问 GoDoc

许可证

MIT License


### 代码注释

```go
// Package cache 提供了一个线程安全的内存缓存实现。
//
// 支持特性:
//   - 过期时间
//   - 最大容量限制
//   - 淘汰回调
//
// 示例:
//
//  cache := cache.NewCache(
//      cache.WithMaxSize(100),
//      cache.WithDefaultTTL(5 * time.Minute),
//  )
//  cache.Set("key", "value")
//  value, ok := cache.Get("key")
package cache

// Cache 定义了缓存的基本接口。
type Cache interface {
    // Get 获取缓存值。
    // 如果键不存在或已过期,返回 nil 和 false。
    Get(key string) (interface{}, bool)

    // Set 设置缓存值。
    // 使用默认的过期时间。
    Set(key string, value interface{})

    // SetWithTTL 设置缓存值并指定过期时间。
    SetWithTTL(key string, value interface{}, ttl time.Duration)

    // Delete 删除缓存值。
    Delete(key string)

    // Clear 清空所有缓存。
    Clear()
}

// NewCache 创建一个新的缓存实例。
//
// 可选配置:
//   - WithMaxSize(int): 设置最大容量
//   - WithDefaultTTL(time.Duration): 设置默认过期时间
//   - WithOnEvict(func): 设置淘汰回调函数
func NewCache(opts ...Option) Cache {
    // ...
}

32.4 版本管理

语义化版本

vMAJOR.MINOR.PATCH

MAJOR: 不兼容的 API 变更
MINOR: 向后兼容的功能新增
PATCH: 向后兼容的问题修复

Git 标签

# 创建 v1.0.0 标签
git tag v1.0.0
git push origin v1.0.0

# 创建 v1.1.0 标签
git tag v1.1.0
git push origin v1.1.0

# 创建 v2.0.0 标签
git tag v2.0.0
git push origin v2.0.0

发布 v2+ 版本

// v1/go.mod
module github.com/example/mylib

// v2/go.mod
module github.com/example/mylib/v2

或者使用主分支:

// go.mod (v2+)
module github.com/example/mylib/v2

go 1.21

32.5 发布流程

1. 准备发布

# 确保代码质量
go test ./...
go vet ./...

# 更新文档
# 更新 README.md
# 更新 CHANGELOG.md

# 整理依赖
go mod tidy

2. 创建标签

# 创建带注释的标签
git tag -a v1.0.0 -m "Release v1.0.0"

# 推送标签
git push origin v1.0.0

3. 验证发布

# 在另一个目录测试
mkdir test && cd test
go mod init test
go get github.com/example/mylib@v1.0.0

4. 发布检查清单

  • 所有测试通过
  • 代码审查完成
  • 文档更新
  • CHANGELOG 更新
  • 版本号正确
  • 标签已创建
  • 发布成功验证

32.6 CHANGELOG

CHANGELOG.md 示例

# Changelog

All notable changes to this project will be documented in this file.

## [1.1.0] - 2024-01-15

### Added
- 新增 `SetWithTTL` 方法
- 支持淘汰回调函数

### Changed
- 优化内存使用

### Fixed
- 修复并发写入时的竞态条件

## [1.0.0] - 2024-01-01

### Added
- 初始版本
- 基本的缓存功能
- 支持过期时间

32.7 完整示例

创建一个工具库

# 1. 创建项目
mkdir stringutils && cd stringutils

# 2. 初始化模块
go mod init github.com/example/stringutils

# 3. 创建代码
cat > stringutils.go << 'EOF'
package stringutils

// Reverse 反转字符串
func Reverse(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

// IsEmpty 检查字符串是否为空
func IsEmpty(s string) bool {
    return len(s) == 0
}

// IsBlank 检查字符串是否为空白
func IsBlank(s string) bool {
    for _, r := range s {
        if !unicode.IsSpace(r) {
            return false
        }
    }
    return true
}

// Truncate 截断字符串
func Truncate(s string, maxLen int) string {
    if len(s) <= maxLen {
        return s
    }
    return s[:maxLen] + "..."
}
EOF

# 4. 创建测试
cat > stringutils_test.go << 'EOF'
package stringutils

import "testing"

func TestReverse(t *testing.T) {
    tests := []struct {
        input    string
        expected string
    }{
        {"hello", "olleh"},
        {"", ""},
        {"a", "a"},
        {"你好", "好你"},
    }
    for _, tt := range tests {
        if got := Reverse(tt.input); got != tt.expected {
            t.Errorf("Reverse(%q) = %q, want %q", tt.input, got, tt.expected)
        }
    }
}

func TestIsEmpty(t *testing.T) {
    if !IsEmpty("") {
        t.Error("IsEmpty(\"\") should be true")
    }
    if IsEmpty("a") {
        t.Error("IsEmpty(\"a\") should be false")
    }
}
EOF

# 5. 运行测试
go test ./...

# 6. 创建 README
cat > README.md << 'EOF'
# StringUtils

Go 字符串工具库。

## 安装

```bash
go get github.com/example/stringutils

使用

import "github.com/example/stringutils"

func main() {
    s := stringutils.Reverse("hello") // "olleh"
    empty := stringutils.IsEmpty("")  // true
}

EOF

7. 初始化 Git

git init git add . git commit -m "Initial commit"

8. 创建标签

git tag v1.0.0

9. 推送到远程

git remote add origin https://github.com/example/stringutils.git git push -u origin main git push origin v1.0.0


## 32.8 小结

本章详细讲解了如何创建和发布 Go 模块:

1. **设计原则**:单一职责、最小化 API、可配置性
2. **项目结构**:标准项目结构和库项目结构
3. **文档编写**:README 和代码注释
4. **版本管理**:语义化版本和 Git 标签
5. **发布流程**:准备、创建标签、验证发布

创建高质量的 Go 模块需要良好的设计、完善的文档和规范的发布流程。在下一章中,我们将学习文件与 IO 操作。