一,整洁代码

1,什么是整洁代码

这里提到了2个关键词:优雅,高效。

优雅就不说了,现在讨论整洁代码的时候,大家都会提到这个词,代码是一种艺术。

高效,这个观点还挺有意思的,尤其是这句:

性能调至最优,省得引诱别人做没规矩的优化,搞出一堆混乱来。

 

二,有意义的命名

1,名副其实

我理解这个和代码自注释意思差不多,当命名做的好,能描述清楚这个变量、函数是做什么的,就不需要注释。

写代码最难的就是命名,命名最难的就是名副其实

2,避免误导

这里提到了2个问题:

(1)特殊字母、词汇

特殊字母比如l、O等,用来做变量名简直是神坑。

特殊词汇,比如UNIX的专有词汇aix,又比如编程通用专有词汇List

(2)很长很接近的词

2个很长的词,都是7个单词拼接起来的,只有中间有一个单词不一样,相似度很高,造成阅读障碍。

3,使用读得出来的名称

编程是一种社会活动,代码不仅要给人看,还要给人读(朗读的读)

4,使用可搜索的名称

这相当于一种编程技巧,不要用过短且普遍存在的词做变量名,除非它的作用域非常小。

比如一个小循环,循环变量用i, j是可以接受的,因为作用域仅限这几行。

5,匈牙利标记法

以前有一种标记法,变量的名称前面加前缀,i表示int,u表示unsigned,g表示全局变量等等。

匈牙利标记法风靡一时是因为,以前的编译器不做类型检查。

现在的编译器做智能化程度比较高,成熟的公司还会用脚本扫描代码不符合自己的编码规范的地方,匈牙利标记法慢慢的被遗弃了。

6,每个概念对应一个词

不要用多个相近的词表示同一个概念。

 

三,函数

在C语言中,最重要的实体就是函数。

1,函数应该尽量短小

if、else、while语句,其中的代码行应该只占一行,一个函数调用语句。

一方面,这个标准很高,并不容易做到,难点在于很容易造成命名困难和过长参数列表,

另一方面,这个标准和我们重构的标准有冲突。我们平常做函数级重构,要把圈复杂度降到7以下,所以拆函数的时候会把代码一段一段的抠出来,而不是纵横交错,把每个if else里面的语句提出来,更不会把while里面的语句提出来。

2,一个函数只做一件事

这也是做设计的时候,多次被提到的概念。

要判断函数是否不止做了一件事,还有一个办法,就是看它是否能再拆出一个函数。

这个标准也挺高的。

3,每个函数都在同一抽象层级

这一点如果没有可以练习的话,也是很难做到的。一般人习惯性的只把较大较复杂代码块提炼出函数,如果语句很简单,只有两三行,就没有提炼,也就造成抽象层级参差不齐。

抽象层级一致对于自顶向下阅读代码很有帮助。

4,函数参数

(1)向函数传入布尔值简直就是骇人听闻的做法,这相当于大声宣布本函数不止做一件事。

(2)利用一些机制减少函数参数数量,比如变成成员函数。

这在C++中比较好实现,在C语言中结构体放函数指针,写法复杂一点。

5,无副作用

这其实也是“一个函数只做一件事”。

函数有副作用,就会造成时序性耦合。

在LLT中,如果要尽可能覆盖所有代码行,前面用例的执行就很容易造成后面的用例失败,因为很多函数都有副作用,全局变量太多了。

在博弈型算法的开发过程中,我也深有体会,尽量让底层的搜索函数、复杂计算函数等大函数无副作用,把修改全局变量的代码都分离出来,集中在离main函数尽可能近的地方,代码的耦合性会小一点,稳定性强一点。

 

四,注释

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

注释存在的时间越长,就会越来越不准确,因为程序员不喜欢维护注释。

1,好注释

(1)对意图的解释

不是解释这句代码是做什么的,而是在写代码的时候,面临必要的选择的时候,作者是怎么想的。

(2)阐释

对一些不好理解的参数或返回值的意义翻译成可读形式。

(3)警示

用于警示其他的程序员。

(4)TODO注释

这个我一般用一长串///这种注释代替,尤其是短期内马上就要修改的地方的标记。

2,坏注释

大多数注释都属此类。

 

五,格式

1,垂直格式

垂直区隔:相关的内容紧密联系在一起,不同的概念用空行隔开。

2,水平格式

(1)行宽

一行120个字符,是显示器的宽度,便于阅读。无需左右滚动是原则。

(2)水平间隔

紧密联系相关的事物连接在一起,相关性弱的事物用空格隔开。

这里有作者喜欢的风格:

return b*b - 4*a*c;

毫不夸张的说和我个人喜欢的风格完全一样!

