有一句名言,我敢打赌你们每个人都听过很多次了。你们知道的。 提醒你命名有多难的那句。
让我们来谈谈命名。
我们经常谈论命名有多难,但很少看到人们谈论如何变得更擅长命名。即使是命名哲学也为命名提供了结构,但并不会为你选择名称。名称不仅仅是我们不必要地陷入其中的一些难题。它们对良好的代码至关重要。好的名称可以让代码库易于上手和使用。坏的名称难以理解,或者更糟糕的是,容易出错。
命名示例
让我们来看一些 JavaScript 中的示例。
function processData(data) {
var result = JSON.parse(data);
return result;
}
仅阅读函数名、参数名和返回变量,我们看到 processData
获取 data
并返回 result
。这些名称几乎没有向读者提供任何信息。当您只想让代码工作或试图保持对更改的灵活时,编写这样的代码很容易,这也没问题。始终建议用新的眼光检查代码以修复问题,并且名称应包含在您的质量检查清单中。
这是一个更具描述性的方法,我们可以用它来编写上一个示例
function parseJson(string) {
var jsonObject = JSON.parse(string);
return jsonObject;
}
技术是使用缩写和首字母缩略词最多的领域之一,它们是优秀名称的关键。FTP 比“文件传输协议”更容易阅读和理解。但在某些情况下,读者可能会被排除在外。
这是一个示例,其中缩写对于编写代码的开发人员来说很方便,但对于需要阅读或参与其中的任何其他人来说却不太方便
function cts(con, ack) {
if (con && ack) {
return true;
}
else {
return false;
}
}
我经常会阅读包含首字母缩略词的代码,然后不得不切换到我的网络浏览器进行搜索,却只找到汽车型号的结果。一个完美的例子是 cts
,它会返回很多凯迪拉克的结果。ack
在搜索中确实会出现,但我宁愿不把它放在我的文本编辑器中。con
可以被误解成多种含义。它是连接吗?计数?容器?也许这是一个骗局。如果您熟悉代码库,这些东西可能很明显,但它会增加加入项目的学习曲线。几秒钟的额外时间可以节省读者多年来的几分钟时间。
以下是前面示例中没有使用缩写的版本
function clearToSend(connectionExists, acknowledgementStatus) {
if (connectionExists && acknowledgementStatus) {
return true;
}
else {
return false;
}
}
让我们转向一些 HTML 示例,因为 HTML 可能是所有语言中名称最多的语言。
<section class="new-promotion-parent">
<img class="logo" src="small-square-logo-monochrome.png"/>
<div class="get-your-coupon">
<p>Get Your Coupon</p>
</div>
</section>
我们可以想象,这里使用“new”这个词可能是因为设计师被告知要更新 promotion-parent
元素,但也要保持对现有类的支持,也许是为了保留旧的 HTML。术语“new”在最初的几个月内是准确的描述,但随着时间的推移,它变得越来越具有讽刺意味。更好的方法可能是添加一个单独的类来描述什么是新的。例如,如果正在实施新的扁平化设计,则可以使用 flat-design
作为类名。作为额外的好处,如果您想重用一些样式,可以使来自现有 promotion-parent
类的 CSS 沿用下来。
类似地,logo
最初看起来像一个合理的类名。然而,不可避免地,第二个 logo 会出现在某个地方,它被命名为 alt-logo
。logo 不断出现,名字也越来越糟糕。大多数资源都有几种变体,例如不同的形状、大小、颜色方案等等。也就是说,small-square-logo-monochrome
也不算是一个好的类名,因为图像本身应该能够在不使类名过时的情况下进行交换。更好的方法可能是使用一个描述资源作用而不是类型或外观的名称。
在这里,div 的语言被用来命名 div 为 get-your-coupon
。HTML 文档的内容旨在不断发展;名称则不然。今天的优惠券代码将来可能成为电子邮件注册,同时保持相同的样式和功能。HTML 是名称经常过于具体而不是过于模糊的地方之一。
以下是考虑了建议后的相同 HTML 代码
<section class="flat-design promotion-parent">
<img class="promotion-branding-image" src="small-square-logo-monochrome.png"/>
<div class="primary-promotion-text">
<p>Get Your Coupon</p>
</div>
</section>
我们甚至可以查看数据库中的名称以获得更好的命名。表通常在应用程序中跨多个非常不同的上下文中使用无数次。
这是一个简化的数据库表
CREATE TABLE `book` (
`id` int(12) NOT NULL,
`title` varchar(50) NOT NULL,
`author` varchar(50) NOT NULL,
`type` bit(1) NOT NULL,
`sort` int(12) NOT NULL,
`order` varchar(25) NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在书籍的上下文中,type
代表什么?是指小说还是非小说?是指平装本还是精装本?也许是指实体书还是电子书?
sort
列同样令人困惑。它是表示 ASC 还是 DESC?它表示用于排序的列?也许它决定排序是否处于活动状态?或者它决定以其他替代顺序显示书籍?
然后是 order
。除了同样模棱两可之外,order 还是 MySQL 和许多其他语言中的保留字。您可以使用反引号 (`
) 来解决 MySQL 中的此问题,但最好完全避免使用保留字。
以下是如何以更具描述性的方式编写该表
CREATE TABLE `book` (
`id` int(12) NOT NULL,
`title` varchar(50) NOT NULL,
`author` varchar(50) NOT NULL,
`cover_type` bit(1) NOT NULL,
`sort_order` int(12) NOT NULL,
`purchase_order_number` varchar(25) NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
命名约定
让我们来谈谈命名约定。
if (oldmanshaven) {
return true;
}
您是将其读作 Old Mans Haven 还是 Old Man Shaven?无论哪种方式,它都会迫使您放慢速度并思考,这会累加起来,并可能有一天导致误解。PascalCase、camelCase、snake_case、kebab-case 都是极好的选择。使用它们,同样重要的是,要保持一致。
以下是相同代码的 snake_case 版本,不太可能被误读
if (old_man_shaven) {
return true;
}
再举一个例子
if (!isNaN(number)) {
alert('not a number')
}
else if (!number > 50) {
alert('number too large')
}
else if (!number < 10) {
alert('number too small')
}
else {
// code here
}
我查看了我最早的一些代码以获取撰写这篇文章的灵感。我没有看到很多糟糕的名称,因为我编写代码的方式没有使用名称。我使用了很少的函数,很少使用赋值,并且会滥用变量,让它们做十多件事。代码中有很多名称通常是您正确抽象事物的标志。
以下是我代码中的一个示例
function validateNumber(number) {
var maximumValue = 50;
var minimumValue = 10;
if (isNaN(number)) {
alert('not a number')
return false;
}
if (number > maximumValue) {
alert('number too large')
return false;
}
if (number < minimumValue) {
alert('number too small')
return false;
}
}
if (validateNumber(number)) {
// code here
}
注意事项
命名事物是一门艺术,而不是科学。在评估名称是好是坏时,需要考虑名称之外的一些因素。
上下文
上下文可以赋予通用术语更多的含义。例如,“item”是一个模糊的名称,但在 getCustomerSalesOrder()
函数的上下文中,我们可以知道它可能指的是购买的产品。一个简短、集中并考虑上下文的函数可以减少对冗长名称的需求。我仍然更喜欢一个好的名称。随着时间的推移,随着函数变得更长或重构,上下文很容易消失。名称是更永久的含义标记。
注释
代码注释对于可读代码很重要,但它们不能独立完成所有工作。注释应该接续名称未完成的部分,提供无法塞进名称中的细节,说明执行某件事的特定方式的原因,或抱怨某个损坏的库。
/* This refers to a product that was purchased and relates to the customer-sales-order class. */
.product-item {
display: block;
text-transform: uppercase;
width: 100vw;
}
阅读长度
较长的名称会产生更多需要阅读的内容和更宽的行。当深入访问数组时,它尤其成问题,例如
$order_details['customer']['ship_to_address']['default']['street_address']['line_1']
也就是说,即使我刚才给出的那个稻草人示例,虽然冗长,但非常清晰,并且没有让我有任何理由停止阅读以思考或查看函数的其余部分以了解上下文。
代码中的名称随处可见
代码文件中的大部分字符可能不是括号或语法,而是名称。它可能是变量名、函数名、HTML 标签或属性、数据库表或列,或者任何我们在任何给定项目中命名的无数事物。
我仍然在努力命名事物。我经常发现自己坐在文本编辑器前一动不动,试图为某个次要变量命名。我了解到,当发生这种情况时,很可能是因为我正在处理难以概念化的事情,这使得为它找到合适的名称变得更加重要。
文章很棒。我见过其中一些例子太多次了。我开始阅读罗伯特·C·马丁的《代码整洁之道》,如果有人想更深入地了解这个主题,我推荐这本书。
命名是我一直很挣扎的事情。它比人们想象的要困难得多!你的文章有一些很好的例子。
我认为ack和acknowledgementStatus之间的区别是有争议的,因为如果必须多次输入后者,它会变得非常繁琐(好的,你的IDE可能会在这里提供帮助)。ackStatus加上合适的注释可能是一种折衷方案。
我想补充的另一点是,注释也可以用于数据库模式中的字段,这一点经常被忽视。
不算太严肃,但相关:https://xkcd.com/1960/
有趣的是,我今天也看到了那个。
组件化使这变得更容易。在小文件上下文中,这对我来说更容易多了。我使用作用域来告诉我如何命名事物。只要它们在组件的上下文中是有意义的,我就不会太在意。
除了使用下划线命名之外,还可以使用驼峰命名法:
old_man_shaven
变成oldManShaven
。我认为这是可读的,并且节省了一些额外的空间。绝对的,但我尽力在这篇文章中使用了所有命名约定的混合,而不是偏爱其中任何一个。正如文章所说,“PascalCase、camelCase、snake_case、kebab-case 都是极好的选择。”。也就是说,我承认snake_case在我心中占有特殊的地位。
不过,这里就会变得很混乱……kebab-case 似乎是 URL(working-towards-better-naming)和 CSS 类首选的命名方式。但随后它不能用作大多数编程语言中的变量名,因为 kebab-case 字面意思是“烤肉串减去大小写”。
然后不要让我开始谈论复数或单数名称。特别是在数据库表中,关于表名应该命名为 Customer 还是 Customers 没有达成共识。
我们能够完成任何实际工作真是个奇迹!
我长期以来一直存在这个难题——是让变量名清晰但冗长,还是保持简短并使用缩写,冒着两个月后无法理解的风险。
有趣的是,Go 语言的约定是选择短名称(甚至是一个字母的名称)(https://talks.golang.org/2014/names.slide#6)
阅读这样的内容,尤其是那本“代码整洁之道”,确实让我的工作方式变得更好。这对你自己有好处,对接手你工作的人也有好处。感谢这篇帖子。
帮助命名约定的一种方法是信息架构。为了使之更容易,请保持一致性。
就像我的导师 Venkat Subramaniam 说的,“给你的第一个孩子取名比给你的变量取名更容易。”
使函数和变量非常具有描述性,例如,
hideFooSection
或var orderPageEditItemButton
。越具有描述性越好。然后,在发布生产代码之前,只需将其最小化即可。这是我的两分钱。