更友好的 warning 消除方案

對於編譯型語言,開啟編譯器的「Treat Warnings as Errors」是非常有益的。它把 warning 當做錯誤,會中斷編譯,強制我們修復問題。在沒有開啟這個功能的時候,warning 會隨著開發不斷積累增多。當數量很多的時候,新增的 warning 不容易被發現,從而掩蓋問題。 對於一個自律的獨立開發者,這個問題是可以避免的,但多人協作的情況下,需要「Treat Warnings as Errors」這樣強制的功能來保障。

但是它有一個問題: 對於開發和調試不太友好。在編譯一些未完成的代碼的時候非常容易出現 warning,而這些 warning 沒有任何實際意義。比如調試時注釋掉了幾行代碼,因此上一行出現了一個「未使用的變數」,打斷編譯,無法向下進行。此時必須手動(遞歸的)解決掉 warning ,影響效率,非常討厭。

(如果不想讀原理,直接的工具在這裡)

在 commit 時禁止 warning

在 commit 的時候禁止有 warning 的代碼提交是一個更靈活的的選擇。開發的時候任你胡搞,只要提交的代碼完好就可以了。雖然 Xcode 沒有類似的功能直接使用,但自己實現原理也不複雜: 通過 git 的 pre-commit hook,檢查是否存在 warning,如果有,則中斷 commit 即可。

如何檢查代碼存在 warning?

Xcode 編譯的時候會把 log 保存成文件。通過添加 run script,列印 $BUILD_ROOT,可以找到當次編譯的生成文件目錄。目錄結構如下

├── Buildn│ ├── Intermediates.noindexn│ └── Products (this is $BUILD_ROOT)n├── Indexn│ ├── DataStoren│ └── UniDBn├── Logsn│ ├── Buildn│ ├── Debugn│ └── Issuesn└── TextIndexn

其中 Logs/Issues 目錄下保存了 xcode 界面中 Issues 展示的信息,也就是我們要找的 warning。

log 以 .xcactivitylog 的文件存儲,它其實就是一個 gzip 文件。解壓後的文件是一個特殊的序列化格式( SLF0 ),解析後才能正確讀取。這裡 示意了解析的方法。大致思路是每段數據先標數值,然後是類型,如果是 string 則後面接前面數值長度的內容。然後把 string 的內容過濾出來,再過濾包含 『warning:』 的行。這樣就找到了所有 warning 的描述。

我們把 warning 的數量存放在 your_project_dir/.warning_checker/last_result

添加 commit hook

git hook 是 git 自帶的功能,我們只需要在 your_project_dir/.git/hooks 添加固定名稱的腳本(可以使用任何腳本語言),git 在對應的時刻就會執行。比如 pre-commit 會在 commit 前執行,如果返回值不等於 0,則中斷 commit。中斷的時候在 stderr 中輸出原因,會被 git 展示出來(無論是命令行, 還是 GUI 程序)。

我們在 pre commit hook 中讀取上一步中的檢查結果,如果存在 warning,則列印出對應原因,返回錯誤,使 commit 中斷。實現我們在 commit 時候禁止 warning 的目的。

集成

在每次 build 的時候執行腳本。一個方式是在 Build Phases 中添加 Run Script 來執行,但它不能後台執行。雖然每次腳本並不耗費多少時間,但還是有感知。另一個方式是添加到 scheme 的 build post-action 中,可以後台執行,不影響 build & run 的流程。

另一個問題是如何添加部署 hook。hook 文件並不在 git 的管理範圍內,只在本機生效。我們可以在 warning 腳本里添加寫入 hook 的代碼,這樣 hook 會隨著腳本的執行添加到每一個使用者的倉庫。

完成的代碼見 github。除了上面所說的內容,代碼里添加了對 warning log 的解析與規則的配置功能。

逐步地清除 warning

每一個想開啟「Treat Warnings as Errors」功能的項目,都會面臨這一個問題: 如何把已有的上百個 warning 清除掉?

問題還是要手動解決,但上面的腳本可以給我們一些幫助:

  • 選擇性地禁止提交 warning。這樣可以分批次分類型地開啟禁止 warning(比如最先禁止 unsed variable),以防止不斷有新的 warning 產生。
  • 找到每個 warning 的 blame,指定給產生問題的人來處理。

warning log 中包含文件路徑、警告內容和對應的編譯器 flag,我們可以做很多事情。對於前者,只需要在腳本中定製檢查規則即可,對於「未使用的變數」,可以用 -Wunused-variable 的編譯器 flag 來過濾。對於後者,因為已知文件路徑和行數,可以通過 git 讀取到 blame。

command = ["git", "blame", "-L%s,+1" % lineNumber, filePath, "-p"] # "git blame -L22,+1 /path/to/file -p" nout = subprocess.check_output(command, cwd=fileDir ) n

Update:

如果你只想把指定的 warning 作為 error,可以直接設置編譯器參數。

使用 `-Werror=`。例如, `-Werror=unused-variable` 會把未使用的變數當做 error。而原來視作 warning 時,由 `-Wunused-variable` 來決定。把這個內容加到 build setting 的 `Other Warning Flags` 下。

如果在開啟「Treat warnings as errors」的時候,想忽略某些 warning,可以使用 `-Wno-error=`。例如在添加 `-Wno-error=unused-variable` 的時候, 「未使用的變數」warning 不會視為 error。[Reference](Using the GNU Compiler Collection (GCC): Warning Options), [all warning flags] (Diagnostic flags in Clang)

( original published on 更友好的 warning 消除方案


推薦閱讀:

TAG:Xcode | iOS开发 | warning |