不过大部分代码格式化工具都不会做成这样,所以我们的编码规范也不是这样,而是如下:

return b * b - 4 * a * c;

 

六,对象和数据结构

1,数据抽象

这里纠正了一个我也一直持有的错误的思想:给类的私有数据成员随意添加共有的get和set方法,只要能用到。

之所以有这个想法是觉得,相比于共有数据成员,用get和set的话,就相当于一个统一的接口,如果未来这个数据成员发生了变化,是比较容易修改的。

但实际上,隐藏实现并非只是在变量上放一个函数层那么简单,隐藏实现关乎抽象

2,对象和结构体

过程式代码难以添加新数据结构,因为必须修改所有函数,面向对象代码难以添加新函数,因为必须修改所有类。

我理解这其实就是数据和函数的矩阵式结构:过程式代码是函数包含数据,面向对象是数据包含函数。

就好像我有人民币纸币和硬币,还有美元纸币和硬币,我还有俩钱包,如果我经常用硬币很少用纸币,那么硬币放一个钱包纸币放一个钱包,如果我经常用人民币很少用美元,那么我人民币放一个钱包美元放一个钱包,币种和面额的关系就类似于数据和函数的关系。

3,demeter律

方法不应调用由任何函数返回的对象的方法。

书中例子:

String outputDir = ctxt.get*().get*().get*()

然后用outputDir拼凑成绝对路径,用来创建临时文件。

优化方案:ctxt类直接定义一个创建临时文件的方法A。

这一块我感觉挺疑惑的,这个例子的意思应该不是在A里面调用get*().get*().get*()吧?

我理解这个例子反映的应该是两个问题,ctxt类直接定义一个创建临时文件的方法A解决了暴露outputDir的问题,而get链的问题应该是通过别的方法解决,比如层层传递,每个类的方法中只能访问它的父类的共有方法。

 

七,错误处理

1,使用异常而非返回码

C语言没有这个机制,C语言的设计机制就是靠返回码来运行的。

2,别返回NULL值,别传递NULL值

 

八,边界

1,整洁的边界

边界上的代码需要清晰的分割和定义了期望的测试。

依靠你能控制的东西,好过依赖你控制不了的东西,免得日后受它控制。

 

九,单元测试

1,TDD三定律

(1)在编写不能通过的单元测试前,不可编写生产代码

(2)只可编写刚好无法通过的单元测试

(3)只可编写刚好足以通过当前失败测试的生产代码。

这3个定律,语法非常接近高等数学中的ε-δ语言,用严谨的语法用一种不直观的表述形式精确的阐述一个概念。

通俗的理解就是,测试代码和生产代码一起写,每一个小周期非常小,书中说的是30秒。

2,保持测试整洁

脏测试等同于没测试(或者更坏)。

测试的可读性甚至比生产代码的可读性还重要

3,每个测试一个概念

一个测试不要做两件事,一个测试其实就是一个函数。

4,FIRST原则

快速、独立、可重复、自足验证、及时

可重复指的是每次运行结果都一样,不依赖于环境,也没有随机行为。

自足指的是结果只有2种,用例success或fail,不需要看其他信息,比如打印。

 

十,分离构造和使用

1,将系统的构造和使用分开

构造和使用混杂,会违反单一职责原则。比如:

  1. Node getNode()
  2. {
  3. if(node == NULL) return new Node();
  4. else return node;
  5. }

这样会造成耦合、很难测试。

2,依赖注入/控制反转

依赖注入/控制反转_nameofcsdn的博客-CSDN博客

 

十一,跌进

1,简单设计四原则

运行所有重复,不可重复,表达了程序员的意图,尽可能减少类和方法的数量

最近参加的演进式设计培训中,简单设计四原则有个差不多的表述:通过所有测试、尽量消除重复、尽可能清晰的表达、没有冗余,重要程度依次降低,主观性依次增加。

 

十二,味道与启发

这一章对应《重构:改善既有代码的设计》这本书,讲到了很多代码坏味道。由于之前看过这本书,所以这里我再把印象中没有的拎出来:

(1)一个源文件中存在多种语言

(2)不恰当的静态方法

这个应该是只有面向对象代码才涉及的,类的静态方法是用类调用的,而不是用对象调用的,所以不能实现多态。

如果一个方法不在类中,那应该就是看实际作用范围,如果只在本文件中作用,那就用static修饰,防止被随意extern

(3)掩蔽时序耦合

书中的例子是,三个无参的函数依次调用,阅读者看不出来他们之间的时序耦合,改成返回值传递做入参的写法,就一眼看出来了。

不过这一条看的没什么感觉,还没有实际体会到这方面,一般也不敢随意去挪动代码顺序。

(4)在较高层放置可配置数据

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。