首页 » 软件开发 » 软件的复杂性:命名的艺术(命名复杂性名称示例组件)

软件的复杂性:命名的艺术(命名复杂性名称示例组件)

神尊大人 2024-07-24 22:28:06 0

扫一扫用手机浏览

文章目录 [+]

想把一个东西写好很难。
为什么呢?因为只要写好了,才会有很好的阅读体验。
我们往往关注了前者而忽略了后者。
我们忘记了代码只写一次,但要读很多次。

写得好是指写出来的东西读起来容易,而不是指写作本身,这一过程会产生大量的共鸣。
它是指,退后一步,从读者的角度来理解所写的东西。

人们必须以人的思维来理解问题,然后用其它人能够理解的方式表达出来。
在我看来,软件属于社会科学的一部分。
我们要搞清楚代码写出来是给谁看的,不是给人看的吗?

软件的复杂性:命名的艺术(命名复杂性名称示例组件) 软件开发
(图片来自网络侵删)

因此,理解如何将思想和过程传达给我们的同行甚至我们自己,这就是编程的核心。

为组件命名

为了说清楚第一个概念,我们来玩一个叫“我们在哪个房间?”的游戏。
我会给出一张图,然后你告诉我这是哪个房间。

3个问题中的第1个

从这个图很容易判断出来是在客厅。
我们从一个组件就能知道所处的房间。
这非常容易,我们继续。

3个问题中的第2个

从这个物体很清楚的知道这是在卫生间。

发现什么规律了吗?房间的名称是一个标签,它定义了这个房间里有什么。
有了这个标签,我们不知道进去看也知道里面有些什么东西。
这足以建立我们的第一个推论:

推论 1: 容器的名称包含了其功能元素

注意这是最基本的“鸭子类型”[译者注:如果它的动作像一只鸭子,那它就是鸭子]。
如果有一张床,那这里就是卧室。

反过来也是如此:基于容器的名称,我们可以推断出它的组成部分。
如果我们谈论一个卧室,很可能它有一张床。
这样产生了我们的第二个推论:

推论 2: 可以根据容器的名称推断其中的组件

显然我们已经有了一些规则,让我们把这些规则应用到下一个房间。

3个问题中的最后一个

哇,床和马桶怎么会在同一个房间?这个房间的定义很模糊,朦朦胧胧,如果一定要用前面的两个推论来为这个房间命名,它只能称为怪物房间。

这里的问题不在于房间里物体的数量,而在于完全无关的事物被看作有同样的功能。
在家里,我们会把相关的有类似作用或意图的物品放在一起。

如果把作用不同的东西胡乱放在一起,就让人搞不明白架构师到底想怎么来使用这些东西。
由于混乱,我们在这里不知所措。

推论 3: 容器定义的明确程度与其内部组件的紧密程度成正比。

这似乎不容易理解,那来看看图示:

如果组件相关,就很容易找到一个好名字[译者注:指容器的名字]。
如果事务各不相干,找个合适的名字就会变得困难。

这里提到的关系,可能是指它们的功能、目的、策略、类型等。
在我们谈到标准之前,关系本身并不包含太多意思。
现在先不要急,我们很快就会讲到。

这对于软件同样适用。
我们有组件、类、函数、服务、应用程序和其它一些东西。
Robert Delaunay 曾经说过“我们的理解与我们的感知相关。
”在当前的技术背景下,我们的代码是否能让读者以最简单的方式感知到业务需求呢?

示例 1: HTTP 领域和汽车 domain and a car

HTTP 是一个领域,它有请求和响应。
如果我们我们在其中放入一个汽车组件,那就不能再称这为 HTTP。
这种情况下它就已经变得混乱了。

示例 2: 通过词语来耦合

在类名中添加 Builder 或者其它以 er 结尾的单词是种常见的模式。
SomethingBuilder、UserBuilder、AccountBuilder、AccountCreator、UserHelper、JobPerformer。

通过名称,我们可以了解三件事情。
首先,在类名中使用 Build 这个动词意味着它是穿着类这件外衣的程序。

