我最近在探索 Go 代码库时偶然发现了一个 有趣的注释[1] 。
"由于海勒姆定律,这段文字不能被更改。"
func (e *MaxBytesError) Error() string { // Due to Hyrum's law, this text cannot be changed. return "http: request body too large" }
- 在此之前,我从未听说过海勒姆定律。
- 快速搜索后发现,这是一个以 Google 的软件工程师 Hyrum Wright[2] 命名的原则。
这个 "定律"[3] 很简单:
对于一个 API 的足够多的用户来说,无论你在合同中承诺什么:系统的所有可观察行为都会被某人依赖。
换句话说,代码中任何可被观察到的行为 — 无论是有意还是无意的 — 最终都会成为某人依赖的对象。
因此在上面的代码中,作者承认错误消息不能更改,因为它可能已被某人在某处依赖。即使看起来微调错误消息很微不足道,但这样做可能会对依赖此特定消息的人造成意外问题。在这种情况下,看似微小的更改可能会破坏现有依赖于"http: request body too large"确切措辞的代码。
例如,以下是如果更改错误消息将受影响的一些开源代码库:
- http: request body too large[4]
这并非唯一实例。我还在 Go 的 crypto/rsa
[5] 和 internal/weak
[6] 包中发现了类似引用海勒姆定律的注释。
它们在这里:
crypto/rsa/rsa.go
[7]
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 // well-specified number of random bytes is included in the ciphertext, in a // well-specified way.
crypto/rsa/pss.go
[8]
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 // well-specified number of random bytes is included in the signature, in a // well-specified way. if opts != nil && opts.Hash != 0 { hash = opts.Hash }
internal/weak
[9]
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.
观察
这显然 并非 Golang 所特有[10] ,并且在 其他[11] 代码库中也有 提及[12] 。
说实话,这整件事让我想起了 JavaScript 多年来的演变,它很大程度上是由对各种奇怪的、非预期的行为的广泛依赖所塑造的。现在,我终于知道该如何称呼这种现象了 — 海勒姆定律(Hyrum's Law)。
最终想法
- 这是个好提醒,要小心改变他人可能依赖的代码,并尝试以不会意外锁定奇怪行为的方式设计事物。
- 更好的是,以尽量减少依赖非预期行为的可能性的方式设计系统。
- 毕竟,你知道那句话:"只需一个小小的改变就能……某些某些……"我不记得整句话了。
😶🌫️️
参考链接
- 有趣的注释: https://github.com/golang/go/blob/5123f38e050c5ee7130d459ea247d998a838b5a1/src/net/http/request.go#L1199
- Hyrum Wright: https://www.hyrumwright.org/
- "定律": https://www.hyrumslaw.com/
- http: request body too large: https://grep.app/search?q=http%3A request body too large&filter[lang][0]=Go
crypto/rsa
: https://github.com/golang/go/tree/5123f38e050c5ee7130d459ea247d998a838b5a1/src/crypto/rsainternal/weak
: https://github.com/golang/go/tree/5123f38e050c5ee7130d459ea247d998a838b5a1/src/internal/weakcrypto/rsa/rsa.go
: https://github.com/golang/go/blob/5123f38e050c5ee7130d459ea247d998a838b5a1/src/crypto/rsa/rsa.go#L517crypto/rsa/pss.go
: https://github.com/golang/go/blob/5123f38e050c5ee7130d459ea247d998a838b5a1/src/crypto/rsa/pss.go#L294internal/weak
: https://github.com/golang/go/blob/5123f38e050c5ee7130d459ea247d998a838b5a1/src/internal/weak/pointer.go#L24- 并非 Golang 所特有: https://grep.app/search?q=hyrum's law&filter[lang][0]=Markdown&filter[lang][1]=C%2B%2B&filter[lang][2]=Python&filter[lang][3]=reStructuredText&filter[lang][4]=Rust&filter[lang][5]=Swift&filter[lang][6]=YAML
- 其他: https://github.com/python/cpython/blob/450db61a78989c5a1f1106be01e071798c783cf9/Lib/urllib/parse.py#L30
- 提及: https://github.com/PixarAnimationStudios/OpenUSD/blob/9b0c13b2efa6233c8a4a4af411833628c5435bde/pxr/base/vt/array.h#L959