《代码整洁之道》

目录

一,整洁代码

1,什么是整洁代码

二,有意义的命名

1,名副其实

2,避免误导

(1)特殊字母、词汇

(2)很长很接近的词

3,使用读得出来的名称

4,使用可搜索的名称

5,匈牙利标记法

6,每个概念对应一个词

三,函数

1,函数应该尽量短小

2,一个函数只做一件事

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

4,函数参数

5,无副作用

四,注释

1,好注释

2,坏注释

五,格式

1,垂直格式

2,水平格式

六,对象和数据结构

1,数据抽象

2,对象和结构体

3,demeter律

七,错误处理

1,使用异常而非返回码

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

八,边界

1,整洁的边界

九,单元测试

1,TDD三定律

2,保持测试整洁

3,每个测试一个概念

4,FIRST原则

十,分离构造和使用

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

2,依赖注入/控制反转

十一,跌进

1,简单设计四原则

十二,味道与启发


一,整洁代码

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,将系统的构造和使用分开

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

Node getNode()
{
    if(node == NULL) return new Node();
    else return node;
}

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

2,依赖注入/控制反转

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

十一,跌进

1,简单设计四原则

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

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

十二,味道与启发

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

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

(2)不恰当的静态方法

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

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

(3)掩蔽时序耦合

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

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

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

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值