第二,它有两个隐藏在内部的元素,User 和 Builder,这意味着可能违反了封闭性原则。

第三,这意味着 Builder 可以访问到 User 的内部工作,毕竟它们彼此纠缠。

这类似于工厂模块。
我们的示例代码在整个代码库中滥用时,它就会成为一个问题。
此外,我得提醒你,在工厂模式中不需要什么类。
应用程序的 createUser() 就能完成工厂的工作。

[译者注:Builder 也是一种模式,所以关于作者的这个观点,请慎思]

示例 3: Base

来看一点实际项目中的例子。
第一个例子是 I18n(国际化)的 Ruby Gem (为了简便起见,只列出了类和方法的名称):

这里的 Base 并不能表达什么意思。
它可以进行配置和翻译,也可以描述一个位置是否可用。
它做了一些各不相同,毫不相干的事情。

示例 4: 名称引导设计

我们在谈论名称如何引导我们的设计时,提到了好几个例子,让我们感兴趣的例子中,有一个如下:

PostAlerter 这个名称暗示我们它的功能是在提交的时候提醒某人。
然而,unread_posts、unread_count 和 group_stats 却很明显在干别的事情,这就使得类名称不太理想。

如果把这三个方法改到名为 PostsStatistics 的类中,表达出来就更清晰,让新接触的人一看就能明白。

示例 5: 奇怪的名称

Spring 框架中有一些例子说明组件做的事情太多,其名称类似于我们的怪物房间。
这里就有一个 (因为这个就太多了点):

class SimpleBeanFactoryAwareAspectInstanceFactory { public ClassLoader getAspectClassLoader() public Object getAspectInstance() public int getOrder()

public void setAspectBeanName(String aspectBeanName)

public void setBeanFactory(BeanFactory beanFactory)

}

示例 6: 改变一下,说说好名称

我们讲了太多不好的名称。
D3 的 arc 中就定义了不错的名称,比如:

这些方法每一个意义都很有完整的意义:它们都是基于弧所拥有的属性来命名的。
下图中我非常喜欢的一点在于它真的很简单。

方法 1: 拆解

应用场景

你不能为类或组件找到好名称,但你知道如何拆解它们,而且期望给它们的组合找到一个好名称。

这包含两个步骤:

确认我们拥有的概念拆解它们

在马桶 + 床的情况下,我们把不同的事物拉开,床在左,马桶在中。
好了,我们终于把事物拆分成两个部分,使它们不再那么别扭了。

如果你不能为某个事物找到好名称的时候,也许是因为你面前不只一件事物。
不过你现在已经知道对多个事物命名是件困难的事情。
当遇到麻烦的时候,不妨确认一下构成这个事物的部分和动作。

示例

我们有一个尚未命名的类,包含 request、response、headers、URLs、body、caching 和 timeout。
把所有这些从主类中拉出来,我们剩下了这样一些组件:Request、Response、Headers、URLs、ResponseBody、Cache、Timeout 等。
如果我们已知这些类的名称,就会相当确定我们正在处理一个 Web 请求。
HTTPClient 会是个不错的 Web 请求组件名称。

遇到困难的代码时,不要一开始就想着整体。
考虑一下部分。

方法 2: 发现新概念

应用场景

某个类并不简单或者不清晰时

发现新的概念需要业务领域的知识。
如果在软件中使用业务术语,因为专业语言已经建立了而且到处都在使用(Evans, 2003),不同专业领域的专业人士就会使用同样的习语。

示例 1: 将组件封装到新的概念中

几年前,一家公司即将失去一份大合同。
为什么呢?因为该团队发布新功能和修复错误的速度太慢。

这个市场电子商务为不同国家的学生提供不同规则的多个支付网关,要求相当复杂。
当我看到付款代码-- PaymentGateway 时,我对其复杂性感到震惊,其中包括:User、UserAddress、CreditCard、BillingAddress、SellerAddress、LineItems、Discounts 等等。

