记一次调试

这是我最近几个月来遇到的最棘手的一个问题:
* 昨天花了4个小时找出第一层次的原因
这个纠结啊,本来和老婆说好准时下班回家吃饭的,结果被这个问题拖了老久。

这是一个gradle的plugin,用来resolve公司内部的dependency的,弄完了跑测试项目的,抛一个NPE,而且NPE还不在自己的代码里面。好吧,把gradle的源代码翻出来看,如果是自己传进去的一个值是null,解决起来也还ok了。结果到里面一看,那是一个loop里面,迭代map的时候出的错 - 可以算是NPE中的最坏情况了把。仔细查看一下代码,好吧,是迭代环境变量的。那行,在自己的代码里把环境变量都打出来谁是null吧。结果全ok,看来是顺序问题,在我打出来的时候,那个null的还是设上呢。既然是环境变量被设,那就在自己的代码里搜搜看有没有可疑点吧,还真找到个地方,一print还是真是在那个地方把PATH设成了null。

照理犯罪现场找到了,解决也就三下两下的事了,于是我打算解决完,发完code review再走。结果发现那个null是被这么设上的:
env_path = plugin_ext.getCompileTimeJNI(jniPaths)

这是groovy语言,plugin_ext是一个gradle的plugin的extension,getCompileTimeJNI是定义在extension中的一个closure,我死活检查getCompileTimeJNI,他也绝对不可能返回null,如果有exception的话,也应该抛出来,而不是返回null。

仔细检查代码,理清逻辑,打印结果,还是毫无头绪,无奈已经7:30多了,还是先回去吧。

* 今天早上大概也两个小时吧,找出的根本原因
周六早上起来, 多少还惦记着这件事,再看看吧。
终于,在观察打印出来的结果时,我注意到一个细节:在打印plugin_ext.getCompileTimeJNI的时候:
第一次是closure的地址
然后调用closure:plugin_ext.getCompileTimeJNI(jniPaths)
第二次就是一个Set了

这说明这个closure的调用有点蹊跷,我已经检查过了closure的实现本身没有问题,那么问题就在这简简单单的一句closure的调用上。 这花了我很长的时间去发现并相信着其实不是一个函数调用,而是一个赋值:
plugin_ext.getCompileTimeJNI(jniPaths)
就是
plugin_ext.getCompileTimeJNI = jniPaths
我不知道为什么gradle要发明这么坑爹的语法 - 这绝对是编码质量与效率的杀手,但是不管怎样,根源问题是找到了:
env_path = plugin_ext.getCompileTimeJNI(jniPaths)
这个赋值语句永远返回null

* 暂时有了一个workaround,还没有比较official的解决方案(if there is one)
你当然可以plugin_ext.getCompileTimeJNI,call,这是这改变了原有api的调用方式,是个breaking change,而且巨丑无比。

我觉得这是设计有点问题,也在咨询gradle官方:http://forums.gradle.org/gradle/topics/call_plugin_extension_property_becomes_an_assignment

目前的workaround,还是基于gradle对extension的奇葩设计:
* 如果你的closure是定义在extension里面的,调用即赋值,挂
* 如果你的closure没在extension定义中,而是在后面使用时加上去的,调用还是调用,ok
所有workaround就是把getCompileTimeJNI移出extension的定义,在外面加。

posted @ 2014-06-28 16:10 lzprgmr 阅读(...) 评论(...) 编辑 收藏

黄将军