【】API设计那些事
Henning, Michi. "API design matters." Communications of the ACM 52.5 (2009): 46-56.
https://dl.acm.org/doi/fullHtml/10.1145/1506409.1506424
当需要多人或多团队协作写软件时,API设计是面临的重要问题。
最近看API设计的书([美]Martin Reddy著 刘晓娜 臧秀涛 林健 译),其中反复引用这篇文献,感觉不错,有必要分享。
1、糟糕的应用程序编程接口困扰着软件工程。我们如何把事情做?
在担任软件工程师超过25年之后,我仍然发现自己低估了完成特定编程任务所需的时间。有时,由此产生的时间表偏差是由我自己的缺点引起的:当我深入研究一个问题时,我只是发现它比我最初想象的要困难得多,所以问题需要更长的时间来解决,这就是作为程序员的生活。很多时候,我确切地知道我想要实现什么以及如何实现它,但这仍然需要比预期更长的时间。当这种情况发生时,通常是因为我正在努力解决应用程序编程接口(API),该接口似乎尽其所能在我的道路上投掷石块并使我的生活变得困难。我发现,即使在软件工程领域取得了25年的进步,这种情况仍然会发生。更糟糕的是,最近用现代编程语言实现的API犯了与20年前用C编写的API相同的错误。API设计似乎有一些难以捉摸的东西,尽管取得了多年的进展,但我们还没有掌握。
好的 API 很难。当我们使用一个好的API时,我们都认识到一个好的API。好的 API 使用起来是一种乐趣。它们工作没有摩擦,几乎从视线中消失:在正确的时间可以正确调用特定工作,可以轻松找到和记忆,有充分的文档记录,具有直观易用的界面,并正确处理边界条件。
那么,为什么会有这么多糟糕的API呢?主要原因是,对于正确设计API的每种方法,通常有几十种方法可以错误地设计它。简单地说,创建一个糟糕的API非常容易,而创建一个好的API则相当困难。即使是很小且相当无辜的设计缺陷也很容易被放大,因为API只提供一次,但被调用了很多次。如果设计缺陷导致代码笨拙或低效,则产生的问题会在调用 API 的每个点出现。此外,孤立的单独设计缺陷很小,可能会以令人惊讶的破坏性方式相互作用,并迅速导致大量的附带损害。
糟糕的 API 很容易。
让我通过例子向您展示看似无害的设计选择如何产生深远的影响。这个例子,我在日常工作中遇到,很好地说明了糟糕设计的后果。(从字面上看,几乎在每个平台上都可以找到数百个类似的例子;我的目的不是特别挑出.NET。(例子省略)
创建一个糟糕的API非常容易,而创建一个好的API则相当困难。即使是很小且相当无辜的设计缺陷也很容易被放大,因为API只提供一次,但被调用了很多次。
2、不良 API 的代价
不良的API设计的后果是众多而严重的。较差的 API 很难编程,并且通常需要编写其他代码,如前面的示例所示。如果不出意外的话,这些额外的代码会使程序变得更大,效率更低,因为每行不必要的代码都会增加工作集大小并减少CPU缓存命中率。此外,与前面的示例一样,糟糕的设计可能会强制执行不必要的数据副本,从而导致固有的低效代码。(另一个流行的设计缺陷是,为预期结果抛出异常也会导致效率低下,因为捕获和处理异常几乎总是比测试返回值慢。
然而,糟糕的API的影响远远超出了低效的代码:糟糕的API比好的API更难理解,更难使用。换句话说,程序员针对较差的 API 编写代码所需的时间比针对好的 API 编写代码所需的时间更长,因此较差的 API 直接导致开发成本增加。糟糕的 API 通常不仅需要额外的代码,还需要更复杂的代码,这些代码提供了更多可以隐藏 Bug 的地方。成本是增加了测试工作量,并增加了在现场部署软件之前未检测到错误的可能性,此时修复错误的成本最高。
软件开发的大部分内容都是关于创建抽象的,而API是这些抽象的可见接口。抽象降低了复杂性,因为它们丢弃了不相关的细节,只保留了特定工作所需的信息。抽象不是孤立存在的;相反,我们将抽象层叠在一起。应用程序代码调用较高级别的库,而这些库通常通过调用较低级别的库提供的服务来实现,而较低级别的库又调用操作系统的系统调用接口提供的服务。抽象层的这种层次结构是一个非常强大和有用的概念。没有它,我们所知道的软件就不可能存在,因为程序员将完全被复杂性所淹没。
API 缺陷在抽象层次结构中发生得越低,后果就越严重。如果我在自己的代码中错误地设计了一个函数,唯一受影响的人就是我自己,因为我是该函数的唯一调用者。如果我在我们的一个项目库中错误地设计了一个函数,我可能会让我所有的同事都受到影响。如果我在一个广泛发布的库中错误地设计了一个函数,可能会有成千上万的程序员遭受损失。
当然,最终用户也会受到影响。痛苦可以采取多种形式,但累积成本总是很高。例如,如果 Microsoft Word 包含一个错误,由于 API 设计不当而导致其偶尔崩溃,则成千上万或数十万的最终用户会浪费宝贵的时间。同样,考虑无数应用程序和系统软件中的众多安全漏洞,这些漏洞最终是由标准C库中不安全的I / O和字符串操作函数引起的(例如和)。这些设计拙劣的API的影响在创建30多年后仍然存在,设计缺陷的累积成本很容易达到数十亿美元。scanf()strcpy()
相反,抽象的分层通常用于轻视不良API的影响:“没关系,我们可以只写一个包装器来隐藏问题。这个论点再错误不过了,因为它忽略了这样做的代价。首先,即使是最有效的包装器在内存和执行速度方面也增加了一些成本(包装器通常远非高效)。其次,对于一个广泛使用的API,包装器将被写入数千次,而首先获得正确的API只需要完成一次。第三,通常,包装器会产生自己的一组问题:.NET函数是围绕底层C函数的包装器;.NET 版本首先无法修复原始接口的不良问题,然后通过省略返回值、获取超时错误以及传递列表而不是集合来增加自己的问题。因此,虽然创建包装器可以帮助使糟糕的API更可用,但这并不意味着糟糕的API无关紧要:两个错误不能成为正确的,不必要的包装器只会导致膨胀软件。Select()
3、如何做得更好
在设计 API 时,可以使用一些准则。这些都不是保证成功的可靠方法,但是了解这些准则并在设计过程中明确考虑它们,使得结果更有可能被证明是可用的。这份清单必然是不完整的,因为主题正义需要一本大书。不过,以下是我在创建 API 时最喜欢考虑的一些事情。
API 必须为调用方提供足够的功能才能完成其任务。这似乎是显而易见的:提供功能不足的API是不完整的。然而,正如无法等待超过35分钟所表明的那样,这种不足之处可以不被发现。在设计过程中仔细检查功能清单并询问“我错过了什么吗?Select()
API应该是最小的,而不会给调用方带来不必要的不便。这个指导方针只是说“越小越好”。API 使用的类型、函数和参数越少,就越容易正确学习、记住和使用。这种极简主义很重要。许多API最终成为便利功能的厨房水槽,这些功能可以由其他更基本的功能组成。(C++具有 100 多个成员函数的标准字符串类就是一个例子。经过多年的C++编程,我仍然发现自己无法在不查阅手册的情况下将标准字符串用于任何非平凡的事情。
在不给调用方带来不适当的不便的情况下,对本指南进行限定非常重要,因为它引起了对实际用例的关注。要很好地设计 API,设计人员必须了解 API 的使用环境,并针对该环境进行设计。是否提供非基本便利功能取决于设计人员预计需要该功能的频率。如果该函数将频繁使用,则值得添加;如果只是偶尔使用,那么增加的复杂性不太可能值得在便利性方面获得罕见的收益。
API文档的一个大问题是,它通常是在API实现之后编写的,并且通常由实现者编写。
如果设计人员预计查找不存在的变量不是常见情况,并且可能指示调用方将视为错误的内容,则引发异常是合适的。如果是这样,则引发异常是完全正确的做法,因为异常会强制调用方处理错误。另一方面,调用方可以查找变量,如果未设置,则替换默认值。如果是这样,则引发异常是完全错误的事情,因为处理异常会破坏正常的控制流,并且比测试 null 或空返回值更困难。
假设我们决定在变量未设置时不引发异常,则有两个明显的选择表明查找失败:返回 null 或空字符串。哪一个是正确的?同样,答案取决于预期的用例。返回 null 允许调用方将根本没有设置的变量与设置为空字符串的变量区分开来,而返回未设置的变量的空字符串使得无法区分从未设置的变量和显式设置为空字符串的变量。如果认为能够进行这种区分很重要,则返回 null 是必要的;但是,如果区别不重要,最好返回空字符串并且永远不要返回 null。
通用 API 应该是“无策略的”;专用 API 应该是“策略丰富的”。在前面的指南中,我提到API的正确设计取决于其上下文。这导致了一个更基本的设计问题,即API不可避免地决定了策略:只有当调用方对API的使用与设计人员的预期用例一致时,API才能以最佳状态执行。相反,API的设计者不能不向调用方规定一组特定的语义和特定的编程风格。对于设计人员来说,重要的是要意识到这一点:API设置策略的程度对其可用性产生了深远的影响。
如果对将要使用API的上下文知之甚少,那么设计人员别无选择,只能保持所有选项的开放性,并允许API尽可能广泛地适用。在前面的查找示例中,这要求为未设置的变量返回 null,因为该选择允许调用方在 API 之上分层自己的策略;通过几行额外的代码,调用方可以将不存在的变量的查找视为硬错误,替换默认值,或将未设置的和空的变量视为等效变量。但是,对于那些不需要灵活性的调用方来说,这种普遍性是有代价的,因为它使调用方更难将不存在的变量的查找视为错误。
这种设计张力几乎存在于每条API之间,应该和不应该是错误之间的线非常精细,并且错误地放置线很快就会引起重大疼痛。对API上下文的了解越多,API就越“法西斯”,它可以设置的策略就越多。这样做是对调用方的帮助,因为它会捕获否则不会检测到的错误。通过仔细设计类型和参数,通常可以在编译时捕获错误,而不是延迟到运行时。努力做到这一点是值得的,因为在编译时捕获的每个错误都少了一个在测试期间或在现场可能产生额外成本的错误。
API 不符合此准则,因为通过覆盖其参数,它设置的策略与最常见的用例直接冲突。同样,.NET API 会针对非阻塞套接字执行此犯罪:如果调用有效但没有准备好任何数据,则会引发异常;如果连接丢失,它将返回零,而不会出现异常。这与调用方的需求完全相反,并且看到这给调用方带来的控制流的混乱是发人深省的。
有时,尽管设计师尽了最大的努力,设计张力仍无法解决。当对上下文知之甚少时,通常会出现这种情况,因为API是低级的,或者根据其性质,必须在许多不同的上下文中工作(例如,通用集合类的情况就是如此)。在这种情况下,策略模式通常可以用于良好的效果。它允许调用方提供策略(例如,以调用方提供的用于维护有序集合的比较函数的形式),从而使设计保持打开状态。根据编程语言的不同,调用方提供的策略可以使用回调、虚函数、委托或模板参数(以及其他参数)来实现。如果 API 提供合理的默认值,则此类外部化策略可以在不影响可用性和清晰度的情况下带来更大的灵活性。(但要小心,不要“推卸责任”,如本文后面所述。
API 应从调用方的角度进行设计。当程序员被赋予创建API的工作时,他或她通常会立即处于解决问题的模式:我需要哪些数据结构和算法来完成这项工作,以及完成这项工作所需的输入和输出参数?从那里开始,这一切都是下坡路:实施者专注于解决问题,而调用者的担忧很快就会被遗忘。下面是一个典型的示例:(省略)
获取可用 API 的一个好方法是让客户(即调用方)编写函数签名,并将该签名提供给程序员实现。仅这一步就消除了至少一半的不良API:API的实现者往往从不使用自己的创作,这给可用性带来了灾难性的后果。此外,API不是关于编程,数据结构或算法SAN API是一个用户界面,就像GUI一样。使用API的用户是程序员,即人类。尽管我们倾向于将API视为机器接口,但它们不是:它们是人机界面。
还有一种观点认为,年长的程序员“失去了优势”。在我看来,这种信念是错误的。年长的程序员可能不会像年轻人那样燃烧那么多的午夜油,但这并不是因为他们年纪大了,而是因为他们完成了工作,而不必熬夜到午夜。
应该推动API设计的不是实现者的需求。毕竟,实现者只需要实现 API 一次,但 API 的调用方需要调用它数百或数千次。这意味着好的API在设计时考虑到了调用方的需求,即使这会使实现者的工作更加复杂。
好的 API 不会推卸责任。在设计 API 时,有很多方法可以“推卸责任”。最喜欢的一种方法是害怕设置策略:“好吧,呼叫者可能想要做这个或那个,我不能确定哪个,所以我会让它可配置。此方法的典型结果是采用 5 个或 10 个参数的函数。由于设计人员没有设置策略的骨干,并且不清楚API应该做什么和不应该做什么,所以API最终的复杂性远远超过必要的程度。这种方法也违反了极简主义和“我不应该为我不使用的东西付费”的原则:如果一个函数有10个参数,其中五个与大多数用例无关,那么调用方每次进行调用时都要付出提供10个参数的代价,即使他们不能不关心额外的五个参数提供的功能。一个好的API很清楚它想要实现什么和它不想实现什么,并且不怕坦率地谈论它。由此产生的简单性通常足以弥补功能的轻微损失,特别是如果API具有精心选择的基本操作,这些操作可以很容易地组合成更复杂的操作。
另一种推卸责任的方式是在效率的祭坛上牺牲可用性。例如,CORBA C++映射要求调用方严格跟踪内存分配和解除分配职责;结果是一个API,使得损坏内存变得非常容易。在对映射进行基准测试时,它的速度非常快,因为它避免了许多内存分配和解除分配。然而,性能提升是一种错觉,因为它不是由API执行脏工作,而是使调用方负责执行脏工作,无论如何都会发生相同数量的内存分配。换句话说,可以提供更安全的API,运行时开销为零。通过仅对API内部完成的工作(而不是调用方和API完成的整体工作)进行基准测试,设计人员可以声称已经创建了性能更好的API,即使性能优势仅归因于选择性记帐。
与 .NET 版本一样,C 版本也会覆盖其参数。这再次反映了实现者而不是调用者的需求:与分配单独的文件描述符输出数组相比,删除参数更容易,更有效,并且它避免了如何再次释放输出数组的问题。然而,所有这一切实际上所做的是将负担从实施者转移到零的净效率增益上。
Unix内核也不是没有瑕疵,偶尔会推卸责任:许多程序员已经诅咒允许某些系统调用中断的决定,迫使程序员明确地处理并手动重新启动中断的系统调用,而不是让内核透明地这样做。
传递责任可以采取许多不同的形式,其细节因API而异。对于设计师来说,关键问题是:我能为来电者做些什么吗?如果是这样,我有正当理由不这样做吗?明确地提出这些问题会使设计成为有意识过程的结果,并且不鼓励“偶然设计”。
在实现 API 之前,应对其进行记录。API文档的一个大问题是,它通常是在API实现之后编写的,并且通常由实现者编写。然而,实施者在精神上受到实施的污染,并且倾向于简单地写下他或她所做的事情。这通常会导致文档不完整,因为实现者过于熟悉API,并假设有些事情是显而易见的,而实际上并非如此。更糟糕的是,它通常会导致API完全错过重要的用例。另一方面,如果调用方(不是实现者)编写文档,则调用方可以从“这就是我需要的”的角度来处理问题,而不会受到实现问题的负担。这使得API更有可能满足调用方的需求,并首先防止许多设计缺陷的出现。
当然,调用方可能会要求一些从实现角度来看不合理的内容。然后,调用方和实现方可以迭代设计,直到他们达成协议。这样,调用方和实现问题都不会被忽视。
一旦记录并实现,API应该由不熟悉它的人试用。最初,该人应该在不查看文档的情况下检查可以理解多少API。如果一个API可以在没有文档的情况下使用,那么它很可能是好的:一个自我记录的API是最好的API。在测试驱动 API 及其文档时,用户可能会提出重要的“假设”问题:如果第三个参数为 null 怎么办?这合法吗?如果我想无限期地等待套接字准备就绪,该怎么办?我能做到吗?这些问题通常可以查明设计缺陷,与文档的交叉检查将确认问题是否有答案以及答案是否合理。
随着计算的重要性不断增加,有些API的正确功能几乎超出了描述。
确保文档完整,尤其是有关错误行为的文档。当事情出错时,API的行为就像事情进展顺利一样,是正式合同的一部分。文档是否说明 API 是否维护了强异常保证?它是否详细说明了在发生错误时输出和输入参数的状态?它是否详细说明了在发生错误后可能仍然存在的任何副作用?它是否为调用方提供了足够的信息来理解错误?(从所有套接字操作中抛出异常并不能解决问题!程序员确实需要知道当出现问题时API的行为,并且他们确实需要获得可以通过编程方式处理的详细错误信息。(人类可读的错误消息对于诊断和调试是很好的,但如果它们是唯一可用的东西,那就不好了,没有什么比必须为错误字符串编写解析器来控制程序流更糟糕的了。
单元和系统测试也会对 API 产生影响,因为它们可以公开以前没有人想到的东西。测试结果可以帮助改进文档,从而改进 API。(是的,文档是 API 的一部分。
编写文档最差的人是实现者,而编写文档的最坏时间是在实现之后。这样做会大大增加接口、实现和文档都出现问题的可能性。
好的 API 是符合人体工程学的。人体工程学本身就是一个主要的研究领域,可能是API设计中最难确定的部分之一。关于这个主题,已经以样式指南的形式写了很多东西,这些指南定义了命名约定、代码布局、文档样式等。然而,除了单纯的风格问题之外,实现良好的人体工程学设计是很困难的,因为它会引发复杂的认知和心理问题。程序员是人类,不是用千篇一律的切割器创建的,所以一个对一个程序员来说看起来很好的API可以被另一个程序员认为只是马马虎虎。
特别是对于大型和复杂的API,人体工程学的主要部分与一致性有关。例如,如果 API 的函数始终以相同的顺序放置特定类型的参数,则该 API 更易于使用。同样,如果 API 建立命名主题,将相关函数与特定命名样式组合在一起,则 API 更易于使用。对于为相关任务建立简单且统一的约定并使用统一错误处理的 API 也是如此。
一致性很重要,因为它不仅使事情更易于使用和记忆,而且还使学习的转移成为可能:在学习了API的一部分之后,调用方还学习了API其余部分的大部分内容,因此摩擦最小。转移不仅在API中很重要,而且在API中也很重要API可以相互采用的概念越多,掌握所有这些概念就越容易。(Unix 标准 I/O 库在很多地方都违反了这个想法。例如,和 系统调用将文件描述符放在第一位,但标准库 I/O 调用(如 and )将流指针放在最后,除了 and ,后者将其放在第一位。这种缺乏并行性的情况让许多人感到不安。read()write()fgets()fputs()fscan()fprint()
良好的人体工程学和让API“感觉”正确需要大量的专业知识,因为设计师必须处理众多且经常相互冲突的需求。在这些需求之间找到正确的权衡是良好设计的标志。
4、API 变革需要文化变革
我相信,在API设计方面可以做得更好。除了具体的技术问题之外,我认为我们需要解决一些文化问题,以克服API问题。我们需要的不仅是技术智慧,还有我们教授和实践软件工程的方式的改变。
教育。早在1970年代末和1980年代初,当我作为一名程序员和获得学位时,一个崭露头角的程序员教育的大部分重点是数据结构和算法。它们是编程的面包和黄油,对列表、平衡树和哈希表等数据结构的良好理解至关重要,对常见算法及其性能权衡的良好理解也是如此。这些也是系统库仅提供最基本功能的日子,例如简单的I / O和字符串操作;诸如 和 之类的更高级别的功能是例外,而不是规则。这意味着对于一个称职的程序员来说,知道如何编写各种数据结构并有效地操作它们是理所当然的。
自那时以来,我们取得了相当大的进展。如今,几乎每个主要开发平台都配备了充满预制数据结构和算法的库。事实上,现在,如果我发现一个程序员在写一个链接列表,那个人最好有一个很好的理由这样做,而不是使用系统库提供的实现。
同样,在这段时间里,如果我想创建软件,我必须从头开始编写几乎所有内容:如果我需要加密,我从头开始编写;如果我需要压缩,我从头开始写;如果我需要进程间通信,我会从头开始编写。随着开源运动的发生,所有这些都发生了巨大变化。今天,开源几乎可用于所有可以想象的可重用功能。因此,创建软件的过程发生了很大变化:今天的软件工程不是创建功能,而是集成现有功能或以某种方式重新打包它。换句话说:今天的API设计比20年前重要得多,不仅因为我们正在设计更多的API,还因为这些API倾向于提供对更丰富,更复杂的功能的访问。
纵观许多大学的课程,似乎这种重点的转变在很大程度上被忽视了。在我读本科的日子里,从来没有人费心去解释如何决定某些东西应该是返回值还是out参数,如何在引发异常和返回错误代码之间做出选择,或者如何决定函数是否适合修改其参数。从那以后,似乎没有什么变化:我的儿子,目前正在我获得学位的同一所大学攻读软件工程学位,他告诉我,仍然没有人费心去解释这些事情。难怪我们看到这么多设计拙劣的API:期望程序员擅长他们从未被教导过的东西是不合理的。
然而,好的API设计,即使很复杂,也是可以教授的。如果本科生可以学习如何编写哈希表,他们也可以学习何时适合抛出异常而不是返回错误代码,并且他们可以学习区分一个糟糕的API和一个好的API。现在需要的是承认这一专题的重要性;我们应当承认这一专题的重要性。许多研究和智慧已经可用,我们需要做的就是把它们传下去。
职业道路。我今年49岁,我写代码。环顾四周,我意识到这是多么不寻常:在我的公司里,我所有的编程同事都比我年轻,当我看到以前的编程同事时,他们中的大多数人不再写代码;相反,他们已经转到了不同的职位(如项目经理)或完全离开了这个行业。我在软件行业到处都看到这种趋势:年长的程序员很少见,很多时候是因为除了某个点之外,他们就没有职业道路。我记得我花了多少精力来抵制被迫“晋升”到一家前公司的管理职位我最终成为一名程序员,但被告知,如果我不愿意进入管理层,未来的加薪几乎是不可能的。
还有一种观点认为,年长的程序员“失去了优势”,不再削减它。在我看来,这种信念是错误的。年长的程序员可能不会像年轻人那样燃烧那么多的午夜油,但这并不是因为他们年纪大了,而是因为他们完成了工作,而不必熬夜到午夜。
这种老程序员的流失是不幸的,特别是在API设计方面。虽然可以学习良好的API设计,但经验是无可替代的。许多好的API都是由程序员创建的,他们不得不在糟糕的API下受苦,然后决定重做这项工作,但这次是正确的。这需要时间和健康剂量的“一次燃烧,两次害羞”来收集做得更好所必需的专业知识。不幸的是,行业趋势是将最有经验的人从编程中提,而就在他们可以充分利用他们积累的专业知识的时候。
另一个趋势是公司将他们最好的程序员推广到设计师或系统架构师。通常,这些程序员作为顾问被分配到各个项目中,目的是确保项目在正确的轨道上起飞,并避免在没有顾问智慧的情况下可能犯的错误。这种做法的意图是值得称赞的,但结果通常是发人深省的:因为顾问非常有价值,在给出了他们的建议之后,他们在实施完成之前很久就被转移到下一个项目,更不用说测试和交付了。当顾问们继续前进时,他们早期的明智建议中的任何问题都不再是他们的问题,而是他们早已抛在后面的项目的问题。换句话说,顾问们永远无法忍受自己设计决策的后果,这是让他们陷入无能的完美方式。保持设计师的敏锐和诚实的方法是让他们自己吃狗粮。任何剥夺设计师反馈的过程最终都注定要失败。
外部控件。几年前,我正在从事一个大型开发项目,由于合同原因,在交付截止日期前不久的关键阶段,该项目被迫进入操作系统升级。升级后,以前工作的系统开始表现得很奇怪,偶尔会产生随机和莫名其妙的故障。追踪问题的过程花了将近两天的时间,在此期间,一大群程序员大多在摆弄他们的拇指。最终,原因是awk函数行为的变化。一旦我们确定了问题,修复程序就微不足道了,我们只需安装以前版本的awk即可。关键是API的一小部分语义的微小变化使项目损失了数千美元,而这一变化是程序员修复不相关错误的副作用的结果。
这个轶事暗示了我们未来越来越需要面对的问题。随着计算的重要性不断增加,有些API的正确功能几乎超出了描述。例如,考虑Unix系统调用接口,C库,Win32或OpenSSL等API的重要性。这些 API 的接口或语义的任何更改都会产生巨大的经济成本,并可能引入漏洞。允许单个公司(更不用说单个开发人员)在没有外部控制的情况下对此类关键 API 进行更改是不负责任的。
打个比方,建筑承包商不能简单地尝试一种新的混凝土混合物,看看它的性能如何。要使用新的混凝土混合物,必须遵循冗长的测试和批准过程,如果不遵循该过程,将受到刑事处罚。至少对于任务关键型API来说,作为自卫问题,类似的过程是必要的:如果世界经济的很大一部分依赖于某些API的安全性和正确运行,那么对这些API的任何更改都应该受到仔细监控。
这种控制是否应该采取立法和刑事处罚的形式是有争议的。立法可能会引入一系列全新的问题,例如扼杀创新和使软件更加昂贵。(微软和欧盟之间正在进行的法律斗争就是一个很好的例子。我看到这种情况发生的真正危险。到目前为止,我们一直很幸运,蠕虫等恶意软件造成的损害相对较小。我们不会永远幸运:第一个利用API漏洞消灭全球10%以上PC的蠕虫将造成如此大规模的经济和人类损害,立法者将被踢到行动中。如果发生这种情况,我们可能会将一组问题换成另一组更糟糕的问题。
立法的替代方案有哪些?多年来,开源社区已经指明了道路:API和实现的公开同行评审已被证明是找出设计缺陷,低效率和安全漏洞的极其有效的方法。此过程避免了与立法相关的问题,在广泛使用API之前捕获了许多缺陷,并且使得当发现零日缺陷时,它更有可能被修复并及时分发补丁。
在未来,我们可能会看到更严格的立法控制和更开放的同行评审的结合。在两者之间找到适当的平衡对于计算和我们经济的未来至关重要。API设计确实很重要,但我们最好在事件失控并删除任何选择之前意识到这一点。