它的构造函数是巨大的,这种复杂性使得很难添加新的规则,因为改动一处会破坏其他规则,同时要求我们改动所有网关适配器。

问题延伸到付款之外的事情。
电子邮件会发送给学生,通过 messaging 类再次聚合所有这些数据。
技术支持也有自己的显示端,这个数据第三次发生聚合,除了在这个特定的地方使用一个叫做 Aggregator(没有上下文的单词)的类。
我们不得不做一些更改以修复这个架构阻碍。

为了解决这个问题,我开始了一个头脑练习。
这是一个如何去做的想法:

在这里,我用于关于此事的这些细节,我需要你(PaymentGateway)对我负责。
如果这是一张桌子,我会组织这些文案,我可能会把它们称为 Invoice(发票)。

那么如果我创建了一个名为“Invoice”的类,这只不过是所有这些其他细节的汇总,这样网关就不需要知道这些规则是如何完成的,因为 Invoice 是知道的?不是插入一百万件物品,我只是把它交给你?

术语 Invoice 未在任何其他地方使用过。
我们花了一个月时间在此重构,一旦完成,我们就能够更快更新软件。

Invoice 是概念的一个很好的示例,它是来自许多源的数据汇总,并且大多数人都知道它是什么。
最终的解决方案增加了 Invoice 类,把它单独注入到网关中,使用门面模式并隐藏其他类。

良好的命名不仅仅是写出美丽的语句,还要更准确地描述以前无法表达的内容。

示例 2: 基于业务域的名称变动

在一个绿地拼车项目中,我们从新设计了该系统,在研究其他交通运输解决方案时发现,在某一天从起点到目的地描述某人旅程的最适当的词是旅行(trip),而这群人被称为乘客(ride)。

我们发布了一个词汇表,所以公司的其余人员可以讨论和分享相同的通用语言。

推出之后,我们的客户总是把 trips 称为 rides 。
不久之后,我们在将客户要求转换为必须完成的任务遇到麻烦,痛定思痛之后,我们决定是时间将 trips 重构为 rides ,将 rides 重构为 carpools 了。
这就解决了在一家公司讲两种不同语言的问题。

示例 3: 抽象的等级

一个人说,移动右腿然后左腿然后右腿,另一个说走路。
两者都是一样的,但后者据说更抽象。

理想情况下,随着代码越来越接近其公开 API ,它越接近于企业术语。
随着它越接近数据库和底层,它使用与其上下文相关的机器术语。
在这之间,存在一个从多到少抽象渐变过程。

在一家公司,一个商人会说 post Tweet,所以一个如 postTweet() 的名字将会比一个公开的 API(比如makeHttpRequest())更有意义。
在一家拥有更多技术服务的公司中,后者将更为充实。

第二,考虑特异性。
postTweet() 非常具体,而 makeHttpRequest() 是如此通用,它可以用于 Facebook 或基本上涉及 HTTP 的任何内容。

一个通用名称可以轻易地重用,代价是不确定性。
这就解释了为什么框架代码与商业软件代码的有如此大的区别。

示例 4: generalization 泛化

很久以前,CMS 中有数据库表 news、history、videos、articles、pages、other。
他们中大多数具有相同的列:title、summary、text。

videos 表具有额外的属性,例如 url(嵌入YouTube);history 表具有日期属性,以便页面可以按年份显示历史事件列表。

所有这些表格看起来像是副本,在这里和那里仅有一些差异,而且添加新的功能需要重写大量的样板。

我将所有这些表折叠成一个称为 contents 的外键,指向一个名为 sections 的表,其中包含 news、history、videos、others 的列表。
现在,contents 的一个编码就足够了。

多年以后,一个朋友不得不写一个小的 CMS,我推荐使用同样的做法。
一旦管理内容的表单完成,它一般花费了1/N时间来实现任何功能,因为对于同一类型的每个新的部分,它都已经完成了。

通过赋予它另一个名字极大得提高生产力。
news 是一类内容,Article 也是一类内容,history 又是一类内容。
所有这些都可以共享相同的属性吗?是的。

