Bug 為什麼不能徹底消除?
遊戲啊、操作系統、網頁啊,總是充滿了各種 bug,修復一個又出一個,有問題的改好了沒問題的又出 bug 了。難道 bug 就不能被徹底清除么?
因為每一個產品功能都可以無限的做深下去啊。
比如,一個最簡單的「評論」功能:既然可以發評論,那麼……
是不是需要改評論?刪評論?發的許可權是否要管理員設置?
那麼改的許可權呢?刪的許可權呢?是否可以引用別人的評論?評論被人引用了是否可以再改?如果可以改那麼是不是要保留修改記錄?如果管理員改了一個評論那麼作者是不是不能再改?評論是否要有數量和時間限制?評論要不要翻頁?如果要翻頁是在本頁翻還是打開新頁?如果是在移動端評論後是跳到自己評論所在頁面還是在當前頁最末加上自己的評論?
給評論點贊後是否可以取消?給評論點贊後是同步還是非同步?給評論點贊是否要限制頻率?別人惡意刷我們怎麼辦?給評論是不是還可以踩?踩了要不要顯示?會影響排位的權重嗎?收到贊多的評論如何排序?如果是樓主點贊是否權重更高?評論字數要不要控制?評論支不支持代碼?評論能不能帶圖片?帶了圖片那麼是不是能上傳?
能上傳之後是不是要刪除?評論內容是否可以搜索?評論是否支持遊客狀態?遊客狀態的評論如何刪除?評論是否要支持舉報?評論文字是否要做關鍵詞的審查?舉報後的操作行為要不要記錄?是不是要xx?是不是xx? xx?從需求到代碼的這個過程是一個不可計算問題,所以必然會出bug的。寫一個程序不出bug只有兩種可能:運氣好到爆(跟經驗無關),或者需求實在太簡單了。
這也是我們為什麼要寫unittest和automation,正因為他是個不可計算問題,所以測試程序是不可能自動產生的,還是要手寫。因此經過多年的研究,我們已經掌握了一整套,只需要能夠給對抗低工資、時間緊和無聊,就可以寫出幾乎完美的unittest和automation的方法。當然了,因為低工資、時間緊和無聊,這項工作是不會有人去做的,因此我們的軟體質量都很低。
很久以前微軟也是崇尚測試的,但是現在發現,某些軟體和操作系統質量那麼爛都那麼受歡迎,想想算了,也不搞了。這是一個好問題。
作為一個用戶,當然可以不必知道一個複雜的軟體是怎麼做出來的。
就好像我去飯店裡吃一個炒雞蛋不用去管那隻雞蛋是怎麼由母雞生出來的。
但是當有人把這個問題提出來,希望了解答案的時候,我也希望提問題的朋友,還有那些同樣懷有這類問題的朋友,能夠明白在這個題目下面作答的朋友,只是來解答問題,並不是解決部分人心中的那種「我就是要用沒有bug的軟體」的需求來的。
「把用戶當上帝」是商家用來要求自己的服務的,但是如果用戶以上帝自居,那麼這個世界沒可能和諧。
希望無論是這個問題,還是任何其他的地方,當你遇到了你不了解的事情的時候,你都應該能明白這個道理。
那麼,接下來,先說結論:
在我寫下這個答案的時候,問題是這樣的:
遊戲啊,操作系統,網頁啊。。。總是充滿了各種 bug,修復一個又出一個,有問題的改好了沒問題的又出 bug 了。難道 bug 就不能完美的解決嗎?
答案是:
不能。為什麼這麼說呢?這是因為當一個軟體產品,包括但不限於:應用、操作系統、遊戲、複雜交互的網頁等等,複雜到一定程度之後,其是否會出現bug根本不以開發者本人的意願為轉移了。無論你軟體工程多麼完美,開發規劃多麼細緻,寫代碼的水平有多麼高,當由無數個組件組合起來形成一個大型的軟體項目以後,其中可能出現不可預知的錯誤的地方的數量是非常龐大的。
你如果只是寫一個在屏幕上輸出hello world 的程序,這個可以保證做到沒有bug
但是你要做一個操作系統,做一個大型MMORPG,就實際上無可避免的會出現bug。
這麼說也許聽起來乾巴巴的像是在詭辯。我相信這時候會有用戶問:
」既然你們如果發現了bug能修復bug,為什麼不能更加細緻的測試,並且在修復全部bug之後再發布這個軟體呢?「
關於這個問題,我提議大家先看看這個:
一個測試工程師走進一家酒吧,要了一杯啤酒
一個測試工程師走進一家酒吧,要了一杯咖啡
一個測試工程師走進一家酒吧,要了0.7杯啤酒一個測試工程師走進一家酒吧,要了-1杯啤酒一個測試工程師走進一家酒吧,要了2^32杯啤酒一個測試工程師走進一家酒吧,要了一杯洗腳水一個測試工程師走進一家酒吧,要了一杯蜥蜴一個測試工程師走進一家酒吧,要了一份asdfQwer@24dg!*(@一個測試工程師走進一家酒吧,什麼也沒要一個測試工程師走進一家酒吧,又走出去又從窗戶進來又從後門出去從下水道鑽進來一個測試工程師走進一家酒吧,又走出去又進來又出去又進來又出去,最後在外面把老闆打了一頓一個測試工程師走進一家酒吧,要了一杯燙燙燙的錕斤拷
一個測試工程師走進一家酒吧,要了NaN杯Null1T測試工程師衝進一家酒吧,要了500T啤酒咖啡洗腳水野貓狼牙棒奶茶1T測試工程師把酒吧拆了一個測試工程師化裝成老闆走進一家酒吧,要了500杯啤酒並且不付錢一萬個測試工程師在酒吧門外呼嘯而過計算機領域有哪些經典的典故或笑話? - 胡颯的回答
這是一個很有趣的笑話,前提是,你看得懂……
看不懂這個笑話的人會覺得這一大段文字似乎有些——荒誕?
是的,但是對於一個大型的,擁有足夠多用戶的軟體產品來說,這個軟體可能遇到的情況,也是」荒誕「的。因為一個軟體開發者(團隊),永遠無法在測試中窮盡他們設計的軟體會被怎樣的使用,和遇到什麼樣的狀況。
複雜的比簡單的更容易出錯。這是真理。
而大型軟體項目,比我們日常能用眼睛看見的任何產品,都複雜的多。
大多數對軟體工程缺乏了解的人,可能會覺得一個軟體似乎也沒啥,不要錢,幾分鐘就下載下來了,然後玩一玩似乎也不過如此,沒什麼了不起的……
但是,很多這些很多人都看不上的不要錢的「小軟體",其複雜程度都遠遠超過我們日常生活中的絕大多數實物產品。包括傳統電視機,微波爐,空調…等等。
軟體產品是拜科技進步所賜,才會以如此低的成本走進我們的日常生活。
所以,對於一款大型軟體來說,沒有bug是也許理論上可以,但是實際上完全不可能的事情。
當然了,BUG也分大小,也有嚴重的或者不嚴重的。
比如對於微軟,在今天,大家也都習慣了不時地收到推送更新的通知了吧。系統要不斷的打補丁,就是修復其中存在的bug。但是,很多人也發現,似乎很多時候,不安裝這些補丁,似乎也沒有就不能用這個windows系統了啊。
是的,微軟能發布一款大型軟體,是已經做過了很多測試,基本上確保沒有巨大的隱患和BUG之後才會發出的。但是正如上面所說,即使強大如微軟,也無法窮盡所有測試的可能,這樣的情況要在軟體產品面市,有更多的用戶參與使用之後才會逐步的暴露出來。
如果發現問題怎麼辦?那就改嘛。
有些問題很嚴重,比如發現了一個安全漏洞,原來大家誰都不知道,也不會影響你的正常使用,但是一旦安全漏洞被曝光,就有可能有黑客專門針對利用這個漏洞搞破壞。這樣的BUG,修復的優先順序就非常高。甚至要動用媒體資源廣告告知,請求用戶即時升級系統,修復bug。
也有的bug其實並不嚴重。在巨大的海量使用樣本中被發現了,比如某種特定的操作,或者某些特別的按鍵等等前提下,有些應用會卡死啦,或者系統會崩潰啦,或者僅僅是造成一些說不清道不白的狀況的事情啦……這些小bug如果定位了,還是會修復的,但是即時不修復,也不會對用戶造成太大的影響。比如一個應用程序用著用著就突然崩潰了,進程莫名其妙的消失了……
一般來說,重新啟動一次就好了。誰知道為什麼崩潰呢?這事兒真的很難講。當然,如果經常崩潰,而且不同的用戶在不同的場合用都經常崩潰,這個軟體的開發者要小心了,再不趕緊修復bug,你的用戶真就要流失光了。這個問題沒有必要扯到什麼哥德爾定律和不可判定之類的問題上。就像你說實際製造出來的東西有不會壞的嗎?沒有。但是你能達到 AK-47 那種抗造的程度也是可以拿出來說一說的。實際的問題是軟體出問題似乎永遠比硬體出的多。
這個問題有兩方面原因。第一是投資方向。硬體設計好以後,只要還在給大家服務,就可以不改功能一直賣同一件產品。廠家只要在其中不斷發現問題修正就行。但是軟體如果不能不斷推出新功能,用戶就沒必要升級。如果軟體嚴格的不增加新功能,總是投入人力 fix bug,完全是可以做到接近沒有 bug 的。但是不行。實際上,崇尚設計的風氣發展開之後,硬體的競爭也激烈起來,小到食品用的刀子也要不斷出新設計,所以很多看似簡單的東西其實現在也經常出華而不實的 bug。有沒有不怎麼加新功能但是投資充足來修 bug 的軟體呢?也有一些,比如 NASA 用的 VxWorks,這種軟體就接近沒有 bug。
第二是用戶的感受方面。硬體其實也會出很多問題。比如汽車裡的零件都有使用壽命。而且這個使用壽命都是打了餘量的。其中有合理的推測,也有工程問題還不能用物理理論解釋,只是通過統計設計的冗餘。但是用戶傾向於把軟體想像成邏輯正確的東西,不能接受通過冗餘和統計來解決問題。而這個問題現在也有所緩解,沒有 bug 的軟體要發展成個人集群那樣的東西,沒有單點故障。當然這樣一來造價就上去了。如果你只是玩個遊戲似乎是不在意小概率的單點故障的。bug在理論上肯定是可以完全消除的,實際上能不能做到0bug,主要看產品的複雜程度了。越是複雜越會容易出bug,是人都可能會犯錯。
我是做bug管理工具的(我們的產品是bugclose),我們後台看不到用戶的bug內容,但是可以看到很多用戶隨便一個項目的bug數據就有幾千條,這些bug都是人工打字錄入的。
拿我們自己的產品來說,剛才看了一下,一年多的時候裡面提交了大概2000多條bug。
所以,如果程序不是很簡單的話,只要有程序員,可能就會有bug。無法避免,只能儘可能通過不斷迭代慢慢減少。
我今天要教大家一個魔法,會了這個魔法,念一句咒語,任何代碼都沒有bug
想知道咒語是什麼嗎?現在就告訴你。
跟我念:「這段代碼沒有任何期望行為。」
A software bug is an error, flaw, failure or fault in a computer program or system that causes it to produce an incorrect or unexpected result, or to behave in unintended ways.
當然,你接下來肯定要問我,能不能寫出符合某specification的軟體呢?能。這,比如 @Belleve 提到的Coq,Hoare Logic,等等等等,都能做。唯一的問題就是,做起來太繁瑣了,證明比實現難很多。這也是為什麼大部分軟體都沒有人做formal verification(Coq,Hoare Logic等)。至於安全性遠遠高於軟體成本的safety critical的東西有沒有人用這種辦法?當然有了!DeepSpec: The Science of Deep Specification,file system: http://css.csail.mit.edu/6.888/2015/papers/haogang.pdf ,基礎演算法:http://www.cs.ru.nl/~james/RESEARCH/types2008.pdf ,分散式系統:https://homes.cs.washington.edu/~mernst/pubs/verify-distsystem-pldi2015-abstract.html ,操作系統:http://web1.cs.columbia.edu/~junfeng/09fa-e6998/papers/sel4.pdf ,編譯器:CompCert - The CompCert C compiler
這就是答案,以下是略相關的東西,開始離題:
能不能降低FV的成本呢?可以,並且一直在降低:在這裡有兩條路,一是用軟體完全自動的證明沒bug(Model Checking, SMT,Dafny @ rise4fun from Microsoft),一是採用半自動化,使你在更高層次(tactic等)上控制證明。
這還是太高,能不能再降低FV的成本呢?如果我們犧牲一部分正確性,可以:我們可以用contract,把static analysis改成dynamic analysis,同時,我們可以做一半不做一半,一定的地方用static analysis證明沒bug,剩下的用contract,降低bug概率/範圍http://arxiv.org/abs/1506.04205
能不能降低學習FV的成本呢?可以,我這裡要舉兩個沒有人會猜到我會一起用的例子:Simply Typed Lambda Calculus, Java Card,前者對於所有程序,都會計算出答案,不會死循環,也不會不能運算下去,後者符合Applet Isolation Principle(http://www.labri.fr/perso/ly/publications/using_coq_to_verify_javacard.pdf)。類型系統就是FV最著名的例子,沒有之一。
能不能反過來用testing幫助formal verification?能!Isabelle就是這樣做的:http://isabelle.in.tum.de/dist/Isabelle2015/doc/nitpick.pdf
可以看出,這裡已經有一條儘管比較粗糙,但是pay as you go的gradual path了,我也希望並預言,隨著CS/CPU的發展,這條path會越來越無縫(adapt cost越來越低),較高層的成本也會慢慢降低,會使得被驗證沒bug的地方越來越多
bug是可以消除的,但是可能有不同的約束。。
- 特定類型的bug,比如division by zero, out-of-bounds array access(換句話說,針對部分屬性)
- 允許false alarms(不過可以通過各種refinment/調參/..減少)
- 限制語言特性使用,比如函數指針乃至動態對象。。
- 可能要加specification,乃至互動式地證明。。也就是犧牲自動化
- 。。。
例子如基於抽象解釋的 Astrée 就能消除部分bug,另外還有基於定理證明/類型/軟體模型檢測/語言運行時/硬體的..
因為 F 系統及其所有超集(強到可以描述需求的類型系統都在此內)的類型居留(以及類型推理)是不可計算的,而這等價自動化地為程序正確性寫證明。SF 有個例子,要求證明你寫的 Imp 堆棧機編譯器——
Fixpoint s_compile (e : aexp) : seq sinstr :=
match e with
| ANum n =&> [:: SPush n]
| AId id =&> [:: SLoad id]
| APlus a b =&> (s_compile a) ++ (s_compile b) ++ [:: SPlus]
| AMinus a b =&> (s_compile a) ++ (s_compile b) ++ [:: SMinus]
| AMult a b =&> (s_compile a) ++ (s_compile b) ++ [:: SMult]
end.
的解釋結果——
Fixpoint s_exec (st : state) (ins : sinstr) (stack : seq nat) : seq nat :=
match ins with
| SPush n =&> n :: stack
| SLoad idx =&> (st idx) :: stack
| SPlus =&> match stack with
| b :: a :: stack" =&> (a + b) :: stack"
| _ =&> stack
end
| SMinus =&> match stack with
| b :: a :: stack" =&> (a - b) :: stack"
| _ =&> stack
end
| SMult =&> match stack with
| b :: a :: stack" =&> (a * b) :: stack"
| _ =&> stack
end
end.
Fixpoint s_execute (st : state) (stack : seq nat) (prog : seq sinstr) : seq nat :=
match prog with
| [::] =&>
stack
| ins :: prog" =&>
s_execute st (s_exec st ins stack) prog"
end.
和他給出的 aeval 總是相同,這玩意的證明——
Lemma s_compile_correct_app : forall (st : state)
(stack1 stack2 stack3: seq nat)
(prog1 prog2 : seq sinstr),
s_execute st stack1 prog1 = stack2 -&>
s_execute st stack2 prog2 = stack3 -&>
s_execute st stack1 (prog1 ++ prog2) = stack3.
Proof.
move=&> st stack1 stack2 stack3 prog1.
elim: prog1 stack1 stack2 stack3.
by move=&> stack1 stack2 stack3 prog2; rewrite cat0s; move=&> &<- &<-.
move=&> a prog1" IHprog1" stack1 stack2 stack3.
elim: a;
by move=&> ?; apply IHprog1" with (stack2 := stack2).
Qed.
Lemma s_compile_correct_stack : forall (st : state) (stack : seq nat) (e : aexp),
s_execute st stack (s_compile e) = [:: aeval st e] ++ stack.
Proof.
move=&> st stack e.
elim: e stack;
try by [];
try move=&> e1 IHe1 e2 IHe2 st0;
apply s_compile_correct_app with (stack2 := aeval st e1 :: st0);
by [rewrite IHe1 |
apply s_compile_correct_app with (stack2 := aeval st e2 :: aeval st e1 :: st0);
[rewrite (IHe2 (aeval st e1 :: st0)) |]].
Qed.
Theorem s_compile_correct : forall (st : state) (e : aexp),
s_execute st [::] (s_compile e) = [:: aeval st e].
Proof.
move=&> st e.
apply s_compile_correct_stack.
Qed.
可不比代碼短,而且用維護證明還需要用一大堆專有記號,比維護代碼困難多了。
更別提現代程序裡面滿天飛的指針和多線程……所以用形式證明維護現代應用程序的正確性替代測試 + 修 bug 是得不償失的行為,然而,對於對正確性要求極高的場合(安全相關、基礎庫等)現在已經開始有人這麼做了。另外補充一點現在不少研究性的函數式語言中已經開始通過增強類型系統來消除常見 bug,這個 @霧雨魔理沙@邵成 懂得多,你們問他好了。軟體真的很複雜,如果你能保證100%測試,那理論上零bug是可能的,但沒有軟體真的能100%測試覆蓋的。舉例說:
if (x) {
state1 = ...;
} else {
state1 = ...;
}
if (y) {
state2 = ...;
} else {
state2 = ...;
}
這算很簡單的邏輯吧,但你要100%測試這段代碼,起碼有四個case吧,這還是最直觀的理解。實際上很可能是八個case,因為後續代碼很可能需要state1和state2之間彼此產生相互依賴或者有什麼關係。況且實際項目的代碼,一萬行都不算大項目,幾萬幾十萬行代碼很普遍,把各種庫包含進去,百萬行的項目也不少見。你就可以想像100%測試是不可能的了。即使你寫了很多測試,但測試代碼自己仍然是代碼啊,是人寫的代碼就可能疏漏出錯,因為實在很複雜,人的腦袋沒有長時間地沉澱,哪怕維護一千行代碼都不是小負荷。
另外,代碼隨時都在改變,如果決定一個運算結果有八個factor,你增加一個factor並不是增加一點複雜度,而是增加某個複雜度級數啊。即使之前八個factor你有100%測試,這一增加你的覆蓋度可能立刻下降20%。這就好像你眼前由一團亂麻,你可以花很多時間把它的脈絡理清,但增加一兩根額外的,你先前的整個脈絡就可能不成立了。
即使現在不增加任何東西了,給你足夠的時間去重新理這團亂碼,但哪個公司有這麼多時間和金錢真的去做100%測試覆蓋呢 .... 所以所有的軟體只能在穩定性達到某個值得時候就ship了,ship的東西永遠是有bug的。
太複雜了。Bug 可以被徹底消除,或者幾乎被徹底消除。但是對於大部分軟體,並不值得用很高的成本確保沒有 bug。
通過測試和足夠多的眼睛可以消除大多數 bug,但這都是人力堆出來的。對於用戶來說,只要 bug 不算太多,新功能就比穩定更吸引人。你停下來一年修 bug,別的產品就可能超過你。
對於需求非常明確的軟體,可以通過形式驗證(formal verification)在數學上證明其正確性。比如,CompCert 就是一個「被證明正確」的 C 編譯器。但可想而知,這樣的證明是極其困難、成本極高的。就上上網、玩玩遊戲,沒必要讓業界最頂尖的專家「證明」其中沒有 bug 吧?這不是bug
這是功能。矛盾的普遍性或絕對性這個問題有兩方面的意義。
其一是說,矛盾存在於一切事物的發展過程中;其二是說,每一事物的發展過程中存在著自始至終的矛盾運動。所以說,「我寫的不是代碼,而是 bug」,在一定程度上,這句話是正確的。Because when you fix one, you create two.
這個問題很有趣,不知道是來自工程師、產品、運營還是老闆
Bug肯定是能徹底消除的,而且都不是理論上能不能消除,而是肯定100%能消除,就看你用什麼角度去認識了。
僅僅針對當前線上版本的某個產品或者某個模塊來說,從產品研發團隊來角度出發:
1、未開發完的不算bug
2、體驗不好的不算bug
3、未考慮周全的也不算bug
但是從使用者、老闆等角度來說:以上1、2、3全部可以歸到bug list里。
這就很明顯了,不是bug不能徹底消除,而是你的產品是不是就這樣了。事實上當然不是,你的產品需要迭代,你的產品需要打磨,你的產品需要加減法等等等,產品在改進「Bug」自然就改不完,這樣就直接導致你們所謂的「Bug」不能徹底消除
有限的投入不可能產出完美的產品。
但是有限的投入卻可以產出可用的產品。
軟體的目標是生產和維護一個能用的產品,而不是一次性交付一個完美的產品。嘿嘿,沒有需求就沒有bug
瀉藥。在下覺得這不是個技術問題,而是個經濟問題。該考慮的是修復bug的成本和修復了bug帶來的收益。首先,軟體越龐大,功能越複雜,可能存在的bug就越多。你寫一個hello world是幾乎不可能搞出bug來的。如果一個軟體不增加新功能,後續工作就是只要一有用戶上報bug就修復掉,那麼最後做到幾乎沒bug甚至完全沒有bug是可能的。問題是,這樣的模式存在極大的浪費——本來可以加入新功能的。什麼,你說這不矛盾啊?——新功能往往也意味著新bug那麼現在問題來了——不停地加入新功能(同時修復bug),不停地修復bug,和撒手不管哪個收益更高?一般來說,不停地修復bug這個選項只有在對新功能需求極低,維護收益極高的前提下才是划算的。所以,極為重要(例如涉及國防安全),功能又相對簡單的程序才會符合題主的要求。(例如美國總統那個核彈小手提箱2333333333)
輸入的不確定性,而程序代碼的處理是基於確定性輸入的,所以不可能沒有bug。所以說要想沒有bug就需要加一些限制條件,基於確定性輸入。另外由於代碼量的增多,難免會出現考慮不到的情況。
因為你在面向未來編程
比方說我寫的程序遇到mac ios之類的玩意,bug擋也擋不住。
推薦閱讀:
※熟練使用vim是一種怎樣的體驗?
※學習一門新技術是先看文檔再實際應用還是直接看別人的應用案例學習如何使用這個技術?
※為什麼將下面這段程序代碼中的for循環加上大括弧會出現異常的結果?(不加的話結果正常輸出。)
※如何看待王垠最新更新的博文《更新》?
※計算機語言中是如何處理sin函數的?