大家好,我是煎鱼。
在我们日常工作中,只要你维护过历史比较悠久的项目。总会遇到一些神奇的代码。其中最莫过于在老代码上,前人让你不要改这块逻辑的注释。
在 Go 中也有一些约定俗成的代码。周末看到了还有点意思,分享给大家。
“该文本不可变更”
在 net/http 标准库中,有以下这段代码:
func (e *MaxBytesError) Error() string {
// Due to Hyrum's law, this text cannot be changed.
return "http: request body too large"
}
注意看上面的注释:“Due to Hyrum's law, this text cannot be changed.”。翻译过来中文含义是:根据海勒姆定律,此文本不可更改。
大家看到可能会犯迷糊。海勒姆定律是什么?教科书上可没有教过这玩意。
海勒姆定律是什么
海勒姆定律(Hyrum's Law) 是一种软件开发领域的概念。由 Google 工程师 Hyrum Wright 提出,他在一次演讲中讨论接口设计和演化问题时提出了这一观察并得到了验证。
核心描述是:
- 无论接口的官方文档或开发者如何严格定义接口的行为,接口的实际行为都会影响其用户。
- 所有可能的用户依赖接口实际行为的方式,最终都会受到接口的实现约束,而不仅仅是其文档描述。
简单来讲,接口的所有行为,无论是显式定义的还是隐式存在的,都会被用户依赖。
即便某些行为不是接口规范的一部分,只要接口表现出了某种行为,使用它的系统或代码可能就会开始依赖这种行为。
Go 源码中的案例
结合前面的案例来看,原作者意识到错误消息无法随意更改,因为可能有某些地方的某些用户依赖于该错误消息。
虽然调整错误消息看起来微不足道,但这种改动可能会对依赖这一特定消息的用户造成意想不到的问题。
这种情况下,一个看似无关紧要的描述修改:"http: request body too large",可能会导致用户的业务代码的中断。从而影响 Go1 的兼容性保障和程序运行。
那 Go 源码里还有没有其他地方有类似的描述呢?挺多的。如下几个,请看注解中的 “Hyrum's Law” 部分。
1、crypto/rsa/rsa.go[1]:
func EncryptOAEP(hash hash.Hash, random io.Reader, pub *PublicKey, msg []byte, label []byte) ([]byte, error) {
// Note that while we don't commit to deterministic execution with respect
// to the random stream, we also don't apply MaybeReadByte, so per Hyrum's
// Law it's probably relied upon by some. It's a tolerable promise because a
// ...
2、crypto/rsa/pss.go[2]:
func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte, opts *PSSOptions) ([]byte, error) {
// Note that while we don't commit to deterministic execution with respect
// to the rand stream, we also don't apply MaybeReadByte, so per Hyrum's Law
// it's probably relied upon by some. It's a tolerable promise because a
// ...
3、internal/weak[3]:
Using go:linkname to access this package and the functions it references
is explicitly forbidden by the toolchain because the semantics of this
package have not gone through the proposal process. By exposing this
functionality, we risk locking in the existing semantics due to Hyrum's Law.
总结
“海勒姆定律” 和 Go 源码中的 “被动实践” 都很好的印证了。只要你开放出去的东西,无论是接口,又或是参数。都有可能神不知鬼不觉中被用户依赖了。当你一旦作出变更时,就有可能产生不兼容。导致第三方系统出错或者产生脏数据,要进行洗数据。
为什么我会知道?因为最近我有一个朋友他遇到了(doge。当然,最好在设计系统或接口时,就要尽可能的减少依赖非预期行为的可能性!
参考资料
[1]crypto/rsa/rsa.go: https://github.com/golang/go/blob/5123f38e050c5ee7130d459ea247d998a838b5a1/src/crypto/rsa/rsa.go#L517
[2]crypto/rsa/pss.go: https://github.com/golang/go/blob/5123f38e050c5ee7130d459ea247d998a838b5a1/src/crypto/rsa/pss.go#L294
[3]internal/weak: https://github.com/golang/go/blob/5123f38e050c5ee7130d459ea247d998a838b5a1/src/internal/weak/pointer.go#L24