“Go 是一门简单的语言” —— 初学者
“Go 是一门极其诚实的语言” —— 被nilpanic 过三次的你
大家好,今天不聊 Goroutine 调度器,也不分析 GC 原理——
咱们来点实在的:如何写出「人话能懂、机器不崩、同事不骂」的 Go 代码?
🚫 1. log.Printf:藏得最深的“叛徒”
func (s *Service) HandleUser(id string) error {
user, err := s.repo.GetUser(id)
if err != nil {
log.Printf("failed to get user: %v", err) // 🚨 危险!
return err
}
// ...
}
你以为你在打日志?
不,你在偷偷调用全局单例 log.Logger!
这就相当于:
“我借了邻居的铲子修自家花园,但从没告诉家人铲子放哪——结果全家翻箱倒柜找铲子,而我在偷偷用。”
✅ 正确姿势:依赖显式化
type Service struct {
repo UserRepository
logger *log.Logger // 👈 明确声明
}
func NewService(repo UserRepository, logger *log.Logger) *Service {
if logger == nil {
logger = log.New(ioutil.Discard, "", 0) // 🎁 usable default!
}
return &Service{
repo: repo, logger: logger}
}
func (s *Service) HandleUser(id string) error {
user, err := s.repo.GetUser(id)
if err != nil {
s.logger.Printf("failed to get user: %v", err) // ✅ 归属清晰
return err
}
// ...
}
💡 金句:
“Go 爱你,所以从不帮你藏依赖;
你若藏它,它必panic还你。”
🧱 2. nil:Go 世界的“薛定谔的猫”
type OutputWriter interface {
Write([]byte) (int, error)
}
func (s *Service) ExportData(w OutputWriter) {
if w == nil {
// 🐱 每次都要问:你到底是不是 nil?
return
}
w.Write([]byte("data"))
}
每次都做 nil 检查?累不累?
Go 的答案是:别问,直接给个“不做事”的默认值👇
var DiscardOutputWriter = nopWriter{
} // no-op writer
type nopWriter struct{
}
func (nopWriter) Write(p []byte) (int, error) {
return len(p), nil // 吞掉所有数据,安静如鸡 🐔
}
// 使用时:
s.ExportData(DiscardOutputWriter) // 安全!无需判空
🔀 3. 循环依赖:当 A 必须认识 B,而 B 又必须认识 A…
type UserService struct {
orderService *OrderService // 😱
}
type OrderService struct {
userService *UserService // 😱😱
}
构造时你发现:
“要造 UserService → 得先有 OrderService → 但造 OrderService 又要 UserService……
于是我连夜写了个god.New()函数。”
下面给出 三种解法——堪称 Go 版《倚天屠龙记》三招:
| 武功名 | 招式 | 适用场景 |
|---|---|---|
| 【乾坤大挪移】—— 合并 | type UserOrderService struct { … } |
两个服务本就是“连体婴” |
| 【太极云手】—— 中介分离 | 引入共享上下文 *Context 或 *Config |
依赖的是“状态”,不是对方本身 |
| 【弹指神通】—— 消息通信 | 用 chan Event 解耦 |
异步场景,不怕延迟 |
// 示例:弹指神通 ✨
type UserService struct {
toOrder chan<- OrderEvent
}
type OrderService struct {
fromUser <-chan OrderEvent
}
func NewSystem() {
eventChan := make(chan OrderEvent, 10)
userSvc := NewUserService(eventChan)
orderSvc := NewOrderService(eventChan)
// 启动 goroutine …
}
📌 提醒:别急着用 interface{} 解循环依赖——
那不是解耦,是把问题藏进any的黑洞里 🕳️。
🧼 4. gofmt:Go 社区的“颜值底线”
“Unformatted code is treated by the community as written by a newbie.”
(未格式化的代码,会被社区认为是新手写的。)
你 push 的代码:
func hello( name string, )error{
return fmt.Errorf("hi %s",name)
}
同事的反应:
“这位同学,我们招的是 Go 开发,不是考古学家。”
✅ 解决方案:
- VS Code:装
Go插件 + 勾选 Format on Save - Goland / IDEA:
Settings → Tools → File Watchers → gofmt - 灰度发布前:
gofmt -l -w .一键救赎
🎯 终极建议:把
gofmt当成刷牙——
每天早晚两次,牙不疼,同事不烦。
🏁 5. 其他“防翻车”清单(速记版)
| 反模式 | 正确姿势 | 幽默备注 |
|---|---|---|
init() 里注册 flag |
改在 main() 里初始化 |
init() 是幽灵代码,测试时它总在你背后偷笑👻 |
包名叫 common / utils |
叫 auth, payment, cache |
“这个包的作用?”——“嗯… 通用?”——“所以它到底干啥?” |
import . "mypkg"(点导入) |
明确写 mypkg.Foo() |
看到 Foo() 时:这变量是哪儿来的?天上掉的? |
| 环境变量当唯一配置源 | 用 flag + 生成 --help |
--help 是程序员的说明书,别逼人去 docker inspect |
go build 直接跑 |
改用 go install |
让 GOPATH/bin/myapp 成为你最可靠的“老朋友” |
🌟 结语:Go 的哲学不是“炫技”,而是“靠谱”
“Make it work, make it right, make it fast.”
—— Kent Beck(Go 人默默点头)
Go 不追求“一行写完快排”,
它追求:
- 📦 依赖清晰
- 🧪 可测可调
- 🚀 启动飞快
- 🤝 交接不哭
正如 Peter 所说:
“Design for testing.”
—— 因为明天的你,会感谢今天没偷懒的自己。
