java代码规范参考文档

一、目的

java代码规范编写主要是为了给开发人员编码时提供一份参考文档,在协作开发及多项目切换开发中,有规范可循,实现代码高质量、高可读以及可维护。本文结合阿里java代码规范、项目实际情况以及个人的开发经验编写而成。本java开发规范的预期读者为系统设计人员、软件开发人员、技术经理。

 

二、适用范围

适用于软件开发人员阅读

 

三、术语表

序号

术语或缩略语

说明性定义

1

魔法值

是指在代码中直接出现的数值,只有在这个数值记述的那部分代码中才能明确了解其含义

2

POJO

简单的Java对象,实际就是普通JavaBeans,仅有一些属性及其getter setter方法的类,没有业务逻辑

3

事务

事务是指一个单元的工作,这些工作要么全做,要么全部不做。事务是必须满足4个条件(ACID)::原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。

4

基本数据类型

基本数据类型包括:byte、short、int、long、float、double

5

包装数据类型

包装数据类型包括:Byte、Short、IntegerLong、Float、Double

6

方法                                                                                                                         

用来解决一类问题的代码的有序组合,是一个功能模块

四、代码规范

4.1、强制遵循

1. 禁用魔法值

除预定义的外,在编码中禁止使用魔术值。建议定义枚举值或者静态常量值,并写好注释说明。

 

反例:

if (key.equals("Id#taobao_1")) {  
//...  
}  

 

正例:

String KEY_PRE = "Id#taobao_1";  
if (KEY_PRE.equals(key)) {  
//...  
} 

 

2. POJO类必须写toString方法

在方法执行抛出异常时,可以直接调用POJOtoString()方法打印其属性值,便于排查问题。说明:使用工具类source> generate toString时,如果继承了另一个POJO类,注意在前面加一下super.toString

 

正例:

public class ToStringDemo extends Super{  
private String secondName;  
@Override  
public String toString() {  
return super.toString() + "ToStringDemo{" + "secondName='" + secondName + '\'' + '}';  
}  
}  
class Super {  
private String firstName;  
@Override  
public String toString() {  
return "Super{" + "firstName=" + firstName + '\'' + '}';  
}  
}  

 

3. 手动回滚事务

事务场景中,抛出异常被catch后,如果需要回滚,一定要手动回滚事务。

 

反例:

@Transactional
public class UserServiceImpl implements UserService {  
@Override  
public void save(User user) {  
//some code  
//db operation  
}  
}  

 

正例1

@Transactional(rollbackFor = Exception.class)  
public class UserServiceImpl implements UserService {  
@Override  
public void save(User user) {  
//some code  
//db operation  
}  
}  

 

 

正例2

public class UserServiceImpl implements UserService {  
@Override  
@Transactional(rollbackFor = Exception.class)  
public void save(User user) {  
//some code  
//db operation  
}  
} 

 

正例3

public class UserServiceImpl implements UserService {  
@Autowired  
private DataSourceTransactionManager transactionManager;  
@Override  
@Transactional  
public void save(User user) {  
DefaultTransactionDefinition def = new DefaultTransactionDefinition();  
// explicitly setting the transaction name is something that can only be done programmatically  
def.setName("SomeTxName");  
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);  
TransactionStatus status = transactionManager.getTransaction(def);  
try {  
// execute your business logic here  
//db operation  
} catch (Exception ex) {  
transactionManager.rollback(status);  
throw ex;  
}  
} 
}

 

4. 单个方法的总行数不超过80行

除注释之外的方法签名、结束右大括号、方法内代码、空行、回车及任何不可见字符的总行数不超过80行。代码逻辑分清红花和绿叶,个性和共性,绿叶逻辑单独出来成为额外方法,使主干代码更加清晰;共性逻辑抽取成为共性方法,便于复用和维护。

5. 及时清理不再使用的代码段或配置信息

对于垃圾代码或过时配置,坚决清理干净,避免程序过度臃肿,代码冗余。如果是暂时注释,请在上方使用 /// 标记说明。

 

正例:

public static void hello() {  
/// 暂时注释.  
// Business business = new Business();  
// business.active();  
System.out.println("it's finished");  
}  

 

6. 集合初始化时,指定集合初始值大小

HashMap使用如下构造方法进行初始化,如果暂时无法确定集合大小,那么指定默认值(16)即可。

 

反例:

Map<String, String> map = new HashMap<String, String>();  

 

正例:

Map<String, String> map = new HashMap<String, String>(16);  

 

7. 内部的实现类用Impl的后缀与接口区别

