前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >程序员之痛点:取个好名字

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

作者头像
大数据文摘
发布2018-05-25 14:19:13
2.6K0
发布2018-05-25 14:19:13
举报
文章被收录于专栏:大数据文摘

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

作者 | 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。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-05-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 大数据文摘 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档