《编写可读代码的艺术》总结与分享
《编写可读代码的艺术》
本书是一本帮助代码写的更好的书,本人阅读时间为国庆一周,读完觉得这本书很有意思,因此准备做个总结,但是厌烦了很多书的总结,枯燥乏味且罗里吧嗦,把书的目录和章节列出来,并且每个部分讲点就结束,实在是即浪费自己的时间也浪费别人的时间,因此本人尽量用精简和核心的话,去重新理解这本书。我决定用新的方式去理解这本书,每个章节、知识点。
本书目的
一句话概括:本书的最终目的是如何写出让别人容易理解的代码。
(你是否看到别人的代码一头雾水,各种稀奇古怪的拆分,各种乱糟糟的函数体,一个方法中几百行,不断涌现的成员变量,各种地方的判空,寥寥无几的注释,我们无法改变别人,但是可以改变自己避免成为这样的人。)
本书结构
一句话概括:从外表(变量名、注释、排版)到内在(更容易理解的逻辑和结构)
1. 外表(变量名,注释,排版)
一句话概括:
- 变量名需要携带更多的信息,类型也好,功能也好,同时作用域越大,变量名也应该涵盖的信息越多。
- 写出让读者理解代码的注释(类级别写全局观的注释,具体细节写怎么做,有坑的地方写很难处理)
- 使用换行与列对齐将“相似的代码看起来很相似”,优化代码的顺序使其更美观,复杂的流水线步骤代码添加空行分割步骤
1.1 变量名
变量名对于他人阅读代码起到了决定的作用,可以通过变量名了解做了哪些事,因此避免tmp和retval无意义的名字,也需要避免多重循环中i,j,k的使用造成理解的混乱。
那么,如何才能写出优雅的变量名呢?
- 从变量的外观,也就是名字的长度来说,名字越长越难理解,但同时涵盖信息会越多,因此在小的作用域里可以采用非常简单的短名字,而在较大的作用域中,需要涵盖更多信息表达更清楚。尽管短名字是好事,但是不可以为了故意缩短而采用奇葩的缩写,也许doc代替document很正常,但是新人真的很难理解BEManager意思是BackEndManager。
- 从变量的结构来说,对于度量单位、特殊进制等,需要加上单位,比如size改为size_mb,delay改为delay_secs等,十六进制的id改为hex_id。因此成员变量中往往会添加m,比如mTextView,m代表的是member,因此可以直接在代码中发现mTextView.setText,你也知道是成员变量的方法,而不是局部变量的方法。
- 从变量的意义来说,最好的变量不会产生误解,那么什么样的变量名才会产生误解,①通常来说发生在边界情况,比如max,min,begin,end,first,last,max和min用来描述最大最小,begin和end用来描述第一个和最后一个的下一个,first和last用来描述第一个和最后一个。②通常来说约定俗成的方法名,例如get(),size(),通常情况下认为这两个方法都是轻量级的,如果复杂度过高可以加上令人感到有负担的词,比如getFrom.....,countSize..等等。
1.2 排版
排版可以分为三种,留白、对齐、顺序。使用好这三种,就可以使代码更易读。
- 从留白方面:留白简单来说就是换行,尽量保证相似代码的一致性,可以通过添加换行引入,以及新增方法来整理不规则的重复。同时不要把所有的方法都放在一个巨大代码块中,应当按照逻辑添加空行进行分组。把代码分成段落。
- 从对齐方面:首要推荐列对齐,因为整齐的边界和列会很容易浏览文本,对于多行相似,但参数不同的代码,更应该使用列对齐,让其参数列对齐,更容易发现不一致。简单来说,就是对齐相似的代码,更容易发现相似代码中的不同,避免因混乱从增大代码阅读成本。
- 从顺序方面:推荐使用声明的顺序,可以更快速的理解代码。
1.3 注释
注释可以从什么地方需要注释?什么地方不需要注释?如何写注释三个方面进行阐述。
- 什么地方不需要注释:通俗来说就是不需要写没有价值的注释,主要有两点,①不要写从代码本身就能快速推断的事实写注释,说白了不要给一些很容易看懂的代码写注释。②不要上来就开始写注释,而是应该把变量or方法的名字改好,好的代码>坏代码+注释,如果方法和变量的名很通俗易懂,那么压根不需要注释。
- 什么地方需要注释:这边作者给出很多意见,提炼总结后,可以换位思考,我们在什么情况下,最希望看到注释,当然是我们需要快速理解一段代码的意图,是否有坑点,以及一些常量的值为什么设置成这样。因此答案呼之欲出:从快速理解代码的意图,可以看出①我们可以在整个文件级别的代码中写上这个文件的作用。②同时我们可以在复杂的包含多个长函数的方法中写上做了什么,核心要点是帮助读者能够快读理解。③最重要的一个观点,写下你的见解评论,例如你觉得这段代码有点累赘,变的越来越乱,就可以写上。从是否有坑点的角度,可以看出③为代码中的缺陷写上注释,例如代码没有完成xxx,代码在xx方面有性能问题。从常量角度,可以④为常量编写注释,也许是延时的ms数,也许是次数,这些数量在当时很正常,不需要注释,但是需要标注为什么这样设置的原因,以便于后期程序可能修改一个高度精细调整过的值。
- 如何写注释:核心三点,最重要一点是假装自己是读者,希望看到什么样的注释,其次是避免使用不明确的词让读者看着一头雾水,最后是在一些特别复杂的函数且难以描述时,可以直接将输入输出的结果来表明特殊情况。
2. 简化逻辑与循环(相对于第一节的外表,更深层)
本章主要是最小化代码中的"思维包袱"使代码容易理解。相比于第一节外表花很小代价就可以应用来说,本章具有进阶的操作以及更好的收益。
2.1 控制流
控制流可以分为条件语句以及循环语句两部分,其中条件语句的核心在于判断,循环语句的核心在于判断条件退出。
- 条件语句:条件语句的核心点在于判断,①判断数量时。推荐使用左值为变量,右值为常量。因为这样更符合日常语言的表达方式,例如if(length < 10)而不是if(10 > length),我们在日常生活中也会很自然的说,如果桌子的长度小于10,而不是如果10大于桌子的长度,用来作比较时,右值会是更稳定的值。②判断布尔值时,推荐先处理正逻辑而不是负逻辑,以及先处理简单的情况。特别是避免if(!url.hasQuery("extParams")),这让人看的真是一脸懵逼,很难反应过来。
- 循环语句:避免使用doWhile(这个没什么好说的,应该不会有人使用doWhile语句)。
- 以上的控制流的重点在于最小化嵌套,也就是嵌套越多越容易发生bad case,而通过提早返回,也就是提前return,就可以减少嵌套,在循环中,也就是continue。
2.2 超长的表达式
遇到超长的表达式,谁看谁懵逼,那么如何才能拆分超长的表达式呢?首先来分析超长表达式的原因,我们在日常的代码中看见超长表达式无非有三个原因。
- 具有很长的拿到某变量的表达式,例如request.user.id == document.loacl.user.id,也许这段表达式很容易理解,但如果这个表达式只是某个表达式中的一个判断条件,那就是很难受了,因此改变的方法就是总结变量,将很长的变量装入一个新变量,目的就是用一个短名字来代替一大块代码。
- 对一些比较离奇古怪的判断简化逻辑,这里推荐使用德摩根定理。简单来说就是“非(P 且 Q) = (非 P) 或 (非 Q)”,在遇到if(!(a && !b))时,应当进行转换为if(!a || b)
- 拆分巨大的语句,往往遇到一些非常庞大的语句时,可以提炼整理共有变量、相似表达式,甚至可以抽成方法进行服用。使读者能够更好的消化。
2.3 变量与可读性
首先作者在书中认为三个重要的观点,这三个重要的观点非常影响变量的定义。
- 变量越多,越难跟踪他们的动向。
- 变量作用域越大,跟踪动向就越久。
- 变量改变越频繁,越难跟踪变量当前值。
 因此这三个重要的观点可以提炼出三个重要的结论,越少的变量、越小的变量作用域、多定义const变量。