对于ServiceDAO类,基于SOA的理念,暴露出来的服务一定是接口,内部的实现类用Impl的后缀与接口区别。

 

正例:

public interface DemoService{  
void f();  
}  
public class DemoServiceImpl implements DemoService {  
@Override  
public void f(){  
System.out.println("hello world");  
}  
}  

 

8. 定义DO/DTO/VO等POJO类时,不要加任何属性默认值

POJO类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE问题,或者入库检查,都由使用者来保证。

 

反例:

POJO类的createTime默认值为newDate(),但是这个属性在数据提取时并没有置入具体值,在更新其它字段时又附带更新了此字段,导致创建时间被修改成当前时间。

public class DemoDO {    
String name = "demo";    
Date createTime = new Date();    
}    

 

正例:

public class DemoDO {    
String name;    
Date createTime;
}    

 

9. 循环体内,字符串的联接方式

循环体内,字符串的联接方式,使用StringBuilderappend方法进行扩展。 说明:反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,造成内存资源浪费。

 

反例1

String result;  
for (String string : tagNameList) {  
result = result + string;  
}  

 

反例2

StringBuilder stringBuilder = new StringBuilder();  
for (String string : tagNameList) {  
stringBuilder.append(string + ",");  
}  
String result = stringBuilder.toString();  

 

正例:

StringBuilder stringBuilder = new StringBuilder();  
for (String string : tagNameList) {  
stringBuilder.append(string);  
stringBuilder.append(",");  
}  
String result = stringBuilder.toString(); 

  

10. 不能在finally块中使用return

不能在finally块中使用returnfinally块中的return返回后方法结束执行,不会再执行try块中的return语句。

 

反例:

public static Long readFileLength(String fileName) {  
try {  
File file = new File(fileName);  
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");  
return randomAccessFile.length();  
} catch (Exception e) {  
logger.error(e.getMessage(), e);  
} finally {  
countDownLatch.countDown();  
return 0L;  
}  
}  

 

11. switch块使用

在一个switch块内,每个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个default语句并且放在最后,即使它什么代码也没有。

 

正例:

switch (x) {  
case 1:  
break;  
case 2:  
break;  
default:  
}  

 

12. SimpleDateFormat 是线程不安全的类

SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类。 说明:如果是JDK8的应用,可以使用LocalDate代替DateLocalDateTime代替CalendarDateTimeFormatter代替SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe

 

正例1

private static final String FORMAT = "yyyy-MM-dd HH:mm:ss";  
public String getFormat(Date date){  
SimpleDateFormat dateFormat = new SimpleDateFormat(FORMAT);  
return sdf.format(date);  
}  

 

正例2

private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
public void getFormat(){  
synchronized (sdf){  
sdf.format(new Date());  
….;  
}  

 

正例3

private static final ThreadLocal<DateFormat> DATE_FORMATTER = new ThreadLocal<DateFormat>() {  
@Override  
protected DateFormat initialValue() {  
return new SimpleDateFormat("yyyy-MM-dd");  
}  
}

 

 

13. 数据类型相等比较方式,equals 和 == 的选择标准

1) 比较的两个对象中其中一个为基本数据类型时使用==

2) 比较的两个对象中两个都为包装数据类型时使用equals

 

例子:

Byte c = (byte)1;  
Byte d = (byte)1;  
Integer a = 200;  
Integer b = 200;  
Integer e = 10;  
Integer f = 10;  
System.out.println(c.equals(d)); // ture  
System.out.println(c.equals(1)); // false  
System.out.println(c == 1); // true  
System.out.println(a.equals(b)); //true  
System.out.println(a == b); //false 因为byte、Integer long short在-128-127范围是共享的堆,超出就会新建一个对象  
System.out.println(e.equals(f));//true  
System.out.println(e == f); //true  

 

14. 基本数据类型与包装数据类型的使用标准

关于基本数据类型与包装数据类型的使用标准如下:

1) 所有的POJO类属性必须使用包装数据类型。

2RPC方法的返回值和参数必须使用包装数据类型。

3) 所有的局部变量推荐使用基本数据类型。

 

反例:

返回类型为基本数据类型,return包装数据类型的对象时,自动拆箱有可能产生NPE

public int demo(){  
Integer result = null;  
return result;  
}  

 

15. 命名标准

包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。

 

反例:

cn.gov.shunde.detentionhouse.alarmModle 
 

 

正例:

cn.gov.shunde.detentionhouse.alarm.modle  

 

16. 常量命名标准

常量命名应该全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。

 

正例:

public class ConstantNameDemo {  
/** 
* max stock count 
*/  
  public static final Long MAX_STOCK_COUNT = 50000L; 
}

 

 

