大家好,我是 lucifer。今天給大家分享我的日常工作中常用的 debug 殺器!
二分程式碼
當我發現一個難以理解和發現的 bug 的時候,我的終極辦法就是二分程式碼。
比如先刪去檔案 a 的一半程式碼(大約),然後測試問題是否還在。
- 如果問題不在了,那麼我們就找到了出問題的程式碼。
- 如果問題還在,那麼我們繼續使用同樣的辦法,繼續刪去檔案 a 的一半程式碼(大約),然後測試問題是否還在。
- 重複上述步驟,直到找到出問題的程式碼
這個方法在我一時沒有思緒或者幫助別人定位問題的時候非常有用。由於這種做法時間複雜度大致是 logn,因此只需要短短几次我們就可以大致定位到問題程式碼。類似地,我們也可以在多個檔案中同時進行二分。
二分提交
更多的時候,我們是在兩次釋出之間發現了一個 bug。而這兩個釋出之間是有若干 commit 的,並且 commit 還不少(幾十個甚至上百個)。
我們也可以使用類似的方法。
- 先切換到兩次釋出之間的中間提交 x(即使得提交 x 相對於兩次釋出之間的距離差最小)。
實際上這個最小距離差要麼是 0, 要麼是 1
- 驗證問題是否存在。如果不存在,我們不能確定就是這個提交的問題,不妨先標記當前提交 c 為 good。如果存在,不妨標記當前提交 c 為 bad。
- 經過上面的標記,我們就可以找到最早呈現 bad 的那次提交,並且最關鍵的是複雜度為 logn,其中 n 為我們需要驗證的提交的總次數。顯然,這個工作量比逐個檢查 commit 要快很多。
不理解其中原理?稍後我們會講。
Git 中的二分查詢
git 的開發者也想到了這一點,因此提供了 bisect 命令來幫助我們做上面這個事情。
使用 bisect 進行問題查詢主要依賴於下面的三個命令:
- git bisect start
啟動一個查詢,start 後面可以加上 good 和 bad 的提交點:
git bisect start [rev bad] [rev good]
如果不加 good 和 bad 的提交點,那麼 git 將會等到我們使用 good 和 bad 命令指定對應的提交點後開始進行查詢
- git bisect good
用來標記一個提交點是正確的,後面可以加上 rev 來指定某個特定的提交點,如果不加,則預設標記當前的提交點。
- git bisect bad
用來標記一個提交點是包含問題的,如果 bad 後可以加上 rev 來指定某個特定的提交點,如果不加,則預設標記當前的提交點。
背後原理
我們來補前面的坑。為什麼經過這樣的標記,我們就可以找到第一個有問題(標記 bad)的提交?並且時間複雜度為 $O(logn)$。
正好力扣有一道原理,我們直接用它來講吧。
題目地址(278. 第一個錯誤的版本)
https://leetcode-cn.com/probl...
題目描述
你是產品經理,目前正在帶領一個團隊開發新的產品。不幸的是,你的產品的最新版本沒有通過質量檢測。由於每個版本都是基於之前的版本開發的,所以錯誤的版本之後的所有版本都是錯的。
假設你有 n 個版本 [1, 2, ..., n],你想找出導致之後所有版本出錯的第一個錯誤的版本。
你可以通過呼叫 bool isBadVersion(version) 介面來判斷版本號 version 是否在單元測試中出錯。實現一個函式來查詢第一個錯誤的版本。你應該儘量減少對呼叫 API 的次數。
示例 1:
輸入:n = 5, bad = 4
輸出:4
解釋:
呼叫 isBadVersion(3) -> false
呼叫 isBadVersion(5) -> true
呼叫 isBadVersion(4) -> true
所以,4 是第一個錯誤的版本。
示例 2:
輸入:n = 1, bad = 1
輸出:1
提示:
1 <= bad <= n <= 231 - 1
思路
可以看出,這個過程和我們上面的描述是一樣的。並且我們的目標都是找到第一個出錯的提交。
需要明確的是:
- 如果一個版本 x 是 good 的,那麼 [1, x] 之間的所有提交肯定都是 good 的,因此待檢測版本變為 [x+1,n]
- 如果一個版本 x 是 bad 的,那麼 [x, n] 之間所有的提交肯定都是 bad 的。由於我們要找到的是第一個有問題的版本,因此待檢測版本變為 [1,x-1]
因此無論我們檢測的版本是 good 還是 bad,我們都可以將待檢測的版本數量變為一半,也就是說我們可以在 $logn$ 次內找到第一個有問題的版本。
如果你看過我的二分專題,應該知道這就是我總結的最左二分。
程式碼
- 語言支援:Python3
Python3 Code:
class Solution:
def firstBadVersion(self, n):
l, r = 1, n
while l <= r:
mid = (l + r) // 2
if isBadVersion(mid):
# 收縮
r = mid - 1
else:
l = mid + 1
return l
複雜度分析
- 時間複雜度:$O(logn)$
- 空間複雜度:$O(1)$
總結
二分大法在日常工作中應用還是蠻多的,二分找 bug 是其中一個很實用的技巧。最簡單的二分找 bug 可以通過刪除檔案內容的方式進行。而如果檔案很多,就很不方便了,這個時候我們可以使用二分提交來完成。
其中的原理其實也很簡單,就是一個樸素的最左二分。如果大家對此不熟悉,強烈建議看下文章中提到的二分專題,兩萬字總結絕對讓你有所收穫。