所有分类
  • 所有分类
  • 未分类

Java编程规范(代码规范)–精选

简介

说明

本文介绍精选的Java编程规范(代码规范)。遵守这些规范,代码的bug数将会大幅减少,代码可维护性、可读性、扩展性会大幅上升。(本文持续更新)

为什么要有编程规范?

编程规范有如下作用:

  1. 提高代码可读性、维护性、扩展性
  2. 提高开发速度、减少bug
  3. 有助于留住人才(接手别人的垃圾代码占离职原因的很大一部分)

好的编程规范是怎样的?

  1. 规范要精简,否则不利于普及

规范的落实

  1. 编程规范要配合代码审核,否则都是空话。
  2. 代码审核必须在代码提测之前进行。(因为如果测试通过了,由于不符合编程规范再改动的话,需要费时间进行回归测试,导致影响上线)
  3. 代码评审要以平台的方式进行:被评审者将代码提到评审平台,然后评审者在此平台进行批注,被评审者进行代码修改。
    1. 这种代码评审基本是毫无卵用的:多个人聚在一个会议室里,盯着一个电脑投屏进行评审。
  4. 代码规范要加入绩效考核(占比在1%~5%之间),否则无法引起重视。
    1. 好的大公司就是这么做的,代码极好,扩展性极好,技术氛围极好,项目的质量极好、用户的反馈极好…

格式

  1. 单个方法不能超过60行,如果超过必须拆分
    1. IDEA大概能显示40行代码,60行对一个方法来说肯定是够用的。
    2. 反例:所有逻辑堆在一个方法里,单个方法成百上千行
  2. 单行代码不能超过100个字符(包括空格),如果超过必须换行。换行时遵循如下原则:
    1. 第二行相对第一行缩进 4 个空格;从第三行开始,不再继续缩进
    2. 运算符与下文一起换行;方法调用的点符号与下文一起换行
  3. if/for/while/switch/do 等保留字与括号之间必须加空格。

命名

  1. 命名可以很长,不能为了缩短长度进行单词的简写(提高可读性)。(例外:接口实现类以Impl结尾等通用的缩写)
    1. 正例:interfaceOperationCode
    2. 反例:intOpCode
  2. 字段名第一个字母必须小写。
    • 重大bug:用lombok,若前两个字母大写,用Jackson序列化时第二个大写会变成小写。
  3. 类名和包名使用单数形式,但是如果类名有复数含义,类名可以使用复数形式
    1. 正例:com.abc.user.detail
    2. 反例:com.abc.users.detail
  4. 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。如果必须要用两个单词,也都要小写
    1. 正例 : com.alibaba.open.util、com.abc.userdetail
  5. 抽象类命名使用Abstract开头;异常类命名用Exception结尾;接口类命名不要添加无用的前缀与后缀(比如:I开头、Interface结尾)
  6. POJO类中布尔类型变量不要加is前缀,否则部分框架解析会引起序列化错误。
    1. 反例:将是否删除定义为:Boolean isDeleted
    2. 正例:将是否删除定义为:Boolean deletedFlag
  7. Controller的url命名规范:
    1. 驼峰命名。
    2. 统一是这种:/表名/增删改查,比如:用户详情的增加接口:/userDetail/add。增删改查对应的英文如下:
      1. 增加:add
      2. 编辑:edit
      3. 删除:delete
      4. 分页查询:page
      5. 列表查询:list

常量

  1. 不要使用一个常量类维护所有常量,应该按常量功能进行归类,分开维护(提高可维护性)。
    1. 正例:缓存相关的常量: CacheConstant;配置相关的常量: ConfigConstant。
  2. 常量命名全部大写,单词间用下划线隔开。
    1. 正例 : MAX_STOCK_COUNT
    2. 反例 : Max_Stock_Count

