- 1.Bug排查基本流程
- 2.常用的一些bug排查方法
- 2.1 搜索引擎
- 2.2 print or logger大法
- 2.3 debug和工具的使用
- 2.4 二分法
- 3.记一次bug排查经历
- 3.1 注意java集合的使用问题
- 3.2 注意触发类加载的时机
- 4.总结
代码出现bug对于程序员来讲再正常不过了,即使再资深的程序员也难逃厄运。那么如何能够快速的排查和解决bug,将成为程序员的基本功。
1.Bug排查基本流程对于程序中出现的bug,我们可以按以流程进行排查:
- 在我们所遇到的绝大部分bug已经被别人修复并且分享出来了,尤其对于不熟悉的bug或者报错,可以通过Google/Stack Overflow/Github等工具进行查找,能够解决一些基本的bug。
- 对于一些不具备debug环境,或者认为debug不够高效,可以采取print 或 logger方式打印对应位置的日志信息,根据日志信息进行分析定位bug。
- 如我们项目所依赖各种环境,无法在本地启动进行调试,可以采取该方法;
- 对于多线程调试,debug方式不是很方便,往往也采取该方法。
一些常用的debug工具:
- chrome develop tools 通常用于对前端问题进行调试;
- IDE debugging 支持本地debug 、 远程debug 、 也可进行多线程调试;
- wireShark(抓包分析工具,能够帮助你快速定位到网络相关的问题);
关于远程调试工具配置可参考这篇博文:
https://blog.csdn.net/qq_37192800/article/details/80761643
大致的流程就是,把代码注释掉一半,保留另一半,运行,看哪一半会导致问题,然后把那一半再分半,再定位到1/4,接下来以此类推,1/8,1/16 一会就到大致的位置了,再分析下那个位置,如下图所示:
在最近项目中,遇到了比较棘手的bug问题,大体情况如下:
需新上线一个功能,在本地单元测试是正常运行,上线后出现了一个无法稳定复现的bug, 且在该bug里面还存在一个其他bug,由于两个bug揉到一起,排查定位难度较高,一度让人崩溃,后续冷静下来,分析定位以及请教同事最终解决,这里记录下所遇到的两个bug:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class TestSetOpera {
public static void main(String[] args) {
Map hashMap1 = new HashMap<>();
hashMap1.put(22L, "test22");
hashMap1.put(33L, "test33");
Set longSet = hashMap1.keySet();
Map hashMap2 = new HashMap<>();
hashMap2.put(33L, "test33");
hashMap2.put(44L, "test44");
longSet.addAll(hashMap2.keySet());
System.out.println(longSet);
}
}
运行报错:
代码中,使用集合时,往Set< Long >集合中直增加了Set< Long >,程序运行时,将会调用抽象类的add(),
该方法将直接抛出异常,导致:java.lang.UnsupportedOperationException。
可以将这段代码:
SetlongSet = hashMap1.keySet();
改为:
SetlongSet = new HashSet<>(hashMap1.keySet());
这样,该段代码在运行时,通过多态的方式,会调用具体实现类的addAll()方法。
3.2 注意触发类加载的时机// 放部分代码,不影响理解
public static HashMap<> CONFIG_MAP;
static {
ScheduledExecutorService servicePool = Executors.newScheduledThreadPool(1);
servicePool.scheduleAtFixedRate(SpeechscriptPool::refresh, 0, 10, TimeUnit.SECONDS);
LOG.info("start background SpeechscriptPool thread successfully.");
}
private static void refresh() {
try {
LOG.info("begin to refresh script in memory.");
Thread.currentThread().setName("refreshscript-thread");
String updateTime = DateUtils.getNSecTime(script_TTL);
// 。。。操作集合的bug
// 读取配置文件更新内容,设置到静态变量中
CONFIG_MAP = getConfig();
// 省略后续代码。。。
}
}
这段代码中,原意是想通过当加载该类时,启动一个定时器,每隔10s,该定时器会读取配置文件中的最新配置设置到静态变量中去,然后当有请求进来,会直接读取静态变量中的最新值做后续操作。程序在集群中部署后,服务重启后,一些请求进来,有时会获取到值,有时会为null,bug无法稳定复现。
后面经过一番排查,找到导致bug的原因:在服务启动时,jvm并没有加载该类,而是直到有请求进来,需要访问该类的静态方法、变量,此时才触发去加载这个类,然后定时器将配置文件中的值读取到静态变量中。又因为静态变量仅存在于jvm内存的方法区中,在分布式场景中,就会出现当请求访问到还没加载到该类到节点时会报null,访问到已经加载该类到节点则正常。
以下为java中触发类加载的时机图:
后面,通过在服务的启动的初始化方法中,访问该类,让该类在服务启动时便加载进来。
对于如何能够尽可能的减少bug总结了一些经验:
- 尽可能的清楚你要做什么,先不着急写第一行代码,多想想代码应该如何设计和组织;
- 尽可能的集中精力写代码(日常中,大家的很多时间都会被割裂的四分五裂,尽可能的的把一些有挑战性的、复杂的coding工作放在一段连续时间内,对于提升效率减少bug都大有帮助);
- 尽可能多的写注释(利己利人);
- 尽可能的掌握所用到技术的一些基本原理,有助于分析所出现bug;
- 弱类型语言尽可能的保持类型一致;



