代码规范

1. 什么才算规范?

        在编译器中,不遵守规则编写就会报错,这叫违反规则。
        那么规范呢,即是人为约定俗成的行业规范。即使我们不按规范编写,代码依然能够跑通。但是没有规范的代码编写习惯:
        一则不利于后期代码维护形成屎山。二则有可能因此受到同事轻视更甚错失晋升机会。

        那么如何书写才算规范呢?
        新人对于代码规范主要是按部就班的根据约定的规范条例进行严格遵守,并将之转化为自我肌肉记忆。这是一个长期的习惯养成,切不可急于求成。

2. 规范书写的好处

        规范的代码首要的好处就是整洁、舒适,一目十行。假如你现在回顾几个月前写的代码很吃力,那么说明你的规范不达标,连作者都无法快速阅读,更别提给同事维护阅读了,只会得到后人的骂声一片。

  • 有助于促进团队协作
            多数项目都是团队开发的,如果没有规范的标准,每一个人都各具特色,那么相互之间的代码阅读必将成为留存多年的噩梦回忆。
  • 有效降低BUG出现
            代码堆砌中,如果没有按照规范进行,格式错乱,出现BUG时排查困难。规范的代码堆砌、规范的出入参、规范的异常处理、规范的日志输出,说实话是确确实实有助于提高开发效率,降低BUG出现的。
  • 降低维护成本
            项目开发到上线只是初始阶段,真正伴随程序猿的应该是运维阶段。运维过程中毫无意外的会出现业务迭代,很多情况都是在原有功能上进行深化需求。这时回顾屎山代码并赋能新迭代,将成为一段不可言说的经历。如同第一点所说的,规范的代码,将拥有较高可读性与推展空间,无疑规范代码是为未来留有可期。
  • 高效Code Review
            代码审查我认为是非常有必要的,即时纠正错误,对代码规范与错误代码进行监督排错。团队的代码审查也是学习提升的机会,阅读同事的代码,对新人的成长也是非常有益。但是不规范的代码进行审查的工作量与难度都是不言而喻的,浪费大量时间却收益甚微。
  • 自我提升
            以上4点都是基于团队而言。对于个人,优秀规范编写的代码,让人阅读愉悦的同时也能让阅读者对编写者的专业性得到认可。代码规范的码农不一定是大拿,但代码编写都不规范的码农一定不是。

3. 代码规范的方案

1. 命名方式

  • 命名语义化,能够达到顾名思义
  • 使用完整单词,切忌自行缩简单词。
  • 切忌使用a、b、c、d此类神秘编号
  • 一些常见的命名规范:
包的命名 (全部小写,由域名定义)
例如:com.cococo.mall
类的命名 (单词首字母大写)  
例如: User
方法的命名 (首字母小写,字母开头大写)
例如:getUserInfo
常量的命名 (全部大写 ,常加下划线)
例如:MAX_VALUE

2. 简洁注释

  • 要求:清晰易懂、简洁明了
  • 内容:做什么、为什么

正例:

/**
* fetch data by rule id
* 
* @param ruleId 规则Id
* @param page 页数
* @param jsonContext json字符串
* @return Result<XxxxDO> 出参
*/
Result<XxxxDO> fetchDataByRuleId(Long ruleId, Integer page, String jsonContext);

3. 方法行数

一个方法内容尽量不超过一屏。如果逻辑过于复杂,就要拆分业务逻辑到为方法。

4. if else 和 if 的选用

反例:

public String test(int type) {
      if (type == 1) {
          //代码1逻辑
          return "1";
      } else {
          //代码2逻辑
          return "2";
      }
}

正例:

public String test(int type) {
      if (type == 1) {
          //代码1逻辑
          return "1";
      }
      //代码2逻辑
      return "2";
}

5. 一行代码长度

一屏内看完,一屏装不下就转多行。

6. 空行分割

方法之间用空行分割,让各模块清晰可见

7. 删除多余代码

如无用的 import 内容、未使用的局部变量、方法参数、私有方法、字段和多余的括号应及时删除

8. 方法入参数量

如果一个方法的入参大于3个,就应该考虑封装成实体进行传输

9. 单一原则

如果两个不同的业务写在一个方法中,用 if else 去区分调用,后期想要调整的时候就有可能相互干扰。 应该要遵循单一原则,一个方法内尽可能写独立的功能。

10. 代码层次

  • if的嵌套不超过3层
  • for循环嵌套不超过2层

11. 代码封装

如果一段代码出现过2次及以上。就必须封装成为接口或方法,由多处调用。

12. 业务代码下沉

很多新人编写代码都喜欢直接在controller里编写。这样的写法导致代码无法复用,应下沉到service层再由controller直接调用。

13. 魔法值

这个是最容易出现,也最容易修改的。很多时候我们会为了减轻工作量,用1、2、3、4这种魔法值来定义对应的类型。导致同事读取代码时无法第一时间判定1、2、3、4所代表的含义。建议做成枚举或常量去定义1、2、3、4所代表的具体业务逻辑。
反例:

    if (key.equals("aaa")) {
            //...
    }