17. 抽象类命名使用Abstract或Base开头

正例:

abstract class BaseControllerDemo{  
}  
abstract class AbstractActionDemo{  
}  

 

 

18. 类名使用UpperCamelCase风格

类名使用UpperCamelCase风格,必须遵从驼峰形式,但以下情形例外:(领域模型的相关命名)DO / BO / DTO / VO / DAO

 

19. 方法名、参数名、成员变量、局部变量使用lowerCamelCase风格

方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase,必须遵从驼峰形式。

 

20. 杜绝完全不规范的缩写

1)杜绝完全不规范的缩写,避免望文不知义。

2)为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达。

3)类名应该是个名词或名词词组的名字,如CustomerWikiPageAccountAddressParser等。

4)变量名应该是名词,如stockNumber username等。

5)方法名应该是一个动词或动词词组,如postPayment()deletePage()save()等。

 

反例:

// 姓名  
String xm = "xiaomin";  
// 编号  
String num = "A001";  

 

正例:

// 姓名  
String username = "xiaomin";  
// 编号  
String number = "A001";  

 

21. 所有的抽象方法(包括接口中的方法)必须要用javadoc注释

所有的抽象方法(包括接口中的方法)必须要用javadoc注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。 说明:如有实现和调用注意事项,请一并说明。

 

正例:

/** 
* fetch data by rule id 
*  
* @param ruleId rule id 
* @param page page number 
* @param jsonContext json format context 
* @return Result<XxxxDO> 
*/  
Result<XxxxDO> fetchDataByRuleId(Long ruleId, Integer page, String jsonContext);  

 

22. 所有的类都必须添加创建者信息

所有的类都必须添加创建者信息。 说明:在设置模板时,注意IDEA@author${USER},而eclipse@author${user},大小写有区别,而日期的设置统一为yyyy/MM/dd的格式。

 

正例:

/** 
* Demo class 
*  
* @author keriezhang 
* @date 2016/10/31 
*/  
public class CodeNoteDemo {  
}  

 

23. 方法注释

方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释。注意与代码对齐。

 

正例:

public void method() {  
// Put single line comment above code. (Note: align '//' comment with code)  
int a = 3;  
/** 
* Some description about follow code. (Note: align '/**' comment with code) 
*/  
int b = 4;  
}  

 

24. 枚举类型字段必须要有注释

所有的枚举类型字段必须要有注释,说明每个数据项的用途。

 

正例:

public enum TestEnum {  
/** 
* agree 
*/  
agree("agree"),  
/** 
* reject 
*/  
reject("reject");  
private String action;  
TestEnum(String action) {  
this.action = action;  
}  
public String getAction() {  
return action;  
}  
} 

 

25. 所有的覆写方法,必须加@Override注解

getObject()get0bject()的问题。一个是字母的O,一个是数字的0,加@Override可以准确判断是否覆盖成功。另外,还有一个好处如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。

 

26. Map/Set的key为自定义对象时,必须重写hashCode和equals

关于hashCodeequals的处理,遵循如下规则:  

只要重写equals,就必须重写hashCode。  

因为Set存储的是不重复的对象,依据hashCodeequals进行判断,所以Set存储的对象必须重写这两个方法。  

如果自定义对象做为Map的键,那么必须重写hashCodeequals

 

4.2、建议遵循

1.各层命名规约

Service/DAO层方法命名规

1) 获取单个对象的方法用get做前缀, 如:getUsergetUserByName等。

2) 获取多个对象的方法用list做前缀,复数结尾,如:listUserslistUsersByIdspageUsersByIds

3) 获取统计值的方法用count做前缀。

4) 插入的方法用save/insert做前缀。

5) 删除的方法用remove/delete做前缀。

6) 修改的方法用update做前缀。

 

正例:

User getUserById(Long id);  
List<User> listUsersByIds(Set<Long> ids);  
void addUser(User user);  
void updateUsername(Long id,String username);  
void removeUser(Long id);  

 

领域模型命名规约

1) 数据对象:xxxDOxxx即为数据表名。

2) 数据传输对象:xxxDTOxxx为业务领域相关的名称。

3) 展示对象:xxxVOxxx一般为接口相关名称。

4 入参对象:xxxPOxxx一般为接口相关名称。

5 业务对象:xxxBOxxx一般为业务相关名称。

6 POJODO/DTO/BO/VO的统称,禁止命名成xxxPOJO

2. 重复代码提炼

建议:

1把重复的代码封装成一个函数,让需要的地方调用这个函数即可;

