
在 go 中,通过专用包定义导出变量并初始化全局资源(如数据库连接)是一种简洁、可行的模式,适用于中小型项目;但需注意线程安全、初始化顺序及可测试性等关键问题。
在 go 中,通过专用包定义导出变量并初始化全局资源(如数据库连接)是一种简洁、可行的模式,适用于中小型项目;但需注意线程安全、初始化顺序及可测试性等关键问题。
在 Go 应用开发中,合理管理全局状态(如数据库连接池、配置对象、日志实例等)是常见需求。你提出的方案——创建独立的 config 包,并在其中声明导出的包级变量(如 var DB *sql.DB),随后在 main() 中完成初始化——完全符合 Go 的设计哲学,语法合法、运行可靠、工程上广泛使用。
例如,一个更健壮的实现如下:
// config/config.go
package config
import (
"database/sql"
_ "github.com/lib/pq" // PostgreSQL 驱动
)
var DB *sql.DB
var Err error
func InitDB(connStr string) error {
DB, Err = sql.Open("postgres", connStr)
if Err != nil {
return Err
}
// 可选:验证连接可用性
if Err = DB.Ping(); Err != nil {
return Err
}
return nil
}
// main.go
package main
import (
"log"
"project/config"
)
func main() {
if err := config.InitDB("host=localhost port=5432 dbname=test..."); err != nil {
log.Fatal("Failed to initialize DB:", err)
}
// 后续其他模块可直接使用 config.DB
}
✅ 优势说明:
-
清晰解耦:配置/资源初始化逻辑集中于
config包,业务代码无需感知创建细节; -
零重复初始化:一次设置,多处复用,避免每个 handler 或 service 重复
sql.Open; -
兼容标准库习惯:类似
net/http.DefaultClient或log.Default()的全局实例模式。
⚠️ 注意事项:
- ❌ 避免在包顶层直接调用
sql.Open(如原问题中var DB *sql.DB; DB, _ = ...),因包级变量初始化发生在init()阶段,此时无法处理错误,也难以控制执行时机; - ✅ 推荐显式
InitXXX()函数,便于错误传播、单元测试(可多次调用或重置)、以及支持延迟初始化(如按需加载); - ? 可测试性提示:若需 mock 全局
config.DB,可在测试中通过config.DB = testDB替换(Go 允许包级变量赋值),但更推荐依赖注入(尤其在大型项目中)以提升可维护性; - ? 并发安全:
*sql.DB本身是并发安全的,但自定义结构体若含非同步字段,需自行加锁或使用sync.Once控制初始化。
总结而言,该模式并非“反模式”,而是 Go 生态中轻量级服务的标准实践之一。随着项目演进,若复杂度上升(如多环境配置、动态重载、依赖闭环),再逐步引入依赖注入框架(如 wire)或配置中心即可——简单始于包级变量,演进源于实际需求。