方法 3: 分组的标准

什么时候使用

当名字很好但是他们不能很好地配合时。

组件可以按照各种标准进行分组,包括物理性质、经济性、感情色彩、社会性和软件中最常用的功能。
Photo 框架根据感情色彩方面分组,而产品则根据经济动机分组。

沙发和电视留在同一个房间,根据功能标准分组在一起,因为它们具有相同的功能或目的,均用于休闲。

在软件中,我们倾向于按功能对组件进行分组。
列出你的项目文件,你可能会看到像 controller/、models/、adapters/、templates/ 等等。

然而,有些时候,这些分组可能不太合理,这将是重新评估模块结构的最佳时间。

示例: 使用策略进行分组

用于自动化文档操作的库根据代码、lints 描述文件(保证格式正确)生成规范文档(比如 API 蓝图)并上传到云中(比如 S3 )。

根据文件格式,将自动进行各种后续决定。
选择 API 蓝图将会选择不同的 linter,不同的测试器和不同的 API 元素转换器。

这里基于一个输入来组合所有这些不同的功能的关键词是策略(strategy)。
此后,该库中包含一个将文件格式、linter、文档测试器和存储提供程序组合在一起的模块或者命名空间,被称为Strategy(策略)。

这使得库可以将业务核心策略中的普通文件操作(如上传者、解析器和命令行等)分开。

利用上下文

每个应用程序都有不同的上下文,包括其中的每个模块,它们中的每个类,每个函数都有。
单独的 User 一词可以表示系统的用户,但也可能是数据库表或第三方服务凭证。

lib/billing/user 与 lib/booking/user 是不同的,但仍然属于用户范畴。

设想一下,每个诸如模块的容器,都是一个桶。
在其中,组件与外界绝缘。
你可以自由地命名这些类,无论你想要什么样的名字。
这使得不必为常见事物寻找深奥的名字而绞尽脑汁。

针对微服务(许多独立的桶)胜过整体式架构(一个内嵌小桶的大桶)的一个强有力的论据是它加强了对每个服务中的责任的限制,因为现在你不能轻易地将完全不相关的事情互相整合在一起。

Billing 位于 BillingApp 内部,booking 位于 BookingApp 内等等。
但在整体架构中,这些相应的服务名称可以是简单的模块名称,但并不是每个人都有责任来保持整洁。

示例: Namespaces (命名空间)

马克正在建立一个需要生成数十万个广告的 ads 平台,然后将其发送到 AdWords、Facebook 和 Bing ,所有这些都通过图形用户界面(GUI)进行管理。

马克从一个称为 Ad 的实体开始,很快就变得膨胀。
AdWords 的广告有 headline_part1 和 headline_part2 ,但 Facebook 里面不是这样,而 Bing 只有一个 headline(标题)。
他需要想办法分开他的实体。
他考虑到不同的语境,以及他如何利用语言的命名空间来表达这一点。
他想出了以下结构:

Adwords::Ad:这表示 Adwords 中一个 ad 对象。
它用于专属于 Adwords 的属性以及可包含在该类的逻辑处理。

Facebook::Ad:和上一个类似,但是它拥有 Facebook 专有的要求和逻辑处理。

Bing::Ad:和上面的类似。

RemoteAdService::Ad:这个作为 Adwords::Ad、Facebook::Ad、Bing::Ad 与系统的其他部分交互的接口。
这意味着这三个类将会拥有同样的公共 API,允许系统使用多态。

Database::Ad:这是 ads 表的 ORM。
它使用 ActiveRecord、DataMapper 或者其他自定义方案。

GUI::Ad:这表示在 UI 上用于显示广告的属性。
它可能包含展示和国际化的功能。

API::Ad:针对那些用于自定义属性的广告的 HTTP 终端,因此序列化的逻辑保存在这里是有道理的。

单词在不同的上下文中可以表示不同的东西,当我们考虑上下文时,我们可以为组件选择更简单的单词。
在这个示例中,我们没有必要做任何复杂动作来找到这些组件名称,因为它们是一回事,ad(广告)。

