Java:如何写好代码,少点bug

前言

工作快三年了。
实习入职,至今2023-04-07,和大佬相比我这还是属于初中级程序员,技术不强。
平时写代码没啥技术含量,但真的挺多同事连基本的CRUD都写不好,不管是代码规范还是安全性问题都有很大的纰漏,所以觉得自己最大的骄傲就是代码规范,bug少,基本没怎么挨领导骂,嘿嘿。

同时希望刚工作不久的同学,尽量少点bug代码写好看点,你后面也会帮别人擦屁股的,同是打工人照顾下后面接手的兄弟

代码规范

代码规范我挑最常见最重要的讲:

  1. controller不要堆业务代码。像switch、for、大量的if else别写在contro,将业务抽到service

  2. controller对异常处理仅仅是打印日志,返回错误信息给前端的,少用try-catch,写个全局异常处理器

  3. 同一性质的业务代码抽成一个方法,并加上注释。举个例子:

比如说新增用户,然后将用户信息同步到另一个系统。业务逻辑如下:
1. 校验用户信息
2. 新增用户
3. 插入用户日志
4. 同步另一系统

这样1可以抽成校验方法,2,3对用户操作也抽,剩下4也抽个。

这样做有最大的好处:

  1. 看代码的人可以清晰知道哪个方法是干啥的,减少无关代码的阅读成本
  2. 降低维护成本。如果你全不抽,都是怼在一个方法里,八成后面添加代码的兄弟也懒得抽方法,直接怼导致方法越长
  1. 尽量最多嵌套两个for,实在需要多个for遍历,看能否在数据库来处理。如果数据量大那就加索引来处理

  2. 不要排斥异常甚至是NPE(空指针异常)。记住,不符合业务逻辑,该抛异常就得抛。NPE尤为强调,不要怕出现就老是 if(obj != null)...来避免进而写自己代码

  3. 新增和编辑contro接口请分开写。不要搞个saveOrUpdate,前期业务代码少可以简单判断处理,后面多了很容易出错

  4. 对于不同性质的实体类,不要用转换工具,如BeanUtils.copyProperties。什么叫不同性质的实体类?举个例子:

有两个对象,都是User实例:同性质
有三个对象,分别是User、UserDO、UserVO:同性质
有四个对象:User、Menu、Role、Department:不同性质

对于不同性质的实体类数据设值,别用BeanUtils.copyProperties等工具类,请老老实实写个方法一个个属性get、set。为后面看代码的兄弟着想下,别让人家老翻哪些属性是对应上的

  1. 对于字典属性,转换为文本请加一个字段,不要覆盖。举个例子:
有个商家编码属性:private String merchantCode。
现在需要商家编码对应的商家名称,就再搞个private String merchanName。获取商家字典映射merchantCode -> merchanName。

为什么这么简单的事还讲?因为我遇到过一个人就不这么做,转换的文本直接覆盖原字段上,导致后面开发兄弟做更新操作,数据库原本保存字典值,后面都更新为中文了

  1. 在表数据不大情况下,请查询全部字段。大家SQL优化最牢记就是不要select *,这也得看具体情况,数据不大情况下完全可以这样做,好处是减少和前端扯皮要数据的工作量

  2. 编辑操作别update前端给的全部字段。编辑某条记录,通常是回显此条所有信息,然后改相应字段,再把改后的数据给到后端。这里有个问题,如果前端不小心改了本来不应在编辑界面的字段,可能导致数据库也修改了。还是举个例子:

假设用户表有 id,userName,sex,idCard(像创建时间啥的就不列了)
界面上只能编辑用户名和性别,但你把用户的全部字段都返回给界面,前端可能不小心动了idCard字段,然后你直接update接收的整个对象,也就会把idCard也更新了。

