近期code review几处小问题集锦
1 线程池使用不当
我们的调度系统需要将一堆会员分配给相应的人员来处理,流程如以下伪代码所示:
public void dispatch() {
while (true) {
List<Member> memberList = getUnassignedMemberList(); //获取所有未分配的会员
for(Member each : memberList) {
singleDispatch(each); //为每一个会员分配相应的人员处理
}
try {
Thread.sleep(1000); //休眠1秒后继续分配
} catch (InterruptedException e) {
}
}
}
为了提高分配的速度,我们打算采用多线程的分配方式。一开始使用的是newCachedThreadPool。
private static ExecutorService executor = Executors.newCachedThreadPool();
public void dispatch() {
while (true) {
List<Member> memberList = getUnassignedMemberList(); //获取所有未分配的会员
for(final Member each : memberList) {
executor.submit(new Runnable() {
@Override
public void run() {
singleDispatch(each); //为每一个会员分配相应的人员
}
});
}
try {
Thread.sleep(1000); //休眠1秒后继续分配
} catch (InterruptedException e) {
}
}
}
在压测时发现,load飙升得很高,通过抓栈发现开启了很多线程。原因是:newCachedThreadPool最大线程数为整型的最大值,每提交一个任务,如果没有线程处理,那就产生一个新的线程。当我们for循环提交任务时,开辟了上百个线程,应用程序马上崩溃。
既然发现了原因,我们马上将调整线程池为newFixedThreadPool,这里我们可以设置最大线程数为4,队列长度为整型的最大值。
private static ExecutorService executor = Executors.newFixedThreadPool(4);
但是压测又发现新问题,线程池里的队列长度不断增长,而且分配不断有异常抛出(异常信息为会员已经被分配过)。
原因是:当未分配会员较多时,可能需要5秒才能分配完,然而executor.submit是异步操作,当休眠1秒钟后,马上又进入下一个循环,队列里又将插入重复的会员,这会导致队列长度不断增长,此外,会导致1个会员被分配后,又继续被分配,导致异常产生。
解决方法:使用invokeAll这一同步语句,意思是只有当提交任务都被执行完后,才执行后续语句。
public void dispatch() {
while (true) {
List<Callable<String>> tasks = new ArrayList<Callable<String>>();
List<Member> memberList = getUnassignedMemberList(); //获取所有未分配的会员
for (final Member each : memberList) {
tasks.add(new Callable<String>() {
@Override
public String call() {
singleDispatch(each);
return "ok";
}
});
}
try {
executor.invokeAll(tasks, 480, TimeUnit.SECONDS); //如果8分钟还未执行完,则超时重新再来(鲁棒性保证)
}catch (Exception e) {
}
try {
Thread.sleep(1000); //休眠1秒后继续分配
} catch (InterruptedException e) {
}
}
}
2 NPE(java.lang.NullPointerException)
2.1 情形1
if(case.getType() == Case.TYPE_SELF) {
...
}
这段代码抛出NPE时,直觉认为case为null导致的,后来打日志发现case并不为null,而case.getType()返回值类型为Integer,为null。
最后发现Case.TYPE_SELF返回的是int类型,而case.getType()是个null,null与int两者一比较就报NPE。
这个问题的诡异之处我们直觉上认为Case.TYPE_SELF是个Integer,所以导致排查问题花费了些时间,因此一个建议这种常量如Case.TYPE_SELF都改为Integer,然后对象间比较的时候使用equals,并增加null判断,就可以避免出现问题。
if(null != case.getType() && case.getType().equals(Case.TYPE_SELF))
2.2 情形2
Integer leftNum = (null != leftNumMap && !leftNumMap.isEmpty()) ? leftNumMap.get(stat.getDepartmentId()) : 0;
这个也抛NPE,我们排查了半个多小时,百思不得其解,后来查看class文件发现了原因。
假设我们代码是:
Map<String, Integer> leftNumMap = new HashMap<String , Integer>();
Integer leftNum = (null != leftNumMap && !leftNumMap.isEmpty()) ? leftNumMap.get("test") : 0;
反编译后的代码如下:
HashMap leftNumMap = new HashMap();
Integer leftNum = Integer.valueOf(null != leftNumMap && !leftNumMap.isEmpty()?((Integer)leftNumMap.get("test")).intValue():0);
为什么会这样呢?《你真的会用 Java 中的三目运算符吗?》一文做了说明。三目运算符的语法规范是这样写的:If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T.
简单的来说就是:当第二,第三位操作数一个为基本类型一个为对象时,其中的对象就会拆箱为基本类型进行操作。
所以,结果就是:由于使用了三目运算符,并且第二、第三位操作数一个是基本类型一个是对象。所以对对象进行拆箱操作,由于该对象为null,所以在拆箱过程中调用null.intValue()的时候就报了NPE。
解决方法很简单:1)要么不用三目运算符,直接使用if else,简单可靠;2)或者将三目运算符操作数都改为对象。
Integer leftNum = (null != leftNumMap && !leftNumMap.isEmpty()) ? leftNumMap.get(stat.getDepartmentId()) : Integer.valueOf(0);
3 Map<K, List<V>>用错
我们代码中经常出现以下这种数据结构,比如key是类目,value是个List,list存储着这个类目下各个子类目的统计数据等。
Map<String,List<MyClass>> myClassListMap = new HashMap<String,List<MyClass>>()
我们插入该数据结构的时候往往得这样写:
void putMyObject(String key, Object value) {
List<Object> myClassList = myClassListMap.get(key);
if(myClassList == null) {
myClassList = new ArrayList<object>();
myClassListMap.put(key,myClassList);
}
myClassList.add(value);
}
当我们希望检查List中的对象是否存在,或删除一个对象,那要遍历整个数据结构,需要更多的代码。
这些代码不仅给可读性带来障碍,更提高了大家出错的概率,比如某一次我们在复杂的业务逻辑中实现putMyObject时,漏写了句:
myClassListMap.put(key,myClassList);
结果导致花费了大量时间才发现这个问题。
当然,细致的测试以及code review能避免出现类似问题,但是有没有一种方法既能从技术上来帮助我们避免此类问题,又能节约代码提高代码可读性?
推荐大家使用google guava包,里面提供了MultiMap数据结构,使用方式如下,非常简洁明了,也不会有机会让我们出错。
Multimap<String, String> myMultimap = ArrayListMultimap.create();
myMultimap.put("女装", "内衣");
myMultimap.put("女装", "羽绒服");
myMultimap.put("女装", "风衣");
myMultimap.put("男装", "皮夹克");
// 获取key "女装"对应的list
Collection<String> womenDressList = myMultimap.get("女装");
// 删除key "女装"对应List中的"羽绒服"
myMultimap.remove("女装", "羽绒服");
近期code review几处小问题集锦的更多相关文章
- code review作业
下面是对结对编程队友12061166 宋天舒的code review 五个优点: 1.代码的风格优秀,注释不多,但是必要的注释还是有的,比如: // 三种模式 // mode1仅统计单个单词 // m ...
- 17款code review工具
本文是码农网原创翻译,转载请看清文末的转载要求,谢谢合作! 好的代码审查器可以大大地帮助程序员提高代码质量,减少错误几率. 虽然现在市场上有许多可用的代码审查工具,但如何挑选也是一个艰巨的任务.在咨询 ...
- Go Code Review Comments 译文(截止2018年7月27日)
持续更新中- 原文最新链接 https://github.com/golang/go/wiki/CodeReviewComments/5a40ba36d388ff1b8b2dd4c1c3fe820b8 ...
- 漫谈Code Review的错误实践
从刚开始工作时到现在,已经写了7年的代码,大部分代码都被人review过,自己也review了很多人的代码.在上一家公司的时候,我负责的一轮面试是专门进行Code Review的练习和经验谈. 通过在 ...
- 【转载】 漫谈Code Review的错误实践
原文地址: https://www.cnblogs.com/chaosyang/p/code-review-wrong-practices.html ------------------------- ...
- Code Review 常见的5个错误模式
原作者:Trisha Gee Code Review 的时候,每个人都会关心最佳实践,但最坏的实践有时可能会更有启示意义. Code Review是研发团队必不可少的,但并不总是正确的.这篇文章指出了 ...
- 我们是怎么做Code Review的
前几天看了<Code Review 程序员的寄望与哀伤>,想到我们团队开展Code Review也有2年了,结果还算比较满意,有些经验应该可以和大家一起分享.探讨.我们为什么要推行Code ...
- Code Review 程序员的寄望与哀伤
一个程序员,他写完了代码,在测试环境通过了测试,然后他把它发布到了线上生产环境,但很快就发现在生产环境上出了问题,有潜在的 bug. 事后分析,是生产环境的一些微妙差异,使得这种 bug 场景在线下测 ...
- Git和Code Review流程
Code Review流程1.根据开发任务,建立git分支, 分支名称模式为feature/任务名,比如关于API相关的一项任务,建立分支feature/api.git checkout -b fea ...
随机推荐
- bs4的学习
soup = BeautifulSoup(html,'html.parser') #'html.parser'是html解析器必须有soup.find_all("a") #等价于 ...
- Linux/Unix双机建立信任教程
Linux/Unix双机建立信任教程 一 需要建立信任关系的2台主机都执行生成密钥输入ssh-keygen -t rsa之后全部默认回车,这样就会在/root/.ssh下生成密钥文件 [root@pl ...
- HDOJ 1512 几乎模板的左偏树
题目大意:有n个猴子.每个猴子有一个力量值,力量值越大表示这个猴子打架越厉害.如果2个猴子不认识,他们就会找他们认识的猴子中力量最大的出来单挑,单挑不论输赢,单挑的2个猴子力量值减半,这2拨猴子就都认 ...
- 解决org.openqa.selenium.WebDriverException: Unable to connect to host 127.0.0.1 on port 7055 after 45000 ms org.springframework.beans.BeanInstantiation
解决方法为将selenium-server-standalone-2.37.0.jar升级至selenium-server-standalone-2.41.0.jar即可. 下载地址:http://s ...
- Java学习笔记16--异常
异常 异常是导致程序中断运行的一种指令流,如果不对异常进行正确的处理,则可能导致程序的中断执行,造成不必要的损失, 所以在程序的设计中必须要考虑各种异常的发生,并正确的做好相应的处理,这样才能保证程序 ...
- JS 菜单栏一直悬浮在顶部代码
只需要把下面代码放到js中: $(function(){ //获取要定位元素距离浏览器顶部的距离 var navH = $(".menu&quo ...
- svc6 控制台程序利用SoapToolkit3.0调用WebService
1. 首先要安装SoapToolkit3.0安装包并安装(我的安装目录为:C:\Program Files\Common Files) 2. 新建vc控制台程序(空项目),项目名称:WinConsol ...
- GATK使用说明-GRCh38(Genome Reference Consortium)(二)
Reference Genome Components 1. GRCh38 is special because it has alternate contigs that represent pop ...
- Top Data Scientists to Follow & Best Data Science Tutorials on GitHub
http://www.analyticsvidhya.com/blog/2015/07/github-special-data-scientists-to-follow-best-tutorials/ ...
- 拾遗:『ext4 Quota』
一.关闭selinux [root@ home]# sestatus -v SELinux status: disabled 示例:临时关闭 [root@ home]# setenforce 示例:永 ...