类型

  1. 禁止使用Map作为方法的入参或返回值,必须使用对象明确列出所有字段。(例外:有些地方只能是Map类型,比如:短信发送接口,参数是不确定的,调用方自定义key和value)
    1. 反例:给别人提供Feign接口时,返回值是Map类型。
    2. 正例:定义一个XxxVO,将字段写到XxxVO里,作为接口返回值。
    3. 正例:查很多用户详情时,为提高接口速度,将单个SQL转成批量SQL(将多个=转化成单个IN),然后将其结果转成key为用户id,value为用户数据的Map,再进行后续操作。
  2. 如果代码内部用到了Map,必须在map定义处写明key和value分别是什么
  3. 这些地方必须使用包装数据类型:所有的 POJO 类属性、RPC 方法的返回值和参数
    1. 即:要用Integer、Long等,不要用int、long
  4. 实体类不要implements Serializable
    1. Serializable是序列化,现在都是使用JSON了,不要再实现Serializable了!(题外话:写代码时要思考,看究竟有什么用,不明白的就去搞明白,不要糊里糊涂地写)。
    2. 当然,有时候就必须要实现Serializable,比如:Dubbo的实体类,默认用的是Serializable的序列化来传递数据,所以必须实现Serializable。

参数与返回值

  1. 不能修改入参对象的字段,如果要修改,必须新创建一个对象
    1. 例外:如果方法就是为了给入参对象赋值的,可以去修改。
  2. 方法入参的个数必须小于5
    1. 如果大于等于5,必须将入参封装为一个类。
  3. 分页接口的请求和响应必须继承公共父类,如下:

分页的请求类

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.time.LocalDateTime;

@Data
public class PageBO {
    @ApiModelProperty("当前页")
    private Long currentPage = 0L;

    @ApiModelProperty("每页个数")
    private Long pageSize = 10L;

    @ApiModelProperty("创建时间开始")
    private LocalDateTime createTimeStart;

    @ApiModelProperty("创建时间结束")
    private LocalDateTime createTimeEnd;
}

分页的响应类

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.List;

@Data
public class PageVO<T> {
    @ApiModelProperty("当前页")
    private Long currentPage = 0L;

    @ApiModelProperty("每页个数")
    private Long pageSize = 10L;

    @ApiModelProperty("总个数")
    private Long totalSize = 0L;

    private List<T> dataList;
}

注释

  1. 所有接口说明都使用knife4j的,不要重复写注释。
  2. 方法注释中没用的说明要删掉,比如:参数后如果没加详细说明,就把这行参数注释删掉。
  3. 文件前不要让Idea自动生成创建时间、创建人。
    1. 原因:这是多余的操作,git已经记录了此文件的创建人和创建时间

复用

  1. 逻辑重复的代码要抽取为同一个(提高复用性)
    1. 正例:页面上的分页查询、Excel导出查询的数据,这两个共用查数据的方法。

单一职责

  1. 每个接口的参数和返回值必须是独立的。
    1. 比如:查询用户的入参和返回值是:UserPageQueryBO,UserPageQueryVO;保存用户的入参和返回值是:UserSaveBO,UserSaveVO。虽然UserPageQueryBO和UserSaveBO会有很多重复的字段,但也不能合并为一个BO。如果进行了合并,会有如下致命缺点:
      1. 看接口入参无法确定用到了哪些字段,必须看代码实现才能确定,极不清晰。
      2. 接口文档不清晰,当导出接口文档时,前端开发人员也不好分辨。
    2. 此情况例外:功能极其接近时允许用同一个参数和返回值。
      1. 比如查询用户需要提供两个接口:分页查询和全量查询,此时可以共用查询入参,将其定义为:UserQueryBO。
  2. 每个类的字段必须是有效的
    1. 比如:查询用户的入参实体类里,只包含必须的字段,比如:用户名、手机号,不要包含用不到的字段,比如:用户id,用户的密码。

数据库