严谨做法,后端一个个把需要修改的字段重新set新对象里,然后再update

  1. 涉及微服务之间的api调用,不要抛异常。如果出现异常,catch捕获后返回error的ResultDTO即可

  2. 少常量硬编码。诸如 str.equals("1"),sex == 1少写,建个枚举或者常量类写好注释比较好

  3. 尽量对象返回null,集合返回new ArrayList(),而不是抛异常。比如说一个方法根据id返回实体,有可能id是空的,直接返回null,调用方自己判断觉得抛异常还是继续其他逻辑

  4. 在插入操作时,如果确定仅仅是操作,那么装配Mapper对象调用insert插入就行;如果需要涉及一定的业务才进行业务,那么就在service写个业务insert方法

  5. 在for中尽量少查询数据库,看能不能直接一次性查出来,然后转为map来处理

  6. 写任何方法,尽量写文档注释,标注此方法的用途,各参数的用途。如下:
    image

image

  1. 在定义形参时,如果形参为数组,如String[],尽量定义成可变数组String...

代码安全

  1. 保证事务。只要insert、update、delete,都问问自己需不需要事务,哪些操作可以看作一个事务。顺便说下@Transactional有种失效场景极易忽视:同一个类A方法调B方法,A没加B加了@Transactional,这种是不起作用的

  2. 使用第三方SDK调用接口,要考虑的失败情况有3种:

  1. 响应respongse == null
  2. 抛出异常
  3. success = false
  1. 在查询数据库数据时,多考虑下表是否是大表,是的话:
  1. 尽量通过分页,限制查询条数
  2. 分批查,然后实体类间属性映射少用(否则容易oom)。
  3. 异步处理
  1. 大家不要害怕空指针。要站在业务的角度,有些不符合业务逻辑的异常该抛就抛,不要获取每个对象都 if(obj != null)

  2. 提供给其他服务或前端的更新接口,在不限制状态可以多次调用的情况下,尽量做幂等代码实现

业务纰漏

  1. 新增/更新漏字段。insert、update字段时,大家基本跟着原型或PRD来的,产品很多时候就不知道表,所以部分字段设值他是不知道的,也没有技术文档让我们照着敲,因此我的建议是多看表多看字段,问问字段设值的时机在啥时候

  2. 表单选填字段,这个很容易忽视,有时候算是个小bug。当你项目使用mybatis-plus,出于安全考虑,一般实体不为null的才进行更新,null的字段不更新,但页面上选填的字段是可能用户不填或者填了然后删掉的,这种情况该如何处理?

  1. 如果字段是字符串类型,直接让前端传“”即可,这样mp就能更新到,但是如果该字段或者和其他字段在数据库表组成了唯一索引,那么在后端就需要手动进行置null处理,示例代码如下:
if( shopInfoDO.getPlatformId() != null && shopInfoDO.getPlatformId().length() == 0) {
                getBaseDao().updateNull(shopInfoDO.getId(),shopInfoDO.getPlatformId(),null);
                shopInfoDO.setPlatformId(null);//这里要设置为空,不然还是空字符下面的更新还是会更新为空字符串
            }
			
			//更新
            getBaseDao().update(shopInfoDO);
  1. 如果字段非字符串类型,比如Bigdmal,Integer,LocalDateTime,这个和前端商定好,如果他们传默认值,就按他们的更新,如果没传,代表我们后端接收为null,也得手动去置null处理,示例代码如下:
if( shopInfoDO.getPrice() == null ) {
                getBaseDao().updateNull(shopInfoDO.getId(),null,shopInfoDO.getPrice());
                shopInfoDO.setPrice(null);//这里要设置为空,不然还是空字符下面的更新还是会更新为空字符串
            }

SQL建议

  1. 如果开发能不用mybatis-plus,尽量不用这玩意。这东西是纯对mybatis做增强没错,但因为有了他的出现,见过非常多的同事SQL能力很差,有的为了偷懒在多表情况也是不写SQL,一顿在应用嵌套for
posted @ 2023-06-13 13:57  爱编程DE文兄  阅读(39)  评论(0编辑  收藏  举报