无意义和新词

多年来,名字变革并获得新的含义。
其他人来补充新的意义。

小助手 Helper:Helper 是那些支持应用程序的主要目标的函数。
但是,那么用来定义应用程序的主要目标的标准是什么呢?应用程序中的所有东西都是用于支持应用程序主要目标的。

在实践中,它们被集中在一个非自然的分组中,为一些其他常用的操作提供可重用模块。
他们倾向于遇到依赖情结Feature Envy,他们需要访问另一个组件的内部数据来工作。
他们也是那些找不到合适名字的东西的缘由。

Base:名为 Base 的类是很久以前在 C# 中在缺少一个更好的名字时用于指定继承的惯例。
例如,汽车和自行车的父类将是 Base 而不是 Vehicle。

尽管微软的编码规范中建议避免了这个名字(Cwalina,2009),但它影响了 Ruby 世界,其中最著名的是 ActiveRecord。
到目前为止,我们依然可以看到在开发者找不到合适名字时使用 Base 作为类名的情况。

Base 的变体包括 Common 和 Utils 。
例如,JSON Ruby gem Common 类具有解析、生成、加载和 jj 的方法,但是这里的 Common 究竟是什么意思呢?

Tasks:在 Javascript 社区有一个指示来调用异步函数,tasks。
它起源于 task.js ,即使原始库不存在,该术语也依然被使用。

团队中的每个人都能理解这个吗? 那就好了。
但是,当一个新人加入该团队,遇到被抛弃在垃圾堆中的 60年代以来就存在的命名法,又会是怎样呢?

我在一个项目中工作,其中一个类的名字,猜猜看,Atlanta。
是的,亚特兰大, 操蛋的亚特兰大。
没有人知道或可以告诉我为什么使用这种叫法。

沟通

Reality exists in the human mind and nowhere else.(事实存在于人的思想中,而不是其他任何地方)” George Orwell

我认为沟通交流的做法是一个利他主义的行为,我们提高技能的努力与我们对他人的关心程度有关。
我们希望人们更容易理解,我们想要消除冲突和障碍。

其次,我们希望别人能理解我们。
通过承认投递消息给接收者是发件人的责任,我们建立一个移情的环境。
这是一个双赢的局面。
没有任何借口不去练习我们的沟通技巧 - 除非你住在丛林中。

随着写作,我们优化阅读,移情的练习可能是枯燥的。
但是,正如生活中的一切,熟练度只会出现在那些常练习的人身上。

标签:

相关文章

语言中的借用,文化交融的桥梁

自古以来,人类社会的交流与发展离不开语言的传播。在漫长的历史长河中,各民族、各地区之间的文化相互碰撞、交融,产生了许多独特的语言现...

软件开发 2025-01-01 阅读1 评论0

机顶盒协议,守护数字生活的新卫士

随着科技的飞速发展,数字家庭逐渐走进千家万户。在这个时代,机顶盒成为了连接我们与丰富多彩的数字世界的重要桥梁。而机顶盒协议,作为保...

软件开发 2025-01-01 阅读1 评论0

语言基础在现代社会的重要性及方法步骤

语言是人类沟通的桥梁,是社会发展的基础。语言基础作为语言学习的基石,对于个人、社会乃至国家的发展具有重要意义。本文将从语言基础在现...

软件开发 2025-01-01 阅读2 评论0

粤语电影,传承文化,点亮时代之光

粤语电影,作为中国电影产业的一朵奇葩,以其独特的地域特色、丰富的文化内涵和鲜明的艺术风格,赢得了广大观众的喜爱。本文将从粤语电影的...

软件开发 2025-01-01 阅读3 评论0

苹果游戏语言,塑造未来娱乐体验的基石

随着科技的飞速发展,游戏产业逐渐成为全球娱乐市场的重要支柱。在我国,游戏产业更是蓬勃发展,吸引了无数玩家和投资者的目光。而在这其中...

软件开发 2025-01-01 阅读1 评论0