記一次除錯

lzprgmr發表於2014-06-28

這是我最近幾個月來遇到的最棘手的一個問題:
* 昨天花了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的定義,在外面加。

相關文章