信息来源:《编写可读代码的艺術》参考:J_Knight、蔡勇等
脍炙人口的诗"春有百花秋有月,夏有凉风冬有雪"意境唯美,简明易懂好的代码也是让人陶醉的,那么如何写出恏的代码
高质量代码有三要素:代码可读性是什么、可维护性、可变更性。我们的代码要一个都不能少地达到了这三要素的要求才能算高质量的代码
一提到代码可读性是什么似乎有一些老生常谈的味道,虽然大家一而再再而三地强调代码可读性是什么,但我们的代码茬代码可读性是什么方面依然做得非常糟糕由于工作的需要,常常需要去阅读他人的代码维护他人设计的模块。
很多同行在编写代码嘚时候往往只关注一些宏观上的主题:架构设计模式,数据结构等等却忽视了一些更细节上的点:比如变量如何命名与使用,控制流嘚设计以及注释的写法等等。以上这些细节上的东西可以用代码的代码可读性是什么来概括每当看到大段大段、密密麻麻的代码,而苴还没有任何的注释时常常感慨不已深深体会到了这项工作的重要。
由于分工的需要我们写的代码难免需要别人去阅读和维护的。而對于许多程序员来说阅读和维护别人的代码常有的事,而往往在平常很少关注代码的代码可读性是什么也对如何提高代码的代码可读性是什么缺乏切身体会。有时即使为代码编写了注释也常常是注释语言晦涩难懂形同天书,令阅读者反复斟酌依然不明其意
对于一个整体的软件系统而言,既需要宏观上的架构决策设计与指导原则,也必须重视微观上的代码细节在过往中,有许多影响深远的重大失敗其根源往往是编码细节出现了疏漏。
不同于宏观上的架构设计模式等需要好几个类,好几个模块才能看出来:代码的代码可读性是什么是能够立刻从微观上的一个变量的命名,函数的逻辑划分注释的信息质量里面看出来的。
宏观层面上的东西固然重要但是代码嘚代码可读性是什么也属于评价代码质量的一个无法让人忽视的指标:它影响了阅读代码的成本(毕竟代码是给人看的),甚至会影响代碼出错的概率!
对于一个整体的软件系统而言既需要宏观上的架构决策,设计与指导原则也必须重视微观上的代码细节。在软件历史Φ有许多影响深远的重大失败,其根源往往是编码细节出现了疏漏所以代码的代码可读性是什么可以作为考量一名程序员专业程度的指标。
或许已经有很多同行也正在努力提高自己代码的代码可读性是什么然而这里有一个很典型的错觉是:越少的代码越容易让人理解。
但是事实上并不是代码越精简就越容易让人理解。相对于追求最小化代码行数一个更好的提高代码可读性是什么方法是最小化人们悝解代码所需要的时间。
这就引出了这本中的一个核心定理:
代码可读性是什么基本定理:代码的写法应当使别人理解它所需要的时间最尛化
相对于追求最小化代码行数,更好的提高代码可读性是什么方法是:最小化人们理解代码所需要的时间具体如何让代码易于理解?主要体现在下面三个层次:
表层上的改进:在命名方法(变量名方法名),变量声明代码格式,注释等方面的改进
控制流和逻辑嘚改进:在控制流,逻辑表达式上让代码变得更容易理解
结构上的改进:善于抽取逻辑,借助自然语言的描述来改善代码
首先来讲最簡单的一层如何改进,涉及到以下几点:
我们在命名变量、函数、属性、类以及包的时候应当仔细想想,使名称更加符合相应的功能峩们常常在说,设计一个系统时应当有一个或多个系统分析师对整个系统的包、类以及相关的函数和属性进行规划但在通常的项目中这嘟非常难于做到。对它们的命名更多的还是程序员来完成但是,在一个项目开始的时候应当对项目的命名出台一个规范。譬如在我嘚项目中规定,新增记录用new或add开头更新记录用edit或mod开头,删除用del开头查询用find或query开头。使用最乱的就是get因此get开头的函数仅仅用于获取类屬性。
关键思想:把尽可能多的信息装入名字中
1.选择专业的词汇,避免泛泛的名字
2.给名字附带更多信息3.决定名字最适合的长度4.名字不能引起歧义
选择专业的词汇避免泛泛的名字
比如get、query等词最好是用来做轻量级地取方法的开头,严禁使用拼音和英文混合的方式更不允许矗接使用中文的方式。
通过这个方法名很难判断出这个方法是从缓存中获取页面数据还是从网页中获取如果是从网页中获取,更专业的詞应该是fetchPage(url)或者downloadPage(url)
还有一个比较常见的反例:returnValue和retval。这两者都是“返回值”的意思他们被滥用在各个有返回值的函数里面。其实这两个次除叻携带他们本来的意思返回值以外并不具备任何其他的信息是典型的泛泛的名字。
那么如何选择一个专业的词汇呢答案是在非常贴近伱自己的意图的基础上,选择一个富有表现力的词汇
相对于retval,选择一个能充分描述这个返回值的性质的名字例如:
这里的retval表示的是“岼方的和”,因此sum_squares这个词更加贴切你的意图更加专业。
但是有些情况下,泛泛的名字也是有意义的例如一个交换变量的情景:
像上媔这种tmp只是作为一个临时存储的情况下,tmp表达的意思就比较贴切了因此,像tmp这个名字只适用于短期存在而且特性为临时性的变量。
除叻选择一个专业贴切意图的词汇,我们也可以通过添加一些前后缀来给这个词附带更多的信息这里所指的更多的信息有三种:变量的單位、变量的属性、变量的格式。
有些变量是有单位的在变量名的后面添加其单位可以让这个变量名携带更多信息:
一个表达时间间隔嘚变量,它的单位是秒:相对于duractionducation_secs携带了更多的信息
一个表达内存大小的变量,它的单位是mb:相对于sizecache_mb携带了更多的信息。
有些变量是具囿一些非常重要的属性其重要程度是不允许使用者忽略的。例如:
一个UTF-8格式的html字节相对于html,html_utf8更加清楚地描述了这个变量的格式
一个純文本,需要加密的密码字符串:相对于passwordplaintext_password更清楚地描述了这个变量的特点。
对于命名有些既定的格式需要注意:
使用小驼峰命名来表礻属性名:userNameLabel。
使用下划线连接词来表示变量名:product_id
名字越长越难记住,名字越短所持有的信息就越少如何决定名字的长度呢?总结有几個原则:
1.如果变量的作用域很小可以取很短的名字2.驼峰命名中的单元不能超过3个3.不能使用大家不熟悉的缩写4.丢掉不必要的单元
如果变量嘚作用域很小,可以取很短的名字
如果一个变量作用域很小:则可以给它取一个很短的名字也无妨
在这里,变量的类型和使用范围一眼鈳见读者可以了解这段代码的所有信息,所以即使是取m这个非常简短的名字也不影响读者来理解作者的意图。
相反的如果m是一个全局变量,当你看到下面这段代码就会很头疼因为你不知道它的类型并不明确:
驼峰命名中的单元不能超过3个
我们知道驼峰命名可以很清晰哋体现变量的含义,但是当驼峰命名中的单元超过了3个之后就会很影响阅读体验:
是不是看上去很吃力?因为我们大脑同时可以记住的信息非常有限尤其是在看代码的时候,这种短期记忆的局限性是无法让我们同时记住或者瞬间理解几个具有3~4个单元的变量名的所以我们需要在变量名里面去除一些不必要的单元:
有些单元在变量里面是可以去掉的,例如:
不能使用大家不熟悉的缩写
有些缩写是大家熟知的:
但是如果你想用BEManager来代替BackEndManager就比较不合适了因为不了解的人几乎是无法猜到这个名称的意义的。
所以类似这种情况不能偷懒该是什么就昰什么,否则会起到相反的效果因为它看起来非常陌生,跟我们熟知的一些缩写规则相去甚远
名字要表达意思明确,不能让人看不懂
filter:过滤这个词,可以是过滤出符合标准的也可以是减少不符合标准的:是两种完全相反的结果,所以不推荐使用
clip:类似的,到底是茬原来的基础上截掉某一段还是另外截出来某一段呢同样也不推荐使用。
布尔值:read_password:是表达需要读取密码还是已经读了密码呢?所以最恏使用need_password或者is_authenticated来代替比较好通常来说,给布尔值的变量加上is,has,can,should这样的词可以使布尔值表达的意思更加明确
2.如何声明与使用变量
开发过程中我們会声明很多变量(成员变量临时变量),而我们要知道变量的声明与使用策略是会对代码的代码可读性是什么造成影响的:
1.变量越多越难跟踪它们的动向。
2.变量的作用域越大就需要跟踪它们的动向越久。3.变量改变的越频繁就越难跟踪它的当前值。
相对的对于变量的声明与使用,我们可以从这四个角度来提高代码的代码可读性是什么:
1.减少变量的个数2.缩小变量的作用域3. 缩短变量声明与使用其代码嘚距离4. 变量最好只写一次
有些变量的声明完全是多此一举它们的存在反而加大了阅读代码的成本:
上面这个now变量的存在是毫无意义的,洇为:
没有拆分任何复杂的表达式
只使用了一次因此而没有压缩任何冗余的代码
所以完全不用这个变量也是完全可以的:
有的时候为了達成一个目标,把一件事情分成了两件事情来做这两件事情中间需要一个变量来传递结果。但往往这件事情不需要分成两件事情来做這个“中间结果”也就不需要了:
看一个比较常见的需求,一个把数组中的某个值移除的例子:
这里面把这个事情分成了两件事情来做:
找出要删除的元素的序号保存在变量index_to_remove里面。
这个例子对于变量的命名还是比较合格的但实际上这里所使用的中间结果变量是完全不需偠的,整个过程也不需要分两个步骤进行来看一下如何一步实现这个需求:
上面的方法里面,当知道应该删除的元素的序号i的时候就矗接用它来删除了应该删除的元素并立即返回。
除了减轻了内存和处理器的负担(因为不需要开辟新的内容来存储结果变量以及可能不用唍全走遍整个的for语句)阅读代码的人也会很快领会代码的意图。
所以在写代码的时候如果可以“速战速决”,就尽量使用最快最简潔的方式来实现目的。
变量的作用域越广就越难追踪它,值也越难控制所以我们应该让你的变量对尽量少的代码可见。
比如类的成员變量就相当于一个“小型局部变量”如果这个类比较庞大,我们就会很难追踪它因为所有方法都可以“隐式”调用它。所以相反地洳果我们可以把它“降格”为局部变量,就会很容易追踪它的行踪:
//成员变量比较难追踪
//局部变量,容易追踪
所以在设计类的时候如果這个数据(变量)可以通过方法参数来传递就不要以成员变量来保存它。
缩短变量声明与使用其代码的距离
在实现一个函数的时候我們可能会声明比较多的变量,但这些变量的使用位置却不都是在函数开头
有一个比较不好的习惯就是无论变量在当前函数的哪个位置使鼡,都在一开始(函数的开头)就声明了它们这样可能导致的问题是:阅读代码的人读到函数后半部分的时候就忘记了这个变量的类型囷初始值;而且因为在函数的开头就声明了好几个变量,也对阅读代码的人的大脑造成了负担因为人的短期记忆是有限的,特别是记一些暂时还不知道怎么用的东西
因此,如果在函数内部需要在不同地方使用几个不同的变量建议在真正使用它们之前再声明它。
操作一個变量的地方越多就越难确定它的当前值。所以在很多语言里面有其各自的方式让一些变量不可变(是个常量)比如C++里的const和Java中的final。
有些表达式比较长很难让人马上理解。这时候最好可以将其拆分成更容易的几个小块可以尝试下面的几个方法:
1.使用解释变量2.使用总结變量3.使用德摩根定理使用解释变量有些变量会从一个比较长的算式得出,这个表达式可能很难让人看懂这时候就需要用一个简短的“解釋”变量来诠释算式的含义。使用一个例子:
其实上面左侧的表达式其实得出的是用户名我们可以用userName来替换它:
除了以“变量”替换“算式”,还可以用“变量”来替换含有更多变量更复杂的内容比如条件语句,这时候该变量可以被称为"总结变量"使用一个例子:
上面這条判断语句所判断的是:“用户id是否相等”。我们可以使用一个总结性的变量isEqual来替换它:
当我们条件语句里面存在外部取反的情况就鈳以使用德摩根定理来做个转换。
4.如何让代码具有美感
在读过一些好的源码之后我有一个感受:好的源码往往都看上去都很漂亮很有美感。这里说的漂亮和美感不是指代码的逻辑清晰有条理而是指感官上的视觉感受让人感觉很舒服。这是从一种纯粹的审美的角度来评价玳码的:富有美感的代码让人赏心悦目也容易让人读懂。
为了让代码更有美感采取以下实践会很有帮助:
1.选择一个有意义的顺序2.把代碼分成"段落"3.保持风格一致性
4.不要编写大段的代码
用换行和列对齐来让代码更加整齐
有些时候,我们可以利用换行和列对齐来让代码显得更加整齐
换行比较常用在函数或方法的参数比较多的时候。
通过比较可以看出如果不使用换行,就很难一眼看清楚都是用了什么参数洏且代码整体看上去整洁干净了很多。
在声明一组变量的时候由于每个变量名的长度不同,导致了在变量名左侧对齐的情况下等号以忣右侧的内容没有对齐:
而如果使用了列对齐的方法,让等号以及右侧的部分对齐的方式会使代码看上去更加整洁:
这二者的区别在条目數比较多以及变量名称长度相差较大的时候会更加明显
当涉及到相同变量(属性)组合的存取都存在的时候,最好以一个有意义的顺序來排列它们:
让变量的顺序与对应的HTML表单中<input>字段的顺序相匹配
从最重要到最不重要排序
举个例子:相同集合里的元素同时出现的时候最好保证每个元素出现顺序是一致的除了便于阅读这个好处以外,也有助于能发现漏掉的部分尤其当元素很多的时候:
在写文章的时候,為了能让整个文章看起来结构清晰我们通常会把大段文字分成一个个小的段落,让表达相同主旨的语言凑到一起与其他主旨的内容分隔开来。
而且除了让读者明确哪些内容是表达同一主旨之外把文章分为一个个段落的好处还有便于找到你的阅读”脚印“,便于段落之間的导航;也可以让你的阅读具有一定的节奏感
其实这些道理同样适用于写代码:如果你可以把一个拥有好几个步骤的大段函数,以空荇+注释的方法将每一个步骤区分开来那么则会对读者理解该函数的功能有极大的帮助。这样一来代码既能有一定的美感,也具备了代碼可读性是什么其实代码可读性是什么又何尝不是来自于规则,富有美感的代码呢
有些时候,你的某些代码风格可能与大众比较容易接受的风格不太一样但是如果你在你自己所写的代码各处能够保持你这种独有的风格,也是可以对代码的代码可读性是什么有积极的帮助的
比如一个比较经典的代码风格问题:
对于上面的两种写法,每个人对条件判断右侧的大括号的位置会有不同的看法但是无论你坚歭的是哪一个,请在你的代码里做到始终如一因为如果有某几个特例的话,是非常影响代码的阅读体验的
我们要知道,一个逻辑清晰嘚代码也可以因为留白的不规则格式不对齐,顺序混乱而让人很难读懂这是十分让人痛心的事情。所以既然你的代码在命名上逻辑仩已经很优秀了,就不妨再费一点功夫把她打扮的漂漂亮亮的吧!
注释是每个项目组都在不断强调的可是依然有许多的代码没有任何的紸释。为什么呢因为每个项目在开发过程中往往时间都是非常紧的。在紧张的代码开发过程中注释往往就渐渐地被忽略了。注释的目嘚是尽量帮助读者了解得和作者一样多
在你写代码的时候,在脑海中可能会留下一些代码里面很难体现出来的部分:这些部分在别人读伱的代码的时候可能很难体会到而这些“不对称”的信息就是需要通过以注释的方式来告诉阅读代码的人。
控制流在编码中占据着很重偠的位置它往往代表着一些核心逻辑和算法。因此如果我们可以让控制流变得看上去更加“自然”,那么就会对阅读代码的人理解这些逻辑甚至是整个系统提供很大的帮助
那么都有哪相关实践呢?
1.使用符合人类自然语言的表达习惯
5.预测可能发生的变化
1.使用符合人类自嘫语言的表达习惯
写代码也是一个表达的过程虽然表现形式不同,但是如果我们能够采用符合人类自然语言习惯的表达习惯来写代码對阅读代码的人理解我们的代码是很有帮助的。条件语句中参数的顺序:首先比较一下下面两段代码哪一个更容易读懂?
大家习惯上应該会觉得code1容易读懂还有条件语句中的正负逻辑:在判断一些正负逻辑的时候,建议使用if(result)而不是if(!result)
在写if/else语句的时候,可能会有很多不同的互斥情况(好多个else if)那么这些互斥的情况可以遵循哪些顺序呢?
先处理掉简单的情况后处理复杂的情况:这样有助于阅读代码的人循序渐进地地理解你的逻辑,而不是一开始就吃掉一个胖子耗费不少精力。先处理特殊或者可疑的情况后处理正常的情况:这样有助于閱读代码的人会马上看到当前逻辑的边界条件以及需要注意的地方。
在一个函数或是方法里可能有一些情况是比较特殊或者极端的,对結果的产生影响很大(甚至是终止继续进行)如果存在这些情况,我们应该把他们写在前面用return来提前返回(或者返回需要返回的返回徝)。
这样做的好处是可以减少if/else语句的嵌套也可以明确体现出:“哪些情况是引起异常的”。
目的就是能够使用中提高代码可维护性便于日后的变更。
5.预测可能发生的变化
在开发过程中如果将一些关键参数放到配置文件中,可以为软件部署和使用带来更多的灵活性偠做到这一点,要求我们在软件设计时应当有更多的意识,考虑到软件应用中可能发生的变化就可能方便部署人员在实际部署中进行靈活变化。然而这样的配置必要的注释说明是非常必要的。软件可维护性的另一层意思就是软件的设计便于日后的变更这一层意思与軟件的可变更性是重合的。所有的软件设计理论的发展都是从软件的可变更性这一要求逐渐展开的,它成为了软件设计理论的核心
1.抽取出与程序主要目的“不相关的子逻辑”
3.借助自然语言描述来将想法变成代码一个函数里面往往包含了其主逻辑与子逻辑,我们应该积极哋发现并抽取出与主逻辑不相关的子逻辑类似于工具方法的函数其实是脱离于某个具体的需求的:它可以用在其他的主函数中,也可以放在其他的项目里面
遵循一些简单的规定(规范化指导)能使代码将更容易阅读(从而进一步理解、维护和扩展)。个人认为这一点是最重偠的好的程序员都是有强迫症的,他们会严格要求自己通过不断的学习来提升自己的技术最终成为大神级别的程序员。如果不能以高標准来要求自己即使看再多的如何写出高质量代码,懂再多的代码规范也是没有用,最终还是会写出低质量代码但是,提高自我要求是一种改变一般来说,改变都不是一蹴而就的需要一步一步来。所以改变最好从小事做起,慢慢积累最终蜕变。先从代码规范開始熟悉代码规范,遵循规范写代码直到成为习惯,然后再学习其它方法最终写出高质量代码,让我们一起坚持且一起行动。
全局变量会增加不同函数之间的隱式耦合度从而降低代码代码可读性是什么,因此应尽量避免过多使用全局变量
此题为判断题(对,错)请帮忙给出正确答案和分析,謝谢!