API 会改变。即使是那些您多年来一直依赖的 API——那些您认为坚如磐石、不可磨灭、永恒不变、纯粹的 API。但请不要害怕,因为本月 Kode Vicious 将就如何应对这种最令人厌恶的改变形式提供他的见解。遇到了同样令人恼火的编程难题?请写信给 Kode Vicious,地址为 [email protected],尽情倾诉您的不满。
尊敬的 KV,
我一直在一个软件团队工作,该团队在几个不同的操作系统平台上生产最终用户应用程序。我最初是构建工程师,负责搭建构建系统,然后是夜间测试脚本,现在我负责几个组件本身,并维护构建系统。我在构建软件时看到的最大问题是 API 缺乏稳定性。当添加新的 API 时,这还可以接受——如果您愿意,您可以忽略这些 API——当删除 API 时,我知道,因为构建会失败。最大的问题是当有人更改 API 时,因为这要等到某些测试脚本——或者更糟的是,用户——执行代码并崩溃时才会被发现。您如何应对不断变化的 API?
变更
尊敬的变更先生/女士,
应对变更的最佳方法是把头埋在沙子里,忽略它。毕竟,我们都可以从过去伟大的管理传统中学习,工程师也不例外。嗯,也许不是。
您指出的问题是构建大型复杂系统时面临的最大挑战之一。软件具有惊人的可塑性,这使得有人会进行更改成为可能(而且不幸的是,很可能发生),这些更改通常会破坏您的系统。许多工程师和程序员没有意识到的是,当他们构建库,或者实际上是任何其他人应该依赖的组件时,API 就成为了他们的代码与所有使用它的人之间的合同。
正如您所指出的,这些合同的变更实际上有三种方式。第一种,添加 API,不会影响您的系统,因为没有人调用它,新的 API 实际上不会造成太大的损害。第二种情况,删除 API,会在您的程序链接时立即导致错误,无论是在编译时还是运行时,所以至少您在尝试使用代码之前就注意到了这一点。最后一种情况是最让您感到痛苦和噩梦的,因为几乎没有自动化的方法来找到看起来相同但实际上不同的 API。在我工作过的一个地方,我们因为找不到更好的词来形容它,或者说,找不到像样的技术撰稿人,所以将其称为“变化性的变化”。
在一个特定的系统中,我们大约 80% 的问题与尝试重新集成不同的子系统有关。正如您所能想象的那样,随着涉及的组件数量的增加,问题会迅速增长。两个相互依赖的子系统至少有一个依赖关系,而四个子系统有六个依赖关系,八个子系统有 28 个,依此类推。测试所有这些可能的组合被称为“痛苦矩阵”。从一组都在变化的模块中构建任何类型的连贯系统,结果证明非常困难,但也有一些解决方案。
操作系统人员早就知道这个问题,因此程序所依赖的 API 往往变化缓慢或根本不变。Unix 和类 Unix 操作系统中的基本 open()、close()、read()、write() 系统调用在 20 多年里一直采用相同的参数并返回相同类型的值。当添加子系统(例如网络)时,会根据需要添加新的函数调用;因此,要打开网络连接,您不会调用 open(),因为这需要更改其参数,从而更改所有已经使用它的代码。相反,您有 socket() 系统调用,它接受不同的参数,但返回的值可供 read() 和 write() 使用。系统程序员也倾向于狭义地定义他们将提供的一组函数,因为他们知道维护任意广泛的 API 集的噩梦。例如,FreeBSD 大约有 450 个可用的系统调用——也就是说,用户程序调用这些 API 来让操作系统做一些事情,例如读取文件、打开套接字或查找时间。虽然这个数字不小,但它是可跟踪和可维护的,而 Posix 库或 Microsoft Foundation Classes 的完整集合中的 API 数量要大得多。
另一种可以从系统编程世界借鉴的技巧是 ioctl(),或 I/O 控制。设备驱动程序编写人员可以使用简单的 open()、close()、read() 和 write() 语义完成大部分工作,因为大多数人对设备的需求是打开或使用它;从中读取数据和向其中写入数据;然后把它收起来,或者关闭它。不幸的是,通常需要具有设备特定的控制,这些控制可以轻松地向上导出到操作系统——例如,将网络设备设置为混杂监听模式或设置其各种地址参数。这些特殊情况是使用 ioctl() 的地方。ioctl() 调用多年来一直被使用和滥用,但基本的设计原则是合理的。始终为自己留一条退路。使用 ioctl() 接口,您可以向子系统添加几乎任何额外的命令,而不会破坏向后兼容性,甚至无需添加新的函数调用。
最后,还有纪律,有些人非常喜欢纪律,但这不是那种杂志。我实际的意思是,必须就如何将更改引入系统做出决定。快速更改目前似乎很流行;所谓的极限编程方法就是这方面的一个例子。当只有您或您的团队拥有您的代码时,快速更改是有效的,但最终其他人会使用它,那时麻烦就开始了。许多工程师只是决定,在某个时候,API 是不可更改的,并且有太多的调用者无法更改,因此任何更改都需要新的 API。
不幸的是,我怀疑我已经解决了您的实际问题,因为除非您和您的团队从头开始编写所有内容,否则您将受制于那些能够并且将会给您带来麻烦的人。我唯一的另一个建议是,您的团队应尽可能少地使用外部 API,并且不要使用太多新的或高级的功能,因为这些功能最有可能发生变化。
KV
KODE VICIOUS,凡人称之为 George V. Neville-Neil,为乐趣和利润从事网络和操作系统代码的工作。他还教授与编程相关的各种科目课程。他的兴趣领域是代码探险、操作系统和重写您的糟糕代码(好吧,也许不是最后一个)。他获得了马萨诸塞州波士顿东北大学的计算机科学学士学位,并且是 、Usenix 协会和 IEEE 的成员。他是一位狂热的自行车爱好者和旅行者,自 1990 年以来一直以旧金山为家。
最初发表于 Queue 第 4 卷,第 7 期—
在 数字图书馆 中评论这篇文章