在 软(🌽)件中随处可见命名(🌷):要给变量、函数、参数、(🍅)类和封包(🌝)命(👫)名,还要给(🍵)源代码及源代(🛸)码所在目录(🎟)命名,甚至还有jar文件、war文件和ear文件(🤳)命名。
但是(🍲),看似简单的命名,也(🐹)是让不少程序员(🕉)头疼的(🕡)问题。 有一些(🔑)小(🏓)伙伴,在(🌇)进行变量命名(🍘)的时候,对于自(🍒)己熟悉的英(❕)文,可(👒)能还(🎩)会用英文命名一下,如果需要命名的部分不会用(🏴)英文(🌻)表达(🔮),或许就(🔡)直接用拼音了。
有的童(🐣)鞋一下想不起来(☔)怎(🕺)么命名,直(🥈)接用拼音直接(🚩)用aa,bb等这样没(📓)有任何代表意义的字母来命名,可读性非常差,可能自己今天(🧟)写的,一个星(📠)期后回来再看,也忘(🐱)记其具(🏻)体代表(🌎)的含(🐅)义了(📢)。
因此,许多人在写代(✏)码之前,总会在想啊想啊(🛐),用什(🐡)么命(🙊)名法好呢(🚛)?对于经(🌕)常在C 、(🎄)Java、Python等主流语言上切换的强迫症来说,换个语言换种(🛫)命名风格简直不要太(⏸)混乱。
既然有这么多命名要(🍪)做(📻),不(🗾)妨做好它。本期内容中,异步(👵)君(💪)为大(🍬)家带来了(🔂)起(⏰)个好名字应遵从的几条(👶)简单规则,一起来看看吧
— 01 —
名副(👞)其实
名副其实说起(🔋)来简单(🌘)。我们想(👬)要强调,这事很严肃。选个好名字要花时间(💽),但省下来的时间比花掉的多。注意命名,而且一旦发现有更好的名称,就换掉旧的。这(💇)么做(😉),读你代码的人(包括你(🦅)自己)(🍁)都(🖼)会(😐)更开心。
变量、函数(👹)或类的(🧟)名称应该已经答复(🏺)了所有的大问题。它该告诉你,它为什(😟)么会存(🍿)在,它(🍈)做什么事(🧥),应该怎么用。如(🌘)果(⛲)名称需要注释来补充,那(💦)就不算是名副其实。
名称d什么也没说明。它(🔋)没有引起读者对时(💓)间消逝的(🛁)感觉,更别说以日计了。我们应该选择指明了计(🤝)量对象和计(💭)量单位的名称:
选择体现本意的名称能让人(😅)更(⚽)容易理(🆚)解(😃)和修改(🌒)代(🏐)码(🥍)。下列代(💶)码的目(⏮)的何在?
为什么(🎛)难以说明上述代(🕗)码要(🏂)做什(🤠)么事?里面并没有复杂的表达式,空格和缩进中规中矩,只用到三个变量(🍇)和两个常量,甚至没有涉及(📂)任何其他类或多(💄)态方法,只是(或者看起来是)一个数组的列表而已。
问题不在(🐋)于代码的简洁度,而在于(🔤)代码的模(🍵)糊度:即(🕣)上下文在代码中未被明确体(🤩)现的程度。上(😿)述(📰)代码要求我们了解类似以下问题(💦)的答案(😳):
(1)theList中是什么(😚)类型的(✉)东西?
(2)(🧛)theList零下标(👐)条目的意义是什么?
(3)值4的意(🔗)义是什么?
(4)我(🥃)怎么使用返回的列表?
问题的答案没体现(😙)在代码段中,可代(🦖)码段就是(🤰)它们该在的(🌅)地方(🛸)。比方说(🏅),我们在开发一种(🕕)扫雷 游戏 ,我们发现,盘面是(👲)名为(🏪)theList的单元格列表,那就(🐕)将其名称改为gameBoard。
盘面上每个单元格都用一个简单(📛)数(🍐)组表示。我(👓)们还发现,零下标条目是一种状态值,而该种状态值为4表示(🚔)“已标记”。只(💒)要改为有意义的名称,代码就会(✝)得到相当程度的改进:
注意,代码的简(🥕)洁(🤧)性并未被触及。运算(🔇)符和常量的数(♿)量全(📩)然保(🛋)持不(🤔)变,嵌套数量也(🚮)全然保持不变,但(♌)代码变得明确多(💗)了(🔅)。
还可(🛑)以更进一步,不用(👴)int数组表示单元格,而是另写一个类。该类包括一个名副其实的函数(称(🤼)为(📊)isFlagged),从而掩(🍱)盖住那个魔术数(💞)[1]。于是(❇)得到函数的(💕)新版(⬇)本:(🕷)
只要(🧞)简单改一下名称,就能轻易知道发生了什么。这就(🙊)是选用好名称(😯)的力量。
—(👍) 02 —
避免(⏪)误导
程序员必须避免留(🍗)下(🥤)掩藏代码本意的错误线索。应当避免使用与本(🚀)意相悖的词,例如(🕒),hp、(💮)aix和(👜)sco都不该用作变量名,因(🐖)为它(🚉)们都是Unix平(🍂)台或(🗑)类(🎠)Unix平台的专有名称。即便你是在编写三角(🐙)计算程序,hp看起(💓)来是一个不错的(🍹)缩写(🐹)[2],但那也可能(🥐)会提(🏎)供错(🌽)误信(🛎)息。
别用accountList来(🔮)指称一组账号,除非(🍈)它真(🔺)的是(🖤)List类型。List一词对程(🕵)序(🍶)员有特(👂)殊意义。如果包(♟)纳账号的容器并非真是(🐳)一个List,就会引起错误的判断(🧢)。
所(♏)以,用accountGroup或bunchOfAccounts,甚至直接用accounts都会好一些。
提防使用(🈂)外(💨)形相似(🕓)度(🥃)较高的名称(🕚)。例如,想区分模块中某处的XYZControllerFor-EfficientHandlingOfStrings和另一处的XYZControllerForEfficientStorage-OfStrings,会花多(📀)长时间(🚫)呢?这两(♑)个词的外形实在太相似了。
以同样(🌷)的方(🤞)式拼(👕)写出同样的概念才是信(💱)息。拼(🎬)写前后不一致就是误导。我们很享受(🚲)现代Java编(🦎)程环境的自动代码完(🎏)成特性。键入某(🍭)个名称的前几个字母,按一下某个热键组合(如果有的话(😃)),就能得(🕴)到一列该(🚻)名称的可能形式。
假如相似的名称依(📲)字母顺序放在一(🏓)起,且差异(⛎)很明显,那就(🐄)会相(🤽)当有(🤚)助益,因为程序(🗽)员多(🐈)半会(🙍)压根不看你的详细(♏)注释,甚至(🐀)不看该类的方(🎦)法列表就直(🍍)接看名字(⛅)挑(😙)一个(🎲)对象。
误导性名称真正(🚐)可怕的例子,是用(🔡)小写字母l和(🍐)大写字母(🧑)O作(🕘)为变量名,尤(📽)其是在组合使(🥣)用的时(🏌)候。当然,问题(🛳)在于它们看起来完全(📕)像是常量“壹”和“零”。
读者可能会认(🧐)为这纯属虚构(🥓),但我们确(🐠)曾见过充斥这类名称的代码。有一次,代码作(👡)者建(🆔)议用(📢)不同字体写变(🤯)量名(👮),好显得更(♑)清楚些,但前(⬛)提是这种方(🥊)案得要通过(💒)口头和书面传递(🚨)给(🔓)未来所有的开(🔚)发(🦀)者才行。后来,只(🌫)是做了简单的重命名操作,就解决了问题,而且也没(💓)引起(🚹)别的问题。
— 03 —
做有意义的(🎿)区分
如果程序员只是(🌵)为满(✝)足编译器或解释器的需要(🅱)而写(🐫)代码,就(🌼)会制造麻烦。例如,因为同一(🌮)作用(💏)范围内两样不(🔆)同的东西不能重名,你(📒)可能会(🎇)随(🧞)手改(🛒)掉其中一个的(🛢)名(⬜)称,有(🐚)时干脆以错误的(🏰)拼写(🍝)充数,结果就会出(🕛)现(🔩)在更正拼写错误后导致编译器出错的(😩)情(🆔)况。
光(😉)是添加数字系(🍡)列或是废话远(🤫)远不(✉)够,即便这足以让编译器满(🕵)意。如果名称(🐇)必须相异,那么其意思也(📎)应该不同才对(👼)。
以数字系列(♐)命(💎)名(a1、a2…aN)是依义命名的对立面。这(🧖)样的名称纯属误导—(🗾)—完全没有提供正确信息,没(👊)有提供导向作者(🎺)意图的线(🔼)索。试(🦅)看(✍):(🏼)
如果参数名改(⏭)为source和destination,这(🐠)个(✈)函数就会像样(🏮)许多。
废话是另一种没意义的区分。假设你有一个Product类,如(⛅)果还有一个名为ProductInfo或ProductData的类(⭕),那它们的名称虽然不同,意思却无区别。Info和Data就像(🎉)a、an和the一样(🥡),是意义含混的废话。
注意,只要(🙍)体现出有意义的区分,使用a和the这样(🃏)的前缀就没错。例(🍡)如,你(❤)可能把a用(🧦)在域内(🍺)变量(🖱),而把the用于函(😦)数参数[5]。但如(⏱)果你已经有一个名为zork的(💦)变量(😱),又想调用一个名为theZork的变量,麻烦就来了。
废话都是冗余。variable一词永远不应当出现在变(💽)量名中。table一词永远(🀄)不应当出现在(〰)表名中(🎇)。NameString会比Name好(🚫)吗?难(🕘)道Name会是一个浮点数?如果是这样,就违(🛎)反了(🗡)关于误导的规则。
设(🤵)想有一个名为Customer的类,还有一(👇)个名为CustomerObject的类,它们的(👈)区别(😅)何在呢?哪一个是表示客户 历史 支付情况的(🎇)最佳方式?
有一个应用反映了这种状况。为(🍒)当事者讳,我们改了(🥃)一(🧝)下,不过犯错的代码的确就(🚄)是这(🎫)个样子:(🧀)
程序员怎么(🏨)知道该(🔌)调用哪个(💢)函数呢?(👿)
如果缺少明确约定,那么变量moneyAmount与money就没区别,customerInfo与(🥇)customer没区别,accountData与account没区别,theMessage也(⛷)与message没区别。要区分名称,就要以读者能鉴别(📴)不同之(🍽)处的方式(🎎)来(👨)区分。
—(🕦) 04 —
使用(📭)读得出来的名称
人类长于记忆和使用单词。大脑(💟)的相当一部分就是用来容纳和处理单(⚽)词的。单词能读得(⚫)出来。人类(🍝)的大脑中有那么大的一块地方用(🍁)来处理言语,若不(🚉)善加(🚒)利用,实(⛅)在(🔺)是种耻辱。
如果名称读不出来,讨论的时候就会像(📰)个傻鸟。“哎,这儿,鼻涕阿三喜摁踢(bee cee arr three cee enn tee)[6]上头,有个皮挨死极翘(🈺)(pee ess zee kyew)[7]整数,看见没?(🧗)”这不(♊)是小事,因为编程本就是(🏡)一(🐃)种 社会 活动。
有一家公司,程(🎭)序(⛽)里面写了一个genymdhms((⏯)生成日(💀)期,年、月、日、时、分(🙅)、(🈳)秒),他们一般读作“gen why emm dee aich emm ess”[8]。我有见字照拼读的(🍤)恶习,于是开口就念“gen-yah-mudda-hims”。
后来好些设计师和分析师都有(🕝)样学样,听起来傻乎乎的(🐲)。我们(🙂)知道典故,所以会觉得很 搞笑 。 搞笑 归 搞笑(🥀) ,实际是在强(🔯)忍糟糕的命名。在给新开发者解释(⛏)变量名的(😭)意义时,他们总是(🤳)读出傻乎(🚯)乎的自造词,而(🎈)非恰当的(⛸)英语词。比较(😬)
现(💚)在读(❕)起来就像人话(🔅)了:“喂(🔣),Mikey,看看这条记录!生成时间戳(generation timestamp)[9]被设置为明天了!不(🤳)能这样吧?”
— 05 —
使(🙂)用(🍏)可搜索的名称
对于单字母名(🤚)称和数(🍧)字常量,有一个问题,就是很难在一大篇文字中(🚋)找出来。
找MAX_CLASSES_PER_STUDENT很容(🐢)易,但想找数(🍒)字7就麻烦了(🎳),它可(🚿)能是某些文(📤)件名或(♍)其他常量定义的一部分,出(🕖)现在因不同意图而采(🎮)用的各(📬)种表达式中(🔙)。如果该常量(🥝)是个长数字,又(🔂)被(💡)人(⏳)错改过(🚿),就会逃过搜索,从而(🔂)造成错误。
同样,e也不是一个便于搜(🛶)索(🍊)的(🕴)好变量名,它是英文中最常用的字母,在每个程序(🛷)、每段代(🍈)码(👧)中都有可能(🆕)出现。由此而见,长(💙)名称(💷)胜(🌇)于短名称,搜得到的名称胜于用自造编码代(👩)写就(😘)的名称。
窃以为单字母名称(🧐)仅(👪)用于短方法中的本地变量。名称长短应与其作用域大(👬)小相对应 [N5]。若(💌)变量或常量可能在代(🚴)码中多处(🦏)使用,则应赋予其便于搜索的名称。再比较:
注意,上面代码(🔘)中的sum并非特(🛏)别有(🈁)用的名称,不过至少搜得到(🎥)它。采(🐨)用能表达意(👣)图的名称,貌(🍊)似(🌽)拉长了函数代(💖)码,但(🕹)要想(🤵)想看(🍍),WORK_DAYS_PER_WEEK比数(🍁)字5好找得多,而列表中(🎚)也只剩下了体现作者意图的名称。
—(🍹) 06 —
避免使用编码
编码已经太多,无谓再自(🀄)找(🚒)麻烦。把类型或作用域编进名称里面(🤡),徒然增加了解(🐩)码(👆)的负担。没理由要求(🛰)每(🥙)位新人(🦄)都在弄(🐠)清(🥖)要应付的代码(🌦)之外((✍)那算(👞)是正常的),还要再(😯)搞懂另一种编码“语言(🔤)”。这对解决问题而言,纯属多(❄)余(⭕)的负担。带编码的名称通常也不便发音,容(🌈)易打错。
匈牙(🚁)利语标记(🏔)法(🎸)
在往昔名称长短很重(😫)要(😯)的时代,我们毫无必要地破坏了(🦗)不编码的规矩,如今后悔(🥥)不迭。Fortran语言要求首字(📞)母(🚝)体现出类型,导致(🤪)了(🍘)编码的产生。BASIC语言的早期版本只允许使用一个字(✝)母再加上一位数(🛃)字。匈(🌺)牙利语标记法[10](Hungarian Notation,HN)将这种(🎍)态势愈演(👩)愈烈。
在(💪)Windows的C语言(💴)API的时代(😀),HN相当重(🍽)要(🕊),那时所有名称(🧀)要么(🚂)是(🐫)一个整数句柄,要么是一个长指(🍖)针或者void指针(🐝),要不然就是string的几种实现(⌚)(有不同(🦄)的(👨)用途和属性)(👳)之一。那时候编译器并不做(🍋)类型检查,程序员(🚓)需要匈牙利语标记法来(🏞)帮助自己记住(🚯)类型。
现代编(💐)程语言具有(🕤)更丰富的(🥊)类型系统(🆘),编译器也记得并强(📍)制使用类型。而且,程序员(🚀)趋向于使用更小的类、更短的方法,好让每个(🕳)变量的定义(😾)都在视野范(🍲)围(🐠)之(🧗)内(🐂)。
Java程序员(🎷)不需(🥗)要类型编码,因为对象是(🥢)强类(⏸)型的,代码编辑环境已经先进(🚛)到在(♌)编译开始前就能监测(🚐)到(🔤)类型(📝)错误的程度!所以,如今HN和其他(⬇)的类型编码形式都纯属多余。它(🧝)们增加(🚭)了修改变量、函数或类的名称或类型的难度,它们增加了阅读代码的难度(♒),它(🏣)们制造了让编码系统误导读者的可能性。
成员前缀
也(🕞)不必(🌷)用m_前缀来标明成员变量。应当把(🔫)类和函数做得足够小,以消除对成(🚚)员(🤩)前缀的需要。你应当使用某种(🌈)可以高亮或用颜色标出成员的(🏈)编辑环境。
此外,人们会很快学会无视前缀(或后缀),而只看到名称中有意义的部分。代码读得越(📄)多,眼中就越没有前缀。最终(😊),前(💣)缀变作(⏪)了不入法眼的废料,变作了旧代码的(🍕)标(🔇)志物。
接口和实现
有时也会出现采用编码的特殊情形。比如,你在做一个创建形状用的抽象工厂(Abstract Factory),该工厂是一个接(🛹)口,要用具体类(🎸)来实现(🏖)。你怎么来命名工厂和具体类呢?IShapeFactory和ShapeFactory吗?(🎿)我喜欢不加修饰的接口。前导字母I被滥用到了说好听点(🌘)儿是干扰,说(🔫)难听点儿根(⏩)本就是废话的程度(🔁)。
我(🔞)不想让(📝)用(🔹)户(🐜)知(🚭)道我给他们的是接口,而就想让他们知道那是一(🛏)个(😐)ShapeFactory。如果在接口(🍖)和实现中必须选其一来(✏)编码的(🕒)话,我宁(👢)肯选择实现。ShapeFactoryImp,甚至是丑陋的CShapeFactory,都比对接口(🔝)名称编码好。
-END-
代码整洁之(😫)道
作者: [美] 罗伯特·C. 马(🤲)丁(Robert C. Martin)(🔭)
译者: 韩(🥣)磊(📺)
内容简介:
软件(👟)质量,不但依赖架构及(👞)项目管(🚶)理,而且与(🍲)代码质量(⛱)紧密相关。这一点(🌗),无论是敏捷开发流派还是传统开(🔔)发流派,都不得(🌚)不承认。
本书提出一种观点(📗):代(⛺)码质量与其整洁度成正比(🚶)。干净的代(🥝)码(🐵),既在质(🍋)量上较为(🧕)可靠,也为后期维护、升级奠定了良(⬛)好(🕖)基础。作为编程领域的佼佼(🍗)者,本书作者给出(🍭)了一系列行之有效的整洁(😝)代码操作实践。这些实践在(😚)本书中(🍆)体现为一(💫)条条规(🎷)则((🚎)或称“启示”),并辅以(❌)来自(🎟)实际项目(➰)的正、反(🐂)两面(👢)的范(🧣)例。只要(🎨)遵循这(🤞)些规则,就能编写出干净的代码,从而有效提升代码质量。
本(➗)书阅读对象为一切有志(🤦)于改善代码质量的(🈂)程序员及技(🍛)术经理。书中介绍的规则均来自作(😭)者多年(🗂)的(🗡)实践经验,涵盖从命名(🍀)到重构的(🍻)多个编程方面(💡),虽为一“家”之言,然诚有可资借鉴的价值。