如何写出规范而整洁的代码是每一位程序员孜孜不倦的追求,这在团队协作中显得尤为重要,从《代码整洁之道》,《阿里巴巴Java开发手册》(附下载链接: https://pan.baidu.com/s/11Yo0_9bqa-WQS5G25MXyrQ 提取码: bg43),《重构:改善既有代码的设计》等书,无不体现人们为之做出的努力。最近读了Robert C.Martin的《代码整洁之道》,做如下总结:
1. 整洁代码
1.1 整洁的代码力求集中,每个函数,每个类和每个模块都全神贯注于一事,完全不受四周细节的干扰和污染。
1.2 减少重复代码,提高表达力,提早构建简单对象(即不要重复代码,只做一件事,表达力,小规模抽象)。
1.3 设计原则的引用,包括单一职责原则(Single Responsibility Principle,SRP),开放闭合原则(Open Closed Principle,OCP)和依赖倒置原则(Dependency Inversion Principle,DIP)
2. 有意义的命名
2.1 如果名称需要注释来补充,那就不算是名副其实。
2.2 在命名时可以选择指明了计量对象和计量单位的名称。
2.3 别用accountList来指明一组账号,除非它真的是List类型,List一词对程序员有特殊意义,容易引起错误的判断,用accountGroup或bunchOfAccount,甚至用accounts都会好一些。
2.4 长名称胜于短名称,搜得到的名称胜于用自造编码代写就的名称,当然短名称若能表达出明显的意图则更好。
2.5 如果接口和实现必须选一个来编码的话,选择实现。ShapeFactoryImpl,甚至是丑陋的CShapeFactory,都比对接口名称编码来的好。
2.6 类名和对象名应该是名词或名词短语,不应当是动词。
2.7 方法名应当是动词或动词短语。
2.8 重载构造器时,使用描述了参数的静态工厂方法名,可以考虑将相应的构造器设置为private,强制使用这种命名手段。(即通过构造器私有化,通过静态方法提供实例对象。)
2.9 当涉及多个概念(类似一系列controller,manager,driver等)时,可以通过公共类特征+s进行统一。
2.10 避免将同一单词用于不同目的,同一术语用于不同概念,即双关语。比如多个类中均有add方法,则按不同的语义情景进行命名,也可以通过给名称加前后缀,以此添加语境加以区分。
3. 函数
3.1 函数应该做一件事。做好这件事,只做这一件事。函数的语句都要在同一抽象层级上。
3.2 别害怕长名称,长而具有描述性的名称,要比短而令人费解的名称好。长而具有描述性的名称,要比描述性的长注释好。
3.3 使用与模块名一脉相承的短语,名词和动词给函数命名。
3.4 如果函数要对输入参数进行转换操作,转换结果就该体现为返回值。
3.5 如果函数需要两个,三个或三个以上参数,就说明其中一些参数应该封装为类了。
3.6 普遍而言,应避免使用输出参数(需求减少)。
3.7 在进行boolean值判断时,应当把指令与询问分隔开来,相当于可以把判断逻辑封装在函数里,用类似xxxIsTrue/Existed代替。
3.8 最好把try和catch代码块的主体部分抽离出来,另外形成函数。
3.9 使用异常代替错误码,新异常就可以从异常类派生出来,无需重新编译或重新部署。
3.10 在不断尝试中消灭重复。打磨代码,分解函数,修改名称,消除重复,甚至缩短和重新安置方法,拆散类,保持测试通过。
4. 注释
4.1 注释总是一种失败,我们总无法找到不用注释就能表达自我的方法,所以总要有注释。尽管有时也需要注释,我们也该多花心思尽量减少注释量。
4.2 用代码去解释你大部分的意图,唯一真正好的注释是你想办法不去写的注释。
4.3 注释的作用是解释未能自行解释的代码,如果注释本身还需要解释,那就太遗憾了。
4.4 虽然Javadoc对于公共API非常有用,但对于不打算作公共用途的代码就令人厌恶了。
5. 格式
5.1 几乎所有的代码都是从上往下读,从左往右读。每行展现一个表达式或一个子句,每组代码行展示一条完整的思路,这些思路用空白行区分开来。(比如不同方法间,展现垂直方向上区隔的作用)。
5.2 被调用的函数应该放在执行调用函数的下面,这样就建立了一种自顶向下贯穿源代码模块的良好信息流。
5.3 水平分割中,强联系的代码单词之间不用空格分开,弱联系的用空格字符加强分隔效果。(类似乘法因子之间没加空格,因为它们具有较高优先级。加减法运算项之间用空格隔开,因为加法和减法优先级较低)。
6. 对象和数据结构
6.1 对象把数据隐藏于抽象之后,曝露操作数据的函数。数据结构曝露其数据,没有提供有意义的函数。(两种定义本质上是对立的)。
6.2 过程式代码难以添加新数据结构,因为必须修改所有函数。面向对象代码难以添加新函数,因为必须修改所有类。
6.3 得墨忒耳律认为:模块不应了解它所操作对象的内部情形,方法不应调用由任何函数返回的对象的方法(类似于调用了返回值结果的方法,效果呈现连续调用,也称“火车失事”)。
6.4 最为精炼的数据结构,是一个只有公共变量,没有函数的类。这种数据结构有时被称为数据传送对象,或DTO(Data Transfer Objects)。DTO是非常有用的结构,尤其是在于数据库通信,或解析套接字传递的消息之类场景中。
6.5 对象曝露行为,隐藏数据,便于添加新对象类型而无需修改既有行为,同时也难以在既有对象中添加新行为;数据结构曝露数据,没有明显的行为,便于向既有数据结构添加新行为,同时也难以向既有函数添加新数据结构。
7. 错误处理
7.1 错误处理很重要,但如果它搞乱了代码逻辑,就是错误的做法。
7.2 使用异常而非返回码,往往使用定义异常+错误码+Msg。
7.3 用特定对象返回代替业务逻辑与错误处理结合,将异常行为封装到指定对象中。
7.4 如果你打算在方法中返回null值,不如抛出异常,或是返回特例对象。如果你在调用某个第三方API中可能返回null值的方法,可以考虑用新方法打包这个方法,在新方法中抛出异常或返回特例对象。(类似于一个中间转换)。
7.5 如果将错误处理隔离看待,独立于主要逻辑之外,就能写出强固而整洁的代码。
8. 边界
8.1 边界上的接口(Map)是隐藏的,它能随来自应用程序其他部分的极小的影响而变动。
9. 单元测试
9.1 TDD三定律:
定律一 在编写不能通过的单元测试前,不可编写生产代码 。
定律二 只可编写刚好无法通过的单元测试,不能编译也算不通过。
定律三 只可编写刚好足以通过当前失败测试的生产代码。
9.2 测试代码和生产代码一样重要,需要保证可读性。
9.3 双重标准,有些事你大概永远不会在生产环境中做,而在测试环境中做却完全没有问题。
9.4 单个测试中的断言数量应该最小化。
9.5 最佳规则也许是应该尽可能减少每个概念的断言数量,每个测试函数只测试一个概念。
10. 类
10.1 系统应该由许多短小的类而不是少量巨大的类组成。每个小类封装一个权责,只有一个修改的原因,并与少数其他类一起协同达成期望的系统行为。
10.2 开放-闭合原则(Open Closed Principle,OCP):类应当对扩展开放,对修改封闭。在理想系统中,我们通过扩展系统而非修改现有代码来添加新特性。
10.3 依赖倒置原则(Dependency Inversion Principle,DIP),本质而言,DIP认为类应当依赖于抽象而不是具体细节。
(未完待续~)