正例:

    String KEY_PRE = "aaa";
    if (KEY_PRE.equals(key)) {
            //...
    }

14. 非空判断

非空判断不要自己写!

  • @NotNull: //CharSequence, Collection, Map 和 Array 对象不能是 null, 但可以是空集(size = 0)。
  • @NotEmpty: //CharSequence, Collection, Map 和 Array 对象不能是 null 并且相关对象的 size 大于 0。
  • @NotBlank: //String 不是 null 且去除两端空白字符后的长度(trimmed length)大于 0

15. MyBatis 不要为了多个查询条件而写 1 = 1

当遇到多个查询条件,使用 where 1=1 可以很方便的解决我们的问题,但是这样很可能会造成非常大的性能损失,因为添加了 “where 1=1” 的过滤条件之后,数据库系统就无法使用索引等查询优化策略,数据库系统将会被迫对每行数据进行扫描(即全表扫描) 以比较此行是否满足过滤条件,当表中的数据量较大时查询速度会非常慢;此外,还会存在 SQL 注入的风险。
反例:

<select parameterType="com.tjt.platform.entity.BookInfo" resultType="java.lang.Integer">
 select count(*) from t_rule_BookInfo t where 1=1
  <if test="title !=null and title !='' ">
   AND title = #{title} 
  </if> 
  <if test="author !=null and author !='' ">
   AND author = #{author}
  </if> 
</select>

正例:

<select parameterType="com.tjt.platform.entity.BookInfo" resultType="java.lang.Integer">
 select count(*) from t_rule_BookInfo t
  <where>
    <if test="title !=null and title !='' ">
      title = #{title} 
    </if>
    <if test="author !=null and author !='' "> 
      AND author = #{author}
    </if>
  </where> 
</select>

UPDATE 操作也一样,可以用标签代替 1=1。

16. 字符串转化使用 String.valueOf(value) 代替 " " + value

把其它对象或类型转化为字符串时,使用 String.valueOf(value) 比 ""+value 的效率更高。
反例:

//把其它对象或类型转化为字符串反例:
int num = 520;
// "" + value
String strLove = "" + num;

正例:

//把其它对象或类型转化为字符串正例:
int num = 520;
// String.valueOf() 效率更高
String strLove = String.valueOf(num);

17. 避免使用 BigDecimal(double)

BigDecimal(double) 存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。
反例:

 // BigDecimal 反例    
 BigDecimal bigDecimal = new BigDecimal(0.11D);

正例:

 // BigDecimal 正例
 BigDecimal bigDecimal1 = bigDecimal.valueOf(0.11D);

image.png
使用new BigDecimal(0.11D);后精度丢失

18. 优先使用常量或确定值调用 equals 方法

对象的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals 方法。

反例:

    //str为null 可能抛空指针异常
    public void f(String str){
        if (str.equals("hi")) {
            System.out.println("hello world");
        }
    }

正例:

    public void f(String str){
        String inner = "hi";
	// 使用常量或确定有值的对象来调用 equals 方法
        if (inner.equals(str)) {
            System.out.println("hello world");
        }
	//或使用: java.util.Objects.equals() 方法
        if (Objects.equals(inner,str)) {
            System.out.println("hello world");
        }
    }

19. 所有的包装类对象之间值的比较,全部使用equals方法比较

对于Integer var=?在-128至127之间的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。
反例:

Integer a = 256;
Integer b = 256;
if (a == b){
    System.out.println("a == b");
}else{
    System.out.println("a != b");
}

正例:

Integer a = 256;
Integer b = 256;
if (a.equals(b)){
    System.out.println("a.equals(b)");
}

image.png

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

关于hashCode和equals的处理,遵循如下规则:

  1. 只要重写equals,就必须重写hashCode
  2. 因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法
  3. 如果自定义对象做为Map的键,那么必须重写hashCode和equals。

反例:

     public class CarNumber{
        private String cityCode; // 城市代号
        private String number; // 车牌号
        public String getCityCode() {
            return cityCode;
        }
        public void setCityCode(String cityCode) {
            this.cityCode = cityCode;
        }
        public String getNumber() {
            return number;
        }
        public void setNumber(String number) {
            this.number = number;
        }
    }
    public static void main(String[] args) {
        //  key为自定义类
        Map<CarNumber, String> map =new HashMap<>(16);

        CarNumber car1 = new CarNumber();
        car1.setCityCode("苏A");
        car1.setNumber("11111");
        CarNumber car2 = new CarNumber();
        car2.setCityCode("苏A");
        car2.setNumber("22222");

        map.put(car1, "车牌号1");
        map.put(car2, "车牌号2");

        // 参数内容和car2对象一样
        CarNumber car3 = new CarNumber();
        car3.setCityCode("苏A");
        car3.setNumber("22222");

        //控制台输出
        System.out.println(map.get(car1));
        System.out.println(map.get(car2));
        System.out.println(map.get(car3));
    }

输出结果为:
image.png

正常理解car2&car3对象从hashMap中取值应该是相同的,但如果不重写hashcode()方法,比较是其地址,car3和car2地址不同,所以不相等!

