借助SpotBugs将程序错误扼杀在摇篮中
背景
最近一年多在一家toB业务的公司,部门主要做的是建筑行业的招投标业务信息化,希望借助软件来达到“阳光、降本、提效”的目的,我刚入职时大概30多家客户,截止现在已经超过100家,发展势头迅猛,随之而来也暴露出来一个很严重的问题:“质量差,线上反馈多。”
大伙每天疲于处理线上反馈,需求响应缓慢,交付速度跟不上市场节奏,长此下去对团队士气是一种打击也会激化研发和业务的矛盾,鉴于此我们对线上反馈进行了逐个分析、打标,最终的结论是:“大部分反馈是由于客户一些个性化需求导致逻辑冲突,太多的开关充斥在代码中有时会顾此失彼,还有一部分完全是编码层面的低级问题,空指针、跨类型的equals等”,对于需求问题由产品经理和研发经理把控,不接稀里糊涂的需求,对于低级别的编码问题由我牵头出具一些优化方案。
研发流程现状
“开发-》测试-》上线-》线上环境复测”,很简洁的一个流程吧,在当初业务量小、团队小的时候确实没什么问题,响应也快,但是现在人员规模30+的团队还是用这种简单的流程,已然是不合适了,必须增加一些手段来保证开发质量,让一些低级错误扼杀在摇篮中,首当其要的是改善现有的研发流程,增加一些必要的自检和code review,初步改善以后为:“拉取私有分支-》开发-》自测-》提交 Pull Request合流-》组内同事code review-》测试-》上线-》线上环境复测”,在初期确实是收到了一些收益,一些低级别的问题通过“人肉”确实能发现不少,但是到了大家都赶进度的时候就完全靠不住,原因你懂得。
上工具
引用我之前公司CTO的一句话:“靠人终归是靠不住的,最好靠遵循规则的机器。“
人都是有惰性的,靠管理制度来提高研发质量这种精神值得鼓励但是不提倡,找到顺手的工具才是我们的解决之道,将工具和管理制度结合起来,接下来就是我们本篇的重点SpotBugs,一个静态代码扫描工具。
静态代码扫描概念
静态源代码扫描是近年被人提及较多的软件应用安全解决方案之一。它是指在软件工程中,程序员在写好源代码后,无需经过编译器编译,而直接使用一些扫描工具对其进行扫描,找出代码当中存在的一些语义缺陷、安全漏洞的解决方案。静态扫描技术已经从90年代时候的,编码规则匹配这种由编译技术拓展过来的分析技术向程序模拟全路径执行的方向发展,由此,这种模拟执行相对的执行路径比动态执行更多,能够发现很多动态测试难以发现的缺陷。
静态代码扫描优点
这个方案的优点在于,无需进行编译、也无需去搭建运行环境,就可以对程序员所写的源代码进行扫描。可以节省大量的人力和时间成本,提高开发效率,并且能够发现很多靠人力无法发现的安全漏洞,站在黑客的角度上去审查程序员的代码,大大降低项目中的安全风险,提高软件质量。
静态代码扫描缺点
传统的静态分析,传统的静态分析都是基于语法解析或者编译器,这些方式分析代码的缺陷是以代码所匹配的规则模式(patterns)去评估代码,只要模式匹配或者相似就报出来。需要人工去分辨出其中的真假,主要存在的问题:
– False positive(误报)
– False negative(漏报)
以上内容来源于百度百科
案例
案例一 空指针
这真的是一个很低级的问题了,不见得是能力问题,稍不留神很容易出,||写成&&,少加个!之类的。
List<Map<String, Object>> resultMap = getBasedao().queryList(sql, paramMap);
if (resultMap == null && resultMap.isEmpty()) { }
修复方法1:
if (resultMap == null || resultMap.isEmpty())
修复方法2:
if (CollectionUtils.isEmpty(resultMap ))
推荐使用工具类的判断方法,将重复性的事情交给工具类来干,减少出错的可能性,而且代码的描述性更强,isEmpty,isNotEmpty等等。
案例二 返回值被忽略
String querySql = "select * from biz_table where id=xxx"; Map<String, Object> query = getBasedao().query(querySql);
if (query == null) {
querySql.replace("biz_table ","biz_table _history");
query = getBasedao().query(querySql);
}
修复方法:
接收返回值,querySql = querySql.replace
案例三 equals两个不相关的对象
这个绝对是能力问题了,“钢筋”和“混凝土”怎么能equals呢,Class都不一样。
List<Map<String, Object>> maps = getBasedao().queryList(sql, map);
if(CollectionUtils.isEmpty(maps)) {
return null;
} if(maps.get(0).equals("")) {
return null;
}
案例四 keySet方式遍历Map比entrySet方式低效
public static String getXmlDataV3(Map<String,Object> params) {
StringBuffer buf = new StringBuffer();
buf.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
buf.append("<root>");
buf.append("<request>");
for (String key:params.keySet()) {
buf.append("<"+key+">"+params.get(key)+"</"+key+">");
}
buf.append("</request>");
buf.append("</root>");
return buf.toString();
}
修复方法
直接使用entrySet遍历,减少不必要的读取
for(Map.Entry<String,Object> me : params.entrySet()){
buf.append("<"+me.getKey()+">"+me.getValue()+"</"+me.getKey()+">");
}
这些问题泄漏出去不知道会引起多少反馈呢?
案例回顾
通过这七个案例可以初步体验静态代码扫描的强大之处,在发布之前帮助发现一些简单但是出错频率很高的问题,将错误扼杀在摇篮中,错误带到生产环境的代价是巨大的,本来是一个2分钟解决的空指针问题,最后可能会发酵成客户的投诉函,整个链条上的同事都要一起填坑。

