
本文介绍一种不依赖全局变量、符合 Go 语言惯用法的方式,将 main() 中创建的运行时配置(如数据库连接)安全、可测试地共享至其他包或文件。核心方案是结合私有全局变量与初始化保护的单例模式。
本文介绍一种不依赖全局变量、符合 go 语言惯用法的方式,将 main() 中创建的运行时配置(如数据库连接)安全、可测试地共享至其他包或文件。核心方案是结合私有全局变量与初始化保护的单例模式。
在 Go 应用中,main() 函数通常是唯一能按需初始化外部依赖(如数据库连接、配置解析、日志句柄)的地方。但许多业务逻辑函数(如 SomeUtilityFuncThatNeedsRuntime)又需要这些已初始化的实例——直接使用全局变量虽简单,却破坏封装性、阻碍单元测试,并引发竞态风险。
推荐做法是实现受控单例(Controlled Singleton):定义一个包级私有变量存储配置实例,提供带初始化检查的获取函数,并禁止外部直接构造。关键在于:该实例必须由 main() 显式注入,而非延迟初始化(避免 init() 或首次调用时才连接 DB,导致错误难以定位)。
以下为完整实践示例:
// config/runtime.go
package config
import "sync"
type RuntimeConfig struct {
DB *DatabaseInstance
AppName string
// ... 其他字段
}
var (
instance *RuntimeConfig
once sync.Once
)
// Set 初始化运行时配置(仅允许调用一次,应在 main() 中执行)
func Set(cfg *RuntimeConfig) {
once.Do(func() {
instance = cfg
})
}
// Get 获取运行时配置,若未初始化则 panic(明确失败,便于调试)
func Get() *RuntimeConfig {
if instance == nil {
panic("runtime config not initialized: call config.Set() in main()")
}
return instance
}
// main.go
func main() {
db := ConnectToDB(/* ... */)
cfg := &config.RuntimeConfig{
DB: db,
AppName: "My app",
// ...
}
config.Set(cfg) // ✅ 唯一合法注入点
// 启动服务...
}
// elsewhere.go
import "yourapp/config"
func SomeUtilityFuncThatNeedsRuntime(i int) int {
runtime := config.Get() // ✅ 安全获取,无 nil 风险
runtime.DB.Save(runtime.AppName, /* ... */)
return i + 1
}
注意事项与最佳实践:
- ✅ 强制显式初始化:
Set()使用sync.Once确保只生效一次;Get()的 panic 提供清晰错误上下文,优于静默返回nil。 - ❌ 避免延迟初始化:不要在
Get()内部自动创建 DB 连接——这会隐藏依赖、增加测试难度,并可能导致多处重复初始化。 - ? 利于测试:测试时可在
TestMain或每个测试用例前调用config.Set(testCfg),轻松替换模拟依赖。 - ? 封装性保障:
instance变量为包私有,外部无法绕过Set()直接赋值,杜绝非法状态。
此模式平衡了简洁性与工程健壮性,既规避了“上帝变量”的反模式,又无需为每个函数传递冗长参数,是 Go 生产项目中管理启动期依赖的主流实践。










