1、项目搭建与准备工作:
(1)需求对接:对接需求后进行讨论分析,针对需求进行拆分功能点,进行细化和思考对关联的功能的联动与影响,设计完数据库和初始的准备工作开始编写业务功能时,尽可能的和需求沟通避免出现过多的偏差(必要时预留可拓展的可能性,代码方法块细化拆分,支持复用)
(2)数据库设计:如状态或者类型多个的情况下,不建议使用数值1、2…等,建议使用对应的英文字符,注意一些字段的非空设置(可以定义默认值),避免字段为空时可能导致的索引失效;业务表关联层次分明,比如一个任务中包含任务主体和相关干系人,进行分为2张表,任务主体表和人员关联表,而不是选择将人员信息存于任务主体表,应该尽可能的拆分表的使用职能
(3)实体类对应数据库字段禁止添加多余字段,数据库中已下划线,实体类使用驼峰命名(约定俗成)
(4)建议:
①查询、新增、修改数据库使用DTO进行传参,传参DTO后缀为xxxxParamDTO,xxxxSearchParamDTO(搜索)、xxxxEditParamDTO(编辑);
②获取的查询结果使用vo接收,结果vo以xxxxResultVo或xxxxInfoVo,也可根据实际使用进行命名;
③禁止使用同一个DTO/VO进行传参和接收查询结果,尽可能的将DTO/VO的使用职能划分清晰
④定时任务后缀以xxxxxScheduled/xxxxTask
⑤如实体类可符合使用需求就无须使用DTO/VO,需注意使用该方式的可能导致文件过多,是否采用此方式,请自行斟酌
⑥各模块枚举如非必要,请独立使用
例如:不同的模块在使用的时候只使用对应的枚举
⑦提交git的时候添加上相应的标识前缀再加上说明,添加代码/接口/类等使用:[add]+说明,修改方法/接口等使用:[mod]+说明,删除方法/类等使用:[del]+说明
⑧修改bug时,提交代码应该加上bug的对应信息,方便后续bug被退回时辅助修改
![在这里插入图片描述](https://img-blog.csdnimg.cn/91bd4d62c160440c9c43c4f15c1f5c95.png
⑨设计表不建议使用外键
2、编写过程中或者修改bug建议
(1)对于业务方法内的代码尽可能的考虑是否可以进行拆分,如有必要应该尽可能的进行代码的优化,甚至是重构;
(2)对于通用的方法在不满足业务需求时不要强行使用,或者不存在可共用可能性的功能时建议重新编写新方法,若存在共用的可能性也尽可能的将自己的方法拆分出去,然后再调用此方法;
(3)编写或修改完后,需对自身的接口/方法进行单元测试(或其他的自测方法,方式不做限定)
(4)总结:高内聚低耦合、保证复用性/通用性、减少冗余,尽可能的多做自测
3、编写过程中的常见问题和优化
(1)在保证业务能够实现且时间充裕的前提下,应该考虑方法的使用性能及用户的使用体验(操作速度的响应),可以一点点的局部进行优化积少成多
优化前,按时间或者推送两条件判断是否执行之后代码的判断:
优化后,优先判断时间,后再查询判断,不符时间的情况下课减少一次查询:
(2)禁止controller接口路径重复。
(3)建议:各功能模块的业务如无需拆分的只在本功能模块中编写,就比如项目中的功能模块A的列表,虽归属于功能模块A,但这是B功能模块的功能,建议不要将B列表的查询在A的controller编写改接口,而应该写入到B的controller。
(4)对于明确的不需要的代码,应该及时删除
(5)建议:接口上都应该添加该接口的说明(接口的作用,开发者,日期),使用idea的可以安装插件 GitToolBox进行辅助,查看改接口的编写人
(6)复杂的方法注释说明应该尽可能的详细
(7)建议:对集合循环建议先进行非空的判断再进行使用,特别是调用其他人封装的方法时,避免调用获取的结果为NULL,导致的bug
(8)建议类的注入使用注解统一
(9)建议不要调用非本业务模块的mapper层(有待商榷),个人观点如下:
①严格按照mvc的分层结构(controller控制层、service业务层、dao持久层),这样项目结构更清晰
②还有一点比如A的service中的方法使用了B的dao,这时A有业务逻辑可以直接调用B的service中的方法,在此情况下是使用B的dao封装一个方法,还是直接引用B的方法呢?
(10)实例化使用类时,如果是通用的传参类,应该在for循环外,以避免内存的浪费(虽然有垃圾回收机制,但仍然不建议在循环中这样使用,毕竟每创建一个对象都会在堆中创建一个空间)
(11)建议,调用本类的方法添加上this.标识
(12)未引用的类应该移除,减少混乱并避免命名冲突,例如:引用了A包的a类但没有使用且不删除,那么其他人如果要使用的是B包的a类的方法m,同时A包的a类也有方法m,且传参都一样,但是内部的逻辑不同获得的结果也不同,这就会导致不必要的问题
(13)只在本类使用的方法使用private修饰,并添加必要的注释说明
(14)如果明确了返回的集合为空可以使用工具类返回空集合,注意该空集合不可进行操作,注:Arrays.asList()的集合不能新加元素,长度固定
(15)相同或高度相似的代码块建议合并,传参使用
(16)注入的类使用修饰符private
(17)禁止使用魔法值,且不做注释
(18)常量判断时,应该常量放在前面,避免空指针的产生
(19)对于明确不会为空的集合也应该进行非空判断,避免之前由于业务变更或者导入数据导致的特殊情况
(20)代码应该简洁明了,去除多余的重复判断
(21)建议表关联不超过3张,对于过多表关联建议加上注释,或者考虑使用逻辑代码使用,避免慢sql
(22)遍历循环命名应该明确,不要太过随意或者根本无关
(23)使用下面这种for循环,必须添加非空判断
(24)属于同一业务功能不建议拆分多个接口进行调用,保证事务性
(25)判断条件语句不要嵌套过深(禁止套娃)
可调整为
(26)使用if语句,应该使用{}包起来,增加可读性,且代码过长应该换行,且非对接三方的业务,请勿在controller层编写业务逻辑,应该严格遵守三层架构,在service层进行编写封装
(27)包装类型的判断应该使用equals
(28)使用switch,必须加上条件内必须加上break,避免穿透
①穿透:当执行符合条件的case子句中没有break,会按顺序往下执行其他的case子句(不进行条件判断),直到遇到break才跳出switch语句块
(29)使用多数据源时,某个接口中使用了多个数据源的类且用到事务,把@Transactional替换成@DSTransactional注解,且@DSTransactional不要与@Transactional混用,也可以采用事务传播机制做处理
(30)大数据量的数据操作,可以考虑使用缓存,或者对数据分割(Lists.partition)或查询的时候使用分页查询出来再进行操作,使用sql考虑使用单表查将所需要的数据都查出来再进行封装,避免表关联导致查询过久
①大量数据sql使用in可能存在数据库超过最大参数个数,可以考虑使用.EXISTS代替
(31)建议:数据量小时使用普通的for循环或增强for,数据量大使用foreach或者stream进行数据处理,数据量过大可以考虑使用parallelStream;或者使用数据分割或分页处理完在进行循环处理
4、编写时的一些技术与心得的分享
(1)对类的判空java.util.Objects:Objects.nonNull、Objects.isNull
(2)对于集合的判空org.apache.commons.collections.CollectionUtils:
CollectionUtils.isNotEmpty、CollectionUtils.isEmpty
(3)建议:对于自身编写支持他人调用的方法返回的结果尽可能不要使用null,严格意义来说返回null代表着你的方法不可信,还要他人调用时在进行一层判断,对调用方不友好,例:
(4)对于实现接口的方法应该加上@Override注解
(5)建议:业务方法尽量不要使用get和set作为前缀命名
(6)代码中尽量不要出现魔法值(常量),如果使用也应该标明对应的常量代表的含义,建议使用定义到枚举中或者常量类中,然后进行调用
(7)If判断层级不要过深,且保证层次分明,可以进行提前返回
(8)建议
(9)建议:判断空时,建议将null至于前面 if( null!=xxx )
(10)建议:传参过多的时候,进行注释说明,或者使用对象进行传参
(11)建议:申明变量或者实例化对象时的命名应该明确的表明使用的含义,而不应该使用一些语义含糊的名称
(12)建议:将一个业务拆成多个独立子方法进行调用,高内聚低耦合
(13)对于弃用的代码或者变量应该及时进行删除
(14)建议:数据库字段对应的数据结果接收实体对应,无特殊需求时不应该篡改重置字段值,可以重新申明一个成员变量进行接收;否则导致一些特殊情况下需要对原字段值进行判断时而改字段已被更改导致的判断错误
(17)建议:非必要情况不要在for循环查询数据库,导致数据连接池的连接数占用过多,影响使用性能
(18)流的使用,常用的事例:
a、获取指定字段集合:
对象:
List<DishesWeekPlan> list = this.queryAllList();
List<String> planIds = list.stream()
.map(DishesWeekPlan::getId)
.collect(Collectors.toList());
Map: List<String> ids = list.stream()
.map(p->p.get("id"))
.collect(Collectors.joining(","));
b、集合的筛选:
List<DishesWeekPlan> list = this.queryAllList();
List<String> plansIds = list .stream()
.filter(d-> {
//不为空判断
return Objects.nonNull(d.getId());
})
.map(DishesWeekPlan::getId)
.collect(Collectors.toList());
c、求和:
Integer result = list.stream().collect(Collectors.summingInt(Student::getAge));
d、自然顺序
List<Student>list=studentList.stream()
.sorted(Comparator.comparing(Student::getAge))
.collect(Collectors.toList());
e、逆序
List<Student> list=studentList.stream()
.sorted(Comparator.comparing(Student::getAge).reversed())
.collect(Collectors.toList());
f、集合转map:
例1:
Map<String,Student> birthdayMap= studentList.stream()
.collect(Collectors.toMap(Student::getBirthday,a->a,(a1,a2)->a1));
//注:出生日一样第一条数据覆盖之后的数据
例4:
Map<Integer, List<Student>> birthdayMap= studentList.stream()
.collect(Collectors.groupingBy(Student::getBirthday));
例3:
Map<String, List<String>> collect = list.stream()
.collect(
Collectors.groupingBy(
Student::getBirthday,
Collectors.mapping(
Student::getName,
Collectors.toList()
)
)
);
g、判断:
boolean headFlag = relatedUsers.stream()
.filter(r -> {
return UserTypeEnum.HEAD_PERSON.getValue()
.equals(r.getUserType()));
}
.findAny().isPresent();
h、Optional可对可能为空对象的使用
SysUser user = userService.getUserByToken(recentNewVo.getToken());
//lambda表达式中使用的变量应该是final或实际上是final
AtomicReference userId = null;
Optional.ofNullable(user).ifPresent(u->{
Optional.ofNullable(user.getUserId()).ifPresent(uid->{
userId.set(uid.intValue());
String userName = “”;//等价于final String userName = “”;
});
});
注:AtomicReference 原子引用,AtomicReference则对应普通的对象引用,底层使用的是compareAndSwapObject实现CAS,比较的是两个对象的地址是否相等。也就是它可以保证你在修改对象引用时的线程安全性。
lambda表达式中使用的变量默认被final修饰
实例:
(1)
(2)
未优化
优化
.
.
.
.
.
.
.
.
.
.
学习收集整理…