详细总结《代码整洁之道》 - 基础篇
本文概述
本文详细总结了本书《代码整洁之道》前文部分的知识点。梳理的具体内容如下:命名规范需要注意的地方;如何编写函数里的代码以及函数之间放置的位置;注释需要注意的地方,代码格式以及在某些情况下需要处理异常。
本文属于基础篇,也是在我们编程工作中经常涉及到的内容。至于本书后部分内容,如测试代码部分,系统设计的规则,以及重构部分,暂不继续进行做个笔记。此后如若有时间的允许下,我就会继续总结下部分内容。若条件不允许,则不会继续写下去了。
“下划线”代表的是本书部分的原文内容。
写于2021年2月21日
正文
(一)有意义的命名
名副其实
给变量,函数或类要起个与它本身含义相符合的名称。如果一旦发现有更好的名称,就换掉旧的。
避免误导
程序员应当避免使用与本意相悖的词。
①不能起个与某平台的专有名称相同的名称。
例如,hp,aix和sco都不该用做变量名,因为它们都是UNIX平台或类UNIX平台的专有名称。
②别使用对程序员有特殊意义的词。
例如,别用accountList来指称一组账号,除非它真的是List类型。如果包纳账号的容器并非是个List,就会引起错误的判断。建议使用accountGroup,甚至直接用accounts都会好一些。
③提防使用不同之处较小的名称。指的是提防使用一组单词外形相似,却无法快速区分开来的名称。
例如,XYZControllerForEfficientHandlingOfStrings 和
XYZControllerForEfficientStrorageOfStrings。由于不同意思的两者外形太相似,则我们需要花点时间才能理解。
④以同样的方式拼写同样的概念才是信息。拼写前后不一致就是误导。
⑴相似的名称依字母顺序放在一起,且差异很明显,就会有助于理解。
例如,ScaleX,ScaleY,ScaleZ,把它们放在一起。
⑵误导性名称,也有与其他概念混在一起交叉式使用。
例如代码:
int a=1;
if(o==1)a=01;
elsel=01;
其中很难看得出o和l是字母还是数字。
做有意义的区分
在同一作用范围内两样或两样以上不同含义的对象重名的情况下,则需要修改其中一个的名称。
①不能干脆以错误的拼写充数。
例如,同一作用范围里,有两个重名的Scale变量。如果随意修改其中一个为Scael,即便编译器通过,也会使编译器出错。
②不能以数字系列依义的对立面来命名,这会造成误导。因为完全没有提供正确信息,也就是没有提供代码个体之间的具体含义。
例如:
copy(char a1[],char a2[])改为copy(char source[],char destination[]),就会像样许多。
③起个名称不能有废话。
不要使用一些与对象毫无影响的词。
例如,有了命名为Product类,还定义一个为ProductInfo或ProductData。两者虽然名称不同,意思却无区别。也就是不管有无Info或Data,意思都同指的是Product。此外,名称含有一些a,an,the也纯属是废话。
从类型方面来讲:
Variable不会出现在变量名中。Table也不应当出现在表名中。像是NameString,CustomerObject都属于废话。
使用读得出来的名称
要使用读得出来的名称,而不要使用把一组单词严重缩写的名称。
例如,genymdhms(生成日期,年,月,日,时,分,秒),应改为generation timestamp。
使用可搜索的名称
若变量或常量可能在代码中多处使用,则应赋其以便于搜索的名称。
例如,WORK_DAYS_PER_WEEK这个常量会在代码中多处使用到,搜索与其相关的代码逻辑时,会比数字5好找。
有争议性的命名法
匈牙利语标记法
匈牙利语标记法的特征便是:名称的前字母描述了该变量的类型,例如bChecked以b开头的变量代表是一个布尔类型的。但是这个标记法是有争议性的,是否被采用,得根据程序员本身使用的习惯或适用于某种情况。
不过本书作者为代码起名称时,不提倡匈牙利语标记法。因为作者认为带有类型标记的字母也纯属是废话。
而我认为这种标记法更多使用在C/C++方面上,尤其涉及到多个复杂的指针时,带有指针类型字母的变量会让人更快地知道这是个什么样的指针变量。至于C#,可能没有多大的影响,例如bChecked可以改成isChecked会更好些。例如chName不如Name好,因为在代码的约定中,Name除了字符串类型之外,不太可能会是一个诸如int的其他类型。
成员前缀
以m_开头来表明成员变量,通常会在老代码中出现。例如在MFC中,封装类里的成员变量就是以m_开头的。反正作者也不喜欢这种命名的方法。尽管在以前的代码中常常出现,但是现在的编译环境大有改进,可以直接把光标停在变量的位置,编辑器自动会弹出相关信息的浮窗,所以成员变量是否以m_开头就显得没那么重要了。
接口和实现
作者喜欢不加修饰的接口,例如ShapeFactory比IShapeFactory好。作者认为首字母带有I表示为一个接口的意思纯属是废话。不过我认为还是要分情况的, IShapeFactory用习惯了,马上就知道这是个接口。而如果是ShapeFactory,我可能会对它进行跳转到声明处,才会弄清楚这是一个类还是接口,甚至习惯性的直接把它当成类去理解。因此,这些命名还是有争议性的。
单字母变量名就是一个问题
在作用域较小,也没有名称冲突时,循环计数器自然有可能被命名为i或j或k.这是因为传统上惯用单字母名称做循环计数器。说到底,作者并不赞同这传统上惯用的单字母名称。作者则认为哪怕作用域再小,也要尽量使用一个明确其含义的名称。
我个人认为要分情况使用,如果函数内有多个循环计数器并且都互相靠得比较近,或者循环体较大时多处使用,则不适用于单字母变量。而如果函数内只有一两个循环计数器,并且相关变量只是用到短小的循环体里的一两处,则可适用单字母变量。
类名
类名和对象名应该是名词或名词短语。
方法名
方法名应当是动词或动词短。
别扮可爱
所谓“扮可爱”的做法是在代码中经常体现为使用俗语或俚语。例如,whack()来表示kill(),不知道whack含义的程序员就不会知道这是一个kill。(whack为美俚,劈砍的意思)
每个概念对应一个词
给每个抽象概念选一个词,命名应当独一无二。
例如,使用fetch,retrieve和get在多个不同类中共同命名到,那我们就很难记得在某处使用的fetch是来自哪个类中哪个方法的。例如,起个getDogName在Dog类里,起个getCatAddr在Cat类里,这样就明确了各自不同的概念。而不是get在Dog和Cat里有着同一种名称,却在使用get时是属于Dog还是Cat就会容易混淆。
别用双关语
避免将同一单词用于不同目的。同一术语用于不同概念,基本上就是双关语了。
例如使用add方法在好多个类里面。只要这些add方法的参数列表和返回值在语义上等价,就一切顺利。但是,如果一个类里有个方法,把单个参数放到群集中,就不能使用add了,而是append或insert才对得上该方法的语义。
使用解决方案领域名称
尽管用那些计算机科学术语,算法名,模式名,数学术语等。例如,使用到冒泡排序算法,就为实现该算法的函数起个名称为BubbleSort。
使用源自所涉问题领域的名称
我认为所涉问题领域的名称一般指的是自己开发该产品所涉及到的领域。例如,函数里调用了一系列属于中间商提供的接口,该函数可以命名为与中间商相关问题或技术领域的名称。例如,有个函数调用了显示百度地图的接口,则该函数可以命名为showBaiduMap。
添加有意义的语境
如果名称是不能自我说明其含义的,那就需要在名称添加前缀,以示有意义的语境。例如,firstName,lastName这些变量,如果不把他们封装成类里,那就需要提供语境了。如果这些变量表示一个地址,可以改为addrFirstName,addrLastName。
不添加没用的语境
该名称的前缀不管是加上还是不加,都不影响到其含义,那就是没用的语境。例如,项目起个名字为GasStationDeluxe,而给其中的每个类都以GSD前缀就不好。例如accountAddress和customerAddress都代表Address,并没有什么区别,所以前缀就显得没有用处。
(二)函数
短小
函数的规则是要短小,尽量要做到更短小。
在20世界80年代,我们常说函数不该长于一屏。假如显示器一屏里面可以显示100行,每行能容纳150个字符。每行都不应该有150个字符那么长。函数也不该有100行那么长,20行封顶最佳。
短小:代码块和缩进
if语句,else语句,while语句等,其中的代码块应该只有一行。改行大抵应该是一个函数调用语句。这样不但能保持函数短小,而且,因为块内调用的函数拥有较具说明性的名称,从而增加了文档上的价值。这也意味着函数不应该大到足以容纳嵌套结构。所以函数的缩进层级不该多于一层或两层。
我不是很确定自己是否弄懂了以上的这段话。我的理解是,作者认为像是if语句,while语句这些,其代码块尽量要成新的一个函数由其调用。这样就可以把多个层级的嵌套结构缩减为一两层。(注:不过我认为这种方式最好适用于嵌套内代码块的多个变量作用在其代码块的作用域里)
例如,
只做一件事
如果函数只是做了函数名下同一抽象层上的步骤,则函数还是只做了一件事。也就是说,作者认为函数应当遵循单一权责的规则:只做一件事,并且只能修改一个地方。封装类和接口也是如此。
函数的抽象层
要确保函数只做一件事,函数中的语句都要在同一抽象层级上。
自顶向下读代码:向下规则
要确保函数只做一件事,函数中的语句都要在同意抽象层级上。我们想要让每个函数后面都跟着位于下一抽象层级的函数。
作者认为函数里应当调用的所有函数都属于同一个抽象层,而函数内调用的函数属于主调用函数的下一个抽象层。
如何理解抽象层以及层级关系呢?
假设我们把炒菜是一道可运行的程序。函数main相当于炒菜过程,函数DoIngredient 相当于做菜的配料(盐,油,蒜等),函数CtrlFire就是控制火候,函数cookingAction是炒菜的翻炒动作。
DoIngredient函数主要工作是处理油盐蒜等配料。但如果发现DoIngredient函数里还能拆分为更具体的抽象层,比如功能分别是:1.为配料添加分量,2.配料之间搅拌在一起,因此前者命名为SetIngredientcontent函数,后者命名为MixingOfIngredients函数。
所以,函数main可以处理同一个抽象层:1.把配料放进锅里;2.炒菜过程要时刻控制火候;3.展现自己的翻(chu)炒(yi)动作。
DoIngredient函数要处理的抽象层:把配料的成分分配好,把该混合一起的配料搅拌在一起。
于是函数main调用DoIngredient,CtrlFire和cookingAction,函数DoIngredient调用SetIngredientcontent和MixingOfIngredients就会显得层级明确,逻辑清晰。
void main()
{
DoIngredient();
CtrlFire();
cookingAction();
}void DoIngredient()
{
SetIngredientcontent();
MixingOfIngredients();
}
函数参数
最理想的参数是零(零参数函数),其次是一(单参数函数),再次是二(双参数函数),应尽量避免三(三参数函数)。有足够特殊的理由才能用三个以上参数。
一元函数的普遍形式
输入参数必须要有明确的含义,不能含糊不清。
例如,bool fileExists(“MyFile“),可能对该文件进行判断是否存在,也可能利用该参数对文件进行读写,这便是含糊不清的传递参数形式。
例如,StringBuffer transform(StringBuffer in),明确知道了对该参数进行转换的意思。
标识参数
标识参数丑陋不堪。向函数传入布尔值简直就是骇人听闻的做法。如果标识为true将会这样做,标识为false将会那样做,违反了函数不止做一件事。
例如,
把render(Boolean isSuite)最好拆分为renderForSuite()和renderForSingleTest()。
二元函数
要注意在函数内声明参数的顺序问题。相关的概念排列靠近在一起。
例如,assertEquals(expected, actual),我们不清楚expected和actual在函数内的位置了。
例如,copy(char *source ,char *dest),就明确了这两个参数具体在函数里扮演怎么样的角色。
三元函数
设想函数assertEquals(message, expected, actual),在某处调用该函数时,我们就容易把这三个参数的位置搞混。
参数对象
如果函数看来需要两个,三个或三个以上参数,就说明其中一些参数应该封装为类了。
参数列表
向函数传入数量可变的参数,使用时要注意数量和排列顺序。
动词与关键字
给函数取个好名字,能较好地解释函数的意图,以及参数的顺序和意图。对于一元函数,函数和参数应当形成一种非常良好的动词/名词对形式。
例如,
write(name)改为writeField(name),它告诉我们,“name“是一个”field”。
例如,
assertEqual改成assertExpectedEqualActual(expected, actual)会好些。
抽离Try/Catch代码块。
Try/catch代码块应当抽离出来,另外形成函数。处理错误的函数应当只做处理错误的这件事,不能参杂有其他事情。
别重复自己
在函数里或者另外函数有着相同的代码逻辑,就属于重复。所以,应当要做到一个函数里只有一处的代码逻辑,不能在某处出现同一段的代码逻辑。
代码命名规范
由于不同的编程或不同的编程环境,会有不同的命名规范。像C++语言,例如m_前缀代表的是成员变量,以On开头的名称通常代表的是一个回调事件处理函数。但在C#,Java等其他语言中,几乎没有出现过有这种规则。
要想把自己写的代码尽量做到规范化,我们还是多参考好的代码例子。
可适用于任何环境的命名方法便是:驼峰命名法
驼峰命名
大驼峰
特点:第一个字母大写,后边每个单词的首字母也大写
应用:类名,接口名,函数名,属性名,命名空间
例如:类:Student;接口:IShapeFactory,函数:SearchAddress;属性名:GetName;命名空间:MathematicalAlgorithm
小驼峰
特点:第一个字母大写,后边每个单词的首字母也大写
应用:变量,函数名
例如:变量:scaleX,函数名:searchAddress
其中,函数名第一个字母可大写也可小写。
起名的词性规则
适用名称为名词或名词短语:
类名,接口名,命名空间,变量名,常量名,枚举名
适用名称为动词或动词短语:
属性名,方法名,事件
命名规范的参考资料:
驼峰命名法:https://blog.csdn.net/jerry11112/article/details/84985026
C++变量命名规范 :http://www.360doc.com/content/12/0314/13/3767901_194245125.shtml
C#命名规范:https://www.cnblogs.com/cg919/p/10512749.html
(三)注释
本书的注释章节也没有过多讲解于注释相关的内容。总之,作者一直提倡程序员注重要做到用代码解释清楚逻辑和概念,以尽量减少不必要的注释。不过在为代码不得不或是我们误以为必要进行注释时,我们也应当注意一些比较重要的地方:
①注释的内容意图要明确,清晰简洁,不能含糊不清,不能有废话出现。
②注释掉的代码一定是在将来是会有用处的,没有用的或是用处不大的代码应当清理干净。最好还是尽量不要有注释掉的代码出现。
③要注重代码的规范,代码能解释清楚的,就要尽量减少不必要的注释内容。
④经过修改代码逻辑之后,原先注释掉的解释或者代码不再有价值时,应当及时删除掉,避免自己或后来开发者会在此后去理会这些没有用的注释而浪费时间。
⑤好的注释应当出现在不得不添加的情况下,比如必须要添加法律信息的情况,为代码难以命名的情况,将来一定会补充代码的情况,某处代码块有着严重警示的情况。
有用的注释:
1.法律信息(例如公司代码规范必要时,版权以著作权声明在每个源文件开头注释处放置) 2.提供信息的注释(用来解释不能用名称解释的代码)
3.对意图的注释。(对代码逻辑或放置位置的决定的解释)
4.阐释(用来解释晦涩难懂的参数或返回值)
5.警示(解释某处代码的作用很重要)
6.TODO注释(作者不提倡使用这种注释)
(四)格式
垂直方向上的区隔
“每组代码行展示一条完整的思路,这些思路用空白行区隔开来。“在哪种情况下,才会使用到空白行,是根据程序员编写代码的具体情况而决定的。封装声明,导入声明,以及每个函数之间,甚至函数内有不同的代码逻辑,都可作为独立的思路而进行空白行相互区隔。除此之外,例如函数内执行if或for语句,也可作为一条思路可用空白行区隔起来。
垂直方向上的靠近
紧密相关的代码应该互相靠近。
例如,实体变量放在一起,变量和函数之间需要用空白隔开为不同的概念
垂直距离
在本文件里,关系密切的概念应该互相靠近。
实体变量
变量声明应尽可能靠近其使用的位置。
实体变量具体放在哪里,需要看自己使用的编程语言风格。在C#中,实体变量都放在类的顶部声明。在C++中,有些情况会把实体变量都放在底部。
相关函数
若某个函数调用了另外一个,就应该把他们放到一起,而且调用者应该可能放在被调用者上面。
概念相关
概念相关的代码应该放到一起。相关性越强,彼此之间的距离就该越短。
相关性应建立在直接以来的基础上,如函数间调用,或函数使用某个变量。但也有其他相关性的可能。相关性可能来自于执行相似操作的一组函数。
例如,实现读写数据库的操作,一些与数据库链接相关的操作方法,应当放在一起;一些与读操作相关的函数,应当放在一起。显示数据的操作方法之放在一起。然后一组数据库链接的函数放在最前,排在第二的一组函数是读写操作的函数,放到最后的是一组显示数据的函数。(此处只是举个例子,当然最好封装成类还是遵循单一权责,把数据库链接的工作封装一个类,把一些类读写的操作封装成另一个类,把显示数据的工作又封装成类)
垂直顺序
被调的函数应该放在执行调用的函数下面。
横向格式
应该尽力保持代码行短小。
水平方向上的区隔与靠近
我们使用空格字符将彼此紧密相关的事物连接到一起,也用空格字符把相关性较弱的事物分隔开。
例如如下代码。作者不在函数名和左圆括号之间加空格。这是因为函数与其参数密切相关,如果隔开,就会显得互无关系。作者把函数调用括号中的参数一一隔开,强调逗号,表示参数是互相分离的。
运算符
乘法因子之间不加空格,因为它们具有较高优先级。而加减法运算项之间用空格隔开,因为加法和减法优先级较低。
例如如下代码:
水平对齐
反例代码:
正例代码:
缩进
类中的方法相对该类缩进一个层级。方法的实现相对方法声明缩进一个层级。代码块的实现相对于其容器代码块缩进一个层级,以此类推。
反例:
正例:
空范围
反例:
正例:
(五)对象和数据结构
一些用户无需了解数据的实现不应当出现在实现整块的代码里,而要分清楚哪些是需要隐藏的代码逻辑,哪些是需要显示给用户看的。(同样用户指的也是程序员本身,或者其他人)。
通常把数据结构分为:对数据操作的结构和对行为操作的结构。
错误处理
如果总是以if语句来判断错误,代码就会多个if语句,显得代码混乱。有些情况使用抛出异常就会更好些。
反例:
正例:
先写Try-Catch-Finally语句
同样的,抛出异常同样的只能做一件事,Try做一件事,Catch处理一件事。如果有多处使用到异常处理,那可能需要专门封装成一个类了
别返回null值
如果代码返回null值,那就得要使用if判断,这样容易出错,会容易忽略掉一些对象为null时却引用了其内部的内容。这时就要考虑到是否需要抛出异常的做法。
别传递null值
如果代码多处传递了null值,也会容易使得编译出错。是否需要抛出异常的处理就看具体的情况了。
(六)类
类的组织
如果有公共静态常量,应该先出现。然后是私有静态变量,以及私有实体变量。很少会有公共变量。
公共函数应跟在变量列表之后。我们喜欢把由某个公共函数调用的私有工具函数紧随在该公共函数后面。
类应该短小
类同样要遵守单一权责的规则,只做一件事。
例如,把行为相关的封装成类,把数据相关的封装成类,修改一个地方要么是行为,要么是对数据的修改。尽量不在一个类里既可以修改行为,有可以修改数据。
另外,描述一个类,不用”if”,”and”,”or”,”but”这些词汇作为类名的一部分,否则这显得类不仅仅只做一件事了。
内聚
如果一个类中的每个变量都被每个方法所使用,则该类具有最大的内聚性。创建这种极大化内聚类是既不可取也不可能的。我们希望内聚性要尽量保持在较高位置。
如果类丧失了内聚性,就要拆分它为几个新的类。
只做一件事的类过长时,该如何做?
如果类里有很多相关并且内聚性较高的变量和方法,也应当分类拆离出新的文件。例如,一个文件负责做初始化工作,另一个文件负责做功能操作。
详细总结《代码整洁之道》 - 基础篇相关推荐
- 【苦练基本功】代码整洁之道 pt1(第1章-第3章)
代码整洁之道 pt1(第1章-第3章) 1 整洁代码 1.1 要有代码 1.2 糟糕的代码 1.3 混乱的代价 1.3.1 什么是整洁代码? 2 有意义的命名 2.1 名副其实 2.2 避免误导 2. ...
- 【苦练基本功】代码整洁之道 pt4(第10章-第12章)
代码整洁之道 pt4(第10章-第12章) 10 类 10.1 类的组织 10.2 类应该短小 10.2.1 单一权责原则 10.2.2 内聚 10.2.3 保持内聚性就会得到许多短小的类 10.3 ...
- 《代码整洁之道》目录—导读
版权声明 代码整洁之道 Authorized translation from the English language edition, entitled Clean Code: A Handboo ...
- 代码整洁之道核心记要(一)
代码整洁之道核心记要 0. 简介 1. 前言 2. 详解 1. 命名 2. 函数 3. 对象与数据结构 感悟 代码整洁之道是鲍勃大叔的经典作品.这本书主要介绍鲍勃大叔的整洁代码的方法,如何写出好代码, ...
- Python好书推荐《Python代码整洁之道》——编写优雅的代码
前言 Python是当今最流行的语言之一.相对较新的领域如数据科学.人工智能.机器人和数据分析,以及传统的专业如Web开发和科学研究等,都在拥抱Python.随着时间的推移,Python有可能会发展成 ...
- 《代码整洁之道》第一章 整洁代码 ---为什么需要整洁代码?
第一章 整洁代码 概述 什么是整洁代码? 开始走向整洁代码 概述 欢迎阅读本栏目的读者,如果你想成为更加优秀的coder,请跟随笔者的观点去解析<代码整洁之道>这本书,相信你会收获颇丰. ...
- C#代码整洁之道读后总结与感想
1. 基本信息 C#代码整洁之道:代码重构与性能提升 ,英文名为Clean Code in C#. 作者:[英] 詹森·奥尔斯(Jason Alls) 著,刘夏 译 机械工业出版社,2022年4月出版 ...
- 代码整洁之道 python_代码整洁之道-编写 Pythonic 代码
原标题:代码整洁之道-编写 Pythonic 代码 来自:Python学习开发(微信号:python3-5) 很多新手在开始学一门新的语言的时候,往往会忽视一些不应该忽视的细节,比如变量命名和函数命名 ...
- 重读【代码整洁之道】
一.前言 [代码整洁之道]很经典,但也有些过时,翻译上也有些啰嗦,但总体上是好书.通过对本书核心内容的摘抄,结合自己的经验,整理了一些精简的点,这样你就省的去啃那本400多页的书了. 软件质量 = 架 ...
最新文章
- linux下装windows驱动,linux下安装windows xp无线网卡驱动
- SharePoint 2010 隐藏快速启动栏(左侧导航)
- python爬虫加密空间_Python爬虫进阶必备 | XX同城加密分析
- python调用js文件报错_python - selenium 运行网页中js脚本报错,提示未定义
- 借助阿里AntUI元素实现两个Web页面之间的过渡——“Loading…”
- 动态改变标题_小米相册更新,新增动态换天/赛博朋克/MIUI12界面等等!
- element-plus Radio 单选框点击失效 无法切换问题
- 考场自动安排工具开发手记
- java非静态内部类如何创建对象实例
- jupyter notebook 快捷键
- Boni Satani谈迁移遗留系统的5个原因
- 矩阵的 Jordan 标准型
- 机器学习实战——逻辑回归和线性判别分析
- QT 界面设计篇(水波纹进度条QProgressBarWater)
- 微信小程序实现素材旋转——非canvas
- iOS读取通讯录功能
- emmx用xmind打开_XMind 里流程图的正确打开方式
- 使用群晖作mineportalbox(1):合理且不折腾地使用群晖硬件和套件
- 区块链改革杭州闭门会议——分享链改机遇,探讨价值联动!
- 基于c语言图像灰度拉伸算法实现,c语言实现图像灰度均衡化