在计算机科学中只有两件困难的事情:缓存失效和命名规范。
—— Phil Karltongit
编写优质代码自己是一件很困难的事情,为何这么说?由于良好的编码风格是为了能更好的理解与阅读。一般咱们会只注重前者,而忽略了后者的重要性。咱们的代码虽然只编写一次,可是在阅读复审时会阅读许屡次。github
良好的编码习惯能够提升咱们的阅读质量,比写做自己要轻松许多,咱们能够站在宏观角度看待问题,远观大局,而不失细节。首先咱们须要理解、分析清楚某个问题,而后用特有的,高效的,言简意赅的方式让更多人明白。对我来讲,应该明确的把软件工程归属到社会科学领域。咱们为谁编写代码,难道不是为了人类吗?(感受原文做者装的有点过)编程
向其余人传递咱们的想法以及编程思想,这就是咱们在编码时要作的。缓存
为了说明咱们的第一个概念,首先来作一个游戏,游戏名为 “咱们住在哪一个房间?”
,以下会为你提供一张图片,请你说说看这是什么房间。网络
问题 1/3框架
从上面的图片不难看出,这确定是客厅。基于一件物品,咱们能够联想到一个房间的名称,这很简单,那么请看下图。异步
问题 2/3函数
基于这张图片,咱们能够确定的说,这是厕所。
经过上面两张图片,不难发现,房间的名称只是一个标签属性,有了这个标签,甚至咱们不须要看它里面有什么东西。这样咱们即可以创建第一个推论:post
能够将这个推论理解为 鸭子类型
。若是有一张床?那么它就是卧室。咱们也能够反过来进行分析。ui
问题:基于一个容器名称,咱们能够推断出它的组成部分。若是咱们以卧室为例,那么颇有可能这个房间有一张床。这样咱们即可以创建第二个推论:
如今咱们有了两条推论,据此咱们试着看下面这张图片。
问题 3/3
好吧,床和马桶在同一个房间?根据咱们的推论,如上图片使咱们很难当即作出判断,若是依然使用上述两条推论来给它下定义的话,那么我会称它为:怪物的房间。
这个问题并不在于同一个房间的物品数量上,而是彻底不相关的物品被认做为具有一样的标签属性。在家中,咱们一般会把有关联的,意图以及功能相近的东西放在一块儿,以避免混淆视听,因此如今咱们有了第三条推论:
这可能比较难理解,因此咱们用下面这一张图来作说明:
若是容器内部元素属性关联性很强,那么咱们更容易找到一个用来讲明它的名字。反之,元素之间的无关性越强,越难以描述说明。属性维度可能会关系到他们的功能、目的、战略,类型等等。关于命名标准,须要关联到元素自身属性才有实际意义。跟着个人思路,咱们将很快明白这一点。
在软件工程方面,这个观点也一样适用。例如咱们熟知的 组件
,类
,函数方法
,服务
,应用
。罗伯特·德拉奈曾说过:“咱们的理解能力很大程度与咱们的认知相关联”,那么在这种技术背景下,咱们的代码是否可使阅读者以最简单的方式感知到业务需求以及相关诉求?
HTTP 自身是一个域环境,它包含着咱们的网络请求与响应状态。若是咱们把一个 Car
的组件放入它的内部,那么咱们不能再称它为 HTTP了,在这种状况下,它会变得让人困惑。
public interface WhatIsAGoodNameForThis { /* methods for a car */ public void gas(); public void brake(); /* methods for an HTTP client */ public Response makeGetRequest(String param); }
有一种常见的命名模式,在名称时后缀附加上 Builder 或 er 一类的结束词,例如:SomethingBuilder
,UserBuilder
,AccountCreator
,UserHelper
,JobPerformer
等等。
例如上图中的名字,咱们能够推断出三件事情。第一,在类名中使用动词 Build
意味着它是具有功能性的。第二,它由两部分组成,一个是 User 用户
,另外一个的 Builder 构造者
,这意味着它们之间可能在封装、维度归类上存在歧义。第三,Builder 构造者
能够在类内部访问 User 用户
的相关逻辑、数据,由于他们在同一纬度空间内。
这一点与工厂模式很类似,有本身的应用场景,当它在咱们的工程中泛滥使用时,这将会是一个很麻烦的问题。另外,须要提醒你们,在工厂模式中,并不必定须要有一个类,经过一个 createUser
的方法足矣很好的实现工厂模式的功能。
让咱们先看几个生活中真实的例子。首先是 i18n Ruby gem
(它的类与方法名称都是很是简练)。
class Base def config def translate def locale_available?(locale) def transliterate end
这里,Base
这个命名自己并无传达太多含义,其中内部结构包含了配置、翻译,区域设置,音译。它们能够看似无关的聚合在一块儿。
一个合理的命名能够引导咱们构建出更为严瑾的组件容器。以下例所示。
class PostAlerter 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_status
并不在这个功能的主要范畴内,从这一点来看,这个类的名称并非很理想。咱们能够将这个三个方法移动到一个名为 PostStatistics
的类中,这样解耦后,事件功能会变得更加清晰,更可预测。
class PostAlerter def notify_post_users def notify_group_summary def notify_non_pm_users def create_notification end class PostsStatistics def unread_posts def unread_count def group_stats end
在 Spring 框架中有一些例子,组件作的事情太多,其名称都很是冗长奇怪。这里只举一个例子(由于实在太多了):
class SimpleBeanFactoryAwareAspectInstanceFactory { public ClassLoader getAspectClassLoader() public Object getAspectInstance() public int getOrder() public void setAspectBeanName(String aspectBeanName) public void setBeanFactory(BeanFactory beanFactory) }
咱们聊了许多不太合理的命名,在 D3
的 arc 中就有许多不错的命名定义,例如:
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; }
上面这个例子中,每个方法都是彻底有意义的:他们都是以 arc
开头。而且他命名风格就像绘制下面的图片同样简练,使人欢喜。
应用场景:当你不能为类或方法找到一个合适的命名,可是你知道如何拆解它们,而且指望给他们的组合找到一个好的名称。
主要有两个步骤:
分辨出他们之间的特色和概念
将它们拆分开
在床和马桶这种特定耦合的场景下,为了拆解他们的不一样之处,咱们将床移动到左侧,将马桶移动到右侧。这样咱们便将两个不一样的事物分离开了。
当你不能为某个事物找到一个好的名称时,也许是由于你所面临的不止一件事物。不过如今咱们已经知道,对多个事物进行命名是一件很是困难的事情,当咱们遇到这类问题时,不妨确认一下构造这个事物的组成部分,以及动做行为。
事例:
咱们现有一个未命名的类,其中包含了 request
,reponse
,headers
,URLs
,body
,caching
,timeout
,把全部这些从类中拉取出来,咱们剩下这样一些组件:Request
,Respone
,Headers
,URLs
,ReponseBody
,Cache
,Timeout
等。若是咱们已知这些类的名称,那么咱们能够肯定这个类是用于处理 Web 请求的,HTTPClient 是一个不错的 Web 请求组件的命名。
当咱们编码遇到困难时,先不要想着总体,先考虑一下局部细节。
应用场景:当一个类并不简单或者内容并不相干。
发现新的概念须要大量业务领域的知识,当软件的命名和业务保持一致时,一个广泛的语言便创建起来,它容许来自不一样专业领域的人来使用相同的语言。
应用场景:当有一个好的命名,可是他们他们之间并不适合。
组件元素以前能够经过各类标准进行分组,譬如组件元素的物理性质,经济性,情感性,社会性以及软件中最经常使用的功能。
在软件工程中,咱们倾向于按功能对组件元素进行分组。若是列出你的项目文件,你可能会看到像 controller/
,models/
,adapters/
,templates/
等等目录名称,而后,有些时候,这些名称组合在一块儿并必定适合,这也是从新评估模块,从新定义,规划命名的时候。
每一个应用程序都有本身不一样的上下文环境,每一个模块、每一个类、每一个方法也一样都有。User
这个词所表明的含义能够是操做系统用户,或是一张数据表,也能够是一个第三方的服务凭证,不一样的上下文环境,它所表示的含义不尽相同。
多年以来,命名规范的演变上变得更具备意义,有更多的人来填补这个陈旧的空缺。
Helper,helpers
是一个支持应用程序实现的主要方式。应用程序实现与定义的标准是什么呢?应用程序中的全部内容都应该支持并实现其主要目标。
在实践中,它们被击中在一个非天然的分组中,为一写其余经常使用的操做提供可重用性。通常状况下,helpers
须要另外一个组件元素的内部数据的依赖。这种命名通常会在找不到合适的名称时折中使用。
Base,许久以前,在 C#
中有须要继承类的命名方式都是以 Base
命名。例如:汽车和自行车的父类都是 Base
而不是 Vehicle
。尽管微软提出建议去避免这类命名方式,但他依然影响了 Ruby
这门语言,其中最具表明性的是 ActiveRecord
类的继承。到目前为止,咱们依然将 Base
看作为开发人员找不到合适命名的一种替代方式。
变更调整后的 Base
含括了 Common
和 Utils
,例如,JSON Ruby gem 的 Common
类具备 parse
,generate
,load
以及 jj
等方法,但这里 Common
真的具有它的含义吗?
Tasks,在 JavaScript
社区兴起了一种经过异步调用函数的方式,这种方式起源于 task.js
,即便目前这个开源库不多再被提使用,可是这个术语流传了下来。
若是团队中全部人都能清楚的理解它的含义,那是可喜的。但若是有新人加入团队,而且他遇到了被抛弃在垃圾堆中的 60 年代便存在的古怪命名,那又怎么办呢?
在我以前的项目工做中,曾遇到过这样的一个类的命名,大家猜猜看,Atlanta
,是的,亚特兰大,操蛋的亚特兰大。没人知道或者能够告诉我为何要起这么个名字,以及含义是什么。
Cwalina,Krzysztof.2009,框架设计指南:可重用 .NET 库的约定、惯用语和模式,第二版。 Boston: Pearson Education, Inc. 206。
Evans, Eric. 2003。域驱动设计:解决软件核心复杂性。Boston: Addison-Wesley Professional。