0 注释规范
注释的恰当用法是用来弥补我们在用代码表达意图时的失败。换句话说,当无法通过读代码来了解代码所表达的意思的时候,就需要用注释来说明。但是也不意味着什么都要加代码,见名知意的方法没有必要加注释
方法注释上除了基本的注释,我还会将产品需求的原文贴重要的部分上去再写上日期,这样做的好处是让别人明白产品需求要求干啥这个方法该干啥
pulic void test(){
/** 大注释,idea会高亮 1. 从excel 获取 vo*/
Workbook workBook = getWorkBook(wookbookStream);
// 小注释:获取成员信息
Sheet userSheet = workBook.getSheetAt(3);
Map<String, UserVO> userVOMap = getUserForExcel(file, userSheet);
// 获取项目vo
Sheet projectSheet = workBook.getSheetAt(0);
ProjectVO projectVO = getProjectForExcel(file, isInsert, userVOMap);
// 获取任务vo
Sheet taskSheet = workBook.getSheetAt(1);
Map<String, TaskVO> taskVOMap = getTaskListForExcel(file, taskSheet, userVOMap);
/** 2. 插入数据 */
if (isInsert.get()){
......
}
/** 3.写入异常信息 */
if (!isInsert.get()) {
.....
}
}
1 避免魔法数
for (Integer id : ids) {
if (id == 0) {
continue;
}
//改造
if (id == Constants.DEFAULT) {
continue;
}
}
2 使用方法封装过长的代码逻辑
使用场景
- try catch内部
protected boolean internalCancel(String appName, String id, boolean isReplication) {
try {
read.lock();
doInternalCancel(appName, id, isReplication);
} finally {
read.unlock();
}
// 剩余代码
}
private boolean doInternalCancel(String appName, String id, boolean isReplication) {
//真正处理下线的逻辑
}
3 重复使用的代码抽象出来
新增某一个功能是我们整体流程仍然是可以复用已有方法的,这时候会有重复逻辑,我们就可以抽出来形成一个方法
4 if判断条件过多或者看不明白在写什么
直接根据if条件,不知道是做什么,因为既有非空判断,又有业务判断
if (((StringUtils.isBlank(person.getName())
|| "testName".equals(person.getName()))
&& (person.getAge() != null && person.getAge() > 10))
&& "汉".equals(person.getNational())) {
// 处理逻辑
}
可以将上面的逻辑按照对应的业务逻辑划, 然后根据变量名做判断
boolean sanyouOrBlank = StringUtils.isBlank(person.getName()) || "testName".equals(person.getName());
boolean ageGreaterThanTen = person.getAge() != null && person.getAge() > 10;
boolean isHanNational = "汉".equals(person.getNational());
if (sanyouOrBlank
&& ageGreaterThanTen
&& isHanNational) {
// 处理逻辑
}
5 多加日志, 符合规范的日志更容易定位问题
日志的作用在于定位问题,因此最好符合以下规范
- 可搜索性,要有明确的关键字信息
- 异常日志需要打印出堆栈信息
- 合适的日志级别,比如异常使用error,正常使用info
需要日志打印的地方
- 请求体:进入方法前打印请求体
- 构建完成:关键参数处理完成后打印日志
- 数据更新:我们有必要知道写库的数据是不是正确的数据
- 条件分支:便于我们分析业务走的哪一条逻辑
- 批量写库:打上数据量大小/执行时间等日志,便于我们分析性能瓶颈
6 使用工具类
| 类型 | 使用 | 说明 |
|---|---|---|
| Objects | Objects.equals(Object a, Object b) | 会对a和b的空指针做判断,可以避免空指针异常 |
| Collections | !CollectionUtils.isEmpty(persons) | persons != null && persons.size() > 0 |
7 减少方法的圈复杂度
我一直觉得, 好的代码读起来应该像故事一样, 有前因, 有后果, 中间娓娓道来. 简单举个例子, 我们可能会遇到在方法中去for循环处理数据的情况. 比如在一个方法中, 套了三层循环.
List<String> orderCodeList = request.getOrderCodes();
// 第一层循环
for(String orderCode : orderCodeList){
// 查询单号对应的单据明细
List<OrderItem> items = this.orderItemService.getByCode(orderCode);
// 第二层循环
for(OrderItem item : items) {
// 执行操作1
// 执行操作2
// 执行操作3
// 第三层循环
for()
}
}
可以把每一层for循环都提取出来, 成为单独的一个方法, 来降低圈复杂度, 提高可读性
List<String> orderCodeList = request.getOrderCodes();
// 第一层循环
for(String orderCode : orderCodeList){
this.processSingleOrder(orderCode);
}
public void processSingleOrder(String orderCode){
// 查询单号对应的单据明细
List<OrderItem> items = this.orderItemService.getByCode(orderCode);
this.processItemsData(items);
}
public void processItemsData(){
for(OrderItem item : items) {
// 执行操作1
// 执行操作2
// 执行操作3
}
}
8 避免在循环中对数据库或远程调用做操作
慢请求的分析, 可能不需要要先去看有没有复杂的关联查询, 或者是不是数据库查询有没有命中索引, 而是先去看是不是有大哥在for循环去请求数据库和dubbo接口. 循环了1w次. 我曾经遇到过多次请求超时都是因为有人在代码里for循环去select , update.
对于这种问题的解决, 将循环调用改成单次的批量接口就可以解决问题. 对于mysql的化in操作就可以解决, 对于外部系统对接的, 双方提供批量接口就可以了.
9 注重单元测试
写过单元测试的会发现, 编写完善的单元测试会占用大量的时间, 一般都会超过需求的开发时间, 但是我还是认为单元测试是必须且重要的. 因为作为研发人员, 才是最了解代码中哪里容易出问题的. 更容易写出发现问题的测试用例. 并且代码迭代或者修改后, 也能更快速的发现问题, 将问题停留在研发阶段去解决, 提高整体的进度.
10 思考优化接口性能
优秀的后端开发,应该保持优化性能的嗅觉。比如避免创建非必要的对象、异步处理、使用缓冲流,减少IO操作等等。
比如,我们可以并行发起,从而降低耗时可以降为。事例如下:
11 可变参数放入配置中心
日常开发中,我们经常会遇到一些可变参数,比如用户多少天没登录注销、运营活动,不同节日红包皮肤切换、订单多久没付款就删除等等。对于这些可变的参数,不用该直接写死在代码。优秀的后端,要做配置化处理,你可以把这些可变参数,放到数据库一个配置表里面,也可以放到项目的配置文件或者apollo上。
比如产品经理提了个红包需求,圣诞节的时候,红包皮肤为圣诞节相关的,春节的时候,为春节红包皮肤等。如果到了元宵节的时候,运营小姐姐突然又有想法,红包皮肤换成灯笼相关的,这时候,是不是要去修改代码了,重新发布了?
从一开始接口设计时,可以实现一张红包皮肤的配置表,将红包皮肤做成配置化呢?更换红包皮肤,只需修改一下表数据就好了。当然,还有一些场景适合一些配置化的参数:一个分页多少数量控制、某个抢红包多久时间过期这些,都可以搞到参数配置化表里面。这也是扩展性思想的一种体现。
12 使用ai提升代码可读性和简洁性
包括但不限于
- 给代码排版,使格式看起来更舒服
- 根据代码执行逻辑,优化变量名,使代码更容易理解
- 解析代码执行效果,使用工具类平替某个执行逻辑,使代码更简洁
- 分析代码可能存在的bug