Milky 的设计哲学

作为 Milky  的设计者,你对这个协议最满意的地方是什么?

是它声称的“取代了 XXX 协议”吗?不是。

是它尽善尽美地覆盖了 QQ 的特性吗?也并没有。

是它有着完善的基础设施和生态吗?还远远不够。

实际上,作为设计者,笔者对 Milky 最满意的地方,在于它从设计之初就秉持的一些理念,总结有以下几点:

对称性和一致性

在 API 和数据结构设计上,Milky 采用对称的命名结构,例如:

在字段的命名上,Milky 也力求对称。在不引起歧义的情况下,表示同一概念的字段总是采用相同的命名方式。例如:

这种对称性的设计使得 API 更加直观和易于记忆。

可空性设计

在 Milky 的数据结构中,你可能会发现有些本来应该可空的字段被设计为不可空。这并非疏忽,而是经过深思熟虑的设计选择。首先,我们定义一个变量的三种状态:

在确定一个字段是否为可空时,主要的原则是:

这样的设计减少了不必要的非空检查,同时给开发者喂了一颗“定心丸”:如果一个字段是可空的,那么就一定用 null 来表示“无值”;而如果一个字段是不可空的,那么就绝不会出现 null

其实一开始只是为了避免 x.isEmpty() ? null : x 这种幽默代码而已,没想到后来越想越有趣,就变成了现在这样。

可持久化性

不知读者在进行 Bot 相关开发时,是否遇到或设计过这样的“组合式”标识符:

private|12345678|11111 group|34567890|22222 request-private-u_nk******************qg-1765647902 request-group-join-1765647976234567

很显然,这样的标识符只有一个目的:将一个包含复杂信息的对象压缩成一个字符串,用于在协议中作为标识符传递。这样的设计虽然保留了信息,但往往无法持久化存储,因为它们的格式可能会随着协议的更新而变化。更典型的情况是,用户从一种 Bot 框架迁移到另一种 Bot 框架时,无法直接使用这些标识符——因为框架 A 用 | 分隔,而框架 B 用 - 分隔!

这是协议设计上一个难以调和的痛点:是抽象出复杂对象的标识符,还是提供持久化存储的能力?所幸,Milky 作为专为 QQ 设计的协议,可以最大限度的采用后者。Milky 的所有标识符在 QQ 协议层面都有其对应的 “backing field”,这些字段直接为 QQ 后端所用,并不会随着协议端的不同而发生变化。典型的例子包括:

协议端与应用端交互时直接使用这些持久化标识符,避免了因标识符格式变化而导致的兼容性问题,同时也方便了协议端的开发。


设计哲学就像底裤,把它套在外面炫耀会显得很奇怪,但绝对不能没有。笔者唯恐被人误以为是在“卖弄设计”,但又觉得有必要把这些理念记录下来,以便日后回顾和改进,于是选择在 Blog 上写下这篇文章。Milky 的这些设计哲学,正是它能够在纷繁复杂的协议设计中保持简洁和一致性的关键所在。

CC BY-NC 4.0 2025 © Wesley Young.