定时任务场景下的代码审查:continue和return的滥用可能引发潜在bug

文章目录

      • 前言
      • for 循环中的continue,break和return
      • 实际业务中的滥用
      • 总结
      • 写在最后

前言

在最近的代码审查中,有帮忙审查了组里一个刚毕业1年不到的应届生,发现他写的其中一段代码将for循环中的break、continue、return滥用,导致了一个潜在的bug风险,这个风险后文我们再来分析。

定时任务场景下的代码审查:continue和return的滥用可能引发潜在bug_第1张图片

for 循环中的continue,break和return

在讲解我实际碰到的业务例子之前,先来简单看看下面的例子

for循环是一种常用的编程结构,它可以重复执行一段代码,直到满足某个条件为止。在for循环中,有时我们需要根据不同的情况,跳过当前的迭代,结束当前的循环,或者结束整个方法。为了实现这些功能,Java语言提供了三个关键字:continue,break和return。

  • continue关键字的作用是跳过当前的迭代,继续执行下一次的迭代。例如,如果我们想要打印出1到10之间的所有奇数,我们可以使用如下的代码:
for (int i = 1; i <= 10; i++) {
  if (i % 2 == 0) { // 如果i是偶数,跳过当前的迭代
    continue;
  }
  System.out.println(i); // 打印出i的值
}
输出结果为:
1
3
5
7
9
  • break关键字的作用是结束当前的循环,跳出循环体。例如,如果我们想要打印出1到10之间的所有奇数,但是当i等于5时,就停止打印,我们可以使用如下的代码:
for (int i = 1; i <= 10; i++) {
  if (i == 5) { // 如果i等于5,结束当前的循环
    break;
  }
  if (i % 2 == 0) { // 如果i是偶数,跳过当前的迭代
    continue;
  }
  System.out.println(i); // 打印出i的值
}
输出结果为:
1
3
  • return关键字的作用是结束当前的方法,并返回一个值(如果有的话)。例如,如果我们想要计算1到10之间的所有奇数的和,我们可以使用如下的代码:
public static int sumOddNumbers() {
  int sum = 0; // 初始化和为0
  for (int i = 1; i <= 10; i++) {
    if (i % 2 == 0) { // 如果i是偶数,跳过当前的迭代
      continue;
    }
    sum += i; // 把i的值加到sum上
  }
  return sum; // 返回sum的值
}
调用该方法的结果为:
25

然而,在使用continue,break和return关键字时,我们需要注意一些潜在的bug。一个常见的bug是在for循环中使用return关键字,导致循环提前结束,而不是跳过当前的迭代。例如,如果我们想要打印出1到10之间的所有奇数,但是错误地使用了return关键字,我们会得到如下的代码:

我后面讲到的实际例子跟这个基本一样,就是错误滥用了return!

public static void printOddNumbers() {
  for (int i = 1; i <= 10; i++) {
    if (i % 2 == 0) { // 如果i是偶数,错误地使用了return关键字
      return;
    }
    System.out.println(i); // 打印出i的值
  }
}
调用该方法的结果为:
1

这是因为当i等于2时,return关键字会结束整个方法,而不是跳过当前的迭代。这样,我们就无法打印出后面的奇数。为了避免这个bug,我们应该使用continue关键字,而不是return关键字。

实际业务中的滥用

上面那是很简单的例子,但在实际公司复杂的业务逻辑中,continue、break和return语句的误用可能导致更严重的错误,甚至难以追踪和修复。

接下来我给出我审查代码中的错误例子:

//定时任务每3分钟执行一次此方法
handleFileItemList(){
	//查询出所有未处理的文件列表---  文件标识flag=0未处理的
    List fileItemList = getFileItemList();  
    //遍历列表,将对应的文件信息赋值到某个页面
    for (FileItem item : fileItemList) {
            ......	
            if(满足此文件信息已经存在于对应页面){
                //将此文件的flag标识置为已处理--flag=1
                setFileItemFlag();
                return;
            }
            .......
            将对应文件信息赋值到某个页面
            setFileInfo2Page();
            //将此文件的flag标识置为已处理--flag=1
            setFileItemFlag();
            .......
         }

}

注意这是一个定时任务的场景: 这里先从DB拉取一个文件信息表,这个文件表有个flag标识此文件是否已经处理过(flag:1已处理,0未处理),对表里面的List数据进行遍历,将对应的文件信息赋值到某个页面,他这里有个判断,如果某个文件信息已经存在于对应的页面了(除了定时任务场景,还会有别的场景会把文件信息赋值到页面),就停止循环,同时将此文件的flag标识置为已处理,直接return!

这个return处理方式说实话,如果一直没人去审查,也不会出现bug,因为针对这个场景是定时任务的,这里因为提前return了,导致后续的FileItem都无法进行处理,但是他会每3分钟执行一次,顶多是执行得慢一点,总能处理完!,如果是别的场景,这个bug应该早就能发现了!

总结

continue,break和return关键字在for循环中的防止乱用的总结如下:

  • continue关键字的作用是跳过当前的迭代,继续执行下一次的迭代。它可以用来跳过某些不需要执行的情况,或者优化循环的效率。
  • break关键字的作用是结束当前的循环,跳出循环体。它可以用来提前终止循环,或者跳出嵌套的循环。
  • return关键字的作用是结束当前的方法,并返回一个值(如果有的话)。它可以用来返回方法的结果,或者提前退出方法。
  • 在for循环中使用return关键字,会导致循环提前结束,而不是跳过当前的迭代。这可能会导致逻辑错误,或者丢失部分结果。为了避免这个bug,我们应该使用continue关键字,而不是return关键字。
  • 在嵌套的for循环中使用continue,break和return关键字,会导致循环的层级混乱。这可能会导致逻辑错误,或者输出错误的结果。为了避免这个bug,我们应该注意它们的作用范围,或者使用标签(label)来指定跳转的目标循环。

写在最后

  • 代码这东西就是这样,一个return 、一个continue,实际上代表的意义千差万别。

  • 像上面的bug,在普通的场景里面其实自己测试的时候可能就很好发现,但是如果在定时任务里面,不去好好审查一下代码,实际上不容易发现,因为最终文件信息都能去赋值到页面里面,只是慢一点而已。但是这也造成了一定的性能损失,明明一次定时任务就可以处理完所有文件,就因为return 的滥用,可能要好几次定时任务才能执行完毕。

你可能感兴趣的:(Java,SE高级,Java,SE初级,bug)