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

上网导航 2023-08-12 377 0条评论
摘要: 在计算机科学中只有两件事情最难:缓存失效和取名字。...

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.

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

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

类名里的动词Build,表示程序上这是一个类中包含有过程,具有复合功能;

它有两个内在的、隐藏的实体,一个User一个Builder,所以可能会有违反封装原则的危险;

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’sarc()可以找到好的取名例子,比如:

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:分解

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

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

分成两步:

识别概念

对它们进行分解

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

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

示例

我们有一个未命名的类,包含了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。他需要想办法分裂他的实体。他思考不同的语境,以及如何利用语言的命名空间来表达这一点。他想出了以下结构:

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

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

无意词和新词

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

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

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

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

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

Base的变体包括CommonandUtils。例如,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://www.90xe.com/post/1754.html发布于 2023-08-12
文章转载或复制请以超链接形式并注明出处技术导航

分享到:

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