SpotBugs安装
直接在idea插件市场安装,安装完以后需要重启。
SpotBugs介绍
https://spotbugs.readthedocs.io/en/latest/introduction.html
SpotBugs使用
1.扫描整个项目
1.1 选中某个模块,比如demo-web
1.2 右键SpotBugs→Analyze Module Files Not including Test Sources(这里只是举例,具体使用哪种扫描策略根据自己的实际情况),如下图所示:
1.3 等待
1.4 查看扫描结果
1.5 查看明细
重点关注Correctness(正确性),逻辑错误,空指针之类的。
2.扫描某个类
对于新写的代码,强烈建议大家扫描,可以发现不少问题
2.1 选中具体的类
2.2 右键SpotBugs→Analyze Selected File
接下来的步骤和之前没有区别,就不啰嗦了。
3.按等级查看bug
SpotBugs将bug按不同维度进行了划分,比如“正确性、性能、坏味道”这是bug类型的维度,还有一个维度是bug等级,SpotBugs将bug分成了四个等级,分别是:
scariest 最可怕
scary 可怕
troubling 令人不安
of concern 令人担忧
点击扫描结果左下角的Group by Bug Rank,就会按等级呈现,真的是一个很酷的功能,工具已经帮我们分组,而且以颜色区分,看到的scariest和scary的你就立马点进去修改一波吧

SpotBugs和Pull Request结合
前面我们对最初的研发流程做了一些优化,增加了自测、Pull Request、Code Review等步骤,短期内确实收到了一些收益,但是时间一长人的惰性再加上时间紧的时候这些流程就开始应付了,鉴于此我们引入工具帮我们发现一些低级别的问题,嵌入到Create Pull Request流程中,合并代码必须提供SpotBugs的扫描结果图,不允许包含scariest和scary级别的问题,这样我们就把工具和管理手段结合起来保障研发质量。
推荐阅读
FindBugs - Wikipedia
https://github.com/spotbugs/spotbugs
https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html
写在最后
工具用的好,下班没烦恼。

