前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >框架源码调试实战之easypoi异常解决方案精讲

框架源码调试实战之easypoi异常解决方案精讲

作者头像
Java深度编程
发布2020-09-28 15:56:02
6920
发布2020-09-28 15:56:02
举报
文章被收录于专栏:Java深度编程Java深度编程

最近有个同事遇到了个棘手的问题,easypoi导出文件出了bug,却不知道是怎么回事,无从下手,无可奈何,由于事态紧急,只能火急火急的求助于我。我问他:“开发的时候功能调通了吗?测试阶段通过了吗?” 同事均回答:“之前测试都没有问题,之前的账号数据可以导出,却唯独是这个不行。我仔细看了我写的代码,根本就不觉得有什么问题啊,不知道原因出在哪里……”

导出的错误文件如下:

正常导出的文件应该是这样的:

在详细了解情况以后我便开始了我的源码探究之路。确实,很多人在遇到此类问题的时候往往想到的就是自己的代码有问题,或者是使用框架不当,未按框架的规则来编写代码才导致出现问题,而极少会想到是自己使用的框架本身就有问题,本身就有bug。要知道框架也是人写的,是人就会犯错!更何况像easypoi是近年来才出现的新型框架,还不太成熟,潜在的bug多,出错的概率就更大些。

我帮人解决问题的同时,我习惯性的是希望帮助人学习到解决问题的能力,而不是仅仅解决这个问题。正所谓授人以鱼不如授人以渔,所以我便亲自在这位向我求助的同事面前掩饰了一番,如何去解决这个问题。

首先,我们将导出正确,和导出错误的两组参数进行收集,然后使用postMan分别进行调制,在关键代码初打上断点,如下图:

从上图可以看出,导出的关键代码在workbook.write(outputStream),于是我先执行能正常导出的debug,教同事用正常的参数去逐步熟悉整体运行的过程;然后我再执行第二遍debug,这时候传入错误导出的参数,当执行到关键代码处的时候观察两者的区别,从而判断问题所在。经过调试,我发现错误的代码再执行到单sheet时,执行代码

Sheet sheet = workbook.getSheet(param.getExportParams().getSheetName());

结果获取到了一个空对象,然后正常能导入时不时空对象,如下:

那么问题就出在这里,正是由于行对象sheet对象为null才导致了后面的报错。

这时候就应该再进入更深层次的代码,探究为何传入为何有时候能获取到sheet对象,有时候却不行。

我们点开 workbook.getSheet(param.getExportParams().getSheetName())方法,继续打断点:

打开源码方法时,常出现的需要选择进入那个实现方法,这时候很多初级程序员就很懵逼,我到底应该进去那个方法啊?有可能会一时半会摸不着头脑,只能一个试。其实这也是有技巧的,这个技巧就是追溯对象的源头。上面是使用workbook调用的,我们往上寻找,是如何得到这个对象的,代码在

//单sheet导出
 workbook = ExcelExportUtil.exportExcel(param.getExportParams(), param.getClazz() , param.getList());

那么我们再进入ExcelExportUtil.exportExcel()方法,如下:

从上可以看出workbook对象是XSSFWorkbook类型的,那么我们使用workerbook对象调方法的时候调的就必定是XSSFWorkbook下的方法,于是就知道了进入workbook.getSheet(param.getExportParams().getSheetName())方法应该选择第三个XSSFWorkbook类型的。其实除了此方式外,还可以再往前追溯入参,往往也能找到答案,本例子中的前端封装参数的时候是指定了ExcelType的类型的,如下图:

通过以上两种途径便可以知道框架的源码方法,众多实现中究竟应该调用那一个源码方法,快速定位方法,能大大节约一个个尝试的时间成本。

接着我们进入gerSheet()方法进一步调试:

我们可以看到sheet页对象的名称和传入的名称不相等,迭代器没有下一个值,于是便return null了:

而正常参数下是能够直接获取到sheet对象的:

正常参数下,判断为false,不会再次进入do while循环中

整个过程的逻辑如下:1.首先创建了迭代器;2.执行了一次do……while循序,在循环中判断迭代器是否还有下一个值,第一次的时候有下一个值于是没有返回null,而是创建了sheet对象;3.第一次循环执行完毕后,才开始判断条件(do……while循环是先执行一次循环,再判断条件),这时候入参名称和sheet的名称相同,取反后便不成立,于是返回了有值的对象,反之则再次进入了循环,这时候迭代器已经没有下一个值了,于是就返回了null。

既然知道了以上的逻辑,那么就已经定位清楚问题了,问题的根源就在于名称不相等了,为啥名称不相等了呢? 这时候我们仔细观察两个名称的值,发现sheet的名称和入参名称name相比,是取了name的前31位字符,为什么会这样子呢?

我们打开.xlsx文档,输入页码名称,发现也是最大只能输入31个字符的,如下图:

原来是因为xlsx文档限制了最大的页码名称只能输入31个字符,所以框架自动截取了前31个字符。这坑爹的框架也不说处理全面一点,留下了这个bug坑苦了广大程序员,哈哈……

既然知道了这个问题,那么如何修复这个框架的bug了? 按理来说这是框架的bug,应该改框架的源码最正确,可这样得反编译后,修改编码了再打包进去,很费时费力。而在入参时每个都做判断会增大代码量,也容易忽视这个问题。介于我已经封装了一个公用的导出组件类,那么我的思路是在公共组件进行处理。

将思路告诉向我求助的同事后,他便写了下面的代码:

 //单sheet页面设置样式
     String sheetName = param.getExportParams().getSheetName();
     if(sheetName.length() > 31) {
         sheetName = sheetName.substring(0,31);
     }
     Sheet sheet = workbook.getSheet(sheetName)

我就点评说这样写并不好,因为你只是把入参的值给改了,但是原参数param中依然保留了旧的,可能出现问题的参数,依然容易引发其它问题,这是一个变成思想的问题,代码应该这么写:

    //单sheet页面设置样式
    //生成Sheet和提示信息
    //xlsx文件的sheet页名称最大只支持31个长度,当传参sheet名称长度>31时,将会无法获取sheet对象,所以需要截取
    String sheetName = param.getExportParams().getSheetName();
    if(sheetName.length() > 31) {
         param.getExportParams().setSheetName(sheetName.substring(0,31));
    }
    Sheet sheet = workbook.getSheet(param.getExportParams().getSheetName());

上面的这种写法才是正确的,更能体现变成思想能力,从源头消除隐患。

总结:经过此次框架的bug,相信遇到的人都应该明白了,不要太过于相信框架而质疑自己的代码有错,当你找不到自己的错误的时候,就该想想是不是自己使用的框架有问题了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-09-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java深度编程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档