程序员之痛点:取个好名字

大数据文摘作品,转载要求见文末

作者 | Alexandre Oliveira

编译 | 张远园,范玥灿,吴蕾

在计算机科学中只有两件事情最难:缓存失效和取名字。

——Phil Karlton

写出好代码很难,为什么?因为好的代码都是易读的。我们总是关注于当下,却不顾及以后;我们总是关注于代码一次写成,但忽视了在以后会被一次又一次地读写。

好代码总是为了方便他人易读在不断优化,这就需要我们将共情作为核心。好代码能帮助别人从读到的一段文字里面去回溯,从其他不同的视角去观察这个问题,这甚至比写代码本身还难;程序员需要去理解这段代码解决了一个人类问题,并且用一个其他人都能明白的方法表达出来。对于我来说,这就是将软件问题放在人类社会的这个大环境中:我们是为哪位读者编写代码?读者能理解么?

所以,如何加工处理、并传递我们的想法给我们的人类同胞们,这才是编程的核心。

根据成员命名

为了阐述我们的第一个概念,让我们来做一个小游戏:“我们在哪个房间?”。我会给你一些图片,你来说出这是什么房间。

问题1/3:有沙发的是什么房间

从家具判断,这一定是客厅。根据一个成员(沙发),我们就可以知道我们在哪个房间。这很容易。

下一个问题。

问题2/3:有坐便器的是什么房间

从图片物体判断,我们非常肯定地说这是洗手间。

看到一个模式了么?房间的名字就是一个标签,定义了房间里的东西。有了这个标签,我们不需要查看容器,就可以知道里面有什么元素。因此我们有了第一个推论:

推论1:容器的名称指示其元素的功能

其实这基本就是鸭子类型(Duck Typing)。如果房间里有床呢?那这就是卧室。

反之也成立:通过容器的名称,我们可以推理其成员。如果我们说的是卧室,那就很可能有一张床。因此我们有了第二个推论:

推论2:通过容器名称我们可以推理其成员

现在我们有了以上的两个推论,让我们试着把它们应用到下一个房间上。

问题3/3:有坐便器和床的是什么房间

坐便器和床在一个房间?这就让这个房间的定义模糊化了。如果必须用推论1和2来对房间进行命名,恐怕得叫怪兽房间了吧。

问题并不在于房间内物件的数量,而在于完全不相关的事物被当做具有相同的功能来对待。在家里,我们将具有相同目的的事物归类,这样会让我们的组织更加简单;如果各个物件功能被混淆、放置一起,我们就不确定设计者想要什么,或者这些事物是被设计来怎么用的。一旦混淆,工作进程就会受阻。

推论3:容器功能的清晰度是跟其成员之间相关性成正比的

以上推论比较难读懂,所以用一张图来阐述:

当成员之间是相互关联的,很容易找到一个好的名字;当不相关的时候,就变得非常困难。相关的方面,可能是功能、目的、它们的策略、类型或者其它。也只有讨论到准则的时候,相关性才有意义。在后面我会讨论到这一点。

软件中,同样适用。我们有成员、类、功能、服务、应用、小怪物。Robert Delaunay说过:“我们的理解与我们的感知有关”。在这个技术背景下,我们的代码可以用最简单的方法,让我们的读者感知到商业的需求么?

示例 1: HTTP 域和car(车)

HTTP是一个域,有请求和回复。但如果我们把一个叫做car的成员放进HTTP,那么我们就不能再叫它HTTP了。在这个案例中,这个类变得很混乱。

public interface WhatIsAGoodNameForThis { /* methods for a car */ public void gas(); public void brake();

/* methods for an HTTP client */ public Response makeGetRequest(String param); }

示例2: 词语的连接

一个普遍的模式是在类名后面加上Builder或者其它er结尾的词。比如:SomethingBuilder. UserBuilder, AccountBuilder, AccountCreator, UserHelper, JobPerformer.

从名字上解析,我们可得到三层意思。

  1. 类名里的动词Build,表示程序上这是一个类中包含有过程,具有复合功能;
  2. 它有两个内在的、隐藏的实体,一个User一个Builder,所以可能会有违反封装原则的危险;
  3. Builder可以访问User的内部工作机制,因为Builder和User就是相互缠绕的啊。