注意事项

  1. 状态、类型等字段Java代码使用枚举类型,数据库必须使用字符串(提高可维护性)
    1. 具体方法见:SpringBoot–在Entity(DAO)中使用枚举类型 – 自学精灵
  2. 创建时间、更新时间、是否删除等字段,必须放到列的最后。(提高可读性)
    1. 后边如果有新加的字段,要加在这些字段之前,永远保持这些字段在最后!
  3. 主键ID一律使用MyBatis-Plus自带的雪花算法来生成。(有利于数据库迁移等)
  4. 查数据的方式要最优,如下方式要优先考虑前边的方式。
    1. lambdaQuery、在代码中用字符串拼MyBatis-Plus条件(用字符串指定字段名)、Mapper中写注解、在XML中写。
  5. 查数据时尽量不要联表查。(因为数据量大时,联表查会很慢)。

建表的必要字段

说明

为了便于排查问题,建表时需要加一些必要的字段:创建人、更新人、创建时间、更新时间、删除标记。

SQL

ALTER TABLE `库名`.`表名` 
ADD COLUMN `id` bigint NOT NULL COMMENT '主键',
ADD COLUMN `create_time` datetime NOT NULL COMMENT '创建时间',
ADD COLUMN `update_time` datetime NOT NULL COMMENT '修改时间',
ADD COLUMN `create_id` varchar(32) COMMENT '创建人ID',
ADD COLUMN `create_name` varchar(32) COMMENT '创建人名字',
ADD COLUMN `update_id` varchar(32) COMMENT '修改人ID',
ADD COLUMN `update_name` varchar(32) COMMENT '修改人名字',
ADD COLUMN `delete_flag` bigint NOT NULL DEFAULT 0 COMMENT '删除标记。0:未删除;其他:已删除';