借助SpotBugs将程序错误扼杀在摇篮中的更多相关文章
- 记录一次网站漏洞修复过程(二):第一轮处理(IIS目录枚举、应用程序错误)
解决IIS目录枚举 当前的IIS版本为7.5 [IIS] => [请求筛选] => [URL]中添加 [拒绝序列] 符号 ~ 应用程序错误 在Global.asax 中添加异常处理代 ...
- RedisDesktopManager 打开报0xc000007b程序错误
RedisDesktopManager 是一个管理redis的工具,很好用,我的电脑可以安装0.8.3版的,最新版到0.9.4了,其中经典版本是0.8.8,可惜0.8.3版之后,我的电脑安装软件后,打 ...
- java程序错误类型及异常处理
一.程序的错误类型 在程序设计中,无论规模是大是小,错误总是难免的.程序的设计很少有能够一次完成,没有错误的(不是指HelloWorld这样的程序,而是要实现一定的功能,具备一定实用价值的程序),在编 ...
- ArcGIS Desktop 遇到严重的应用程序错误
由于项目初验,忙了几个月(感觉忙得并不值),好久都没更新博客了. 一.问题 在关闭ArcMap时,ArcGIS Desktop 遇到严重的应用程序错误.环境是Windows 10,新装的系统.以前出现 ...
- .Net Core 控制台程序错误:Can not find runtime target for framework '.NETCoreApp,Version=v1.0' compatible with one of the target runtimes: 'win10-x64, win81-x64, win8-x64, win7-x64'.
.Net Core 控制台程序错误:Can not find runtime target for framework '.NETCoreApp,Version=v1.0' compatible wi ...
- (原创)解决远程桌面连接远程应用时,出现 '应用程序错误: '0x7c931780'指令引用的 '0x89abcdef' 内存。该内存不能为 'read'"
公司的部分应用为cs结构,没有web版的,这些应用的外部访问基本都是通过使用windows server 2008 r2的远程桌面服务来实现的. 个人感觉微软远程桌面服务问题很多,今天有同事使用Rem ...
- C#怎样处理xml文件的大于号和小于号等常用符号(xml符号引发的程序错误)
在程序中由xml配置而成的sql语句要转换为C#支持的sql语句 <settings> <select> a.*</select> <from> (se ...
- ORA-00257: 归档程序错误。在释放之前仅限于内部连接
今天发现oracle数据库连不上,报错:ORA-00257: 归档程序错误.在释放之前仅限于内部连接 马上联想到可能是空间满了,一看磁盘目录,果然. 解决方法如下: 1:查看归档日志目录. 登陆账号后 ...
- .net 禁止远程查看应用程序错误的详细信息,服务器上出现应用程序错误
打开页面时出现以下错误 "/"应用程序中的服务器错误. 运行时错误 说明: 服务器上出现应用程序错误.此应用程序的当前自定义错误设置禁止远程查看应用程序错误的详细信息(出于安全 ...
随机推荐
- Ubuntu系统报错The system is running in low-graphics mode
Ubuntu系统报错:The system is running in low-graphics mode 我遇到过两次这种请况,这次解决了.很nice! 在csdn上搜到的大部分操作是: 鼠标进入系 ...
- Wireshark查找与标记数据包
查找数据包 按Ctrl-F. 查找数据包提供了4个选项: 显示过滤器(Display filter):该选项可以让你通过输入表达式进行筛选,并只找出那些满足该表达式的数据包.如:not ip, ip. ...
- js知识梳理6:关于函数的要点梳理(2)(作用域链和闭包)
写在前面 注:这个系列是本人对js知识的一些梳理,其中不少内容来自书籍:Javascript高级程序设计第三版和JavaScript权威指南第六版,感谢它们的作者和译者.有发现什么问题的,欢迎留言指出 ...
- LC-76
给你一个字符串 s .一个字符串 t .返回 s 中涵盖 t 所有字符的最小子串.如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" . 注意: 对于 t 中重复字符 ...
- 一些有用的工具,iftop,iotop,htop,glances
一些有用的工具: yum install glances -y资源监控工具GLANCESglances 可以为 Unix 和 Linux 性能专家提供监视和分析性能数据的功能,其中包括:CPU 使用率 ...
- asp.net core启动源码以及监听,到处理请求响应的过程
摘要 asp.net core发布至今已经将近6年了,很多人对于这一块还是有些陌生,或者说没接触过:接触过的,对于asp.net core整个启动过程,监听过程,以及请求过程,响应过程也是一知半解,可 ...
- Codeforces Round #706 (Div. 2)B. Max and Mex __ 思维, 模拟
传送门 https://codeforces.com/contest/1496/problem/B 题目 Example input 5 4 1 0 1 3 4 3 1 0 1 4 3 0 0 1 4 ...
- 今天学弟问我pip如何永久换源?
pip如何永久换源 临时使用 我们在使用Python开发的时候,经常要下载第三方模块,最常用的方式就是直接pip install 模块名,但是默认是使用国外的源,从pypi仓库中查找目标模块,不管是网 ...
- 使用Typora + 阿里云OSS + PicGo 打造自己的图床
使用Typora + 阿里云OSS + PicGo 打造自己的图床 为什么要打造图床? 让笔记远走高飞 试问以下场景: 我们要把 markdown 笔记放到某博客上,直接进行复制即可.但因你的图片存储 ...
- git提交代码到GitHub操作-简易版(后续完善)
一.git上传代码到GitHub 1.远程仓库GitHub创建好一个新仓库注意仓库名 2.本地建一个目录写代码,目录名与仓库命名一致 3.在目录下右键 git Bash here 打开git终端命令行 ...