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、Integer、Long、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方法
在方法执行抛出异常时,可以直接调用POJO的toString()方法打印其属性值,便于排查问题。说明:使用工具类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的后缀与接口区别
对于Service和DAO类,基于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. 循环体内,字符串的联接方式
循环体内,字符串的联接方式,使用StringBuilder的append方法进行扩展。 说明:反编译出的字节码文件显示每次循环都会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块中使用return,finally块中的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代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替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类属性必须使用包装数据类型。
2) RPC方法的返回值和参数必须使用包装数据类型。
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)类名应该是个名词或名词词组的名字,如Customer、WikiPage、 Account、AddressParser等。
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
关于hashCode和equals的处理,遵循如下规则:
只要重写equals,就必须重写hashCode。
因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法。
如果自定义对象做为Map的键,那么必须重写hashCode和equals。
4.2、建议遵循
1.各层命名规约
Service/DAO层方法命名规约
1) 获取单个对象的方法用get做前缀, 如:getUser、getUserByName等。
2) 获取多个对象的方法用list做前缀,复数结尾,如:listUsers、listUsersByIds、pageUsersByIds等。
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) 数据对象:xxxDO,xxx即为数据表名。
2) 数据传输对象:xxxDTO,xxx为业务领域相关的名称。
3) 展示对象:xxxVO,xxx一般为接口相关名称。
4) 入参对象:xxxPO,xxx一般为接口相关名称。
5) 业务对象:xxxBO,xxx一般为业务相关名称。
6) POJO是DO/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)数据对象:xxxDO,xxx即为数据表名,建议只在dao层及service层出现。
2)数据传输对象:xxxDTO,xxx为业务领域相关的名称,可以穿梭在各层中。
3)展示对象:xxxVO,xxx一般为接口相关名称,建议只在controller层及service层出现,并且只作为接口或方法返回对象。
4)入参对象:xxxPO,xxx一般为接口相关名称,建议只在controller层及service层出现,并且只作为接口或方法参数对象。
5)业务对象:xxxBO,xxx一般为业务相关名称,根据业务需求,可以穿梭在各层中。
五、参考资料
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"键
本文来自博客园,作者:咔咔皮卡丘,转载请注明原文链接:https://www.cnblogs.com/anquing/p/14595448.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库