2如果重复的代码是出现在互为兄弟类中,也就是这两个类有共同的父类,可以把相同的代码提到父类中;

3如果代码只是类似而不是完全相同,可以把相同的部分和相异的部分提炼出来,设计成模板模式;

4如果两个互不相干的类出现重复的代码,可以把相同的部分,提到一个父类中或提取到一个接口中;

5重复代码的类只属于某一个函数,而且让另外的类可以直接调用这个类中函数。

3. 发散式变化

问题:如果增加一个新功能,需要对原有功能做出大量的修改。这就是散发式变化的征兆。

处理:如果发生变化的两个方向自然地形成了先后次序(比如说,先从数据库取出数据,再对其进行金融逻辑处理),就可以用拆分阶段将两者分开,两者之间通过一个清晰的数据结构进行沟通。不要让一段代码同时处理不同的事情

4. 主从关系明确的类应使用内部类

当一个对象A是另一个对象B的属性时,并且对象A只会被对象B引用,此时应该将对象A定义为对象B的内部类,有利于避免对象A被其它对象引用或者被修改的风险。

 

正例:

public class FaceDetailVO {  
private Long faceId;  
private List<TogetherPersonInfo> togetherPersons;  
public static class  TogetherPersonInfo{       
private String name;         
private String idCard;  
}  
}  

 

5. 方法参数列表应该使用内部基本类型还是自定义DTO

定义方法时,参数较多,会让代码变得混乱。但使用对象对参数进行封装,可读性又差,而且无法保证必要参数都有赋值。从而建议使用以下的判断依据:

1)方法参数超过4个,可以通过创建DTO对象对方法入参进行封装。

2)如果方法参数不超过4个,可以直接作为入参。

例子:

@Data  
public class CheckPasswordDTO {  
private String username;  
private String password;  
}  
// 不建议  
Boolean checkPassword(CheckPasswordDTO checkPasswordDTO );  
// 建议,更直观  
Boolean checkPassword(String username,String password);  

 

6. 方法参数及方法返回值不建议使用Map

 Map是一种键值对集合,使用Map作为方法参数及方法返回值,会让调用者无法直观地了解Map里到底是什么数据,并且容易造成调用者使用不当问题。在迫不得已,不得不使用Map的情况下,需要进行详细的对键值对进行描述。

 

反例:

Map<Long, User> userMap(){  
// key 为用户id
HashMap<Long, User> userMap = new HashMap<>();  
// do something  
return  userMap;  
} 

 

正例:

List<User> listUsers(){  
ArrayList<User> users = new ArrayList<>();  
// do something  
return  users;  
}  

 

7. 业务方法中,尽可能使用抛出异常来代替if-else

在编辑业务方法时,要尽量突出主业务逻辑,故针对一些必要条件的判断,应该在不满足的情况下直接抛出异常,而不是在代码中编码大量if-else嵌套。

 

反例:

public Boolean  login(User user){   
if (user != null) {  
if ( user.getUsername() != null && user.getUsername() != "") {    
if (user.getPassword() != null && user.getPassword() != "") {    
if (user.getStatus() != null && user.getStatus() != 0) {    
// do something    
return true;    
}    
}    
}   
}   
return false;    
}

 

正例:

public void  login(User user){    
if ( user == null) {  
throw BusinessException.operate("用户对象不能为空");  
}  
if ( user.getUsername() == null || user.getUsername() == "" ) {    
throw BusinessException.operate("用户名不能为空");    
}    
if ( user.getPassword() == null || user.getPassword() == "") {    
throw BusinessException.operate("密码不能为空");    
}    
if ( user.getStatus() == null || user.getStatus() == 0) {    
throw BusinessException.operate("用户已注销");    
}                
// do something          
}    

 

8. 循环体内,不要操作数据库

建议:

查询时将数据批量查询出来,得到List集合;

根据List集合得到相关外键集合,根据外键集合查询相当记录,并转为Map

后面通过根据相应的外键从Map中取出相应的对象;

 

正例:

List<Supervisor> records = iPage.getRecords();  
Set<Long> identityIds = records.stream().map(Supervisor::getIdentityId).collect(Collectors.toSet());  
Map<Long, Identity> identityMap = identityService.listByIds(identityIds).stream().collect(Collectors.toMap(Identity::getId, Function.identity()));  
ArrayList<SupervisorVO> supervisorVOs = new ArrayList<>();  
for (Supervisor record : records) {  
Identity identity = identityMap.get(record.getIdentityId());  
SupervisorVO supervisorVO = SupervisorVO.builder()  
.gender(identity != null ? identity.getGender() : (byte)0)  
.id(record.getId())  
.idCard(record.getIdCard())  
.name(record.getName())  
.build();  
supervisorVOs.add(supervisorVO);  
}  

 

