代码规范实践
Posted ephemeral
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了代码规范实践相关的知识,希望对你有一定的参考价值。
规范等级说明
- 级别I: 默认级别,要求所有项目必须遵守。
- 级别II: 建议所有项目中遵守。
- 级别III: 鼓励各个项目根据实际情况执行。
1.格式与命名规范(Formating and Naming Conventions)
1.1 缩进
使用Tab缩进。(I)
1.2 换行
每行80字符。(I)
if,for,while语句只有单句时,如果该句可能引起阅读混淆,需要用" {"和"}"括起来,否则可以省略。
//错误,需要使用花括号{}括起来
if (condition) if(condition) doSomething(); else doSomething();
1.3 命名规则
- 不允许使用汉语拼音命名 (I)
- 代码命名不能以下划线或美元符号开始或结束,反例: _name / __name / $Object / name_ / name$ / Object$ (I)
- 命令必须用驼峰形式 (I)
-
常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长;(I)
- 遇到缩写如XML时,仅首字母大写,即loadXmlDocument()而不是loadXMLDocument()
- Package名必须全部小写,尽量使用单个单词;(I)
- Interface名可以是一个名词或形容词(加上‘able‘,‘ible‘, or ‘er‘后缀),如Runnable,Accessible。
为了基于接口编程,不采用首字母为I或加上IF后缀的命名方式,如IBookDao,BookDaoIF。 - 页面部件名建议命名为:btnOK、lblName或okBtn、nameLbl。(II)
其中btn、lbl缩写代表按钮(Button)、标签(Label)。 - 局部变量及输入参数不要与类成员变量同名(get/set方法与构造函数除外)
- 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类 命名以它要测试的类的名称开始,以 Test 结尾 (I)
-
枚举类名建议带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开;(I)
-
各层命名规约:
A) Service/DAO层方法命名规约 (I)1) 获取单个对象的方法用findBy做前缀。(I)
2) 获取多个对象的方法用list做前缀,
3) 如果是查询用search做前缀。
4) 获取统计值的方法用count做前缀。
5) 插入的方法用save(推荐)或insert做前缀。
6) 删除的方法用remove(推荐)或delete做前缀。
7) 修改的方法用update做前缀。B) 领域模型命名规约 (I)
1) 数据对象:xxx,遵循驼峰,xxx即为数据表名,例如 User。
2) 数据传输对象:xxxDTO,xxx为业务领域相关的名称。
3) 展示对象:xxxVO,xxx一般为网页名称。
4) POJO是DO/DTO/BO/VO的统称,禁止命名成xxxPOJO。 -
常量定义
-
常量必须先定义(I)
-
1.4 声明
- 修饰符应该按照如下顺序排列:public, protected, private, abstract, static, final, transient, volatile, synchronized, native, strictfp。
- 类与接口的声明顺序(可用Eclipse的source->sort members功能自动排列):
- 静态成员变量 / Static Fields
- 静态初始化块 / Static Initializers
- 成员变量 / Fields
- 初始化块 / Initializers
- 构造器 / Constructors
- 静态成员方法 / Static Methods
- 成员方法 / Methods
- 重载自Object的方法如toString(), hashCode() 和main方法
- 类型(内部类) / Types(Inner Classes)
同等的类型,按public, protected, private的顺序排列。
1.5 格式
-
IDE的text file encoding设置为UTF-8; IDE中文件的换行符使用Unix格式, 不要使用 windows 格式 (I)
2.注释规范(Document Convertions)
2.1 注释类型
2.1.1 JavaDoc注释
略。
2.1.2 失效代码注释
由/*...*/界定,标准的C-Style的注释。专用于注释已失效的代码。
/* * Comment out the code * String s = "hello"; * System.out.println(s); */
2.1.3 代码细节注释
由//界定,专用于注释代码细节,即使有多行注释也仍然使用//,以便与用/**/注释的失效代码分开
除了私有变量外,不推荐使用行末注释。
class MyClass { private int myField; // An end-line comment. public void myMethod { //a very very long //comment. if (condition1) { //condition1 comment ... } else { //elses condition comment ... } } }
2.2 注释的格式
- 注释中的第一个句子要以(英文)句号、问号或者感叹号结束。Javadoc生成工具会将注释中的第一个句子放在方法汇总表和索引中。
- 为了在JavaDoc和IDE中能快速链接跳转到相关联的类与方法,尽量多的使用@see xxx.MyClass,@see xx.MyClass#find(String)。
- Class必须以@author 作者名声明作者,不需要声明@version与@date,由版本管理系统保留此信息。(II)
- 如果注释中有超过一个段落,用<p>分隔。(II)
- 示例代码以<pre></pre>包裹。(II)
- 标识(java keyword, class/method/field/argument名,Constants) 以<code></code>包裹。(II)
- 标识在第一次出现时以{@linkxxx.Myclass}注解以便JavaDoc与IDE中可以链接。(II)
2.3 注释的内容
2.3.1 可精简的注释内容
注释中的每一个单词都要有其不可缺少的意义,注释里不写"@param name -名字"这样的废话。
如果该注释是废话,连同标签删掉它,而不是自动生成一堆空的标签,如空的@param name,空的@return。
2.3.2 推荐的注释内容
- 对于API函数如果存在契约,必须写明它的前置条件(precondition),后置条件(postcondition),及不变式(invariant)。(II)
- 对于调用复杂的API尽量提供代码示例。(II)
- 对于已知的Bug需要声明。(II)
- 在本函数中抛出的unchecked exception尽量用@throws说明。(II)
- 类、类属性、类方法的注释必须使用 Javadoc 规范,使用/**内容*/格式,不得使用 //xxx 方式
- 所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、 异常说明外,还必须指出该方法做什么事情,实现什么功能
- 所有的类都必须添加创建者信息 方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码对齐
- 所有的枚举类型字段必须要有注释,说明每个数据项的用途 代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑 等的修改
2.3.3 Null规约
如果方法允许Null作为参数,或者允许返回值为Null,必须在JavaDoc中说明。
如果没有说明,方法的调用者不允许使用Null作为参数,并认为返回值是Null Safe的。
/** * 获取对象. * * @ return the object to found or null if not found. */ Object get(Integer id){ ... }
2.3.4 特殊代码注释
- 代码质量不好但能正常运行,或者还没有实现的代码用//TODO: 或 //XXX:声明
- 存在错误隐患的代码用//FIXME:声明
3.编程规范(Programming Conventions)
3.1基本规范
- 当面对不可知的调用者时,方法需要对输入参数进行校验,如不符合抛出IllegalArgumentException,建议使用Spring的Assert系列函数。 (II)
- 隐藏工具类的构造器,确保只有static方法和变量的类不能被构造实例。
- 变量,参数和返回值定义尽量基于接口而不是具体实现类,如Map map = new HashMap();Map map = Maps.newHashMap();
- 代码中不能使用System.out.println(),e.printStackTrace(),必须使用logger打印信息。(I)
3.2 异常处理
- 重新抛出的异常必须保留原来的异常,即throw new NewException("message", e); 而不能写成throw new NewException("message")。(I)
- 在所有异常被捕获且没有重新抛出的地方必须写日志。 (I)
- 如果属于正常异常的空异常处理块必须注释说明原因,否则不允许空的catch块。(I)
- 框架尽量捕获低级异常,并封装成高级异常重新抛出,隐藏低级异常的细节。(III)
- 不要捕获 Java 类库中定义的继承自 RuntimeException 的运行时异常类,如: IndexOutOfBoundsException / NullPointerException,这类异常由程序员预检查 来规避,保证程序健壮性
- 异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低 捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请 将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的 内容
- 有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回 滚事务
- finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch
- 不能在 finally 块中使用 return,finally 块中的 return 返回后方法结束执行,不 会再执行 try 块中的 return 语句
- 方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分 说明什么情况下会返回 null 值。调用方需要进行 null 判断防止 NPE 问题 (I)
-
防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:
1) 返回类型为包装数据类型,有可能是null,返回int值时注意判空。反例:public int f(){ return Integer 对象}; 如果为 null,自动解箱抛 NPE。
2) 数据库的查询结果可能为null。
3) 集合里的元素即使isNotEmpty,取出的数据元素也可能为null。
4) 远程调用返回对象,一律要求进行NPE判断。5) 对于Session中获取的数据,建议NPE检查,避免空指针。
6) 级联调用obj.getA().getB().getC();一连串调用,易产生NPE。
3.3 JDK
- 重载方法必须使用@Override,可避免父类方法改变时导致重载函数失效。
- 不需要关心的warning信息用@SuppressWarnings("unused"), @SuppressWarnings("unchecked"), @SuppressWarnings("serial") 注释。
3.4 日志
-
应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架(I)
SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class); -
对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式,(I)
logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);
-
异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么往上 抛。正例:logger.error(各类参数或者对象toString + "_" + e.getMessage(), e); (I)
4.OOP规范
-
避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可(I)
-
所有的覆写方法,必须加@Override 注解(I)
-
不能使用过时的类或方法
-
Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals ,正例: "test".equals(object); 反例: object.equals("test"); (I) 说明:推荐使用java.util.Objects#equals (JDK7引入的工具类)
-
所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较 (I)
-
定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值
-
构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中 (I)
-
POJO 类必须写 toString 方法。使用 IDE 的中工具:右键> generate->toString 时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString。 说明:在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排 查问题。修改默认模板方法 (I)
-
当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起, 便于阅读
-
类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter方法
-
字符串的联接方式,使用 StringBuilder 的 append 方法进行扩展 (I)
-
final 可提高程序响应效率,声明成 final 的情况 ,
a>不需要重新赋值的变量,包括类属性、局部变量
b>对象参数前加final,表示不允许修改引用的指向
c>类方法确定不允许被重写 -
在 if/else/for/while/do 语句中必须使用大括号,即使只有一行代码,避免使用 下面的形式:if (condition) statements;
-
循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接,进行不必要的 try-catch 操作(这个 try-catch 是否可以移至循环体外)
-
接口入参保护,这种场景常见的是用于做批量操作的接口
-
方法中需要进行参数校验的场景,
-
调用频次低的方法
-
执行时间开销很大的方法,参数校验时间几乎可以忽略不计,但如果因为参数错误导致,中间执行回退,或者错误,那得不偿失
-
需要极高稳定性和可用性的方法
-
对外提供的开放接口,不管是RPC/API/HTTP接口
-
敏感权限入口
-
-
方法中不需要参数校验的场景
-
极有可能被循环调用的方法,不建议对参数进行校验。但在方法说明里必须注明外部参数检查
-
底层的方法调用频度都比较高,一般不校验。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。一般 DAO 层与 Service 层都在同一个应用中,部署在同一 台服务器中,所以 DAO 的参数校验,可以省略
-
被声明成private只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参 数已经做过检查或者肯定不会有问题,此时可以不校验参数
-
-
在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度
-
注意 Math.random() 这个方法返回是 double 类型,注意取值的范围 0≤x<1(能够 取到零值,注意除零异常),如果想获取整数类型的随机数,不要将 x 放大 10 的若干倍然后 取整,直接使用 Random 对象的 nextInt 或者 nextLong 方法
-
获取当前毫秒数 System.currentTimeMillis(); 而不是 new Date().getTime() ,
如果想获取更加精确的纳秒级时间值,用 System.nanoTime()。在 JDK8 中,针对统计 时间等场景,推荐使用 Instant 类
-
任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存
-
不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁 (I)
Iterator<String> it = a.iterator(); while(it.hasNext()){ String temp = it.next(); if(删除元素的条件){ it.remove(); } }
-
集合初始化时,尽量指定集合初始值大小,说明:ArrayList尽量使用ArrayList(int initialCapacity) 初始化
-
使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历
-
利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的 contains 方法进行遍历、对比、去重操作
-
获取单例对象需要保证线程安全,其中的方法也要保证线程安全,资源驱动类、工具类、单例工厂类都需要注意
-
创建线程或线程池时请指定有意义的线程名称,方便出错时回溯
-
线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
-
-
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
-
-
【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为
static,必须加锁,或者使用 DateUtils 工具类。
正例:注意线程安全,使用 DateUtils。亦推荐如下处理:
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } };
说明:如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替 Simpledateformatter,官方给出的解释:simple beautiful strong immutable thread-safe。
-
并发修改同一记录时,避免更新丢失,要么在应用层加锁,要么在缓存加锁,要么在 数据库层使用乐观锁,使用 version 作为更新依据。说明:如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次 数不得小于 3 次。
-
多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获 抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题
-
5.自动代码检查
使用Eclipse与 Inellij IDEA的代码校验功能已经排除了很多问题。
- Eclipse:在Windows->Preferences->Java-Compiler->Errors/Warnings中,按本文档将一些原来Ignore的规则打开。
- IDEA:在Setting->Errors中设定规则,调用Analyzer->Inspece Code进行校验。
6.其他
uap关于list的遍历
List result = biz.getList(); if(result!=null){ for(int i=0;i<CheckList.checkListSize(result);i++){ // TODO } }
addWhere("param=123");
改成
addWhere("param","=","123");
以上是关于代码规范实践的主要内容,如果未能解决你的问题,请参考以下文章