什么是好的 API?






4.33/5 (2投票s)
关于什么是好的 API 的帖子
背景
我在工作中的职位使我在如何编写代码方面拥有一定的自由度,更重要的是,我能影响他人如何编写代码。最近我和一位同事讨论了我心目中的好 API 是什么样的,从宏观角度来看。我们讨论的背景是开发一个基于 C# 的 API,但这实际上适用于任何面向对象的 API。
我有两个关键点想阐述,虽然它们不是唯一重要的事情,但我认为它们经常被忽视。第一点是如何**使用**你的 API,即如何调用方法和使用结果。第二点是,如果他人想要扩展你的工作并实现自己的类,他们应该如何**实现**你的 API。以下是我试图传达的要点。
用法
作为程序员,当你使用一个 API 时,你希望它很简单。如果你在使用现有的具体类,你希望方法易于调用,并且你希望这些方法的返回值是有用的。那么,在创建 API 时如何实现这一点呢?我的指导原则是:
- 使方法的输入尽可能通用
- 使返回值尽可能信息丰富
很简单,对吧?如果你的输入足够通用,你就可以传入各种东西。例如,如果你的函数接受一个 ReadOnlyCollection<string>
,那么这个函数不一定像接受 IEnumerable<string>
的函数那样容易使用。如果还不清楚,那是因为 IEnumerable<string>
是一个更通用的类型。有了 IEnumerable<string>
,我就可以传入数组、列表、队列、堆栈或任何集合。我可以传入任何实现了 IEnumerable<string>
的对象!相反,如果我要求一个 ReadOnlyCollection<string>
,那么所有拥有其他各种集合类型的调用者都需要进行一些转换才能使其成为 ReadOnlyCollection<string>
。
至于第二个要点,你在调用函数时希望获得尽可能多的信息。这几乎和参数的论点完全相同,但工作方式相反。设想一下,如果我的函数返回一个 IEnumerable<string>
。这意味着任何调用我函数的人只能访问一个可以枚举以获取 string
值的东西。好吧,这不算太糟……但如果实际上每个调用你方法的人都需要一个 string
列表呢?如果使用你方法的一种常见方式是获取你函数的 IEnumerable
结果,从中创建一个列表,然后添加一些其他项。你的 API 基本上创建了一个额外的步骤,要求调用者从你的返回值中创建列表。那么……为什么不直接返回一个列表呢?如果你看看你的具体实现,并且注意到你在函数内部工作时很可能确实使用了类似列表(或其他具体的集合)的东西,这一点就更清楚了。为什么要返回一个更难使用的东西?
实现
这一切的另一面是其他开发者将如何实现你 API 中提供的接口(或扩展类)。你猜怎么着?我刚才为简化调用者生活所做的所有论点,对于实现你 API 的接口的人来说,基本上是反过来的。
如果我的接口要求传入一个 IEnumerable<string>
,那么我唯一能做的就是枚举它。也许在我自己的实现中这没问题……但如果其他人实现你的接口,但却从拥有一个列表(list)中获益匪浅呢?也许他们可以通过知道集合中有多少项,或者通过检查第 100个项是否是特定值来做出出色的优化?然而,他们只能枚举,所以这变得很困难。
至于返回值,之前我争辩说,对于调用者来说,返回尽可能多的信息是很好的。考虑这个例子。如果我在我的 API 中创建了一个自定义集合类,它拥有各种很棒的元数据。为了举一个完全随机的例子,让我们假设我有一个整数集合类,并且它上面有所有这些花哨的属性,告诉我平均值/中位数/众数。调用者会说这太棒了!太好了!通过调用这个简单的函数,我获得了如此多的信息!然而,你的接口的实现者现在正在思考:“哦,该死……首先你把我的输入限制得如此基础,然后我必须**以某种方式**返回那个花哨的对象?!我该怎么做?!”
摘要
总结一下我在这里写的内容,我认为 API 的一个好的指导原则是:
- 使输入足够通用,以简化调用者的生活,并为方法实现者提供足够的信息。
- 使返回值尽可能信息丰富,但不要给方法实现者带来创建复杂类(和添加依赖项)的负担。
很简单,对吧?如果你的 API 被设计成其他人不会扩展它(实际上只有那些调用你方法的人),那么你就可以完全偏向于为调用者设计!