不能在init中调用gorm.open或sql.open,因其会同步预建连接池导致启动卡顿、错误不可见且无法恢复;应改用sync.once配合显式初始化函数,在main或首次调用时按需建立并可控处理错误。

Go 项目一旦多人协作或对外开源,不按约定规范写,三天后自己都看不懂 init 里为什么调了两次 flag.Parse()。
Go module 初始化必须显式声明 GO111MODULE=on 且用完整域名
本地开发时 go mod init 不加参数会生成 module example.com 这类占位名,后续发布到 GitHub 就得改 go.mod 和所有 import 路径——改漏一处就 import cycle not allowed。CI 环境更麻烦,某些旧版 CI 默认关 module 模式。
- 初始化前先设环境变量:
GO111MODULE=on go mod init github.com/username/repo-name - 模块名必须和代码托管地址一致,哪怕你用自建 GitLab,也要写成
gitlab.example.com/group/project - 别用
go mod init .自动推导,它可能取当前目录名(如cli),后期迁移成本极高
main 函数只做三件事:解析命令行、初始化依赖、启动服务
常见反模式是把数据库连接、配置加载、HTTP 路由注册全塞进 main(),导致无法单元测试、无法复用 CLI 逻辑、一改就崩。Go 的 main 是入口不是胶水层。
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
- 把配置加载抽成
config.Load(),返回结构体,不要直接读os.Getenv或flag.String到全局变量 - 依赖注入用构造函数传参,比如
http.NewServer(cfg, db, logger),而不是在main里db = sql.Open(...)后直接赋值给包级变量 - 命令行解析统一用
github.com/spf13/cobra,子命令对应独立cmd/*_cmd.go文件,避免main.go超过 100 行
错误处理必须区分 error 类型,不能全用 fmt.Errorf 包装
用 fmt.Errorf("failed to open file: %w", err) 看似标准,但下游无法用 errors.Is(err, fs.ErrNotExist) 判断具体错误类型,日志里也只剩字符串,没法结构化提取错误码。
- 底层 I/O 错误直接透传,比如
os.Open返回的*os.PathError保留原样 - 业务错误定义为自定义类型,实现
Unwrap() error和Error() string,例如type ValidationError struct{ Msg string } - 日志打点时用
log.Error("read config", "err", err, "path", cfgPath),而不是log.Error(err.Error())—— 结构化字段才能被 ELK 或 Loki 正确索引
测试覆盖率不是目标,TestMain 和接口 mock 才是关键
盲目追求 80%+ 覆盖率容易写出一堆只测 nil != nil 的假测试。真正影响稳定性的,是并发场景下资源释放顺序、第三方服务不可用时的 fallback 行为。
- 集成测试用
TestMain统一启停测试用的 Redis / PostgreSQL 容器,避免每个TestXxx都docker run一次 - 对 HTTP 客户端、数据库驱动等外部依赖,用 interface + mock 实现,比如定义
type DBExecutor interface { Exec(query string, args ...any) (sql.Result, error) },测试时传入内存 mock 实现 - 性能敏感路径(如 JSON 序列化)必须写
BenchmarkXxx,用go test -bench=.验证,别靠“感觉”说“这个应该很快”
Go 规范最易被忽略的不是语法,而是「什么时候该拆包」:一个 pkg/cache 包里混着 LRU 实现、Redis 封装、本地文件缓存,看着省事,实际改 Redis 密码就得测全部缓存路径。边界感比语法糖重要得多。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