详解

  • 创建时间和更新时间的字段名
    • 阿里开发手册的规范:
      • 嵩山版、华山版等:表必备三字段:id,create_time,update_time。(推荐)
      • 泰山版等:表必备三字段:id,gmt_create,gmt_modified。(不推荐)
    • 注意:时间相关的字段名应该有“time”才好。而且有一些Mock工具(例如:ApiFox)会根据字段名自动构造假数据,字段中带“time”工具就会自动生成很接近实际的mock数据,有利于前端自测代码。
  • 更新时间不要设置为自动更新
    • 不要让更新时间自动更新,即:不要这样写:ADD COLUMN `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT ‘修改时间’,
    • 原因:
      • 数据库服务器很可能没有正确设置时区,如果“更新时间”字段使用了数据库服务器的时区进行了自动更新,那这个时间就是不正确的。
      • 以我工作的几家公司来看,数据库服务器的时区基本都是错误的。
    • 正确的方法是:“创建时间”和“更新时间”全部通过应用来自动生成。比如MybatisPlus的自动填充:MyBatis-Plus-自动填充的用法 – 自学精灵
  • 删除标记不要使用0,1来表示,在删除时应该将id的值赋值给delete_flag
  • 数据库的时间字段对应的Java的DAO的字段类型要用LocalDateTime
    • 现在21世纪了,不要再用Date了。
    • LocalDateTime的优点:
      • 可明确知晓:这是个日期+时间的字段。(Date不能一眼看出是日期还是时间还是两者)
      • LocalDateTime的格式化等操作是线程安全的。
      • LocalDateTime的方法很丰富。

上边的字段必须有公共的实体类(CommonEntity),使用Xxx extends CommonEntity的方式,不要在自己实体类手写这些字段。

CommonEntity如下:

package com.example.knife.common.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;

import lombok.Data;

import java.time.LocalDateTime;

/**
 * 数据库公共实体类
 */
@Data
public class CommonEntity {
    /**
     * 主键
     */
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    /**
     * 修改时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    /**
     * 创建人ID
     */
    private String createId;

    /**
     * 创建人名字
     */
    private String createName;

    /**
     * 更新人ID
     */
    private String updateId;

    /**
     * 更新人名字
     */
    private String updateName;

    /**
     * 删除标记。0:未删除;其他:已删除
     */
    @TableLogic("id")
    private Long deleteFlag;
}

API选用

  1. 时间类型必须使用JDK8的类型,比如:LocalDateTime,LocalDate,LocalTime
    1. 不要使用Date类型(Date的API难用,Date含义不清晰(不知道是日期还是时间)多线程格式化时要考虑线程安全等)。这老一套的东西该摒弃了。
  2. 小数数字类型必须使用BigDecimal
    1. double和float会失真。
    2. BigDecimal使用时也要小心,尽量学一下再用,它还是要注意一些问题的,比如:若不指定精度,除不尽的算术操作会抛异常。详见:Java之BigDecimal-使用教程 – 自学精灵
    3. 题外话:对于IT来说,不是说知道某个技术就行了,而要深入下去,无论是普通的API还是高级技术,无论是哪个技术都有需要注意的地方,都可能有坑。如果对技术浅尝辄止,那么就很难进步,很可能就是一年的经验用十年,难以成长为技术大佬。
  3. Lombok的使用
    1. 不要使用@Accessors,可以使用@Builder
      1. 为了方便给字段赋值,lombok提供了一些注解,但不要用@Accessors,因为它生成的set方法有返回值,会存在问题:有些中间件会判断方法是否有返回值进而进行操作,比如:EasyExcel。(如果使用了@Accessors,会导致导入Excel时取不到值)
      2. 题外话:lombok等中间件固然很方便,但任何API都要选用,不要全部采用。其中一个重要的原则就是:不要改变正常的逻辑,比如:set方法就不应该有返回值。
  4. 要使用@Autowired,不要用@Resource。
    1. Spring的项目,就尽量用Spring的东西。

技术选型

必须使用主流且稳定的技术栈(见:Java后端开发技术选型 – 自学精灵

不得使用如下技术(如下技术都有稳定、成熟的同类技术可以替代):

不稳定,bug多的技术

  1. fastjson
  2. hutool

资源消耗很大的技术

  1. FileBeat

全局处理

异常

  1. 必须将错误信息作为异常抛出来,让全局异常处理器去处理。
    1. 不能自己返回错误信息。(会增加代码的复杂度,如果代码有多个调用,层层传递错误信息会无法维护)
    2. 不能自己捕获了异常然后不处理信息。(会导致无法排查问题)

包装返回值给前端

  1. 自己不要去包装返回值给前端,由AOP统一去包装。(减少代码量,便于维护)

feign调用

  1. 自己不要去包装返回值给调用方,由AOP统一去包装。(减少代码量,便于维护)
  2. 调用方不要手动去解析包装数据,需要由统一的解码器去处理。

逻辑

  1. json字符串解析为对象
    1. 一个json字符串必须有一个完全对应的java对象。(代码可读性高)
    2. 必须一步到位将json字符串解析为整个java对象,不能解析完外层再解析内层。
  2. get方法不能有其他逻辑,必须直接返回字段本身。
  3. 如果必须要手动捕获异常,必须要输出详细堆栈 将堆栈打出来
    1. 如果有日志组件,必须使用日志组件。
    2. 如果没有日志组件,用log.error(“xxx”, e); 
  4. 所有地方必须判断null
    1. 如果是null,必须抛出异常,信息为:xxx不能为null。
  5. 禁止将业务对象作为Map的key,禁止覆写equals和hashCode。
    • 因为别人也会用这个业务对象,改了这些会导致别人的业务出问题!

其他

  1. 代码中不得存在被IDEA警告的代码,若存在则必须看IDEA的提示并进行修改。(包括:错的单词拼写,泛型警告,优化的提示等)

1

评论0

请先

显示验证码
没有账号?注册  忘记密码?

社交账号快速登录