这就像是一个工厂模式。当这个类在滥用代码库的时候,我们的示例就会带来问题。需要提醒的是,在工厂模式里,我们并不需要创建一个类。应用的createUser()就可以很好地实现工厂模式。

示例3: Base

让我们来看一些真实的示例。第一个I18n Ruby gem(为了简洁起见,只提供类和方法的名字):

class Base def config def translate deflocale_available?(locale) def transliterate end

在这里,Base这个类没有表达任何含义。它可以设置(configure)、翻译(translate),以及判断locale是否可得(available)。它做的是一些不同的、无关联的事情。

示例4: 取名对设计的指导意义

当我们讨论到这些类名是如何影响我们的设计时,Discourse有一些例子。以下的例子引起了我们的兴趣:

classPostAlerter def notify_post_users def notify_group_summary def notify_non_pm_users def create_notification def unread_posts def unread_count def group_stats end

PostAlerter 这个名字表示这个类的功能是提醒人们接收新闻发布信息。

但是unread_posts, unread_count 和group_stats ,这些很明显地是在说其它的事情,这就使得这个类名不是那么理想。把以上三个放进一个PostsStatistics 的类里,就会使类更加清晰明了,便于新人理解。

Class PostAlerter def notify_post_users def notify_group_summary def notify_non_pm_users def create_notification end

classPostsStatistics defunread_posts defunread_count defgroup_stats end

示例5: 怪兽名字们

Spring框架里有一些例子,因为成员太多,所以起了一些怪兽一样的类名。以下是一个:

classSimpleBeanFactoryAwareAspectInstanceFactory { public ClassLoadergetAspectClassLoader() public Object getAspectInstance() public intgetOrder() public void setAspectBeanName(String aspectBeanName) public void setBeanFactory(BeanFactorybeanFactory) }

示例6:一个好名字的例子

