可复用的Go代码需遵循接口窄、实现松原则:接口仅含1–3个必要方法,命名体现职责,配置通过构造函数传入,错误分层处理并避免过早抽象。
Go 里复用的核心不是继承,而是组合 + 接口。如果 interface{} 太宽(比如塞了 10 个方法),调用方很难只用其中 2 个,又得实现全部——这直接劝退复用。真正可复用的接口往往只有 1–3 个方法,比如 io.Reader 就只有 Read([]byte) (int, error) 一个方法。
io.ReadWriter 就是 io.Reader 和 io.Writer 的嵌入,而非重新定义Go 包里导出的类型、函数、变量,名字本身就在传递复用意图。比如叫 NewHTTPClient() 暗示它返回的是通用 HTTP 客户端;而叫 NewPaymentClient() 就锁死了场景,别人不敢拿去复用。
ParseJSON()、RetryWithBackoff(),比 DoSomething() 明确得多SendOrderEmail()),换成更通用的 SendEmail(),让调用方自己组装参数retry 而非 paymentretry,否则其他模块想用还得复制一份一旦包初始化时读环境变量、硬编码 endpoint、或者在 init() 里注册 handler,这个包就很难被不同项目安全复用——你改配置就得改源码,或者引入副作用。
NewService(opts ...ServiceOption),用函数式选项模式SetGlobalTimeout(30 * time.Second) 这类全局状态控制,它会让并发测试和多实例共存变得脆弱os.Getenv("API_URL")
type ServiceOption func(*service)func WithTimeout(d time.Duration) ServiceOption { return func(s *service) { s.timeout = d } }
func NewService(opts ...ServiceOption) *service { s := &service{} for _, opt := range opts { opt(s) } return s }
如果包里所有错误都直接返回 fmt.Errorf("failed to dial: %w", err),上层无法区分是网络问题、认证失败还是参数错误,也就没法针对性重试或降级——复用时只能全盘兜底,成本飙升。
ErrNotFound、ErrTimeout),用 errors.Is() 判断redis.Nil 映射为 ErrNotFound,隐藏实现细节真正难的不是写可复用的代码,是忍住不在第一个项目里就把“看起来以后会用到”
