HackerTube English?
软件边界与变化 →

一个模块究竟答应了调用者什么?

想换掉一个模块,却发现别处依赖它碰巧返回的顺序。从「接口写了什么」往前走一步:哪些决定本该藏在里面,又有哪些行为已经被人当成承诺?

本文的问题 · 7
  1. 少即是少处理例外与纠缠
  2. 用小接口和深实现减少使用者负担
  3. 小对象与小接口,不是同一个尺寸
  4. 模块边界既保护演进,也冻结选择
  5. 可观察的实现细节会变成兼容负担
  6. 使用者依赖了可观察行为之后,实现细节就可能变成兼容性承诺
  7. 模块名称限制多版本,内容哈希允许共存

少即是少处理例外与纠缠

API 设计演讲建议不确定时删去不必要内容;Simple Made Easy 将简单解释为关注点不纠缠;两者共同强调减少使用者必须理解的额外选择与依赖。

对照原文与时间戳 · 2
When it doubt, leave it out.
How To Design A Good API and Why it Matters · ▶ 24:42–24:45
start seeing interconnections between things that could be independent
"Simple Made Easy" - Rich Hickey (2011) · ▶ 59:28–59:32

删减 API 选项只是一个设计手段;它不自动保证底层关注点已解耦。

用小接口和深实现减少使用者负担

Bloch 建议 API 保持小而清晰,拿不准的功能先不加入;Ousterhout 的深模块则以简单接口承载大量底层功能。两者共同指向一种具体设计收益:把复杂实现留在边界之后,让调用者只需理解少量稳定概念。

对照原文与时间戳 · 2
When it doubt, leave it out.
How To Design A Good API and Why it Matters · ▶ 24:42–24:45
very simple interface with a very large amount of functionality underneath it
A Philosophy of Software Design | John Ousterhout | Talks at Google · ▶ 14:24–14:29

公共 API 的功能取舍与内部模块深度是不同层级的设计问题;小接口本身不保证内部模块设计良好。

小对象与小接口,不是同一个尺寸

Metz 谈类和方法的粒度,以及彼此知道得更少;Bloch 谈 API 暴露的功能范围,并建议拿不准时不加入。可以比较内部拆分与外部接口收缩这两个不同设计维度。

对照原文与时间戳 · 2
make smaller things
RailsConf 2014 - All the Little Things by Sandi Metz · ▶ 0:54–0:58
When it doubt, leave it out.
How To Design A Good API and Why it Matters · ▶ 24:42–24:45

更多小类不必意味着更好的公共 API;不能把两段合并成对任意粒度的越小越好。

模块边界既保护演进,也冻结选择

Conway 演讲指出沟通成本越高,跨边界迭代越少;Bloch 指出 API 一旦有人使用就难以删除。合读可以把变更成本拆成跨团队协调和对既有调用方的兼容承诺,分别检查设计边界的代价。

对照原文与时间戳 · 2
the higher the cost of communication the less iteration will happen there
The Only Unbreakable Law · ▶ 27:06–27:16
not be able to take it out once you put it into an API.
How To Design A Good API and Why it Matters · ▶ 24:57–25:03

组织沟通成本与公共 API 兼容性是不同约束,不能把所有接口稳定性问题归因于组织图。

可观察的实现细节会变成兼容负担

Python的slots、类型与模块缓存被生态依赖,阻碍实现调整;Swiss Table演讲将同一现象明确表述为用户会依赖所有可观察行为,并用随机化抑制偶然迭代顺序成为契约。

对照原文与时间戳 · 2
people actually depend in very bizarre ways on some of the internals
How Python was Shaped by leaky Internals, Armin Ronacher, Flask framework · ▶ 4:31–4:38
anything a user can observe about the implementation of your API
CppCon 2017: Matt Kulukundis “Designing a Fast, Efficient, Cache-friendly Hash Table, Step by Step” · ▶ 5:19–5:27

不是所有内部行为都必须隐藏;Python演讲同时肯定栈状态暴露带来的调试收益。

使用者依赖了可观察行为之后,实现细节就可能变成兼容性承诺

解释器、哈希表和公共 API 是不同场景;能观察到某种行为,不等于它已被正式保证。

对照原文与时间戳 · 3
people actually depend in very bizarre ways on some of the internals
How Python was Shaped by leaky Internals, Armin Ronacher, Flask framework · ▶ 4:31–4:38
they will rely upon it, given enough users.
CppCon 2017: Matt Kulukundis “Designing a Fast, Efficient, Cache-friendly Hash Table, Step by Step” · ▶ 5:27–5:32
not be able to take it out once you put it into an API.
How To Design A Good API and Why it Matters · ▶ 24:57–25:03

模块名称限制多版本,内容哈希允许共存

Python演讲指出sys.modules以模块名定位对象,现有代码与pickle依赖这一语义,因此同库多版本难共存;Unison则把引用绑定到内容哈希,区分同名但不同定义,使多版本依赖无需争夺全局名字。

对照原文与时间戳 · 2
we would have to make S modules also have a version identifier
How Python was Shaped by leaky Internals, Armin Ronacher, Flask framework · ▶ 39:11–39:16
then it would be perfectly fine to allow as many different versions of the same library in our code base
"Unison: a new distributed programming language" by Paul Chiusano · ▶ 26:04–26:16

Unison不是对Python问题的补丁;类型在边界相遇时仍需要转换,运行时与生态不同。

读完再想一步

挑一个准备修改的接口:哪些行为有明确约定,哪些只是碰巧一直没变?