天天看點

《Effective Debugging:軟體和系統調試的66個有效方法》一第7條:試着用多種工具建構軟體,并将其放在不同的環境下執行

本節書摘來自華章出版社《effective debugging:軟體和系統調試的66個有效方法》一書中的第1章,第1.7節,作[希]迪歐米迪斯·斯賓奈裡斯(diomidis spinellis),更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視

有時我們可以通過改變環境來鎖定一些難以捕獲的bug。例如,我們可以用另外一款編譯器來建構這個軟體,也可以切換到其他的運作時解釋器、虛拟機、中間件、作業系統或cpu架構上。由于那些環境可能會更加嚴格地檢查輸入資料,或能通過其結構來凸現程式中的錯誤(參見第17條),是以可以幫助我們發現原來很難找到的一些bug。如果程式不夠穩定、總是發生無法重制的崩潰問題,或移植起來不太順利,那就應該試着把它放在另外一種環境下進行測試,這使得我們能夠使用更為先進的調試工具,例如,一款圖形界面很漂亮的調試器或dtrace等(參見第58條)。

在其他作業系統裡面編譯或運作軟體,可以把我們對api的用法所做的錯誤假設暴露出來。例如,由于某些c及c++頭檔案聲明了很多不一定會用到的實體,是以我們總是認為隻要把這些頭檔案包含進來就夠了,這可能導緻我們忘記将另外一個必需的頭檔案包含進來,進而使客戶遇到移植方面的問題。此外,某些api的實作方式在各種作業系統之間會有很大的差別,例如,solaris、freebsd及gnu/linux系統會采用不同的方式來實作c程式庫,而桌面版與移動版的windows api,目前也是基于不同的代碼庫來實作的。請注意,那些在底層采用c程式庫和api的解釋型語言也會受到影響,如javascript、lua、perl、python或ruby等。

對于c和c++這樣較為接近硬體的語言來說,底層的處理器架構會對程式的行為造成影響。過去幾十年間,intel x86架構與arm架構分别成為桌面市場與移動市場的主流,于是,那些在位元組序上面(sparc、powerpc)或是在對空指針的解引用上面(vax)偏離主流的架構,就變得不那麼流行了。然而,x86架構與arm架構在處理未對齊的記憶體通路及記憶體布局時依然有所差別。例如,如果在奇數記憶體位址處通路兩位元組的值,那麼就有可能令某些arm架構的cpu出錯,或令cpu表現出非原子的(non-atomic)行為。在其他架構上面進行未對齊的記憶體通路,可能會嚴重影響程式的性能。此外,結構體的大小,以及其中各成員距離結構體開始處的偏移量,在這兩種架構中也是有差別的,對于老版本的編譯器來說,這種差別尤其突出。還有一個更為重要的問題在于:當你把代碼從32位架構移植到64位架構,或是從一個作業系統移植到另一個作業系統的時候,長整數(long)及指針值等原始類型所占據的大小可能也會有所改變。下面這個程式可以顯示出五種原始類型所占據的位元組數。

《Effective Debugging:軟體和系統調試的66個有效方法》一第7條:試着用多種工具建構軟體,并将其放在不同的環境下執行
《Effective Debugging:軟體和系統調試的66個有效方法》一第7條:試着用多種工具建構軟體,并将其放在不同的環境下執行

在較為典型的幾種環境之下,上述程式所給出的結果分别是:

《Effective Debugging:軟體和系統調試的66個有效方法》一第7條:試着用多種工具建構軟體,并将其放在不同的環境下執行

由此可見,把軟體放在其他架構或作業系統中運作,可以幫助我們對其進行調試,并檢測出移植方面的問題。

對于移動平台來說,各種裝置之間的差距要比桌面平台更大,它們不僅在作業系統的版本方面有所不同(大多數手機與平闆廠商都會對原始的android系統進行修改,并把改版後的系統安裝在裝置上),而且在硬體方面也有着相當大的差別,如螢幕分辨率、操作界面、記憶體及處理器等。這使得我們在開發移動軟體時,更有必要将其放在各種不同的裝置上進行調試,為此,很多移動app的開發團隊都有許多種移動裝置。

在其他執行環境中調試代碼,主要有三種方式:

1.?在工作站安裝虛拟機軟體,并且用虛拟機來運作各種不同的作業系統。這種辦法還有一個好處,就是可以保留各種執行環境的原始鏡像,我們對虛拟機進行配置時,是在這個原始鏡像的基礎之上進行修改的,如果有必要,我們可以把虛拟機恢複到原始狀态。

2.?使用小型的廉價計算機。如果你主要面對的是x86架構,但同時又想嘗試arm cpu,那麼最簡單的辦法就是使用raspberry pi(樹莓派)。這是一種基于arm的微型裝置,能夠運作很多種流行的作業系統。它可以連接配接網線,或通過wi-fi上網。我們可以在這種裝置上嘗試gnu/linux的開發環境,這對于主要在windows或os x系統上調試代碼的人來說是很有幫助的。此外,如果你平常使用的是windows系統,那麼可以買一台mac mini,這樣就能夠輕松地切換到os x開發環境了。

3.?租用基于雲端的主機,并在上面運作你想使用的作業系統。

要想用各種不同的編譯器與運作時環境來調試代碼,我們固然可以安裝新的作業系統或使用新的裝置,但除此之外還有一種辦法,那就是設法使自己這台工作計算機上面的開發環境變得更加豐富。這樣,我們就可以看到由其他開發工具所給出的錯誤與警告資訊,并且可以用那些工具對代碼中的某些方面進行更為嚴格的檢查,看看它們有沒有遵從相關的規範。與使用靜态分析工具(參見第51條)所帶來的好處類似,同時使用多種編譯器,可以使我們發現更多的問題,這其中既包括移植方面的問題,也包括邏輯方面的問題。例如,如果某一款編譯器對代碼檢查得比較寬松,而另外一款編譯器檢查得比較嚴格,那麼就可以揭示出移植方面的問題;如果某一款編譯器不對某個邏輯問題發出警告,而另外一款編譯器對此發出了警告,那麼就可以揭示出邏輯方面的問題。隻要是符合文法的代碼,編譯器基本上都可以把它編譯成可執行的檔案,但是它們有時不太能夠檢查出對程式設計語言的誤用,例如,即便代碼所引入的頭檔案裡面聲明了一些沒有公開釋出的元素,有些編譯器也依然會直接編譯通過,而不會指出相關的問題,為此,我們應該多用幾種編譯器進行編譯,以便把這方面的問題暴露出來。為了使開發環境變得更加豐富,我們在調試軟體的過程中,不僅要使用主流的工具,而且還要同時安裝并使用一些替代産品。下面給出幾條建議:

開發.net framework程式的時候,不僅要使用microsoft的工具與環境,而且還要同時使用mono。

用ada、c、c++、objective c或其他相關的語言來編寫程式時,要同時使用llvm與gcc這兩種編譯器。

開發java程式時,要同時使用openjdk(或由oracle公司基于相同的代碼庫所提供的jdk)及gnu classpath這兩種開發包。此外也要注意把程式放在一種以上的java運作時環境裡面執行。

開發ruby程式時,不僅要使用作為參考實作的cruby來進行開發,而且還要嘗試其他vm,如jruby、rubinius及mruby。

還有一種更為大膽的做法,那就是把程式裡面的一部分代碼改用另外一種語言來重新實作。如果你要調試的是個比較麻煩的算法,那麼這種做法就很有用處。最為典型的情況是:你起初用了一種較為低級的語言來實作這個算法,然後發現這種實作方式無法運作,于是,你考慮采用python、r、ruby、haskell或unix shell等更為進階的語言來重新實作它。在實作過程中,你應該使用這些語言所提供的進階特性,例如,可以對集合、管道與過濾器進行操作,并使用高階函數(higher-order function)等,這樣能夠幫你實作出一個可以正常運作的算法。當你迅速查明算法的設計問題并将實作中的錯誤加以修複之後,如果覺得這種新的實作方式在性能上無法滿足要求,那麼可以回過頭去,用原來的語言或某種較為接近cpu的語言,把算法再重新實作一遍,并采用各種對比式的技術進行調試(參見第5條),使其能夠正常運作。

要點

用多種編譯工具來建構軟體,并将其放在各種平台中執行,可以給調試工作提供很多有價值的思路。

如果遇到了一個很難調試的算法,那麼可以考慮改用進階語言将其重新實作一遍。