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

这篇具有很好参考价值的文章主要介绍了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 com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.time.LocalDateTime;

@Data
public class PageRequest {
    @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 PageResponse<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)中使用枚举类型_IT利刃出鞘的博客-CSDN博客
  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 '创建人ID',
ADD COLUMN `update_id` varchar(32) COMMENT '修改人名字',
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--自动填充的用法_IT利刃出鞘的博客-CSDN博客
  • 删除标记不要使用0,1来表示,在删除时应该将id的值赋值给delete_flag
    • 如果用0,1表示,会影响唯一索引。见:MyBatis-Plus--解决逻辑删除与唯一索引的问题--方法/实例_IT利刃出鞘的博客-CSDN博客
  • 数据库的时间字段对应的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系列--使用/教程/实例_IT利刃出鞘的博客-CSDN博客
    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后端开发技术选型_IT利刃出鞘的博客-CSDN博客)

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

不稳定,bug多的技术

  1. fastjson
  2. hutool

资源消耗很大的技术

  1. FileBeat

全局处理

异常

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

包装返回值给前端

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

feign调用文章来源地址https://www.toymoban.com/news/detail-454898.html

  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的提示并进行修改。(包括:错的单词拼写,泛型警告,优化的提示等)

到了这里,关于Java编程规范(代码规范)--精选的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包