- 越少的变量:在日常的代码中,最容易累赘的便是临时变量,在日常开发中很容易遇到一些临时变量,这些临时变量大多数是为了拆分复杂的表达式,开发者以为能用到,但是没有用到,导致冗余的结果,因此有如下三个建议:①拆分复杂表达式的临时变量注意是否真的需要。②对于一些中间结果的临时变量看是否可以立即处理进行优化。③减少为了控制流的判断专门提取的临时变量,可以直接用。
- 越小的变量作用域:在开发中,要避免使用全局变量,因为全局变量很难维护,代码可能会意外改变全局变量的值,尽管本意是使用局部变量。
- 多定义const变量:永久固定的变量更容易思考,无法永久固定时,也尽量变量少改动。
3. 重新组织代码
相比于第二节是在语句层面上进行的改动,本章聚焦于函数级别对代码做出的改动,该章提出的方法也是最容易采用的方法,在日常开发中很容易去使用且维护。我受本章的影响最深,也是改变最大的地方,能够立竿见影的改变整体的代码的复杂度。
3.1 抽取不相干子问题
一句话总结:“把一般代码和项目专有代码分开”,从日常代码的开发中来看,大部分代码都是一般代码,通过建立一大组方法来解决一般问题,剩下的则是程序的特有专有部分去使用这个一般代码。因此在开发过程中,需要积极的“抽取不相干的子问题”,将不相干的子问题抽取出方法,能够从根本上改进代码。同时还伴随另外一个好处:对于子问题的解决更加完善,同时更好的复用。
具体的方法论
- 对某段代码块进行分析,这段代码的高层次目标是什么?
- 对每行代码进行思考,这行代码是为了目标工作吗?是不是实现最高层次目标?
- 如果不是,直接抽取代码到独立方法中。
例如一个简单的例子,找到距离给定点距离最近的位置。伪代码阐述很容易。首先通过for循环遍历所有的点,其次每个点计算之间的距离同时比较,输出最短距离的位置。我们可以看出,计算每个点之间的距离,这段代码就不是为了最高层次目标工作的,因此可以直接将该代码块抽出为一个方法,这样整体的逻辑更加紧密,更容易理解。
3.2 方法只干一件事
这个指导很容易理解,一个方法只干一件事。此处从两个地方进行解读,一是写代码时,需要保证一个方法只干一件事,最难的是如何准确描述程序的这些每一件事。二是读别人代码读不懂时,最重要的步骤是:将这段代码的所做的所有任务列举出来,有一些任务可以很容易的变成单独的函数,从而更容易理解代码。
3.3 寻找更简洁的解决方案
这里的标题过于简单粗暴,实际上包含两点,第一点,把想法变成代码,而不是边写代码边思考。第二点,少写代码,多复用轮子。
第一点类似于“橡皮鸭技术”,把代码的逻辑结构通过自然语言说出来,可以更好的写出更自然的代码,同时在描述中提到的重点短语和词汇,可以提炼出来当作子问题。
第二点则是目前部门在推崇的(因为包体原因)复用和下线无用代码。首先需要评估拆分需求,把问题削减成一个简单的问题,其次尽量复用已有的轮子,推荐新人先熟悉周边的库,提前花费15分钟熟悉已有的周边库,能够节省时间与少写很多代码,因为已有的库往往经历过大量的设计、调试、文档、优化、测试,非常有价值。最后就是需要及时的下线无用代码,让整体代码更加的精简。
 
                     
                    
                 
                    
                 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号