Clean Code读书笔记

第一章 整洁代码
1.编程要做什么
代码呈现了需求的细节,在某些层面上,这些细节无法被忽略或抽象,必须明确。而将需求明确到机器可以执行的细节程度,就是编程要做的事。
2.项目过程中经常遇到这样的问题:
(1)初期进度很快,后来进度特别迟缓,每次修改代码就要修改其他几处代码
(2)团队合作时代码容易混乱,每个人的代码风格和规范都不一样,别人修改后代码更加混乱
(3)自己写的代码回过头去看时要看很久才知道这段代码的意图
3.代码混乱的代价
发布周期长,缺陷难以修复,装载时间久,崩溃几率大
代 码一旦混乱,团队生产力就容易持续下降,想要增加人手来提高生产力并不可取。第一,新人不熟悉系统的设计;第二,大家都有着提高生产力的压力,更容易制造 混乱的代码。开发团队就想做全新的设计,那就只能分成两队,旧团队负责维护现有系统,新团队不仅要实现旧系统的所有功能,还要跟上对旧系统的持续改进。在 新系统功能不足以抗衡旧系统之前,新系统是不会发布的。可是有两点需要注意:新系统要花多久才能上线,时间久的话会面临开发团队人员变动的问题。
当你发现自己的代码很混乱时,你是会因为各种原因,如进度紧张,修改难度大等而置之不理,还是回过头来修改呢?记得一句话,later equals never。
4.好的代码的要求
(1)有意义的命名,能在字面上表达其含义,代码要在字面上表达其含义,字面编程
(2)力求集中,每个函数、类、模块都专注于做一件事,且这些函数、类、方法、实体等尽可能少
(3)消除重复
(4)有单元测试并能通过测试
如果每个例程都让你感到深合己意,那就是整洁代码,如果代码让编程语言看上去像是专为解决那个问题而存在,就可以称之为漂亮的代码。
 
第二章 有意义的命名
1.项目中有很多需要命名的对象
变量、函数、参数、类、包、源代码及源代码所在目录、jar文件、war文件、ear文件等
通过命名可以告诉别人它为什么存在,做什么事,应该怎么用
每段代码都有其实际应用的场景,一段代码如果难以理解,很多时候并不是因为代码复杂,而是代码的模糊度较高,难以明确代码的意图。
2.如何正确命名
(1)不要以数字系列命名,尤其是在某个方法的参数列表上,且参数最好概念明确
(2)相似的类要加以区分,请Product类、ProductInfo类、ProductDataProductObject类的区别
(3)使用读的出来的名称,可以使用谐音,如2,4
(4)使用可搜索的名称,找MAX_SIZE_PER_PAGE容易还是100容易,变量名不可过短,也不要过于普遍,要方便查
(5)方法名最好是动词或动词短
(6)为每个抽象概念选一个词作为一贯使用的命名方式,如get,fetch,retrive
(7)添加有意义的语境,如firstName,lastName,state好呢,还是addrFirstName好呢,实际上添加了前缀之后,读者会明白这 些变量属于一个更大的语境,还可以有更好的做法,创建一个名为address
 
第三章 函数
1.基本要求

(1)第一规则是短小,第二规则还是短小。函数多少行才算短小呢?

Bob说,20行左右的代码为

(2)个函数都只做一事,做好这一件事

如果一个函数做了多个事就代表有多个原因可以导致函数被修

是,怎么确定,那么多行代码,就只做了这么一件事呢?

public static string RenderPageWithSetupAndTeardowns

(PageData pageData,Boolean isSuite) throw Exception

{

   if(isTestPage(pageData))

   {

       includeSetupAndTeardownPages(pageData,isSuite);

   }

   return pageData.getHtml();

}

看起来像是做了三件事:判断是否为测试页面;如果是,则包含设置和分析步骤;返回Html。

Bob 提供了”To”()原则,也就是以To起头段落来描述这个函数。

(To) RenderPageWithSetupAndTeardowns,检查页面是否为测试页,如果是测试页,就包含设置和分析步骤,无论是不是测试页,都返回HTML

这三件事情都处于一个抽象层次上,所以RenderPageWithSetupAndTeardowns做了一件事。

断函数只做了一件事还有另一个方法:看能否再拆出一个函数

(3)每个函数都依序把你带到下一个函数

调用的方法在前面,被调用的方法在后面。这样就能保证代码是从上往下看的了

2.函数参数

最理想的参数数量是零,其次是一,再次是二,尽量避免三。参数少也方便测试。

参数多时容易出现以下问题:
(1)参数的排列顺序,每个参数的实际意义,如assertEquals(expected,actual)
(2)传参时千万不要穿boolean,这样函数肯定不止做一件事,true的时候一件事,false的时候又是一件事。
(3)参数多的时候想要测试覆盖所有可能值的组合就会很困难

(4)果一个函数需要三个或三个以上的参数,那么就要考虑其中的一些参数是否可以封装成类或者这些参数本就属于某个类。很多时候的一些参数,只是某个类的某个概念的一部分。

3.函数编写细则

(1) 函数和参数应当形成一种良好的动词/名词对形式,比如write(name)和writeField(name),assertEqual()和 assertExpectedEqualsActual(expected,actual),可以将参数的名称加入到函数名中

(2)函数要么“做什么事”,要么“回答什么事”,即将指令和询问分隔开

(3)避免副作用。函数尽量只做一件事,否则会导致时序性耦合和顺序依赖

