第三章 命名
本章涵盖:
- 为什么我们应该关心命名
- 如何使命名更合理
- 如何在语言、语法和句法方面做出选择
- 上下文如何影响名称的含义
- 糟糕命名的案例研究
不管我们是否喜欢,名称无处不在。在我们构建的每个软件系统和设计或使用的每个API中,都有名称潜伏在每个角落,它们将存在比我们预计更长的时间。因此,选择好的名称是重要的(尽管我们并不总是认真考虑我们的命名选择)。在本章中,我们将探讨API的不同组件的命名,一些选择良好名称的策略,将好名称与差名称区分开的高级属性,以及在不可避免地做出困难的命名决策时指导我们的一些一般原则。
3.1 为什么名称很重要
在软件工程的世界中,几乎不可能避免为事物选择名称。即便我们只需要编写使用语言关键字(例如class,for或if)的代码块,这在最好的情况下也是难以阅读的。考虑到这一点,编译软件是一个特例。这是因为在传统的编译代码中,我们的函数和变量的名称只对那些可以访问源代码的人是有意义的,因为名称本身通常在编译(或压缩)和部署过程中消失。
另一方面,当设计和构建API时,我们选择的名称更加重要,因为它们是API的所有用户将看到和与之交互的内容。换句话说,这些名称不会简单地被编译隐藏,而是对外可见。这意味着我们需要对我们为API选择的名称进行非常多的思考。
显而易见的问题是:“如果发现糟糕的命名,我们不能更改名称吗?”正如我们将在第24章中学到的,更改API中的名称可能非常具有挑战性。想象一下更改源代码中频繁使用的函数的名称,然后意识到您需要进行大量的查找和替换,以确保您更新了对该函数名称的所有引用。尽管这可能有点不方便(在某些IDE中可以很容易),但这当然是可能的。但是,请考虑如果此源代码可供公众构建到其自己的项目中。即使您可以以某种方式更新所有公共源代码的所有引用,始终会有您无法访问并且因此无法更新的私有源代码。
换句话说,在API中更改面向公众的名称有点像更改您的地址或电话号码。要成功地在所有地方更改此数字,您必须与曾经拥有您电话号码的每个人联系,包括使用纸质地址簿的祖母和曾经访问它的每个营销公司。即使您有一种方法与拥有您号码的每个人联系,您仍然需要他们做更新联系信息的工作,而这可能是他们太忙无法完成的工作。
既然我们已经看到选择好的名称(并避免更改它们)的重要性,这引出了一个重要的问题:什么是一个“好”名称呢?
3.2 什么是“好”的名称
正如我们在第1章中学到的那样,好的API需要表达能力强,简单且可预测。而名称则非常相似,只是它们不一定是“可操作的”(换句话说,名称实际上并不执行任何操作)。让我们分别看看这些属性以及一些命名选择的示例。首先是表达性。
3.2.1 表达性
比任何其他方面都重要的是,名称必须清晰地传达其命名的事物。这个事物可能是一个函数或RPC(例如,CreateAccount
),一个资源或消息(例如,WeatherReading
),一个字段或属性(例如,postal_address
),或者完全不同的东西,比如一个枚举值(例如,Color.BLUE
),但读者应该清楚地知道这个事物代表着什么。这可能听起来很容易,但是对于不了解在特定领域中上下文的人,就显得比较困难了。这种上下文通常是一个巨大的优势,但在这种情况下更像是一种负担:它使我们在命名事物时表现糟糕。
例如,术语“topic”在异步消息传递的上下文中经常被使用(例如,Apache Kafka或RabbitMQ);然而,它在机器学习和自然语言处理的一个特定领域中也被使用(topic modeling)。如果你在你的机器学习API中使用术语“topic”,用户可能会对你指的是哪一种类型的主题(topic)感到困惑,这并不奇怪。如果这是一个真实的用例(也许你的API既使用异步消息传递,又使用主题建模),你可能希望选择一个比“topic”更有表达力的名称,比如“model_topic”或“messaging_topic”,以防止用户混淆。
3.2.2 简洁性
虽然有表达力的名称当然很重要,但如果名称过长而没有增加额外的清晰度,它也可能变得繁琐。使用之前的例子(“topic”在计算机科学的多个不同领域中使用),如果一个API只涉及异步消息传递(例如,类似于Apache Kafka的API),与机器学习无关,那么“topic”已经足够清晰和简单,而“messaging_topic”就不会增加太多价值。简而言之,名称应该具有表达力,但只有在名称的每个附加部分都增加了价值以证明其存在的合理性时才能具有表达力。
另一方面,名称也不应过于简化。例如,想象一下我们有一个需要存储一些用户指定偏好的API。资源可能被称为“UserSpecifiedPreferences”;然而,“Specified”并没有为名称增添太多内涵。另一方面,如果我们简单地将资源命名为“Preferences”,那么这是不清楚是谁的偏好,并且在以后需要存储和管理系统或管理员级偏好时可能会引起混淆。在这种情况下,“UserPreferences”似乎是具有表达力和简单之间的平衡点,总结在表3.1中。
表3.1 在表达性和简洁性之间选择
名称 | 说明 |
---|---|
UserSpecifiedPreferences | 具有表达力,但不够简洁 |
UserPreferences | 简洁,并有足够的表达性 |
Preferences | 过于简洁 |
3.2.3 可预测性
现在我们已经讨论了表达性和简洁性之间的平衡,还有一个非常重要的方面:可预测性。想象一个API使用名称“topic”将类似的异步消息组合在一起(类似于Apache Kafka)。然后该API在其他地方使用名称“messaging_topic”而不是“topic”,这将导致一些非常令人沮丧和不寻常的情况。
代码3.1 不一致的命名示例
function handleMessage(message: Message) {
// 这里我们使用`topic`读取给定消息的主题
if (message.topic == "budget.purge") {
client.PurgeTopic({
// 这里我们使用了`messagingTopic`来表示相同的概念
messagingTopic: "budget.update"
});
}
}
在这种情况下,且不考虑是否会导致用户困扰,请考虑我们可能违反的一个重要原则。通常情况下,我们应该使用相同的名称表示相同的事物,使用不同的名称表示不同的事物。如果我们将这个原则看作是公理,这就引出了一个重要的问题:“topic”与“messagingTopic”有何不同?毕竟,我们使用了不同的名称,所以它们必须代表不同的概念,对吧?
通常一个基本的目标是允许API的用户学习一个名称,并继续在此基础上构建知识,以便能够预测未来的名称(例如,如果它们代表相同的概念)会是什么样子。通过在API中一致使用“topic”来表示“给定消息的主题”(以及在表示不同概念时使用其他名称),我们允许API的用户基于他们已经学到的知识去构建应用,而不是混淆这些名称,并迫使用户研究每个名称以确保它的含义与他们的假设相符。
现在我们对好的名称的一些特征有了了解,让我们继续探讨在API中命名事物时可以作为防护栏的一般指导原则。首先从语言、语法和句法开始。
3.3 语言,语法与句法
虽然代码的本质是二进制,基本上存储为数字,但命名是一个我们使用语言来表达的主观构造。与编程语言不同,编程语言对于什么是有效的和什么是无效的有非常明确的规则,而语言的演变更多地服务于人类,使规则变得不太明确。这使得我们的命名选择变得更加灵活和模糊,这既是一件好事也是一件坏事。
一方面,模糊性使我们能够命名事物以足够普遍的方式来支持未来的工作。例如,将字段命名为image_uri
而不是jpeg_uri
,可以防止我们限制自己只能使用单一的图像格式(JPEG)。另一方面,当有多种表达相同事物的方式时,我们通常倾向于互换使用它们,这最终使我们的命名选择变得不可预测(参见第3.2.3节),并导致API变得令人沮丧和繁琐。为了避免其中一些问题,即使“语言”具有相当大的灵活性,通过强加一些我们自己的规则,我们可以避免失去对于一个良好的API非常重要的可预测性。在本节中,我们将探讨与语言相关的一些简单规则,这些规则有助于在为事物命名时减少一些我们不得不做出的武断选择。
3.3.1 语言
虽然世界上有许多语言,但如果我们不得不选择一种在软件工程中使用最广泛的语言,目前美式英语是最有竞争力的选择。这并不是说美式英语比其他语言更好或更差;只是如果我们的目标是在全球范围内实现最大的互操作性,使用美式英语以外的任何语言都可能成为阻碍而不是帮助。
这意味着应该使用英语语言概念(例如,使用BookStore而不是Librería),并且通常应优先选择常见的美式拼写(例如,color而不是colour)。这还有一个额外的好处,就是可以很好地适应ASCII字符集。只有在美式英语从其他语言中借用了一些词汇的情况下才有一些例外(例如,café)。
这并不意味着API的注释必须使用美式英语。如果API的受众群体完全位于法国,提供法语文档可能是有意义的。然而,使用API的软件工程师团队可能会使用其他API,这些API不太可能专门面向法国的客户。因此,即使API的受众群体不使用美式英语作为他们的主要语言,API本身仍应依赖于美式英语作为所有使用大量不同API的各方之间的共享通用语言。
3.3.2 语法
鉴于API将使用美式英语作为标准语言,这会引发一些复杂的问题,因为英语并不是最简单的语言,有许多不同的时态和语气。幸运的是,发音不是问题,因为源代码是一种书面语言,而不是口语语言,但这并不一定消除了所有潜在的问题。
与其试图规定美式英语语法在API中命名事物的每一个方面,不如简要介绍一下最常见的一些问题。让我们首先看看行为类词语(例如,RPC方法或RESTful动词)。
祈使动词
在任何API中,都会存在类似于编程语言“函数”的东西,这些函数执行API期望的实际工作。这可能是一个纯粹的RESTful API,仅依赖于特定的预设操作列表(Get、Create、Delete等),那么在这里你没有太多事情要做,因为所有操作都将采用<标准动词><名词>的形式(例如,CreateBook)。在允许非标准动词的非RESTful或面向资源的API中,我们有更多选择来命名这些操作。
REST标准动词有一个共同的重要方面:它们都使用祈使语气。换句话说,它们都是动词的命令或指令。如果这听起来有点费解,请想象一下军队中的教官对你大喊要你做某事:“创建那本书!”“删除那个天气记录!”“存档那个日志条目!”尽管这些命令对军队来说有点荒谬,但你确切地知道你应该做什么。
另一方面,有时我们编写的函数的名称可能采用陈述语气。一个常见的例子是当一个函数正在调查某事时,例如C#中的String.IsNullOrEmpty()。在这种情况下,“to be”动词采用了陈述语气(询问有关资源的问题),而不是祈使语气(命令服务执行某些操作)。
虽然函数采用这种语气基本上没有什么问题,但在Web API中使用时会留下一些重要的问题。首先,对于看起来可以在不询问远程服务的情况下处理的事情,“isValid()实际上是否会导致远程调用,还是在本地处理?”虽然我们希望用户认为所有方法调用都经过网络,但这种外观上类似于无状态调用,而实际上经过网络调用会有点误导用户。
其次,响应应该是什么样的?以一个名为isValid()的RPC为例。它应该返回一个简单的布尔字段,说明输入是否有效吗?如果输入无效,它应该返回一个错误列表吗?另一方面,GetValidationErrors()更清晰:如果输入完全有效,它将返回一个空列表,如果输入无效,则返回一个错误列表。关于响应的形式没有任何的困惑。
介词
在选择名称时,另一个令人困惑的领域涉及介词,如“with”、“to”或“for”。尽管这些词在日常对话中非常有用,但在Web API的上下文中,特别是在资源名称中使用时,它们可能暗示API存在更复杂的基本问题。
例如,一个图书馆API可能有一种列出图书资源的方法。如果这个API需要一种方法来列出图书资源并包含负责该书的作者资源,可能会忍不住为此组合创建一个新资源:BookWithAuthor(然后通过调用ListBooksWithAuthors或类似的方法进行列出)。乍一看,这似乎没问题。但是当我们需要列出嵌入了Publisher资源的Book资源时呢?或者同时包含Author和Publisher资源?在我们意识到之前,我们将有30个不同的RPC需要调用,这取决于我们想要的不同相关资源组合。
在这种情况下,我们想在名称中使用的介词(“with”)表明了一个更基本的问题:我们希望一种方法来列出资源并在响应中包含不同的属性。我们可能会使用字段掩码(field mask)或视图(view)(见第8章)来解决这个问题,同时避免使用这个奇怪的名称。在这种情况下,介词是一个指示,有时表明了一些并不太对劲的迹象。因此,即使介词可能不应完全被禁止(例如,可能会将字段称为bits_per_second),但这些棘手的小词就像代码异味一样,暗示着某些地方不太对劲,值得进一步调查。
复数
在API中,我们通常会选择事物的名称采用单数形式,例如Book、Publisher和Author(而不是Books、Publishers或Authors)。此外,这些名称往往在API中被赋予新的含义和用途。例如,Book资源可能会在某个地方被一个名为Author.favoriteBook
的字段引用(参见第13章)。然而,当我们需要谈论这些资源的多个实例时,情况有时会变得混乱。使事情变得更加复杂的是,如果API使用RESTful URL,一堆资源的集合名称几乎总是复数形式。例如,当我们请求单个Book资源时,URL中的集合名称几乎肯定是类似/books/1234
的形式。
对于我们用作示例的名称(例如Book),这并不是什么大问题;毕竟,提到多个Book资源只需要在名称后添加一个“s”即可将其变为复数形式Books。然而,有些名称并不那么简单。例如,想象一下我们正在为足科医生办公室制作一个API。当我们有一个Foot资源时,我们需要打破只需添加“s”的模式,从而形成一个feet集合。
这个例子确实打破了模式,但至少它是清晰而明确的。如果我们的API涉及人员,因此有一个Person资源,那么集合是persons还是people呢?换句话说,应该通过类似/persons/1234还是/people/1234的URL来检索Person(id=1234)?幸运的是,我们关于使用美式英语的指南规定了一个答案:使用people。
其他情况可能令人更加沮丧。例如,想象一下我们正在为水族馆制作一个API。章鱼资源的集合是什么呢(译者注:octopus的复数形式可以是octopuses或octopi或octopodes)?正如你所看到的,我们选择美式英语有时会让我们后悔。然而,最重要的是我们选择一种模式并坚持下去,这通常涉及迅速查找语法学家认为正确的答案(在这种情况下,“octopuses”是完全可以接受的)。这也意味着我们永远不应该假设资源的复数形式可以通过简单添加“s”来创建,这是软件工程师寻找模式时常见的诱惑。
3.3.3 句法
现在我们进入了命名更为技术性的方面。与我们之前看到的其他方面一样,当涉及到句法时同样的准则也适用。首先,选择一种方式并坚持下去。其次,如果有现有的标准(例如,美式英语拼写),就使用该标准。那么在实际情况下这意味着什么呢?让我们从大小写开始。
大小写
在定义API时,我们需要为各种组件命名,这些组件包括资源、RPC和字段等。对于这些组件,我们往往使用不同的命名规范,这有点像一种用于渲染名称的格式。通常,这种渲染仅在如何将多个单词串在一起形成单个词法单元方面显现出来。例如,如果我们有一个表示名字的字段,我们可能需要将该字段命名为“first name”。然而,在几乎所有编程语言中,空格是词法分隔符,因此我们需要将“first name”合并为一个单元,这为很多不同的选项打开了大门,比如“驼峰命名法”、“蛇形命名法”或“烤肉串命名法”。
在驼峰命名法中,单词通过将第一个单词之后的所有单词首字母大写后进行拼接,因此“first name”将呈现为firstName(就像骆驼一样有大写字母作为驼峰)。在蛇形命名法中,单词使用下划线字符连接,如first_name(看起来有点像蛇)。在烤肉串命名法中,单词使用连字符连接,如first-name(看起来有点像串在一起的烤肉串)。根据用于表示API规范的编程语言不同,不同的组件以不同的命名规范呈现。例如,在Google的Protocol Buffer语言中,消息(message)(类似于TypeScript接口)的标准是使用大驼峰命名法,如UserSettings(请注意大写的“U”),字段名称使用蛇形命名法,如first_name。另一方面,在OpenAPI规范中,字段名称采用驼峰命名法,如firstName。
正如前面提到的,具体的选择并不是那么重要,只要在整个API中一致使用这些选择。例如,如果你在Protocol Buffer 消息中使用名称user_settings,很容易认为这实际上是一个字段名称而不是消息。因此,这很可能会让使用API的人感到困惑。说到类型,让我们简要地看一下保留字段。
保留关键字
在大多数API定义语言中,会有一种方式来指定存储在特定属性中的数据的类型。例如,我们可以用firstName: string
,以在TypeScript中表示名为firstName的字段包含原始字符串值。这也意味着术语string在不同位置的代码中具有某种特殊含义。因此,在API中使用受限制的关键字作为名称可能是危险的,应尽量避免。
如果感到很困难,花一些时间思考字段或消息真正代表的内容,而不是选择最容易的选项。例如,与其使用“to”和“from”(在诸如Python之类的语言中是那些特殊的保留关键字),你可能想尝试使用更具体的术语,如“sender”和“recipient”(如果API涉及消息),或者“payer”和“payee”(如果API涉及支付)。
考虑API的目标受众也很重要。例如,如果API只会在JavaScript中使用(也许它被设计为仅在Web浏览器中使用),那么其他语言(例如Python或Ruby)中的关键字可能不值得担心。尽管如此,如果不费力,最好还是避免其他语言中的关键字。毕竟,你永远不知道你的API何时可能会被哪一种语言使用。
既然我们已经讨论了一些技术方面的内容,让我们跳到更高的层次,谈谈我们的API所处和所运作的上下文如何影响我们对名称的选择。
3.4 上下文
虽然单独的名称有时可能足以传达所有必要的信息以便发挥作用,但更多时候我们依赖于名称的使用上下文来辨别其含义和预期用途。例如,在API中使用术语"book"时,我们可能指的是存在于Library API中的资源;然而,我们也可能指的是在Flight Reservation API中执行的操作。正如你可以想象的,相同的词语和术语在不同的上下文中可能有完全不同的含义。这意味着在选择API名称时,我们需要牢记API所处的上下文。
重要的是要记住,这是双向的。一方面,上下文可以为一个名称赋予额外的价值,否则该名称可能缺乏具体含义。另一方面,当我们使用具有非常具体含义但在给定上下文中并不太合适的名称时,上下文可能会误导我们。例如,"record"这个名字如果没有附近的上下文可能不太有用,但在音频记录API的上下文中,这个术语会被API的一般上下文赋予额外含义。
简而言之,虽然在给定上下文中如何命名事物没有严格的规则,但要记住的重要事情是我们在API中选择的所有名称与该API提供的上下文紧密相连。因此,在选择名称时,我们应该意识到上下文以及它在选择名称时可能赋予的含义(无论是好是坏)。
让我们稍微改变方向,谈谈数据类型和单位,特别是它们如何影响我们选择名称。
3.5 数据类型和单位
尽管许多字段名称在没有单位的情况下就具有描述性(例如,firstName: string),但其他一些字段如果没有单位可能会非常令人困惑。例如,想象一个名为“size”的字段。根据上下文(参见第3.4节),这个字段可能有完全不同的含义,也可能有完全不同的单位。我们可以看到相同的字段(size)在不同情境下可能具有完全不同,而且在许多情况下可能令人困惑的含义和单位。
代码3.2 不同接口使用相同字段
// size字段的单位令人困惑。它是以字节为单位的大小吗?
// 还是音频的持续时间(以秒为单位)?或者是图像的尺寸?还是其他什么?
interface AudioClip {
content: string;
size: number;
}
interface Image {
content: string;
size: number;
}
在这个例子中,size字段可能有多重含义,但这些不同的含义也将导致非常不同的单位(例如,字节、秒、像素等)。幸运的是,这种关系是双向的,这意味着如果单位出现在某个地方,含义将变得更清晰。换句话说,sizeBytes
和sizeMegapixels
比仅使用size更为清晰和明显。
代码3.3 通过加入单位信息使字段名称更清晰
// 通过加入单位信息使size字段名称更清晰
interface AudioClip {
content: string;
sizeBytes: number;
}
interface Image {
content: string;
sizeMegapixels: number;
}
这是否意味着我们在所有情况下都应该简单地包含任何给定字段的单位或格式?毕竟,在像上面展示的情况中,这肯定会有利于避免任何混淆。例如,想象一下,我们想要在像素资源中存储图像的尺寸以及字节大小。我们可能有两个字段,分别称为sizeBytes和dimensionsPixels。但尺寸实际上有两个数字:我们需要长度和宽度。一种选择是使用字符串字段,并以某种常见的格式提供尺寸。
代码3.4 使用字符串字段存储以像素为单位的图像尺寸
interface Image {
content: string;
sizeBytes: number;
// 字段的格式在字段本身的前导注释中说明。
// 字段的单位是明确的(像素),但基本数据类型(string)可能令人困惑。
// The dimensions (in pixels). E.g., "1024x768".
dimensionsPixels: string;
}
尽管这个选项在技术上是有效的,并且当然是清晰的,但它显示了一种过于追求始终使用基本数据类型的倾向,即使在某些情况下这样做可能并不合理。换句话说,就像有时在名称中包含单位使名称变得更清晰和可用一样,其他时候在使用更丰富的数据类型时名称可能变得更清晰。在这种情况下,我们可以使用一个Dimensions
接口,其中包含长度和宽度的数值,其中名称中包含了单位(像素)。
代码3.5 依赖于更丰富数据类型来表示图像尺寸
interface Image {
content: string;
sizeBytes: number;
// 在这种情况下,维度字段的名称无需在名称中包含单位,因为更丰富的数据类型传达了含义。
dimensions: Dimensions;
}
interface Dimensions {
// 该字段的单位清晰可见(像素),无需任何特殊的字符串格式。
lengthPixels: number;
widthPixels: number;
}
在这种情况下,"dimensions" 字段的含义清晰明了。而且,我们不必解包(unpack)字段本身的一些特殊结构细节,因为 "Dimensions" 接口已经为我们完成了这个任务。让我们通过一些案例研究总结一下,在选择API中的名称时如果没有采取适当的预防措施会出现哪些问题。
3.6 糟糕命名的案例研究
关于如何选择好的名称,以及在选择过程中需要考虑的方面看上去已经很清楚,但通过一些真实世界的例子来看一下那些不太恰当的命名依然是值得的。此外,我们还可以看到这些命名选择的最终后果以及它们可能引起的潜在问题。让我们首先看一个命名问题,其遗漏了一个微小但重要的部分。
微小的含义(subtle meaning)
如果你走进一家Krispy Kreme的面包房要10个甜甜圈,你期望得到10个甜甜圈,对吧?如果你只得到了8个甜甜圈,你可能会感到惊讶吧?也许如果你只拿到了8个甜甜圈,你会认为这家店肯定已经卖光了甜甜圈。如果说必须先要8个,之后再要求2个才能得到你想要的10个,你一定不会觉得这是个合理的过程。
但是,如果你一次只能要求最多N个甜甜圈呢?换句话说,你只能问收银员:“我能要最多10个甜甜圈吗?”你会得到任意数量的甜甜圈,但永远不会超过10个。(请记住,这可能导致你一个甜甜圈也得不到!)突然间,上一个甜甜圈店的奇怪行为有了解释。尽管这很不方便(我还没有见过一家采用这种订购系统的甜甜圈店),但至少不是令人困惑和惊讶的。
在第21章,我们将学习一个设计模式,演示如何在List标准方法操作中分页浏览大量资源,这种方式既安全又清晰,并且可以很好地适应大量资源。结果发现,仅能要求最大值(而不是确切数量)正是分页模式的工作原理(使用maxPageSize字段)。
Google的工程师们(出于历史原因)遵循了描述的分页模式,但有一个重要的区别:request请求不是指定maxPageSize
,而是指定pageSize
以表示“给我最多N个条目”。这三个缺失的字符(max)导致了极大的混淆,就像点甜甜圈的人一样:他们认为他们正在要求确切的数量,但实际上他们只能要求最大数量。
最常见的情况是有人要求10个项目,却只得到8个,并认为可能没有更多项目了(就像我们可能会认为甜甜圈店的甜甜圈已经卖完了)。实际上,情况并非如此:仅因为我们得到了8个并不意味着店里没有了甜甜圈;这只是意味着他们需要到后面去找更多。这最终导致API用户错过了许多项目,因为他们在实际列表结束之前停止了对结果进行分页浏览。
虽然这可能令人沮丧并导致一些不便,但让我们看一下由于混淆字段的单位而导致的一个更为严重的错误。
单位
时间回到1999年,NASA计划将火星气候轨道器调整到离表面约140英里的轨道。他们进行了大量计算,以确定应用的动力,以使轨道器达到正确的位置,然后执行了这次行动。不幸的是,很快团队注意到轨道器并没有到达它应该在的位置。它并没有在距离表面140英里的地方,而是低得多。实际上,后来的计算似乎显示轨道器距离表面仅有35英里。遗憾的是,轨道器能够生存的最低高度是50英里。正如你所预料的那样,低于这个高度意味着轨道器很可能在火星大气中被摧毁。
在随后的调查中,发现Lockheed Martin团队使用的是美制单位(具体而言是lbf-s或磅力秒),而NASA团队使用的是国际单位制(具体而言是N-s或牛顿秒)。一个快速的计算显示1磅力秒等于4.45牛顿秒,这最终导致轨道器获得了超过所需冲动力四倍以上的量,最终使其降至最低高度以下。
代码3.6 MCO计算API的简化示例
abstract class MarsClimateOrbiter {
CalculateImpulse(CalculateImpulseRequest): CalculateImpulseResponse;
CalculateManeuver(CalculateManeuverRequest): CalculateManeuverResponse;
}
interface CalculateImpulseResponse {
// 这里计算了脉冲,但没有提供单位!
// 这通常意味着我们可以将该输出作为下面`CalculateManeuverRequest`的输入。
impulse: number;
}
interface CalculateManeuverRequest {
impulse: number;
}
如果集成点在字段名称中包含了单位,错误就会显而易见。
代码3.7 如果在接口字段中包含了单位
// 这里显而易见,由于单位不同,你不能简单地将一个API方法的输出直接输入到下一个方法中。
interface CalculateImpulseResponse {
impulsePoundForceSeconds: number;
}
interface CalculateManeuverRequest {
impulseNewtonSeconds: number;
}
显然,火星气候轨道器比这里所描绘的要复杂得多,而且很可能无法仅仅通过使用更具描述性的名称就避免这种情况(事件详细信息)。尽管如此,这个例子很好地说明了为什么描述性的名称是有价值的。它可以帮助突显差异,特别是在不同团队之间进行协调时。
3.7 练习
-
想象一下你需要创建一个用于管理重复日程的API(“例如此事件每月发生一次”)。一位资深工程师认为,仅存储事件间隔的秒数对所有用例来说已经足够。另一位工程师认为API应该提供不同的字段,表示各种时间单位(例如,秒、分钟、小时、天、周、月、年)。哪种设计更好地涵盖了预期功能的正确含义,从而是更好的选择?
-
在你的公司,存储系统使用千兆字节作为测量单位(10^9 bytes)。例如,创建共享文件夹时,你可以通过设置
sizeGB = 10
来将大小设置为10GB。现在推出了一个新的API,其中网络吞吐量以吉比特(2^30 bits)为单位,并希望以吉比特为单位设置带宽限制(例如,bandwidthLimitGib = 1
)。这个差异是否太过微妙从而可能会令用户感到困惑?为什么?
本章总结
以下要点总结了本章讨论的原则:
- 简洁性、表达性和可预测性:良好的名称,就像良好的API一样,应该简洁、富有表达力且可预测。
- 语言和语法的一致性:在语言、语法和其他任意方面,通常最好选择一种标准并始终保持一致。
- 名字中的介词可能是设计问题的信号:名字中的介词可能表明存在一些需要解决的潜在设计问题。
- 上下文感知:名称从使用它们的上下文中获得意义。在选择名称时,考虑更广泛的上下文至关重要。
- 在名称中包含单位,使用丰富的数据类型:在原始数据类型中包含单位并利用更丰富的数据类型可以增强名称的清晰度,传达额外的信息。
这些原则有助于创建清晰、易于理解和易于维护的 API。