作者:HW-SQH
谷歌规范中有的条目,尽量跟其一致,没有的,也与C/C++规范的靠近。像语句,表达式,空格和空行,注释,C/C++/Java家族有相当多共通的地方。
有时,妥协也是艺术。
编程规范的设计跟软件系统的设计一样,也要充分考虑兼容性、一致性、可读性。
尽量避免“一会儿向左,一会儿向右”,给开发团队造成浪费。也不能太抽象而难以落地,也不能太具体太啰嗦而失去广泛适用性。
这需要我们广泛阅读经典文献著作,也懂得如何抽象与提炼,宁缺毋滥。
[ps: 2种声音:C///报告很细节,编程规范太粗了;编程规范太细了,整改工作量太大了... oops]
预计后续的发展方向,可以继续向深水区前进,持续提升编程能力和基础质量。
继续参照经典著作,例如《Effective Java》第3版,《重构》第2版,深入理解并提炼更多的编程实践,把更多bad practice的检查落实到工具的自动化检查(与修复)中去。 这时,为了更加地工具友好(准确性,完整性),社区应该提供必要的充分的bad写法,提供足够的good写法。否则,工具的不精确,可能会僵化地告警而导致团队教条式执行,反而把代码改得更烂,例如见过static final int NUM_128 = 128。
其它优秀的规范,例如Petroware公司的代码风格,也可以吸取精华。 Petroware的风格是我见过最细致的了,仅仅是风格就有86条。它也把must, should 和 can 的条目作了一定的区分。还有《 Java编码指南 编写安全可靠程序的75条建议 》,这属于可信代码的范围。
Learning to program is more than learning the syntactic and semantic rules of a programming language. It also requires learning how to design programs. Any good book on programming must therefore teach program design. -- Ralph Johnson
关键思想是,一致的风格比“正确”的风格更重要 -- 《编写可读代码的艺术》。
代码的可读性可理解性可维护性,对于干净可信是极其重要的。
或许,对于非英语母语的程序员来说,命名依然是排名第一的困扰。命名是门艺术,应该符合人体工程学Ergonomics。
以下条目或可考虑逐步加入,对其它语言也有参考意义。也可以作为Code Review的扩展参考。
1. 变量有长的生命范围就取长的名字表达,有短的生命范围可取短的名字表达,长度2~31字符。
也有例外:i,j,k等循环变量长度可为1;Lambds除了用it这类保留字外,其它也常用o1和o2,obj,item,elem
在安卓中有时会也见到较长的,例如GardenPlantingDetailViewModelFactory
2. 变量应该在声明时初始化,尽量声明在最小生命范围,尽量保持最小的生命周期
3. 类级的变量不应该声明为public的,应提供getter/setter
4. 对象的名称是隐式的,应该在方法名称中避免使用;正交,不能有二义性,保持唯一性可减少副作用的错误
line.getLength(); // NOT:line.getLineLength();
5. is前缀用于布尔变量和方法(第三方对JavaBeans的实例属性的处理例外考虑)
isSet,isVisible,isFinished,isFound,isOpen
布尔变量的setter方法必须用set前缀 void setFound(boolean isFound);
is前缀 有一些替代方案,在某些情况下更适合
6. 用户界面组件名称应以元素类型为后缀
nameTextField,leftScrollbar,mainView,minLabel,printerDialog
7. n或者numberOf前缀应该用于表示多个对象的变量
nPoints,nLines,numberOfBooks
8. 默认接口实现可以default为前缀
class DefaultTableCellRenderer implements TableCellRenderer { // ... }
9. 专用命名术语:把信息放进名字
set/get: 用于直接访问字段/属性,且不耗时的情况,专用于JavaBeans、POJO
update,modify,更新,修改某个对象的字段/属性,常常返回void
from,convert,transform,常用于把一个值从一个类型转换为另一个类型的值,有参数有返回
fetch,query:一般用于远程获取数据的情况,有返回值
calculate,compute:用于耗时的计算方法,有返回值
valueSet.computeAverage();
matrix.computeInverse()
find:从集合或者数据库查找某些item的方法,且有可能耗时
vertex.findNearestVertex();
matrix.findSmallestElement();
init/initialize:可用于建立对象时初始化
printer.initializeFontSet();
其它专业名词有:
send,或者deliver,发送一个值;dispatch,route,forward,分派/路由/转发请求
find,或者search,extract,locate,返回搜索的elements
start,或者create,begin,open
make,或者setUp;
build,newInstance,常见于建造者模式、工厂模式
10. 互补性的实体必须使用互补的名称
put/take,get/set, add/remove, create/destroy, start/stop, insert/delete, success/failure,increment/decrement, begin/end, first/last, up/down, min/max, next/previous, offer/poll, open/close, show/hide, send/receive,load/store/save,etc.
11. 函数(返回对象的方法)应该以它们返回的内容命名返回值,对称工整
即get,fetch,compute,find等得到值的函数要处理,Bar bar = foo.getBar();
void方法常常用于处理副作用。
提高可读性。明确了该单元应该做什么,特别是它不应该做的所有事情。这再次使代码更容易保持副作用。
方法内部也要保持对称,抽象层次一致,不要凹凸不平。
12. Singleton类应该通过方法getInstance返回它们的唯一单例
13. 代表工厂创建实例的类可以通过方法new[ClassName]来实现,容器类的用of创建,基本类型用valueOf创建,对象实例转换用toXxx
14. 函数式编程中的常见命名,尽量满足Monad风格,包括map,flatMap,fold等,参见Optional
15. 契约式设计、防御式编程有利于编写安全可靠的代码。应该使用@NotNull,@Nullable等注解来提升可靠性
16. Javadoce注释要有意义且合理,完整且适当,不要包含多余的信息,例如方法名。每个tag要有定义,例如
Each element needs a definition. This includes @param
@throws SpecialException // Bad!
@throws SpecialException if val is null or fails validation // Good!
17. 对于 简单的布尔方法,直接返回表达式而不是用if,例如
18. 总是显示指定访问修饰符,如果是包访问,则写注释说明。
19. 不要包含不必要的代码,例如多余的死代码,未用到的变量,冗余的赋值或比较,例如
20. 不要导入默认的包,例如java.lang,不要显式地extends Object
21. 精通语法,掌握新的标准库API,减少圈复杂度,让代码更加简洁可读可理解,例如
22. 增加单元测试的条目,阿里的规范也有。单元测试没有受到应有的重视,但它对于重构、健壮性是至关重要的
a. 每个测试是独立的,不应假设测试执行的顺序
b. 不仅要测试happy path,还应包括边界条件
c. JUnit测试不应该打印 任何东西
d. 使用特定的断言。JUnit提供了 assertEquals, assertArrayEquals, assertNull, assertSame等广泛的断言
下面 assertEquals() 就提供了更多有用的信息:
23. 附录 阿里的Java开发手册 中单元测试:
1. 【强制】好的单元测试必须遵守 AIR 原则。
说明: 单元测试在线上运行时,感觉像空气(AIR) 一样并不存在,但在测试质量的保障上, 却是非常关键的。好的单元测试宏观上来说,具有自动化、独立性、可重复执行的特点。
A: Automatic(自动化)
I: Independent(独立性)
R: Repeatable(可重复)
2. 【强制】单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执 行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元测试中不准使用 System.out 来进行人肉验证,必须使用 assert 来验证。
3. 【强制】保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间 决不能互相调用,也不能依赖执行的先后次序。
反例: method2 需要依赖 method1 的执行, 将执行结果作为 method2 的输入。
4. 【强制】单元测试是可以重复执行的,不能受到外界环境的影响。
说明: 单元测试通常会被放到持续集成中,每次有代码 check in 时单元测试都会被执行。如果单测对外部环境(网络、服务、中间件等) 有依赖,容易导致持续集成机制的不可用。
正例: 为了不受外界环境影响,要求设计代码时就把 SUT 的依赖改成注入,在测试时用 Spring 这样的 DI 框架注入一个本地(内存)实现或者 Mock 实现。
5. 【强制】对于单元测试,要保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级别,一般是方法级别。
说明: 只有测试粒度小才能在出错时尽快定位到出错位置。单测不负责检查跨类或者跨系统的交互逻辑,那是集成测试的领域。
6. 【强制】核心业务、核心应用、核心模块的增量代码确保单元测试通过。
说明: 新增代码及时补充单元测试,如果新增代码影响了原有单元测试,请及时修正。
7. 【强制】单元测试代码必须写在如下工程目录: src/test/java,不允许写在业务代码目录下。
说明: 源码构建时会跳过此目录,而单元测试框架默认是扫描此目录。
8. 【推荐】单元测试的基本目标:语句覆盖率达到 70%;核心模块的语句覆盖率和分支覆盖率都要达到 100%
说明: 在工程规约的应用分层中提到的 DAO 层, Manager 层,可重用度高的 Service,都应该进行单元测试。
9. 【推荐】编写单元测试代码遵守 BCDE 原则,以保证被测试模块的交付质量。
B: Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
C: Correct,正确的输入,并得到预期的结果。
D: Design,与设计文档相结合,来编写单元测试。
E: Error,强制错误信息输入(如:非法数据、异常流程、非业务允许输入等),并得到预期的结果。
10. 【推荐】对于数据库相关的查询,更新,删除等操作,不能假设数据库里的数据是存在的,或者直接操作数据库把数据插入进去,请使用程序插入或者导入数据的方式来准备数据。
反例: 删除某一行数据的单元测试,在数据库中, 先直接手动增加一行作为删除目标,但是这一行新增数据并不符合业务插入规则, 导致测试结果异常。
11. 【推荐】和数据库相关的单元测试,可以设定自动回滚机制,不给数据库造成脏数据。或者对单元测试产生的数据有明确的前后缀标识。
正例: 在 RDC 内部单元测试中,使用 RDC_UNIT_TEST_的前缀标识数据。
12. 【推荐】对于不可测的代码建议做必要的重构,使代码变得可测,避免为了达到测试要求而书写不规范测试代码。
13. 【推荐】在设计评审阶段,开发人员需要和测试人员一起确定单元测试范围,单元测试最好覆盖所有测试用例。
14. 【推荐】单元测试作为一种质量保障手段,不建议项目发布后补充单元测试用例,建议在项目提测前完成单元测试。
15. 【参考】为了更方便地进行单元测试,业务代码应避免以下情况:
构造方法中做的事情过多。
存在过多的全局变量和静态方法。
存在过多的外部依赖。
存在过多的条件语句。
说明: 多层条件语句建议使用卫语句、策略模式、状态模式等方式重构。
16. 【参考】不要对单元测试存在如下误解:
那是测试同学干的事情。本文是开发手册,凡是本文内容都是与开发同学强相关的。
单元测试代码是多余的。 系统的整体功能与各单元部件的测试正常与否是强相关的。
单元测试代码不需要维护。一年半载后,那么单元测试几乎处于废弃状态。
单元测试与线上故障没有辩证关系。好的单元测试能够最大限度地规避线上故障。
24. 附录 一些参考文献:
《代码大全》提到过,名字的长度不超过20个字母,限制嵌套超过3层;“降低复杂性是编写高质量的代码的关键”;
《实现模式Implementation Patterns》提到过方法在5~15行;
《高质量程序设计艺术》提到过函数在10~20行;
Android AOSP代码风格,提到过方法行数不超过40行;
《Effective Java》第3版,提到匿名类少于10行,Lambda表达式1~3行;
《这样编码才规范 128个编码好习惯》,提到过名称不超过32字符。





































苏公安网备 32011402010933号