Go项目必须用gofmt+goimports统一格式化,禁用viper默认行为,import路径须与包名一致,结构体字段导出性由首字母决定,配置加载需明确时机与作用域。

Go 项目必须用 gofmt + goimports 统一格式化
不格式化的 Go 代码在团队协作中会立刻引发 merge 冲突和 review 障碍,而且 gofmt 是 Go 官方强绑定的格式工具——它不是“推荐”,是语言层面的约定。你写的代码只要没过 gofmt -w,就等于没写完。
实操建议:
- 所有编辑器(VS Code / Goland)必须启用保存时自动运行
gofmt和goimports -
goimports要替代原生go fmt,它能自动增删 import、按组排序(标准库 / 第三方 / 本地包),还能处理别名导入(如opentracing "github.com/opentracing/opentracing-go") - CI 流水线里加一步:
git diff --exit-code $(go list -f '{{.Dir}}' ./...) | grep -q '^$' || (echo "format error"; exit 1),防止未格式化代码合入主干
配置管理别碰 viper,默认行为就是调试噩梦
用 viper 加载 config.yaml 却不生效?大概率是它偷偷把系统环境变量(比如 PATH、SHELL)全映射成小写 key,覆盖了你的 database.host。这不是 bug,是默认开启 viper.AutomaticEnv() 的副作用。
更危险的是:多个 test 共享一个全局 viper 实例,前一个 test 没 clean 掉 env 或 file source,后一个 test 就读到脏数据,且错误只报 "cannot decode",不指明哪行 YAML 错。
立即学习“go语言免费学习笔记(深入)”;
正确做法是手动控制流程:
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
- 用
ioutil.ReadFile("config.yaml")或os.ReadFile(Go 1.16+)读文件 - 用
yaml.Unmarshal解析进结构体 - 再用
mapstructure.Decode填充嵌套字段(注意 tag 写mapstructure:"xxx") - 所有逻辑无全局状态、可单元测试、编译期校验类型
包名和 import 路径必须一致,禁止相对路径
写 import "../net" 看似省事,但会导致构建失败、IDE 跳转失效、CI 环境无法 resolve 包路径。Go 的 import 路径本质是模块坐标,不是文件系统路径。
实操要点:
- 包名 = 目录名 =
go.mod中 module 名的末段(如module github.com/yourorg/project,子目录pkg/http对应包名http) - 第三方包用完整路径:
import "github.com/spf13/cobra",不要缩写或别名(除非必要,如命名冲突) - 匿名导入(
_ "some/driver")必须加注释说明用途,例如// register mysql driver
结构体字段首字母大小写决定导出性,别靠 Get/Set 模糊边界
Go 没有 private/public 关键字,靠首字母大小写控制可见性。写 password string 是包内私有;写 Password string 就对外暴露——这规则简单直接,但常被误用为“反正我加了 GetPassword 就安全了”。
真实问题:
- 一旦字段导出(大写),JSON/YAML 序列化默认包含它,可能意外泄露敏感字段
- 用
json:"-"或yaml:"-"tag 可屏蔽,但得每个地方显式写,容易漏 - 如果真要封装逻辑(如密码哈希),应该用方法(
func (u *User) SetPassword(raw string)),而不是靠字段访问控制 - 别写
GetRoles()这种模糊命名,用AcquireRoles()或LoadRoles()明确语义
最易被忽略的其实是配置加载时机与作用域:多环境配置(dev/staging/prod)不该靠 if-else 在代码里切换文件名,而应由启动参数或环境变量决定加载哪个文件,且解析失败必须在 main 初始化阶段 panic,不能拖到第一次调用才暴露。

