9. 领域模型使用区域

建议:

1数据对象:xxxDOxxx即为数据表名,建议只在dao层及service层出现。

2)数据传输对象:xxxDTOxxx为业务领域相关的名称,可以穿梭在各层中。

3)展示对象:xxxVOxxx一般为接口相关名称,建议只在controller层及service层出现,并且只作为接口或方法返回对象。

4入参对象:xxxPOxxx一般为接口相关名称,建议只在controller层及service层出现,并且只作为接口或方法参数对象。

5业务对象:xxxBOxxx一般为业务相关名称,根据业务需求,可以穿梭在各层中。

  

 

五、参考资料

5.1、javadoc标签使用

1. 常用标签

标签

说明

@author

作者标识

@version

版本号

@return

对函数返回值的描述

@deprecated

标识过期API(为了保证兼容性,仍可用,但不推荐用)

@throws

构造函数或方法会抛出的异常

@exception

@throws

@see

引用,查看相关的内容,如类,方法,变量等,必须顶头写

{@link .#成员}

引用,同@see,但可写在任意位置

{@value}

对常量注释,如果其值包含在文档中,通过改标签引用常量的值

{@code}}

{@code text}将文本标记为code,会被解析成 text } ,Javadoc成只要涉及到类名或者方法名,都需要使用@code进行标记

@param

说明方法的参数

@inheritDoc

用于继承父类中的Javadoc,父类的文档注释,被继承到了子类

 

vm中要加:

错误“编码 GBK 的不可映射字符”

-encoding utf-8 -charset utf-8

 

错误:未知标记:date

-tag date:a:"日期:"

2. 方法上的文档标注

写在方法上的文档标注一般分为三段:

第一段:概要描述,通常用一句或者一段话简要描述该方法的作用,以英文句号作为结束

第二段:<p>详细描述,通常用一段或者多段话来详细描述该方法的作用,一般每段话都以英文句号作为结束,段中分行使用<br>

第三段:文档标注,用于标注参数、返回值、异常、参阅等

 

方法必要的标签

/**

*概述.

*

*<p>描述.

*

*@param

*@return

*@author

*@since

*/

例子:

/** 
* 添加缓存. 
* 
* <p>支持将已实现Serializable的类存入缓存中. 
* 
* @author wangquanqing 
* @since 2020/12/15 17:03 
* @param key 唯一标识 
* @param value 数据体 
* @param timeout 过期时长,单位秒 
* @return void 
*/  
<T extends Serializable> void put(String key, T value, Integer timeout);  

 

 

3. 类上的文档标

写在类上的文档标注一般分为三段:

第一段:概要描述,通常用一句或者一段话简要描述该类的作用,以英文句号作为结束

第二段:<p>详细描述,通常用一段或者多段话来详细描述该类的作用,一般每段话都以英文句号作为结束,段中分行使用<br>

第三段:文档标注,用于标注作者、创建时间、参阅类等信息

 

类必要的标签

/**

*概述.

*

*<p>描述.

*

*@author

*@since

*/

例子:

/** 
* 缓存服务接口. 
* 
* <p>缓存服务接口. 
* 
* @author wangquanqing 
* @since 2020/12/15 17:12 
*/  
public interface CacheService {  
}  

        

4.idea类注释模板配置

路径:File>Settings>Editor>File and Code Templates

在File Header文档中添加:

/**
* @author ${USER}
* @since ${DATE} ${TIME}
*/

 

 

 

5.idea方法注释模板配置

路径:File>Settings>Editor>Live Templates

点击右上角+,新增Template Group,例如myGroup

选中新增的myGroup,点击右上角+,新Live Template,命名为*

模板内容:

*
* $summary$.
*
* <p>$describe$.
*
* @author $user$
* @since $date$ $time$
$params$
* @return $returns$
*/

 

 

 

点击Edit variables,按下图选择表达式

 

 

 params中的Default value为:

groovyScript("def result=''; def params=\"${_1}\".replaceAll('[\\\\[|\\\\]|\\\\s]', '').split(',').toList(); for(i = 0; i < params.size(); i++) {result+='* @param ' + params[i] + ((i < params.size() - 1) ? '\\n ' : '')}; return result", methodParameters())

使用方法:

在方法名上敲写

/**

然后按"Tab"键

 

      

posted @ 2021-03-30 09:47  咔咔皮卡丘  阅读(2171)  评论(0编辑  收藏  举报