正例:

    public class CarNumber{
        private String cityCode; // 城市代号
        private String number; // 车牌号
        public String getCityCode() {
            return cityCode;
        }
        public void setCityCode(String cityCode) {
            this.cityCode = cityCode;
        }
        public String getNumber() {
            return number;
        }
        public void setNumber(String number) {
            this.number = number;
        }
    //在CarNumber 类中重写equals()和hashCode()方法;
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            CarNumber carNumber = (CarNumber) o;
            return Objects.equals(cityCode, carNumber.cityCode) &&
                    Objects.equals(number, carNumber.number);
        }

        @Override
        public int hashCode() {
            return Objects.hash(cityCode, number);
        }

    }



    public static void main(String[] args) {
        //  key为自定义类
        Map<CarNumber, String> map =new HashMap<>(16);

        CarNumber car1 = new CarNumber();
        car1.setCityCode("苏A");
        car1.setNumber("11111");
        CarNumber car2 = new CarNumber();
        car2.setCityCode("苏A");
        car2.setNumber("22222");

        map.put(car1, "车牌号1");
        map.put(car2, "车牌号2");

        // 参数内容和car2对象一样
        CarNumber car3 = new CarNumber();
        car3.setCityCode("苏A");
        car3.setNumber("22222");

        //控制台输出
        System.out.println(map.get(car1));
        System.out.println(map.get(car2));
        System.out.println(map.get(car3));
    }

输出结果为:
image.png
原理:

  • HashMap中的比较key是这样的,先求出key的hashcode(),比较其值是否相等,若相等再比较equals(),若相等则认为他们是相等的。若equals()不相等则认为他们不相等。
  • 如果只重写hashcode()不重写equals()方法,当比较equals()时只是看他们是否为同一对象(即进行内存地址的比较),所以必定要两个方法一起重写。
  • HashMap用来判断key是否相等的方法,其实是调用了HashSet判断加入元素是否相等。

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

尽量在初始化时指定集合的大小,能有效减少集合的扩容次数,因为集合每次扩容的时间复杂度很可能时 O(n),耗费时间和性能。
HashMap使用如下构造方法进行初始化,如果暂时无法确定集合大小,那么指定默认值(16)即可。
反例:

        Map<String, Object> stringObjectHashMap = new HashMap<>();
        HashSet<Object> objects = new HashSet<>();

        //初始化list,往list 中添加元素反例:
        int[] arr = new int[]{1,2,3,4};
        List<Integer> list = new ArrayList<>();
        for (int i : arr){
           list.add(i);
        }

正例:

        Map<String, Object> stringObjectHashMap = new HashMap<>(16);
        HashSet<Object> objects = new HashSet<>(16);

        //初始化list,往list 中添加元素正例:
        int[] arr = new int[]{1,2,3,4};
        //指定集合list 的容量大小
        List<Integer> list = new ArrayList<>(arr.length);
        for (int i : arr){
           list.add(i);
        }

22. 养成追求完美代码的习惯及多review一下代码。

数据库表设计规范

建表

  1. 表名只能使用字母、数字、下划线。一律小写。
  2. 创建表时指定字符集为utf8mb4
  3. 表存储引擎统一为InnoDB
  4. 所有表字段必须有comment,有多个枚举值域的全部写清楚。
  5. 主键为id, 类型为bigint,自增,并有unique key索引。
  6. 所有表必须有创建时间字段create_time 和更新时间字段update_time。
  7. 所有字段必须都是NOT NULL, 根据业务需要定义DEFAULT值。
  8. 中间表必须以【tmp_】开头。备份表必须以【bak_】开头。定期清理。
  9. status、type等字段使用tinyint 或者smallint 类型。
  10. 小数类型必须使用decimal。禁止使用float和double。
  11. 是否概念的字段,必须使用is_xxx的方式命名,类型为unsigned tinyint (1表示是,0表示否)。
  12. 文本数据使用varchar。
  13. 时间类型使用timestamp。不建议datetime类型。因为timestamp使用4字节,datetime使用8个字节。
  14. 金钱相关字段,建议使用int 或者bigint,程序使用乘以10000和除以10000进行存取。
  15. 手机号使用varchar(20)存储。
  16. 库名、表名、字段名见名知意,建议使用名词而不是动词。
  17. 尽可能不使用TEXT、BLOB类型。
  18. 禁止在数据库中存储图片、文件等。
  19. 禁止使用MySQL的关键字和保留字作为表名或列名。
  20. 将过大字段拆分到其他表中。
  21. 表中字段过多应拆表。

索引

  1. 索引类型必须必须为BTREE。
  2. 单表数据个数不超过7个。
  3. 多使用联合索引,把区分度最高的字段放在最前面。
  4. 多表join时,连接列必须有索引。
  5. 唯一索引名以【uk_字段名】方式命名,普通索引以【idx_字段名】方式命名。

最后,创作不易,如果对大家有所帮助,希望大家多多支持,有什么问题也可以在评论区里讨论😄~

QQ图片20210826154613.jpg

Q.E.D.


知道的越多,不知道的越多