(4)抽离try/catch代码块,它们容易搞乱代码结构,应该另外形成函数,且该函数中只处理错误

 

第四章 注释

1.释的恰当用法是弥补我们在用代码表达意图时遭遇的失败

需要写注释并不是一件值得庆贺的事,注释写的好并不会提高你的代码质量。

2.注释有以下几个缺点

(1)代码在变动、演化,而我们不总会去维护注释,所以注释就难以精确
(2)最真实最精确的还是代码,代码还能反映一些细节。
(3)注释是不能美化糟糕的代码的。
3.释的几个使用场合
(1)公司代码规范要求编写与法律有关的注释,比如版权和著作声明
(2)提供一些基本信息,如解释某个抽象方法的返回值

//Return an instance of the Responder being tested

protected abstract Responder responderInstance();

(3)示。警告其他程序员会出现某种后果
(4)TODO注释有时,有理由用//TODO形式在源码中放置要做的工作列表,无论TODO的目的如何,都不是在系统中留下糟糕代码的借口,一定要定期查看,删除不再需要的。(5)编写公共API中的JavaDoc
 
第五章 格式
1.良好的代码格式,会使得我们阅读更容易,一套共同的格式会让我们查找理解更快速。每个团队都应该遵循一套固定的代码格式规范,整个软件系统的统风格统一,而不是各自为政各成一体。

2.垂直方向

量声明在函数顶部,实体变量声明在类的顶部

关函数放在一起,被调用的函数放在调用函数的下面

3.水平方向

意使用空格

一行代码的宽度:120字符之内,最好不要拖动横向滚动条。

进,尤其是在有循环语句、判断语句的时候

 

第六章 对象和数据结构

1.对象和数据结构的区别

(1)数据结构中的对象只是数据,面向对象中的对象包括了数据和行为。

(2)数据结构暴露其数据,没有提供有意义的函数;对象把数据隐藏于抽象之后,暴露操作数据的函数。

(3)数据结构难以添加新的的数据类型,因为需要改动所有函数,面向对象的代码则难以添加新的函数,因为需要修改所有的类

任何一个复杂的系统都会同时存在数据结构和对象,我们需要判断的是要添加的是新的数据类型还是新的行为函数。

2.迪米特法则:模块不应了解它所操作对象的内部情形。

C的方法f只应调用以下对象的方法:

(1)C

(2)由f创建的对象;

(3)作为参数传递给f的对象;

(4)由C的实体变量持有的对象;

方法不应调用由任何函数返回的对象的方法,换句话说,只和朋友说话,不和陌生人说话。以下就是违反该法则的一段代码:

final String outputDir=ctxt.getOptions().getScratchDir().getAbsolutePath();

当然,迪米特法则的前提是对象,如果是数据结构,没有什么行为,则他们自然会暴露其内部数据结构,迪米特法则也失效了

如果数据结构只简单的拥有公共变量而没有函数,对象拥有私有变量和公共函数,这个问题就不会混淆。

 

第七章 错误处理

1.错误处理很重要,但是如果它搞乱了代码逻辑,就是错误的做法。

2.要注意的几点

(1)先写try-catch-finally

异常的奇妙之一在于,它在程序中界定了一个范围,在执行try部分的代码时,表明了可以随时取消执行,并在catch语句中继续。

在编写可能抛出的异常时,先写出try-catch-finally语句,能够帮你明确代码的目的是什么,无论在try中的代码出什么错都是一样的

(2)使用不可控异常

如果你在方法中抛出可控异常,而catch语句在三个层级之上,你就得在catch语句和抛出异常处的每个方法签名中声明该异常

个调用该函数的函数都要修改,捕获新异常,或者在其签名中添加合适的throw子句,封装被打破,因为在抛出路径中的每个函数都要去了解下一层级的异常细节。

(3)给出异常发生的环境说明

应该创建信息充分的错误消息,并和异常一起传递出去,消息中应该错误类型和失败操作,如果有日志系统,就应该传递足够的信息给catch块,并记录下来。这样可以方便的判断错误的来源和住所。

(4)别返回null

在方法中返回null值,不如抛出异常或返回特例对象

注意一下,这个emptyList不支持add作,也不支持get操作。

 

第八章 边界

1.优雅的使用第三方库

大多数人是通过花好几天阅读文档,再决定怎么使用,然后编写。最后不免陷入漫长的调试找代码中的缺陷中。因为学习第三方库代码很难,整合第三方代码也很难

2.学习性测试

 

(1)到最基础的文档(用来给第一次使用的人看的),开始阅读文档。每读完几个的api,便开始整合完成你想要的某一个功能,写一个类的一个函数将其封装起来。

 

(2)完成你初步罗列出来的功能便可以开始测试,如果不需要深入理解他人的代码的话,完成所需功能即可。如果想要开发超过百行的有关代码,还是把最基础文档的api全部测试一遍好。

 

(3)测试:对函数分别调用,从中弄懂参数和返回值的真正意义,并以此弄清当前函数整合的所有api干了什么。

 

(4)测试完成后,便应该只用自己封装起来的函数来写自己旳程序。当需要调用新的api,如果这个api属于之前的某个功能,就写进那个功能对应的函数,如果是新的功能,则应该考虑写新的类、新的的函数。

 

posted @ 2016-11-30 15:35  LittleMoon  阅读(1320)  评论(0编辑  收藏  举报