现代软件架构的复杂性要求需要多人或多团队协同完成开发。在这种背景下,如何高效地协同完成软件的开发呢?对软件工程来说,编程规范是在编程层面对软件开发者的规范或标准。对软件来说,适当的规范和标准绝不是为了消除代码内容的创造性、优雅性,而是限制过度个性化,并以一种普遍认可的统一方式一起做事,提升协作效率,降低沟通成本。代码的字里行间流淌的是软件系统的血液,质量的提升是尽可能少踩坑,杜绝踩重复的坑,切实提升系统稳定性,码出质量。 接下来将从多个方面介绍Java编程规范。如命名风格、注释风格、代码排版风格、变量规范、类型规范、方法规范、接口规范、异常规范、日志规范、其他等。
命名风格
好的命名风格可以让代码更容易理解、维护、使用。编码先从命名风格上进行培养。
【强制】文件编码格式必须是UTF-8
在新增或修改文件时,文件的编码格式必须统一。编程领域,文件编码格式必须是UTF-8,不应为了个人方便使用其他编码格式, 如GBK、ASCII等。
【强制】空白字符只能是空格,不能使用制表符
禁止使用制表符。不同操作系统对制表符的转码不同。不建议使用Tab缩进。如果使用 Tab 缩进,必须设置 1 个 Tab 为 4 个空格。
【强制】标识符仅能使用字母、数字、下划线,且不能以下划线开头
这个规范是Java语言标识符定义规则。建议控制标识符长度不超过31个。如果太长,则需考虑换种角度定义标识符。
【强制】禁止使用拼音与英文混合方式命名或直接使用中文的方式命名
基于英文编码已是事实标准。在定义标识符时,不应将拼音与英文混合或直接使用中文。对国际通用的拼音,可以视同为英文,如BeiJing等。
【强制】禁止使用任何语言的种族歧视性词语
随着种族意识的弱化,不应使用使用种族歧视性词语。如不应使用 blackList/whiteList/slave,而应使用blockList/allowList/secondary等。
【强制】标识符定义风格限制在PascalCase、CamelCase、SnakeCase
标识符定义风格主要有PascalCase、CamelCase、SnakeCase三类。PascalCase也称UpperCamelCase,CamelCase默认指lowerCamelCase。针对缩写、包名、 接口、类、注解、枚举类型、非常量字段、方法、方法参数、局部变量、静态常量、枚举值、泛型、异常等,其命名应遵从不同的风格。 具体要求如下表所示。
类型 | 风格 | 示例 |
---|
缩写 | 全小写 | 如dto、tcp、html、do。注意,不同厂商对其规范不同。如ali则要求缩写全大写 | 包名 | 全小写且不含下划线 | 如github、courage007等。注意,连续的单词简单连接在一起,不能使用下划线,如springboot、modelmvc等 | 接口、类、注解、枚举类型 | PascalCase | 如User、Entity、Readable、@Service、ColorEnum。针对枚举类型,使用Enum后缀标识其是枚举类型 对于测试类,统一使用Test后缀。对于抽象类使用Abstract或Base开头类名。对于异常类,统一使用Exception后缀。枚举类型通常是名称或名词短语。接口名称可以是名称或名称短语,也可以是形容词或形容词短语 | 成员字段、方法参数、局部变量 | CamelCase | 如userName、userId等。对于final修饰的局部变量或成员变量,也应将其作为非常量字段处理 | 静态常量、枚举值 | SnakeCase | 如MAX_COUNT、EXPIRED_TIME、RED、GREEN等。不要使用魔鬼数字,要用有意义的常量替代。 | 方法名 | CamelCase | 如getColor、setValue等。方法名通常是动词或动词短语。常见的模式有:is + 布尔属性值,get + 非布尔属性值,has+名称/形容词,动词、动宾短语。部分允许介词的场景:onCreate、toString等 | 泛型 | SnakeCase | 如E、T、T_INNER等。尽量使用单个字符表示,少数场景下需要使用多个字符表示,且使用下划线分割 |
【推荐】对使用了设计模式的标识符,在在命名时需体现出具体模式
将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。如UserFactory、LoginProxy等;
注释
注释主要有以下三方面的作用:1.能够反映代码的设计思想和主要功能;2.能够描述业务含义,方便其他程序员了解代码背后的含义;3.提示示例代码,方便调用者使用。对于注意的中英文没有特别要求,对于面向国内开发者或公司内部的项目,其注释推荐使用中文,对于面向国际环境或开源的项目, 其注释推荐使用英文。
【强制】必须遵从Javadoc规范
类、类成员、类方法的注释必须使用Javadoc规范。对于override注解修饰的方法,不要求遵循Javadoc规范。对于测试方法、不强制遵循Javadoc规范。
【强制】文件头注释必须包含版权许可
无论是商业软件,还是开源软件,都应包含版权许可,以说明该软件所有权或可用权。
【强制】所有的类都必须添加创建者和创建日期
新增类文件,必须添加创建者和创建日期。
【强制】不用的代码片段,如import、废弃代码,应删除掉,而不是注释掉
不使用的import应删除,没有被引用的字段、方法应删除。不要在已提交生产环境的代码中保留注释的代码。
【建议】正式商用的代码不应包含TODO/FIXME等注释
通过测试验收的代码或已经商用的代码,不应在代码中包含TODO/FIXME的注释。因为已通过测试的代码或商用的代码,理论上应该将所有已知问题解决, 这也符合墨菲定律。
【建议】注释应精简准确
好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的一个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释又是相当大的负担。
代码排版
如果将编程看成创作,那么一个个类文件就是一页页的内容。书籍发版讲究排版,以方便读者阅读,编程同样也需要保证排版。
【强制】避免单文件过长,关键代码不应超过2000行
避免引入上帝类。在编码的过程中,要按照单一职责的原则,合理规划类的功能。对于自动生成的临时代码,则不强制要求。
【强制】一个源文件应按照版权、包名、import、类名的顺序定义
注意,import语句不能使用通配符,应严格指明引入的类。
【建议】import引入的包应按照当前模块、二方件、三方件、JDK的顺序
源文件中引入的对象应根据其类型进行统一,以保证风格统一。
【建议】类或接口应按照静态变量、静态块、实例变量、实例块、构造方法、实例方法顺序声明或定义
其中实例方法又可根据访问权限约定其顺序,如先pulic方法,再protected方法,最后private方法。
【强制】严格使用大括号
按照左括号和右括号,在使用大括号时应保证: (1) 左大括号前不换行 (2) 左大括号后换行 (3) 右大括号前换行 (4) 右大括号后还有 else 等代码则不换行;表示终止的右大括号后必须换行。 在if、else、switch、for、do和while等语句中,即使程序体为空,也应使用大括号,且使用规则不变。
【强制】左大括号前必须加空格
【强制】if、else、switch、for、do或while等保留字与括号之间必须加空格
【强制】任何运算符的左右两边都应加一个空格
【强制】使用空格进行缩进,每次缩进4个空格
不应使用制表符代替4个空格。如果使用tab缩进,应确保对应4个空格(有些编辑器默认会将tab缩进对应成2个空格,如vs-code)。
【强制】注释的双斜线与注释内容之间有且仅有一个空格
示例如下:
String testStr = "test";
【强制】每行限长120个字符
一行的内容不应过长,国际上推荐单行限制在120个字符以内。对超过120个字符的行,应换行。换行时,第二行相对第一行应缩进4个空格, 从第三行开始不再继续缩进。
【建议】减少不必要的空行,保持代码紧凑
在编码时,有些同学会使用空行来分隔不同的业务含义。这种场景的推荐做法是拆分方法。
变量规范
【强制】不能用浮点数作为循环因子
Java语言中浮点数不是精确存储,不能用浮点数作为循环因子,否则可能导致无限循环问题。
【强制】精确计算时不要使用float或double
Java语言中浮点数不是精确存储,需要精确计算时,可以使用BigDecimal。
【强制】浮点数相等判断时不能直接使用==或equals
Java语言中浮点数不是精确存储,需要判断相等时,可以先转换成BigDecimal,或在精度允许的情况下,使用差值。
【强制】禁止使用构造方法 BigDecimal(double)的方式把 double 值转化为 BigDecimal 对象
new BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。 如:BigDecimal g = new BigDecimal(0.1F); 实际的存储值为:0.10000000149。优先推荐入参为 String 的构造方法, 或使用 BigDecimal 的 valueOf 方法,此方法内部其实执行了 Double 的 toString,而 Double 的 toString 按 double 的实际能表达的精度对尾数进行了截断。
【强制】BigDecimal 的等值比较应使用 compareTo()方法,而不是 equals()方法
BigDecimal的 equals()方法会比较值和精度(1.0 与 1.00 返回结果为 false),而 compareTo()则会忽略精度。
方法规范
【强制】避免方法过长,尽量控制在80行以内
过长的方法意味着方法功能不单一,过于复杂,或过分拆分细节。大方法不容易维护,应尽量控制方法的长度。
【强制】方法的代码块不宜嵌套过深,不要超过4层
一般来说,方法的时间复杂度要控制在
O
(
n
2
)
O(n^2)
O(n2)范围内,少数情况下可接受时间复杂度
O
(
n
3
)
O(n^3)
O(n3)的方法。对应地,方法的嵌套层级 也不应过深。 使用卫语句可以有效地减少if语句相关的嵌套层级。此外,还可以考虑使用Stream来减少嵌套和圈复杂度。
【强制】静态方法应使用类名来调用,而不要使用实例或表达式
使用实例来调用静态方法容易产生混淆,让他人以为是实例上的方法。
【强制】所有的重写方法,必须加@Override注解
当子类重写父类方法或实现接口方法是,需要加上@Override注解。尽管@Override注解不是强制,但是对于商用代码, 必须加上@Override注解,以标识这是一个重写方法。
【强制】不应在方法内部修改入参
如果需要修改入参,可以通过定义局部变量的方式实现,不应直接修改入参。
【建议】方法的参数个数不建议超过5个
在方法重构中有一个规则,方法的参数不应超过5个。对需要超过5个场景,要么选择拆分方法,要么选择将多个参数整合成一个(定义一个对象)。
【建议】对于返回集合的方法,不建议返回null
如果对返回集合的方法返回null,还需在调用方进行判空处理。如果未进行判空,则会导致空指针异常。
类型规范
【强制】构造方法禁止加入业务逻辑,对于初始化逻辑,应单独在init方法中执行
【强制】不要在父类方法中执行可能被子类覆盖的方法
当在父类方法中执行可能被子类覆盖的方法时,会导致构造方法的执行不可预知,加大问题的定位难度。
【强制】重写equals方法时,应同时重写hashCode方法
集合在进行索引时,会优先基于hashCode进行判断(一旦hashCode不同,则认定其不存在)。
接口规范
【建议】接口定义中去掉多余的修饰词
在接口中,字段缺省是public static final),方法缺省是public abstract。无需再次补充这些修饰词。
【建议】推荐使用接口去定义一个常量类
接口中的常量只能是静态常量,使用接口定义常量类,可以减少public static final修饰词的编写(在接口中,字段缺省是public static final)。
异常规范
【强制】异常捕获后,不要用来做流程控制,条件控制
可以吞掉异常,但是不应基于异常进行流程控制,条件控制。异常设计的初衷就是解决程序运行中的各种意外。
【强制】不要通过一个空的catch块吞掉异常
当需要忽略异常时,不要使用空块,至少要打印系统日志,记录异常信息。反例:
try {
} catch(IOException e) {
}
【强制】不要在finally块中使用return、break或continue
try 块中的 return 语句执行成功后,并不马上返回,而是继续执行 finally 块中的语句,如果此处存在 return 语句, 则在此直接返回,无情丢弃掉 try 块中的返回点。
【强制】在调用RPC、二方包或动态生成类的方法时,捕获异常时必须使用Throwable类拦截
为避免调用时,无法找到方法,捕获异常时,应使用Throwable,避免NoSuchMethodError无法捕获。
【建议】防止NPE场景
编码过程中,需时刻关注空指针异常,避免因空指针异常导致业务异常。
【建议】一个方法不应抛出超过5个异常
方法抛出过多的异常,会加重调用方的异常处理工作,同时也表示该方法承担了过多的职责,不符合职责单一的原则。
日志规范
【强制】不应使用System.out或System.err打印异常,而应使用日志框架
【强制】不应直接使用日志实现框架中的API,而应使用日志门面框架的API
应面向接口编程,而不应面向实现编程。这样做,有利于统一日志处理方式——更换日志实现框架是,无需修改现有代码。
【强制】应根据日志场景选择合适的日志级别,避免只使用一种级别
开发环境可以输出trace或debug日志,生产环境不应输出trace或debug级别日志。
【强制】打印日志时,不应打印敏感信息
对于敏感信息,不应通过日志打印出来,这会导致敏感信息泄露,带来安全问题。
【建议】尽量使用英文记录日志,不建议使用其他语言
参考
Java开发手册 嵩山版 阿里巴巴
|