坏名字的例子已经够多了,在D3’s arc(https://github.com/d3/d3-shape/blob/master/src/arc.js)可以找到好的取名例子,比如:

export default function() { /* ... */ arc.centroid = function() { /* ... */ } arc.innerRadius = function() { /* ... */ } arc.outerRadius = function() { /* ... */ } arc.cornerRadius = function() { /* ... */ } arc.padRadius = function() { /* ... */ } arc.startAngle = function() { /* ... */ } arc.endAngle = function() { /* ... */ } arc.padAngle = function() { /* ... */ } return arc; }

这里的所有的方法都表达了一个意思:它们都表示了圆弧的组成。我特别欣赏下面这张图,清晰明了。

方法1:分解

什么时候使用: 这个类找不到好的名字,但是你对各成员已经有了独立的概念,这时候你想为这些小组取个好名字。

分成两步:

  1. 识别概念
  2. 对它们进行分解

在坐便器+床这个场景中,我们把床推到左边、坐便器推到右边。这样,我们就有了不同的事物,我们可以自然而然地给出不同的命名。

当你不能为一些事物给出好的名字时,可能在你面前的不止一件事物;目前你所知道的,给不同的事物命名很难。当你遇到麻烦的时候,试着分解你面前的事物。

示例

我们有一个未命名的类,包含了request、 response、 headers、URLs、body、 caching和timeout.把所有的成员从主类(main class)中拉出来,我们就有了Request、 Response、 Headers、URLs、ResponseBody、 Cache、Timeout等等。如果我们所拥有的只是这些类的名字,我们可以非常肯定我们是在处理一个网页请求。对于网页请求一个很好的命名就是HTTPClient。

当这个代码很难取名,不要首先想到整体!不要!想一想部分。

方法2:发现新概念

什么时候使用: 当类不简单或者不连贯。

发现新概念需要商务领域的知识。当软件与商务使用相同的术语时,一切就进行了统一,不同领域的专家用着同一种语法。

示例1:将多种元素封装于一个新概念中

曾经,有个公司几乎要丢失一单大合同,为什么呢?因为那个团队在开发新功能和问题处理方面,速度特别慢。

这个市场电子商务为不同国家的学生提供不同规则的多个支付网关,需求确实比较复杂。当我读到支付代码PaymentGateway的时候震惊了,这是有多么复杂啊。代码里面还有相当一部分依赖条件,包括:User,UserAddress, CreditCard, BillingAddress, SellerAddress, LineItems,Discounts,等等。这个巨大的构造函数使得他们很难再添加新规则,因为添加任何一条规则都会打破其他规则,并需要去改动网管适配代码。

这个问题甚至影响到了支付之外的服务。为了聚集消息类数据,他们给学生发送了邮件。技术支持有自己独立的屏幕来看数据的第三次汇聚,除了这个特别的用到了名为Aggregator类的地方(类名也就是本义)。

于是,我们不得不做点什么,来挽救这个架构缺陷。

处理这个问题,我想到了一些主意:我需要你( PaymentGateway)为我处理一些支付细节。如果我是一张课桌,或许我需要把这些发票帮我整理好。所以说如果我创建一个名为 invoice的类,仅用来处理这些细节信息的汇聚,那么网关是不需要知道那些细节的,因为invoice会处理的,对吗?与其注入无数个对象,我是否可以尝试只传递一个给你?

Invoice这个术语之前从来没有应用过。我们花了一个月的时间进行重组,然后我们就能够更为灵活地对代码进行改动。Invoice是一个很好的实例,它描述了汇聚的概念而且大多数人都能看懂。最终解决方案是向Gateway单独注入了Invoice类作为接口,用于屏蔽其他更多类。

好的命名不仅仅是优美的词汇,而是要用精准的语言去表达代码的内涵。

示例2: 根据业务领域的调整命名

在一个未开发的拼车项目中,我们从头设计我们的系统。在对其它交通方案的研究中,对于某人在某天从某处出发到某目的地的这一旅行,最合适的词是trip;而这一些旅行的人,就被称为ride。我们打印了一个词汇表,这样公司的人就可以讨论并且分享使用这些共同的词汇。

但产品发布之后,我们的客户总是把我们的trips称为rides。很快,我们就在将客户的请求转换为我们需要做的事情上,产生了问题。痛定思痛,我们觉得是时候该把trips改变为rides、rides改变成carpools。这样就解决了一个公司说着两种不同语言的问题。

示例3:抽象级别

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

理想情况下,当代码越来越接近其公共API,它越接近于企业术语。随着它接近数据库和金属,它会使用更多的计算机术语。在这之间,抽象程度逐步降低。

在一家公司中,一个业务人员会说postTweet,所以例如postTweet()这样的一个名字,将在公共API比makeHttpRequest()更说得通。在一家拥有更多技术服务的公司中,后者的表达方式也足够清楚。

二,考虑特异性。postTweet()是非常具体的,因为makeHttpRequest()是通用的,可以用于Facebook或基本上涉及HTTP的任何内容。一个通用名称可以轻易地被重复使用,会导致含义不清晰。这就解释了为什么框架代码与商业软件代码有如此大的区别。

Example 4: 概括

很久以前,一个CMS有数据库表的新闻,历史,视频,文章,页面等。他们大多数有相同的列,标题,摘要,文本。Videostable有额外的属性例如url(嵌入YouTube)和有adate属性的历史,所以网页会显示一年的历史事件列表。所有这些表格看起来都是副本,在这里和那里有一些差异,如果要添加新功能,就需要重写大量样板。

我拆散了所有那些表,整合进一个类,名为contents,并用一个外部指针指向一个表格,名为section,包含新闻,历史,视频等清单。现在,一段代码的contents就足够了。几年后,一个朋友不得不写一个小的CMS,我建议他说,我把所有这些表折叠成一个被称为contents的外键,指向一个叫做“sections”的表,其中列出了相同的方法。一旦管理content的表单完成,它花费1/N的时间来执行,因为对于同一类型的每个新的部分来说,都是一样的。

通过命名的方式将过程通用化,会在很大程度上提高生产力。新闻是一个Content,文章是一个Content。历史是一个Content。所有这些都可以共享相同的属性吗?是。那调查是不是Cintent?哦,不,不是Content。

方法三:分组标准

什么时候使用:当名字很好,但他们不能很好地相配时。

组件可以通过各种标准进行分组,包括物理性质,经济性,情感性,社会性和软件中最常用的功能。相框根据情感方面分组,而产品则根据经济动机分组。沙发和电视留在同一个房间,根据功能标准分组在一起,因为它们具有相同的功能或提供休闲的相同目的。

在软件中,我们倾向于按功能对组件进行分组。列出你的项目文件,你可能会看到像controller /,models /,adapters /,templates /等等。然而,有些时候,这些分组并不让人舒服,这就是时候来重新评估模块结构了。

示例:按策略分组

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

根据文件格式,将自动进行各种后续决定。选择API蓝图将会选择不同的linter,不同的测试器和不同的API Elements转换器。这里的关键词策略是基于一个输入strategy来组合所有这些不同的功能。此后,该库包括一个称为strategy策略的模块(或名称空间),该模块将文件格式,linter,文档测试器和存储供应商组合在一起。这使得库可以将业务核心策略中的普通操作文件(如上传者,解析器和命令行)分开。

利用上下文

每个应用程序都有不同的上下文,同样的,其中的每个模块,它们内的每个类,到每个功能也是这样。User这个名字可以单独表示系统用户,也可能是数据库表或者第三方服务凭证。lib/billing/user 与lib/booking/user不同,但仍然是用户。

想象一下,每个容器,如模块,都是一个bucket。在其中,组件被封装,与外界绝缘。你可以自由命名这些类,无需为一些寻常的事物去创建出生僻的名称。

一个在整体式架构(一个大容器,其中有一些小容器)中的微服务(许多独立的容器)的强有力的论据是,它强制限制每个服务中的责任,因为你无法轻松地将完全不相关的事情互相纠缠在一起。 BillingApp内部的几张同能,BookingApp内预订功能等等。在一个单一的架构中,这些相应的服务名称可以是简单的模块名称,但并不是每个人都会恪守原则保持代码井井有条的。

示例:命名空间

马克正在建立一个需要生产成千上万条广告的广告平台,然后发送到AdWords(谷歌),脸书和必应,所有这些都通过图形用户界面进行管理。

马克从一个称为Ad的实体开始,很快开始膨胀。AdWords的广告有headline_part1和headline_part2,脸书不会,而必应只有headline。他需要想办法分裂他的实体。他思考不同的语境,以及如何利用语言的命名空间来表达这一点。他想出了以下结构:

  • csdcAdwords :: Ad:这表示Adwords中的广告对象。它具有Adwords独有的属性,逻辑可以包含在此类中。
  • Facebook ::Ad:与上一个相同,除了它具有脸书的具体要求和逻辑。
  • 必应::Ad: 和上面一样.
  • RemoteAdService :: Ad:这是Adwords::Ad,Facebook::Ad,Bing::Ad和其余部分之间的界面。这意味着这三个类都将具有相同的公共API, 允许系统利用多态。
  • Database::Ad:这是广告表的对象关系映射(ORM)。它使用ActiveRecord,DataMapper或任何自定义解决方案。
  • GUI :: Ad:这表示在UI中显示广告所需的属性。它可能具有演示和国际化功能。

•API :: Ad:广告的HTTP端点将具有自己的自定义属性,因此序列化逻辑存在于此处。

词语可以意味着不同的东西,这取决于上下文,当我们利用上下文时,我们可以为组件选择更简单的单词。在这个例子中,我们不需要复杂的过程就能找到这些组件名称,因为它们是一回事,广告。

无意词和新词

年复一年,名字也在发展并被赋予获得新闻的含义。新词不断涌现,填补空缺。

助手(helper):助手是支持应用程序的主要目标的功能。但是,那么定义应用程序的主要目标是什么?应用程序中的所有内容都支持应用程序的主要目标。

在实践中,它们被集中在一个非自然的分组中,为一些其他混杂的,常用的操作提供可重用性。他们倾向于Feature Envy,他们需要访问另一个组件的内部数据来工作。他们是找不到正确名称东西的借口。

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

尽管微软的建议避免了这个名字(Cwalina,2009),但它显然通过ActiveRecord影响了了Ruby界。到目前为止,我们仍然将Base看作开发人员找不到名称的类名。

Base的变体包括Common and Utils。例如,JSON Ruby gem 的Commonclass具有解析(parse),生成(generate),加载(load)和jj的方法,但是常见的是什么意思呢?

任务:在Javascript社区有一个习惯来调用异步函数,叫tasks。它从task.js开始,即使原始库不存在,该术语也被使用。

团队中的每个人都明白吗?如果真是那样就好了。但是,当一个某人新加入团队,遇到了和60年代以来存在的已然被抛弃在垃圾中命名法,会发生什么情况呢?

我在一个项目中工作,猜一下这个类的名字,亚特兰大。是的,亚特兰大。真见鬼,没人说的清楚为啥叫那名字。

沟通

“现实仅存在于人的思想,而非其他任何地方。”乔治·奥威尔(George Orwell)

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

其次,我们希望别人了解我们。令收到消息的人理解这条消息是发件人的责任,如果能够接受这个观点,我们营造一个共情的环境,一个双赢的局面。没有任何借口不去刻意练习我们的沟通技巧 - 除非你住在丛林中。

随着写作,我们优化阅读,练习共情等方面的技巧可能是很辛苦的。但是,正如生活中的一切,熟能生巧是最朴素的真理。

书目提要:

Cwalina, Krzysztof. 2009. Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, Second Edition. Boston: Pearson Education, Inc. 206.

Evans, Eric. 2003. Domain-Driven Design: Tackling Complexity in the Heart of Software. Boston: Addison-Wesley Professional.

扫码报名Strata Data Conference大会

大数据文摘专享优惠截至5月5日

来源:https://hackernoon.com/yes-python-is-slow-and-i-dont-care-13763980b5a1

关于转载如需转载,请在开篇显著位置注明作者和出处(转自:大数据文摘 | bigdatadigest),并在文章结尾放置大数据文摘醒目二维码。无原创标识文章请按照转载要求编辑,可直接转载,转载后请将转载链接发送给我们;有原创标识文章,请发送【文章名称-待授权公众号名称及ID】给我们申请白名单授权。未经许可的转载以及改编者,我们将依法追究其法律责任。联系邮箱:zz@bigdatadigest.cn。

原文发布于微信公众号 - 大数据文摘(BigDataDigest)

原文发表时间:2017-05-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏ThoughtWorks

谈谈命名|TW洞见

今日洞见 文章作者、部分图片来自ThoughtWorks:黄博文。本文封面来自网络。 本文所有内容,包括文字、图片和音视频资料,版权均属ThoughtWorks...

372100
来自专栏Bug生活2048

如何培养良好的编程习惯?

首先你的项目结构要清晰,养成良好的文档结构分类习惯是很有必要的,举个最简单的web网站项目。

26220
来自专栏PPV课数据科学社区

最让程序员感到崩溃的10种编程语言

很显然,软件开发领域中的程序员对编程语言最有发言权。一种语言可能是一些程序员的最爱,但它同时也是另一些程序员的噩梦。如果你在编程领域呆了一段时 间,你就迟早会发...

60650
来自专栏开源优测

[接口测试 - 基础篇] 02 你应该掌握的Python3接口测试内功

概述 本文主要介绍基于Python3进行接口测试时,应该掌握Python3哪些基本的能力,主要从以下几个方面进行说明。 Python3基本语法 ...

35660
来自专栏CDA数据分析师

一个初级python web后端开发工程师的面试总结

原文链接:https://blog.csdn.net/ayocross/article/details/56509840

19730
来自专栏企鹅号快讯

PHP的学习路线

1、PHP的学习路线 任何网站全都是由网页组成的,也就是说想完成一个网站,必须先学会做网页,掌握静态网页的制作技术是学习开发网站的先决条件。 因此我们要学习HT...

753100
来自专栏编程

32行代码实现微信聊天机器人

在智能手机无所不能的今天,聊天机器人大家并不陌生。语音、文字的,随口都能说出几个。记得最早火起来的,当属人人网上的小黄鸡了吧,那个@小黄鸡风靡一时的时代,如今...

66350
来自专栏WeTest质量开放平台团队的专栏

浅谈软件工程师的代码素养

“程序是写给人读的,只是偶尔让计算机执行一下。” ——Donald Ervin Knuth(高德纳)

647130
来自专栏程序人生 阅读快乐

Linux Shell编程与编辑器使用详解

本书由浅入深,全面、系统地介绍了Linux技术,书中提供了大量实例,供读者实战演练。另外,本书有很多关于Linux下的命令操作内容,所以对于每个命令、每个管理设...

12610
来自专栏大数据技术学习

大数据学习路线

学习要根据自身情况来定,如果你是零基础,那就必须先从基础Java开始学起(大数据支持很多开发语言,但企业用的最多的还是JAVA),接下来学习数据结构、Linux...

18130

扫码关注云+社区

领取腾讯云代金券