Clean Code读书笔记
(1)有意义的命名,能在字面上表达其含义,代码要在字面上表达其含义,字面编程
(2)力求集中,每个函数、类、模块都专注于做一件事,且这些函数、类、方法、实体等尽可能少
(3)消除重复
(4)有单元测试并能通过测试
如果每个例程都让你感到深合己意,那就是整洁代码,如果代码让编程语言看上去像是专为解决那个问题而存在,就可以称之为漂亮的代码。
(4)使用可搜索的名称,找MAX_SIZE_PER_PAGE容易还是100容易,变量名不可过短,也不要过于普遍,要方便查找
(5)方法名最好是动词或动词短语
(1)第一规则是短小,第二规则还是短小。函数多少行才算短小呢?
Bob说,20行左右的代码为佳
如果一个函数做了多个事就代表有多个原因可以导致函数被修改
但是,怎么确定,那么多行代码,就只做了这么一件事呢?
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做了一件事。
判断函数只做了一件事还有另一个方法:看能否再拆出一个函数
调用的方法在前面,被调用的方法在后面。这样就能保证代码是从上往下看的了
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.注释有以下几个缺点
//Return an instance of the Responder being tested
protected abstract Responder responderInstance();
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属于之前的某个功能,就写进那个功能对应的函数,如果是新的功能,则应该考虑写新的类、新的的函数。