近期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 ...
随机推荐
- Python-类的继承
类的继承 面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制.继承完全可以理解成类之间的类型和子类型关系. 需要注意的地方:继承语法 class 派生类名(基类名):/ ...
- Selenium2+python自动化17-JS处理滚动条
前言 selenium并不是万能的,有时候页面上操作无法实现的,这时候就需要借助JS来完成了. 常见场景: 当页面上的元素超过一屏后,想操作屏幕下方的元素,是不能直接定位到,会报元素不可见的. 这时候 ...
- java利用jxl操作Excel
/** * 把从数据库查询到的数据,写入电子表格 * * @throws Exception */ public void createXls() throws Exception { Dao dao ...
- Hdu OJ 5113 Black And White (2014ACM/ICPC亚洲区北京站) (搜索)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5113 题目大意:有k种颜色的方块,每种颜色有ai个, 现在有n*m的矩阵, 问这k种颜色的方块能否使任 ...
- 使用 IntraWeb (39) - THttpRequest、THttpReply
在其它服务器脚本语言中熟悉的 Request.Response(THttpRequest.THttpReply) 在 IntraWeb 中算是幕后英雄了, 用户基本不需要直接操作它们了. IW 默认 ...
- XE3随笔10:TSuperType
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms ...
- Spring 4 官方文档学习(十二)View技术
关键词:view technology.template.template engine.markup.内容较多,按需查用即可. 介绍 Thymeleaf Groovy Markup Template ...
- 在ios下提示“@synthesize of ‘weak’ property is only allowed in ARC or GC mode”
现在的项目是手动内存管理,所以在引入第三方资源库时候,很多资源库更新以后都开始使用arc进行编码,这样就导致两种代码风格不一致,有的时候可能开发者也没有注意到这些问题,反正用的时候也没有报错,就直接使 ...
- KMP算法(快速模式匹配)
详细理解看这里:http://kb.cnblogs.com/page/176818/ 或者这里:http://blog.csdn.net/yutianzuijin/article/details/11 ...
- 【状压dp】【bitset】bzoj1688 [Usaco2005 Open]Disease Manangement 疾病管理
vs(i)表示患i这种疾病的牛的集合. f(S)表示S集合的病被多少头牛患了. 枚举不在S中的疾病i,把除了i和S之外的所有病的牛集合记作St. f(S|i)=max{f(S)+((St|vs(i)) ...