上面一篇主要说了如何写出整洁干净的函数,单一职责,短小,不重复等。下面继续后续内容。
[b]三、 注释[/b]
亲身体会,这是一个纠结争议的问题。注释多了好还是少好?注释能比代码更精确吗?代码能让人一目了然吗?
注释的[b]恰当[/b]使用是为了弥补我们用代码表达意图时遭遇的失败。注释总是一种失败,太多的注释并不值得庆贺。因为注释会撒谎,代码在变动在演进,程序员不能坚持维护注释。[b]只有代码是真实的。[/b]记得同事郜代表的名言“代码是不会骗人的!”,如雷贯耳。
有些注释是必要的,也是有利的。
好注释:
1) 法律信息。比如类开头的一些Copyright…
2) 对意图的解释
3) 警示,提醒
4) 公共api中的javadoc,这个相信大家体会很深,没有详细的jdk的javadoc我们很难使用它们。但这个javadoc需要精确,长期的维护,以免误导或错误。
坏注释
1)所谓每个函数都要有javadoc或每个变量都要有注释的规矩是愚蠢可笑的,同意啊!!
2)不需要类似修改记录一样的注释,我们有代码管理工具,历史信息可以查看。
3)注释掉的代码不需要保留,直接删掉。我们的代码库可以查到历史记录。
短函数不需要太多描述,[b]给短函数取个好名字比注释好的多[/b]。
[b]四、 格式[/b]
代码格式关乎沟通,沟通是开发者之间的头等大事,项目组至少要采取统一的格式要求,比如缩进,变量定义,空行等。[b]团队制定的规则,所有人都要遵守。[/b]
适当的空行可以让读者对独立的逻辑块一目了然。
变量尽可能在靠近使用的地方定义,循环变量总要在循环体里定义。
相关函数调用,同一个类中调用者尽可能在被调用者前面。
概念相关的函数尽可能靠近。
[b]五、 对象和数据结构[/b]
对象和数据结构是抽象的结果。对象对外隐藏数据暴露行为,数据结构对外暴露数据,几乎没有行为,数据结构很适合传送数据,比如作为方法参数,成员变量。两者可以综合考虑使用。
使用对象尽量遵循“最少知识原则”不要链式调用,如a.getB().getC().getD();
可以看B,C,D是否可以转为纯数据结构被包含在A中,可以a.b.c.d的使用,不违反Demeter原则。这点可以放个疑问,貌似这样实现也不怎么优雅??
[b]六、 错误处理[/b]
[b]不要因为错误处理而搞乱了正常代码逻辑。[/b]
1. 使用异常而非返回码
2. 使用不可控异常
这一点还是值得讨论的,检查异常和非检查异常各有用处。《clean code》作者认为检查异常破坏了开闭原则,当底层的方法修改,增加了一个检查异常则所有调用到这个方法的地方都要增加异常的捕获。但是也不至于底层一个小小的异常就直接导致程序终止吧,比如数据库里查出的一个值格式不正确此时可以采取很多处理,比如给个默认值等等,依场景而定。所以这点还是搁置吧,具体问题具体分析。
3. [b]别返回null值。[/b]
如果一个方法可能返回null值,则在调用的地方会有很多的if(obj != null),一处不判断就有可能抛出个空指针,也导致代码很脏,这点深有体会,也曾经全员整改过返回null值的情况,比如返回list的方法可以把null改为空的list。
4. [b]别传递null值[/b]
传入null值在方法刚开始还是要判断null值。但如果是对外提供api,那没办法了,必须判断,谁知道调用者传过来的是什么呢?
[b]七、 单元测试[/b]
单元测试也应跟生产代码一样整洁,测试代码也必须要维护。
关于单元测试的标准可以参考其他书籍,比如junit的标准,独立,可重复等。每个单元测试只测试一个功能。
关于TDD本人一直觉得有个很大的问题,单元测试要在什么层次做?每个方法都要?这样重构时会有很大的麻烦,任何一个方法的改动都会导致大片的ut失败,难道这就是目的?要行覆盖? 本人所待过的项目组的原则是在业务层面ut,保证功能,当然这会有遗漏,但本人还是很赞同的。大家怎么看呢?
[b]八、 类[/b]
1.类应该短小
[b]类的第一规则就是应该短小,第二规则就是要更短小[/b]。是的,跟函数看起来一样,但短小的定义不一样。函数的短小是以代码行数衡量(原书话语,个人认为行数只是一方面,其实跟类也是一样,权责也要小,就是单一权责嘛),但类的短小是权责,不要上帝类。
和函数一样,当命名觉得不太好涵盖类的功能时,就是类的权责太多了。
1)单一权责原则(SRP)
系统应该由许多短小的类而不是少量巨大的类组成。每个小类封装成一个权责单一的类,并与少数其他类一起协同达成期望的系统行为。
2)内聚
类应该只有少量实体变量,并且类中的每个方法都应该操作一个或多个这种变量。方法操作的变量越多,则此方法跟类的关系越内聚。
3)保持内聚性就会得到许多短小的类
2.为了修改而组织,要符合开闭原则
[b]九、 系统层级[/b]
一个系统肯定会有很多模块,要有负责全局的,有负责细节的,抽象层级和实现层级不能混乱,保持系统的整洁。
每个应用程序都应该关注起始过程。如,对象的初始化。
Public Service getService()
{
If(null == service)
{
Service = new MyService(…);
}
Return service;
}
这就是所谓的延迟初始化,也有一定的好处。然而也遇到了问题,依赖了MyService的实现,硬编码。
这种情况出现在一处也不会有太大问题,当系统中多次出现时就会造成缺乏模块组织性,甚至重复代码。个人认为这点不是很绝对,延迟加载也是有好处的,核心是不要造成系统组织混乱就可以了。
1. 工厂
使用工厂模式方法构造对象,让使用的模块不用担心创建。
2. 依赖注入
这是一种强大的将构造和使用分离的策略,由容器去管理对象的生命周期。
3. 代理模式和AOP
当一些横切事务需要统一处理,如日志,性能统计,事务处理等横切功能可以统一使用aop方式管理。
4. 不要过度设计
[b]十、 迭进[/b]
通过迭进设计达到整洁目的。
前面就说过,整洁的代码不是第一稿就能写出来的,写完一稿要经过重构。
Kent Beck(极限编程创始人之一)关于简单设计有四个原则,对于创建具有良好设计的软件有莫大的帮助。
1) 运行所有测试
2) 不可重复
3) 表达程序员的意图
4) 尽量减少类和方法的数量
这四点重要性从上到下减小,实践时可以根据情况排序。
[b]被无数次强调的原则就是测试是重构的前提。没有用例不要重构!当然不能没有用例。[/b]
[b]重复是一切坏味道的起源。[/b]最简单的重复形式是代码块一样或及其相似,还有功能很相似的方法。方法抽取,或提升公共方法到父类是常用的方法,模板方法模式是一种移除高层级重复的通用技巧。
表达力就可以按上面讲的准确的命名,短小的函数来达到表达自己要完成的功能或设计,比如类名中包含Command或Visitor。
尽可能少的类,重要性会排在最后面,一般这种情况发生是因为教条式的分解方法或类导致,比如为每个类创建接口。
[b]三、 注释[/b]
亲身体会,这是一个纠结争议的问题。注释多了好还是少好?注释能比代码更精确吗?代码能让人一目了然吗?
注释的[b]恰当[/b]使用是为了弥补我们用代码表达意图时遭遇的失败。注释总是一种失败,太多的注释并不值得庆贺。因为注释会撒谎,代码在变动在演进,程序员不能坚持维护注释。[b]只有代码是真实的。[/b]记得同事郜代表的名言“代码是不会骗人的!”,如雷贯耳。
有些注释是必要的,也是有利的。
好注释:
1) 法律信息。比如类开头的一些Copyright…
2) 对意图的解释
3) 警示,提醒
4) 公共api中的javadoc,这个相信大家体会很深,没有详细的jdk的javadoc我们很难使用它们。但这个javadoc需要精确,长期的维护,以免误导或错误。
坏注释
1)所谓每个函数都要有javadoc或每个变量都要有注释的规矩是愚蠢可笑的,同意啊!!
2)不需要类似修改记录一样的注释,我们有代码管理工具,历史信息可以查看。
3)注释掉的代码不需要保留,直接删掉。我们的代码库可以查到历史记录。
短函数不需要太多描述,[b]给短函数取个好名字比注释好的多[/b]。
[b]四、 格式[/b]
代码格式关乎沟通,沟通是开发者之间的头等大事,项目组至少要采取统一的格式要求,比如缩进,变量定义,空行等。[b]团队制定的规则,所有人都要遵守。[/b]
适当的空行可以让读者对独立的逻辑块一目了然。
变量尽可能在靠近使用的地方定义,循环变量总要在循环体里定义。
相关函数调用,同一个类中调用者尽可能在被调用者前面。
概念相关的函数尽可能靠近。
[b]五、 对象和数据结构[/b]
对象和数据结构是抽象的结果。对象对外隐藏数据暴露行为,数据结构对外暴露数据,几乎没有行为,数据结构很适合传送数据,比如作为方法参数,成员变量。两者可以综合考虑使用。
使用对象尽量遵循“最少知识原则”不要链式调用,如a.getB().getC().getD();
可以看B,C,D是否可以转为纯数据结构被包含在A中,可以a.b.c.d的使用,不违反Demeter原则。这点可以放个疑问,貌似这样实现也不怎么优雅??
[b]六、 错误处理[/b]
[b]不要因为错误处理而搞乱了正常代码逻辑。[/b]
1. 使用异常而非返回码
2. 使用不可控异常
这一点还是值得讨论的,检查异常和非检查异常各有用处。《clean code》作者认为检查异常破坏了开闭原则,当底层的方法修改,增加了一个检查异常则所有调用到这个方法的地方都要增加异常的捕获。但是也不至于底层一个小小的异常就直接导致程序终止吧,比如数据库里查出的一个值格式不正确此时可以采取很多处理,比如给个默认值等等,依场景而定。所以这点还是搁置吧,具体问题具体分析。
3. [b]别返回null值。[/b]
如果一个方法可能返回null值,则在调用的地方会有很多的if(obj != null),一处不判断就有可能抛出个空指针,也导致代码很脏,这点深有体会,也曾经全员整改过返回null值的情况,比如返回list的方法可以把null改为空的list。
4. [b]别传递null值[/b]
传入null值在方法刚开始还是要判断null值。但如果是对外提供api,那没办法了,必须判断,谁知道调用者传过来的是什么呢?
[b]七、 单元测试[/b]
单元测试也应跟生产代码一样整洁,测试代码也必须要维护。
关于单元测试的标准可以参考其他书籍,比如junit的标准,独立,可重复等。每个单元测试只测试一个功能。
关于TDD本人一直觉得有个很大的问题,单元测试要在什么层次做?每个方法都要?这样重构时会有很大的麻烦,任何一个方法的改动都会导致大片的ut失败,难道这就是目的?要行覆盖? 本人所待过的项目组的原则是在业务层面ut,保证功能,当然这会有遗漏,但本人还是很赞同的。大家怎么看呢?
[b]八、 类[/b]
1.类应该短小
[b]类的第一规则就是应该短小,第二规则就是要更短小[/b]。是的,跟函数看起来一样,但短小的定义不一样。函数的短小是以代码行数衡量(原书话语,个人认为行数只是一方面,其实跟类也是一样,权责也要小,就是单一权责嘛),但类的短小是权责,不要上帝类。
和函数一样,当命名觉得不太好涵盖类的功能时,就是类的权责太多了。
1)单一权责原则(SRP)
系统应该由许多短小的类而不是少量巨大的类组成。每个小类封装成一个权责单一的类,并与少数其他类一起协同达成期望的系统行为。
2)内聚
类应该只有少量实体变量,并且类中的每个方法都应该操作一个或多个这种变量。方法操作的变量越多,则此方法跟类的关系越内聚。
3)保持内聚性就会得到许多短小的类
2.为了修改而组织,要符合开闭原则
[b]九、 系统层级[/b]
一个系统肯定会有很多模块,要有负责全局的,有负责细节的,抽象层级和实现层级不能混乱,保持系统的整洁。
每个应用程序都应该关注起始过程。如,对象的初始化。
Public Service getService()
{
If(null == service)
{
Service = new MyService(…);
}
Return service;
}
这就是所谓的延迟初始化,也有一定的好处。然而也遇到了问题,依赖了MyService的实现,硬编码。
这种情况出现在一处也不会有太大问题,当系统中多次出现时就会造成缺乏模块组织性,甚至重复代码。个人认为这点不是很绝对,延迟加载也是有好处的,核心是不要造成系统组织混乱就可以了。
1. 工厂
使用工厂模式方法构造对象,让使用的模块不用担心创建。
2. 依赖注入
这是一种强大的将构造和使用分离的策略,由容器去管理对象的生命周期。
3. 代理模式和AOP
当一些横切事务需要统一处理,如日志,性能统计,事务处理等横切功能可以统一使用aop方式管理。
4. 不要过度设计
[b]十、 迭进[/b]
通过迭进设计达到整洁目的。
前面就说过,整洁的代码不是第一稿就能写出来的,写完一稿要经过重构。
Kent Beck(极限编程创始人之一)关于简单设计有四个原则,对于创建具有良好设计的软件有莫大的帮助。
1) 运行所有测试
2) 不可重复
3) 表达程序员的意图
4) 尽量减少类和方法的数量
这四点重要性从上到下减小,实践时可以根据情况排序。
[b]被无数次强调的原则就是测试是重构的前提。没有用例不要重构!当然不能没有用例。[/b]
[b]重复是一切坏味道的起源。[/b]最简单的重复形式是代码块一样或及其相似,还有功能很相似的方法。方法抽取,或提升公共方法到父类是常用的方法,模板方法模式是一种移除高层级重复的通用技巧。
表达力就可以按上面讲的准确的命名,短小的函数来达到表达自己要完成的功能或设计,比如类名中包含Command或Visitor。
尽可能少的类,重要性会排在最后面,一般这种情况发生是因为教条式的分解方法或类导致,比如为每个类创建接口。