代码整洁之道
行百里者半九十,但是到五十的时候就发现,有很多的地方是要提前注意到的。
大狗的代码规范:
- 命名规则1,函数/变量命名应当名副其实,在提供尽量短,尽量精确的同时,避免使用任何的混义词。对于函数,动作最好保持在最前面,且使用和老代码一致的含义。
- 函数规则1,函数的应当提供流程上相同层面的抽象,不同层面的抽象应当隐藏到函数里面的函数去。每个函数应当尽量短于100行。除此之外,函数内部实现的时候也应该操作同层级的对象,而不应该将不同层级的对象混淆。函数的编写者应当提供一种,暴露行为,隐藏数据的函数抽象。非常具体,告诉改了什么操作的函数是在函数内部,给抽象层级更高的函数使用的操作。
- 函数规则2,函数应当尽量简洁,重复代码抽象,使用包了一层的异常处理做失败检查和清理操作。
第一章:整洁代码
总结起来是写代码的总览三条:
- 能通过所有测试;
- 没有重复代码;
- 体现系统中的全部设计理念;
- 包括尽量少的实体, 比如类、 方法、 函数等。
第二章:命名
如何命名变量函数?
- 名副其实:代码/变量的名字应当说明大部分问题,它是干什么的,它坐了什么事情,应该怎么用。比方说list就不如server_list,4不如定义一个对象cell[marked]更具体
- 避免误导:将变量的抽象结构和具体结构区分开,比方说你实现的是set的功能,用的是list的数据结构,那么不要命名为list。还有个例子,比方说“l”和“1”长得很像。
- 参数名应当可以精确区分:比方说有一个Product类,然后有一个ProductInfo类和ProductData类,那么名字不一样,但是却没法区分这些。再比方说Variable如此的粗糙,变量,什么变量?
- 使用可读的人话:getymdhms(生成年月日时分秒),就远不如genDateAndTime好,不要用那种给非人看的代码
- 放弃该死的前缀吧
- 避免灵机一动的命名,避免思维映射:如果某个东西有专有领域的命名,用它用它用它,不要犹豫换个通俗易懂的名字,完全没有必要。如果有
- 对于某个类的名词,既然是对象就用名词,不要用动词:实际上可以类比,对数据做保护,对数据做封装,别对动作做保护/封装。
- 对于函数的命名,应当动作提前,尽量避免出现语义不明的new初始化等之类
- 统一概念,每个概念用一个词,比方说ADD就是添加,那么ADD应该一直是添加,而不该是两个求和。
- 不要在名字里面添加没用的语境。对于地址而言,address明显就是具体的地址。而NetAddress冥想就不如URL,尽量短,尽量有效话,尽量精确。
第三章:函数
如何将函数缩短?
- 函数行数应当少于100行
- 函数应当每次只做一件事:每个函数实际上可以分解为,为了XXX,干YYY。利用这种方式,将相同层侧的抽象提取出来,然后组织为第一层。
- 每个函数一个抽象层级。自顶向下读代码: 向下规则
- 函数参数应当尽量少,尽量将参数减少到三元以内。一部分情况下,一元函数的对象可以是event(事件)等参数,但是总而言之,不要传入BOOL类型的变量做参数,因为TRUE和FALSE是一个非常令人迷惑的标识,不如在确定TRUE/FALSE之后调用不同的函数。当参数的个数超过三个的时候,就需要将参数封装为类了。=有点类似TLS自动机里面的SESSION查询那块=
- 数据传送对象:这个本来是第六章的内容,我直接挪过来了。
- 函数的名字应当尽可能简单清晰,比方说writeField(name)能清晰的让人理解写name字段的值,就比write(name)好
- 无副作用,函数应当只提供代码承诺的服务,应当避免任何未承诺的服务,对比到自动机,比方说函数内部命名是message_certficate_hello,然后内部却修改了sslp->sstate。这就是非常典型的副作用
- 分隔指令和询问,换言之不要让一个动词set的意思和seted一样,令人混淆,不过这个我觉得还好
- 使用异常处理来做清理操作,而不是用返回值,因为返回值会将事情搞复杂。实际上这个就类似平时代码里面的goto:clean_up。
- 异常处理外面包一层,这样子不会破坏异常处理的结构
- 结构化编程:尽量让大函数有一个统一的入口和出口,不要直接返回,类比例子是你优化之前的handle_file_sync_cmd,糟糕的流程,随时都可能返回。最后改成了统一入口和出口。
- 不断检查和取消重复:没有谁能写出来完美的程序,不断尝试抽象和提取,不断检查,提取重复代码。
第四章:注释
第五章:格式
没啥好说的,一直的代码风格就成
第六章:对象和数据结构
- 我们提供的对象和具体的对象并不一样,比方说一个车的类,和具体的汽车并不一致,我们将汽车的属性隐藏到了车这个类的里面。
- 著名的得墨忒耳律(The Law of Demeter)认为, 模块不应了解它所操作对象的内部情形。换言之,类C的方法f只应该调用一下对象的方法:C,由f创建的对象,作为参数传递给f的对象,由C的实体变量持有的对象。
- 火车失事问题:这种问题常常是因为引入了一连串的方法,就类似火车前进,这种代码是的某个具体的函数知道了当前ctxt对象拥有的属性和结构,想想看一个高抽象层面的函数了解了一大堆细节,这合适吗?还是那句话,层级干层级的事情
Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();
- 另一种混杂不是函数内部操作不同层级对象的问题了,另一种是将对象和数据结构混杂在一起。
第七章:错误处理
- 使用统一的异常处理,而不是分开写,这样子能使代码简洁很多
第八章:边界
第十章:类
- SRP,单一权责原则,类或模块应有且只有一条加以修改的理由。如果改代码的时候发现有两个修改理由,说明改继续抽象了
- 内聚:类应该只有少量实体变量,类中的每个方法都应该操作一个或多个这种变量。如果大部分的实体变量没用到,很可能你应该单独抽取这些变量到别的类型了。
- 为了修改而组织,减少之间的依赖
- DIP原则,类应当依赖于抽象,而不是代码
第十一章:系统
这章可以说是屠龙只技,为什么要看这个?因为打算重构certm的代码和逻辑。命名和函数角度的整洁属于低层次抽象,同样需要关注高层次抽象。
- 将系统的构造和使用区分开,起始过程的逻辑和起始过程之后的运行时逻辑应当拆分开。
结尾
唉,尴尬