應該如何理解 Erlang 的「任其崩潰」思想?


「就讓它崩潰」(Let it Crash)這個思想很好,不過這個思想和Erlang沒有什麼直接關係,這個思想適用於任何一種語言、任何一種平台、任何一種服務。

很多年前,我第一次聽到這個觀念,是在微軟去上Jeff Richter講C#的課,Jeff Richter可是微軟技術教育的老前輩了,他在課上就說:別去捕獲你不知道該怎麼處理的exception,由它去吧。

我一愣,問道:不去捕獲exception,那我們的程序就崩了呀。

Jeff說:讓他崩,因為crash is awesome!!!

我呆了,勉強同意他的觀點,倒不是因為我被說服了,只是因為他是一個老同志老前輩。

幾個月後,我處理了一個線上bug,狠狠被刺痛了一回,徹底改變了我的觀點。

那一整子,正好我輪上on-call,產品環境總是爆出的一個問題, .net服務進程跑著跑著就死了,可是又搞不清楚什麼原因,每次只能重啟伺服器,然後去分析dump文件,分析又分析不出什麼所以然來,就這麼折騰了一個星期,是不是就有on-call電話打過來要處理,真是苦不堪言。

最後,是一個晚上我突然靈光一下,心想……(此處省略10000字),我當時怎麼分析出來的不重要,重要的是最後我發現問題的根源,是因為前人寫的code太銼了,很多exception被拋出來之後,都被catch住,然後打了一個log,然後繼續運行,這就是邪惡的根源

本來,當發生這些exception的時候,可以直接crash的,這樣工程師只要檢查crash時間之前的log,就能夠很容易發現問題;可是,前人寫得代碼卻生吞了這些exception,然後裝作沒事人一樣繼續運行,程序的狀態已經不正常了,卻依然在苟且運行,這樣的不正常狀態越積越多,終有撐不住死掉的時候,但是,那已經是幾周之後了,工程師怎麼可能會把表現的問題和幾周前的log關聯起來!

所以說,吞掉exception,不讓該crash的情況crash,害人害己,我深深咒罵這麼寫code的前人。

很多初級選手選擇吞掉exception堅持不crash,是為了讓服務「持續穩定運行」,為了讓服務「具有高可用性」,錯!錯得厲害!要讓服務穩定而高可用,靠的可不是一台伺服器,應該用多服務的方式來應對,即使在產品環境下,出了不能處理的exception,就應該由它去,不該你處理的異常就別去處理,讓調用棧上流的去處理,如果調用棧上層也沒有人處理,那就崩潰吧,暴露問題總比隱藏問題要好。

回想Jeff Richter所說的:當exception發生的時候,表示不可預料的事情發生了,每個函數只應該處理它能夠處理的exception,如果不能處理,就放它過去,交由上面的(人)去處理,處理不了就讓它崩潰。

Let it Crash!


速錯的WiKi解釋:Fail-fast - Wikipedia

網上查到的Joe Armstrong的話:「Who first thought of FailFast, I have found a reference in Jim Grays (1985) paper "Why do computers stop and what can be done about it, is there an earlier reference? 」,這段話可以在這個頁面找到:http://wiki.c2.com/?FailFast

相關paper可以在這裡找到:http://www.hpl.hp.com/techreports/tandem/TR-85.7.pdf

paper中第一處關於fail-fast的描述:「In addition each
module is constructed to be fail-fast -- the module either functions
properly or stops [Schlichting].」

從paper以及wiki中的描述,我覺得可以總結如下:一個踐行速錯原則的系統應該是良好模塊化的,每個模塊只在正常提供功能以及停止運作之間二選一。

我最初是在用Erlang做項目時體會到了速錯,後來在用Go開發項目時也實踐了速錯。

從工程的角度我更傾向於可描述的編程規範而不是設計哲學,所以我一直嘗試用工程的語言來描述速錯原則。

我最近的總結是:只處理調用者需要感知到並加以處理的錯誤,其餘都是異常。

舉個例子:如果我們所開發的項目使用的是胖客戶端模式,客戶端緩存了用戶餘額以及商品價格。我們有一個業務介面是消耗用戶餘額購買商品。在這個模式下,正常的客戶端是不會在餘額不足時發起業務介面請求的。所以對於服務端業務介面的開發者來說,不需要處理以及返回餘額不足的錯誤給客戶端,當餘額不足時只管讓客戶端斷線。

再舉同情況的反例:如果我們開發的項目使用的是瘦客戶端模式,客戶端發起請求前並不知道餘額以及商品價格,這時候服務端就需要返回餘額不足的業務錯誤給客戶端,讓客戶端進一步提示用戶。

再舉個例子:假設我們開發的項目是為每個用戶維護一個文件用來保存用戶的餘額,這個文件是在用戶註冊時創建的。前面例子所說的業務介面在這種設計下,就不應該處理文件讀寫失敗或者文件不存在沒許可權等各種錯誤,就應該讓服務拒絕為有問題的用戶服務,拋出異常並記錄日誌。因為此時已經不可能提供正確服務。如果勉強提供了服務,很可能就會導致更多問題,甚至污染數據。

希望以上內容對大家有參考作用。


在超算上寫並行程序,所以不算程序員。

有次跑之前測試好了的程序,當時規模比較大、結果要的急,之前又測試過就有點大意了,沒有讓程序崩掉,結果程序卡住,耗掉了全年全組10%的計算資源。

由於科學計算要求的是正確性,任何異常原則上講是必須退出查錯的。

補充:我現在是反面教程…… 前兒還被拉出來教育研究生。因此我現在對接盤異常等自動化非常謹慎。


想像你在寫一個銀行轉賬系統。發生異常後,你有兩種處理策略。

1.直接崩潰掉。這樣最差的情況就是客戶投訴你系統不穩定。你把bug改好後,再上線就好了。

2.把異常catch住,帶病運行。這種情況下系統已經處於不穩定狀態,一旦把客戶資金轉錯了,就要賠償客戶真金白銀。萬一轉錯金額大到公司無法承受,公司就只能破產了。這就是所謂的一行代碼搞垮一家公司。

當然也有很多情況並沒有上面例子那麼緊要,但仍然建議直接崩潰。這樣就可以在開發測試階段儘快的暴露問題,從而提高上線時的質量。


其實很多問題的查證,性價比真的不高,說到頭還不如讓程序重啟一次呢。

微信高大上吧?前幾年跟裡面的一個朋友聊天,說是有些內存泄露問題實在不想查,第二查這些問題影響開發效率,於是就定義了一個閾值,內存到了這個點就自動不接收新的鏈接了,處理完畢當前的請求之後自己exit,然後等監督的程序重新拉起來。這裡的核心點在於外面有一個接入層,檢測到這個服務處於這種不再接收新請求的狀態後,會自己轉發請求到別的服務上。這樣其實對服務情況沒什麼影響。(如果描述有誤請補充)

我之前在的另外一個業務量也挺大的組也是類似的處理。外面一個接入層負責轉發請求到業務伺服器上,同樣會檢測請求有沒有正常處理返回,沒有的話轉發一份給別的業務服務去處理。同樣也是因為業務服務上有些實在難查的問題,還不如就讓它重啟了。

所以,「let it crash」是個挺實在的處理哲學。做為單體的服務固然是可用性降低,但是由於有外部的其他組件來協助,整體服務的可用性並沒有太大影響。

Erlang的可貴之處在於,把這一套形成了方法論,並且有組件去幫你落實這個做法。


鼓勵崩潰,是一種編程哲學。如果你有過這種經驗,很可能會對這種哲學表示讚許——程序總規模幾十萬行,自己負責維護的模塊中有幾萬行C代碼,要與其餘幾人維護的其他模塊相互通訊、聯合行動。糟糕的問題出現了:不知何時起,程序變得不穩定了,系統的數據段內某些隨機地址的內容被無緣無故的修改了(野指針,你對自己說)。但異常發生時未必能立即表現出來,當問題表現出來時,已經無從查找原因了... 這是程序員的噩夢。

在這種時候,你可能會有一個夢想——問題一出現,就能立刻停下來多好;這樣就可以了解到底是哪段缺德的代碼,造成了這一噩夢。

這就是「鼓勵崩潰」。

為什麼erlang可以鼓勵崩潰,而其他語言沒有這麼做?這是由erlang與OTP的特點決定的。「輕量級進程」,"supervisor行為模式",這些特性造就了「鼓勵崩潰」思想能夠在erlang系統中成為現實。

在erlang系統中,當崩潰發生時,受影響的是承擔簡單任務的當前「輕量級進程」,本來它已經遇到問題,無法正確返回預期的結果了,所以崩潰就崩潰好了;而且崩潰傳導到一定程度(通常是到上級或上上級 supervisor)就會被控制住,而且能夠自動修復崩塌部分。只要監控模型設計的合理,系統可以承受崩潰而不死。

承受崩潰之後,還得到了一筆財富:崩潰的過程,都有詳細記錄。按照這個記錄找問題應該輕鬆多了,而且修復之後,還能夠在線升級,這個很體貼。


「就讓它崩潰」什麼時候被erlang搶注了?這不早就是「有正規開發經驗」的程序員的共識了嗎?

「就讓它崩潰「(let it crash) 是 KISS(Keep It Simple, Stupid)原則在異常處理基本原則方面的直接推論。

就讓它崩潰並不是草率,更不是不處理異常;它的意思是,處理異常必須極端慎重:除非極其確定異常出現的根本原因、且處理後能100%保證程序處於正常狀態,否則——就讓它崩潰。

舉個例子。假如你寫了一個函數供其他模塊調用,然後在參數檢查中,發現外部傳入的一個數據超出了正常範圍,應該怎麼辦?

很多系統喜歡搞個設計,讓你返回個ARG_ERROR或者拋個ARG_ERROR的異常;然後調用者識別返回值或者捕獲異常之後,丟棄這次請求或者搞一些其他處理然後繼續……

——這就叫「防禦性編碼」,簡稱「作死」。

可能很多人會感到奇怪:不就應該這樣做嗎?為什麼叫它「作死」呢?

因為你並不知道為什麼這個數據會超出正常範圍:或許只是用戶輸入錯誤;但也有可能是某個模塊寫溢出弄髒了數據、或者多個線程使用同一個資源時沒有處理好鎖;甚至,是因為八竿子打不著的另一個模塊覆蓋了自己的返回地址、導致程序莫名跳轉到了你這個邏輯線上……

一旦因為後幾個原因造成了數據異常,那麼你後續執行的每個邏輯都是在玩火——你可能記下錯誤的信息、衝掉其他用戶的數據、導致文件系統損壞;甚至是……讓程序走進莫名流程、出現無法解釋的偶發故障:種種奇葩,不可盡數。

只有一種情況,你才應該提示和恢復「ARG_ERROR」異常,那就是在處理用戶界面(或其它外部輸入)的代碼里:只有在這裡,你才可能清楚的知道,唯一導致ARG_ERROR的可能就是用戶輸入錯誤或(惡意或非惡意的)胡亂輸入隨機信息。

注意我加粗了「可能」二字。因為「唯一導致ARG_ERROR的可能」並不是「天然正確」,你需要很多很多的測試才能保證這一點。

除此之外的幾乎所有情況下,含糊的提示arg_error/拋arg_error異常,都是垃圾代碼的標誌性特徵。

————————————————

我猜很多人會很不服氣——其實我不是猜的,而是早年就被很多人這樣質疑過甚至罵過。

他們會說,如果這麼搞,你的程序豈不是動不動就要崩潰了?

對初學者/不負責任的、低水平的工程師,的確是「程序動不動就崩潰」;但這些崩潰正是他們提高水平、以及提高程序質量的契機。

很容易理解,實質上,「就讓它崩潰」並不是「不處理異常」;恰恰相反,它等於如下兩個要求:

1、必須慎重對待每一個異常情況,然後只允許處理自己完全明白來龍去脈、且能保證程序狀態的異常/錯誤情況;搞不懂的「就讓他崩潰」,不準打腫臉充胖子

2、必須深入學習、提高自己的知識水平,否則就難以明白異常/錯誤的來龍去脈,繼而就只能寫經常崩潰的程序。

和「防錯型代碼」的「報個error繼續,盡量別讓程序崩潰」態度相比,高下立見。

嗯,按照以往的經驗,有人會繼續追問甚至譏諷、謾罵:

——你太幼稚、太理想化了!

——現實中不可能這樣!

——你沒實際寫過程序吧!

——你知道某某重要工程(經常拿登月什麼的上綱上線)崩潰了是什麼後果嗎!

回答恰恰是:問出這種問題的人才太年輕、太天真、太不知天高地厚了。

你真的以為,給一個不明原因的異常數據報個ERROR甚至WARNING,錯誤就真的消失不見了嗎?

你這樣糊弄,領導面前不炸、測試/實驗/實用一萬次不炸,它就永遠不會炸了嗎?

是的,哪怕最頂級的專家,也會遇到複雜的、找不到原因的疑難雜症;但疑難雜症並不是只會搞搞面子工程的「防禦性編碼」所能解決的——業界自有正規解決方案

比如,我們可以監控進程狀態,必要時甚至可以主動殺死異常進程(然後快速拉起一個新的服務進程)——比如嵌入式工程師非常熟悉的「看門狗「,就是硬體內置支持的這類機制。

更為重量級的,還可以利用進程池之類機制,把「工作進程替換」的效率優化到極致;同時藉助消息隊列之類機制,確保事務級的可靠性——這才可能做到真正的7x24小時可靠性。

——————————————————————

走過這樣一個輪迴之後,我們再回頭比較「防禦性編碼」和「就讓它崩潰」的好壞,就很容易看出兩者的境界差異、並能對KISS(Keep It Simple, Stupid)原則能有一點點感性認識了:

1、「防禦性編碼」是「不知道敵人來自何方,所以我先多搞幾條防線」;實質上,在編程領域,這些防線僅僅起到了「減少了崩潰次數」的作用,卻導致錯誤被隱藏、被擴散,最終錯的撲朔迷離——而這些撲朔迷離的錯誤進一步增加了「防禦」的需求。

不僅如此。「防線」本身也還可能出漏洞,這些漏洞增加了出錯的幾率……於是不光「防出了大面積潰瘍」,而且防線本身也在製造潰瘍:現在你不光要寫容錯代碼了,你還得寫容錯代碼的容錯代碼(的容錯代碼……)。

2、「就讓它崩潰」則「不見兔子不撒鷹」;於是或者徹底抓到錯誤的發生點、搞明白錯誤出現的原因、然後通過正確的代碼(而不是修補bug的補丁)直接把錯誤徹底剔除;或者老老實實承認「這玩意兒我也不知道哪出錯了」、然後在高層次另外設計機制簡單粗暴的安全解決問題。

——寫代碼有兩種方式,一是複雜到沒有明顯的錯誤,二是簡單到明顯沒有錯誤。你選哪種?


沒有痛過的人不知道這種哲學厲害之處。

我現在寫代碼,特別是c++,滿腦子就是let it crash,不然不知道哪個犄角旮旯里越個界拋個錯,神仙都救不回來。

整個服務不能掛?沒關係,架構上使用多進程+共享內存/memcache等手段即可

只要監控到位重啟及時備用到位,let it crash是墜吼的……


不熟悉 erlang,不過 joyent 這篇講 node.js 錯誤處理的文章挺有 let it crash 精髓: 《Joyent | Error Handling》

首先文章定義了 Operational Errors 和 Programmer Errors:

  • Operational Error:表示一個正確的程序在運行時報告的錯誤;這些錯誤不是程序本身的 bug,而可能是操作系統的報錯、網路錯誤、用戶輸入不正確、超時、500、內存不足等;
  • Programmer Error:一般屬於程序中的 bug,比如錯誤的訪問 undefined 對象、錯誤的類型傳遞;

有時兩者會出於同一個 root cause 而一體兩面地出現:如果一個服務端出現 bug 導致崩潰,客戶端收到 500 將視為 Operational Error;如果對 Operational Error 的處理不正確,這也屬於 Programmer Errror。

兩者的重要差異在於:Operational Error 是一個正確的程序必須要處理的;而 Programmer Error 屬於 bug,程序本身並不能處理。

那麼遇到 Programmer Error 應當怎麼辦呢?這就引出了 Let it crash 策略。

有些 web 框架在請求中遇到 Programmer Error 的異常之後,會吞掉異常繼續響應請求,這很常見,但是異常之後可能已經有奇怪的地方變得不一樣了:

  • 部分公共狀態可能被污染為 null、undefined;
  • 資料庫連接可能泄露,甚至可能留著一個未關閉的事務;
  • socket 可能沒有正確關閉;
  • 內存可能泄露;

這些錯誤往往有累加效應,到最終展露癥狀時就很難找 root cause 了。比如,當你資料庫被日積月累的連接泄露連爆的時候,引入這個連接泄露的變更在哪個 commit?

文章認為從 Programmer Error 恢復的最佳方法便是立即 crash,配合一個 supervisor 之類的 restarter,出現問題時立即 crash,並重啟恢復到一個乾淨的初始狀態。此外,服務端的 Programmer Error 應被客戶端視為 Operational Error,客戶端應當對此作處理。


這年頭大家都只知道let it crash這種廣告詞,不記得stateless這種術語了?

Erlang的let it crash是什麼思想?是一種crash沒關係老子無所謂的思想。

為什麼沒關係?

核心是因為Erlang儘可能不讓程序具有任何狀態,也就是stateless。

沒有stateless(嚴格地說,minimal state),什麼快速恢復啦狀態檢測啦都是無米之炊。

——————閑扯淡的分割線——————

為什麼stateless就沒關係?

因為stateless了,意味著程序只要重新開始,就能接著之前死掉的地方用無限投幣大法續關。

為什麼Erlang能無限續關?

因為Erlang設計為你存儲狀態很痛苦的樣子。這樣你儘可能把狀態放在外部系統,或者狀態可以簡單的恢復出來,程序本身儘可能只做把一個狀態轉換成另一個狀態的事情,而不去存儲狀態。這樣只要外部系統沒崩,Erlang程序就可以無限續關,但是不能S/L大法。

那麼其他語言能不能無限續關?

很顯然,這事和語言無關。只要你的代碼做到stateless,你的代碼照樣可以無限續關。Erlang只是設計上強調了這一設計元素罷了。


Erlang的思想是避免進行防禦性編程,再是任其崩潰

一切的前提都是要合理的設計,合理利用Erlang的特性,在這樣的前提下,讓我們可以專註核心任務,並確保任務在出錯時能夠按預計的那樣崩潰。不是說發生預料外的崩潰都可以不管就能指望程序自己恢復,而是我們早就預料到了崩潰的情況,控制了崩潰的影響範圍,並設計了恢復任務的手段。

不是說糟糕的設計導致的崩潰也能指望它自己恢復,糟糕的設計導致的問題,任何機制都是然並卵。

在使用C等大部分語言的時候,任何一處崩潰將導致進程崩潰,所以很多防禦性編程,比如無休止的參數合法性檢測、try-catch等,目標無非是出錯的時候能返回錯誤而不至於崩潰(當前期望的功能仍然沒有完成,然後上層仍然要繼續處理這個錯誤);在Erlang裡面你只需要專註處理你期望的數據,比如輸入參數會作為除數,那麼你不需要判斷它必須不等於0,直接使用就好,當出現與你期望的不同的時候(比如傳入了0值作為除數),那這個時候你的處理進程自然就崩潰了,而我們不需要去關注這樣的崩潰事件,只要有正確的重啟手段。典型情況,比如網路服務,一個用戶連接對應一個進程,當接收到非法的用戶數據(比如期望的命令參數未提供),程序在處理用戶命令時並不會去檢查參數的完整性(缺少必要參數)、合法性(期望的整數,結果傳入的字元串),當遇到這樣的非法情況時,對應的用戶進程也就自然崩潰了,體現在客戶端就是連接斷開。而服務端如果有與用戶進程相關的其他任務,則設計上應該是相互link的,這樣跟用戶相關的資源也會自然被釋放。


在《Erlang/OTP 並發編程實戰》里,我把 let it crash 譯作「放任崩潰」。之所以可以放任,是因為不怕崩潰。Erlang 對付崩潰的辦法是重啟,而重啟之所以有效,是因為可以將脫韁的狀態重置回受控的初始狀態。普通軟體系統懼怕崩潰的原因是因為重啟的代價太高。更確切地說,普通軟體系統中狀態管理的粒度太粗,導致重啟後狀態恢復的代價太高。

對於 Erlang 來說,由於可以在 Erlang 虛擬機內輕易創建大量高度隔離的進程,可以實現極細粒度的狀態管理,因而單個進程重啟時狀態恢復的代價也得以顯著降低。這使得通過重啟實現狀態重置進而實現故障恢復成為一種經濟有效的手段。

「鼓勵」崩潰的前提是高度隔離的細粒度狀態管理。實施要件是程序必須以合理的方式將狀態劃分為細粒度的樹形結構並映射到進程的 supervision tree 中去。

對於這個問題,先前的這個回答中也有討論。


立即崩潰是一個好的設計思路,但不討論重啟效率的崩潰都是在耍流氓,從工程實現來說,erlang vm是一個立即崩潰的的好實踐,為何這麼說呢,我們一點點剖析。

1. 立即崩潰是一個好的設計思路

  • 很顯然,這樣可以讓設計和編碼輕裝上陣,會非常輕鬆

  • 立即崩潰,錯誤的程序不會進一步佔據資源,cpu,內存會相應立即釋放

2. 不討論重啟效率的崩潰都是在耍流氓

對於java或c,當出錯時整個線程退出,無疑是愚蠢的,讓我們想想,一般在java對快速崩潰的實踐中,這種情況都是線程復用,代碼段跳出的形式在進行,即使這樣的辦法偶爾也會帶來一些問題,比如上下文清理不幹凈,但絕對不會輕易嘗試殺死線程並重啟一個線程,否則外來攻擊光憑錯誤就可以直接讓進程不可服務,這個代價太高了。

3. erlang vm是一個立即崩潰的的好實踐

  • 看了erlang的設計思路,是非常清奇的,它和那些僅對內存做抽象的vm類型的語言不同,它的設計更進一層,它對線程也做了抽象。

  • 一般VM類的語言,它的運行模型和概念,是和操作系統基本保持一致的,比如hotspot jvm的線程和操作系統的線程1:1,比如jvm的spinlock,CAS,基本上就是直接調用的jni介面,下面一套操作系統api就完事

  • erlang首先是一個soft real-time的vm,它有自己的process概念,它的process和操作系統線程概念完全不同,也不存在n:m的綁定關係,process 運行時間的分配也是完全由erlang vm自行決定,當然對於上面的論點,最重要的點是,erlang的process啟動和殺死的效率都比線程高太多了,這讓erlang擁有了快速崩潰的第一優勢,重啟效率
  • erlang的supervisor 和worker模式,這個模式在理解上,和微服務中borg的那套監控重啟模式特別像,簡單說就是你可以定義好worker崩潰後的重啟模式,由supervisor來做監控重啟(比如1:1重啟,一旦出錯前面的服務全部重啟,這裡不一一舉例)

最後再總結下,erlang的快速崩潰和現在流行的微服務類的調度工具有一定的共通之處,印證了好的工程實踐都有相似性,但是也不要忘了快速崩潰都是有前提的,也不要在寫代碼的時候犯了聽風就是雨,畢竟做工程還是要從工程中來回工程中去。


在一個架構良好,功能完備(有專門的Crash Report模塊)的大型C++項目中也是鼓勵崩潰的。

然而現實是很多自以為很聰明的程序員總是喜歡用

try

{

....

}

catch (...)

{

}

注意catch block裡面是空的

更讓人抓狂的是在非常top的levle裡面把所有代碼包起來,比如main函數里。出了問題跟傻子一樣。

所以看一個人C++的水平,看他寫了多少try catch,以及try block的scope和catch 的過濾表達式就知道了。


因為你無法避免出錯, 那麼出錯了是繼續跑+將錯誤數據傳導下去, 還是掛掉+列印現場+迅速重啟呢?

其實就是出錯後顯式地通知調用者.


避免錯誤擴散到更大範圍,在萌芽時就處理掉。


程序語言中的故障大概分兩種類型

01 程序自身的邏輯錯誤。比如說,該以米為單位的時候不小心以厘米為單位,對一個長度為零的表做一個取元素的操作……

這種故障可以根除,而且會反覆出現。對於可以根除的故障,我們應該儘可能去根除它,fp通過類型系統來解決程序中的邏輯錯誤

02 外部環境的異常情況。比如說,網路的中斷,磁碟讀寫錯誤……

這種外部故障無法根除,但只是偶爾出現。對於無法根除的故障,我們需要在故障發生之後儘可能地妥善解決它

「就讓它崩潰」只是整個解決問題過程的前半部,真正的關鍵是後半部「快速重啟服務」

這個方法建立在一個關鍵的假設之上:錯誤是偶爾發生且難以重現的。對於難以重現的錯誤,出錯後趕緊崩潰再重啟,故障往往就解決了

與 @fleuria 引用的那篇文章的觀點相反,erlang這套方法不應應用在程序內在的邏輯錯誤上(但不是不可以這樣用)。如果錯誤是程序內部固有的,會反覆頻繁出現的,那樣反覆快速重啟只能視為一種臨時修補的手段

erlang這種方法不具有普遍性,其他語言很難發揮出這種方法的潛力,原因如下:

01 erlang建立在純粹的消息傳遞之上

任意兩個進程之間沒有任何瓜葛,因此也就沒有必要保證進程內部操作的原子性,進程可以在任何時候被幹掉。其他語言大部分都要考慮到操作的原子性, 進程的崩潰必須是一個精心設計的操作,錯誤的崩潰會帶來災難性的影響

02 erlang的進程開銷極低,重啟極快

崩潰只是第一步,關鍵是重啟。據我所知目前能跟erlang比拼一下的也只有haskell(ghc),其他語言能否做到極低開銷的進程重啟使其達到實用化呢?


這稱不上啥思想吧。

因為Erlang有真正的消息機制,就是可以放心的crash。

你就想想,你一個C語言的庫函數,難道出錯你就直接abort?

對於程序來說,錯誤是客觀,異常是主觀的。大部分程序語言,很遺憾,都分不清這兩者的區別


erlang的認知世界裡程序是一種現實世界邏輯結構的映射,是all in control的。超出control的情況請問機器君有足夠的智能處理嗎?很明顯沒有,那就交給悲摧的碼猴處理吧。很邏輯很形而上的思路。


Erlang的這一套設計思想要得以實現的前提是你得以實踐的前提地去使用OTP, 否則它的這套思想也無法實現


這個話題很熱鬧。天哥還專門寫了專欄來反駁諸位。然而也沒說對。

「就讓它崩潰」這一編程思想,其實沒這麼複雜。就是把「任務/狀態封裝」在進程里而已。

至於快速重啟只是加分項,不是必須的。

任務監控也不在這一思想的定義中,它只是一種處理手段。

你完全可以不監視,不重啟,把錯誤返回給用戶。你要保證的是,任務出錯後,能盡量回滾到任務執行前的狀態。

就連封裝任務用的小進程也不一定是必要的。思想不要太漿化(家有水表),你也可以用一個不共享狀態的閉包來模擬不是?只不過這時候你不能用「崩潰」來形容了,可以叫「就讓它失聯」。

然而叫什麼都不重要,你理解它的關鍵就可以卡卡西出很多種近似的方案,然後你就達到了「禪」的境界,可以看著自己的肉體說「就讓它坐化」。


客戶端崩潰——1分鐘備份一次用戶數據。

伺服器崩潰——只有一台機器也好意思叫伺服器?沒有看門狗也好意思叫節點?

面向KPI崩潰——崩潰本來是正常的,現在竟然要扣錢?跳槽!


「let it crash」 這個思想其實很容易被誤解,事實上你可以更多的把它當作一句廣告詞來看。

我們在用 Erlang 的時候能更多的關心程序正確的執行流程,並且很少處理錯誤;讓 VM 去捕獲異常,並且然監督者從原始狀態重啟失敗的進程。

Erlang 與其他防禦性編程語言不一樣,在這裡,「異常」 是一個偏中性的詞語。異常發生時,我們要做的不是去修復它,而是儘可能減少其波及範圍。這時最好的方法就是把這一部分拋棄掉,然後 OTP (或者其他的組件)會幫你重新恢復。鑒於 Erlang 在設計的時候部件都十分的輕量,所以這樣的消耗很小。

所以我們編程的時候不需要考慮異常情況嗎?

No!

打個比方,你的一個進程裡面有一些從用戶那裡收集到的關鍵數據,然後突然 crash 了,重啟之後數據沒了,你怎麼辦?或者是這個進程要與另外一個進程交易,然後通信到一半 crash 了,交易處於什麼樣的狀態?

這些都是 「let it crash」 沒有提到的。Erlang 無法幫你恢復這些關鍵的狀態與數據,所以這些事情需要你自己去做,你需要自己選擇持久化方案,提交協議,通信協議,考慮罕見的競態條件。

「let it crash」 描述了 Erlang 的容錯性,但是這個特性並不是僅靠語言 VM 就能保證的,只有在精心設計的系統中,你才能 「let it crash」。


上面說的很好的。但是有一些誤解。加一句吧。

erlang中 let it crash的思想的很重要的一個發源地是 80-20的原因。我們實際的產品中,80%的時候都在處理20%的極端的case,這些case沒有什麼人用,但是為了保證系統的完整和正常工作,這些case又是必須要處理的,不處理就會飛指針,就會崩潰掛掉,core dump。

erlang的解決方法是,盡量只處理80%正常的case,如果遇到20%的極端就情況就let it crash,就讓它掛掉好了。這些掛掉的進程所處理的,是我們不希望遇到或者不能夠處理的情況,這些情況對我們的產品,服務來說不是必須的。

上面說到就算掛掉還會用supervision tree重啟的,其實是個誤解。supervision tree一般用來處理一些關鍵服務,比如OAM,比如網路監測等等。對於一般事務,我們一般不會用application supervision tree來處理,沒什麼原因,太繁瑣,因為大部分事務的時間其實比較短,我們只要link一下知道有進程掛了就可以了。對於這些事務進程,絕大多數情況下不需要重啟。 結合上面說的,這些掛掉的進程很可能是因為處理了20%這些極端case造成的。

對於這些短進程而言,保證他們的健壯性其實是另外一個話題,比如如果在整個系統掛掉的情況下熱備切換,各種資源掛了的時候如果容錯等等。 這種時候其實不會let it crash的。

至於怎麼區分掛了是因為80%的正常case還是20%的極端用例是另外一個大話題了。


這個思想強調的是如何從錯誤中恢復!這對軟體的可用性而言是非常重要的。軟體不是完美的東西,它可能因為bug等各種原因崩潰,不能服務。那如何把不完美的軟體跑出高質量的服務就是關鍵。這時候從錯誤中快速恢復就是個非常牛逼的思想!


強答一發,雖然exception讓人深惡痛絕,谷歌規範還明令禁止,但用好了還是很省事的,比如Berkeley db ,運行的很穩,他的exception只用來拋出有用的事前定義好的error 或warning ,看字面的意思就能很快定位到問題,用error code 也可以實現,但就沒那麼方便


我來唱個反調,所有思想都有它擅長和不擅長的領域。 「let it crash」 在遊戲行業,無論是客戶端編程和伺服器端編程就是做大死。 伺服器端通常的情況就是crash成本巨大,並且不可控,絕大多數情況下,運營中伺服器一次當機的成本遠大於解決crash原因能帶來的收益。 上線遊戲客戶端的情況是開發者知曉crash發生和收集crash信息/log 的成功率近乎0 。絕大多數玩家遇到遊戲crash 不會主動聯繫客服發生了什麼,自動crash報告和收集能覆蓋的範圍也很有限。

在遊戲行業里 let it crash 的思路就是巨大的作死,無數的遊戲因為伺服器當機或者客戶端crash突然夭折。而另一些你以為那些穩定的成功遊戲的真相其實是伺服器最多能穩定幾周,但它們運營每周例行維護,保證了它幾年不出運營事故。

-------------------

其實大多數 to C 的軟體都是類似的情況。以上各個高票答案回答的都是 to B 的情況: crash-&> 重啟 的成本可控或很低,潛在問題的風險成本可能巨大。

換個場景想想,另一種to C的情況就是 載著你自己的汽車上路, 代碼運行 catch到異常,你是期望這個無人駕駛的程序員是想 給你穩住、警告、安全停車再說,還是期望他信奉直接 在高速行使中 「let it crash」 的教條,現場crash(然後失去對車輛的控制)?

-------------------

補充,let it crash 的思想對於遊戲開發還是有用的,但只限於只在 版本的內部測試QA階段是有價值的,因為這個階段就是要儘可能暴露問題,而crash和重啟的成本近乎於0。

-------------------

再補充:最常見的濫用這個思路的處理是明明可以安全忽略或妥當填埋的小問題,放任它crash,美其名曰「let it crash」


看了所有的答案,有些無語,在專欄寫了篇文章:https://zhuanlan.zhihu.com/p/25070432


按照我的理解,這是fail-fast思想,在我參與的產品中,不僅是erlang,C語言的編程哲學也是「就讓他崩潰」,但是這個是有一個要求的就是,你的crash的影響不大,對於erlang來說沒啥大問題,對於C語言的程序來說,就是一個進程當掉了,那就是說你的進程在當掉之後能夠循序重啟,並且重啟後對業務影響不大。

另 failfast參見鏈接

http://en.wikipedia.org/wiki/Fail-fast


崩潰了還有log可以看

跳過錯誤繼續跑都不知道哪裡出問題了

寧可各種崩潰能讓我找到問題的


重啟能解決大部分的問題,把車禍現場信息留下來就好


這種哲學反對的是所謂「防禦性編程」,而後者的關鍵問題在於,其實你是防不住的。

當然把這個哲學落實到實踐中其實是需要有很多基礎設施的,erlang很好地完成這個任務。


3詞答案很多不了解 Erlang 的 let it crash 機制,就想當然的 yy。

剛好我在公司里用過兩年 Elixir 的小組裡工作,我也來班門弄斧一下。

@陳天 的解釋已經很好了,可惜點贊率不高。我根據自己的經驗,綜合一下:

Let it Crash 僅僅只能用在 Erlang/Elixir 中。

  1. Let it crash 並不是指你可以不做任何 defensive coding,該用自己腦子避免的錯誤都要自己來處理。總不能你的 http 介面返回502 而不是自定義的可預知錯誤吧? Let it crash 是為了避免如依賴的開源庫不可預知的錯誤,你依賴的資料庫不可預知錯誤等等,避免你對每個操作都不信任導致一大堆的錯誤判斷邏輯(是的,golang 就是反例子)。
  2. 你或許會說,這樣子其他系統也可以做。但是,其他靠 如docker 檢測重啟的系統,重啟進程需要數百毫秒甚至秒級,跟 Erlang 重啟OTP 里自身 process 的小到可以忽略不計的時間是完全不一樣的。
  3. 接2, 如 @陳天 所指的例子,別人發現你輸入某些 pattern 就會有延遲(或許重啟中,轉接給其他伺服器了),即使你又10台做負載均衡,做 backup,人家一發 x 個,服務全掛了??這開玩笑嗎。 Erlang 可以保證每個 OTP 里有 x 個 process,重啟時間極小,可以 handle 這種情況。
  4. @達達 所說的

出錯了沒感知,甚至任其連續崩潰重啟直到出現更嚴重的錯誤才被發現

如果故障自動重啟,但業務持續無法響應,比如就是你前面舉得例子,依賴的第三方介面改了返回值,重啟服務是沒用的,業務只會持續出錯。

如果單個業務無法工作倒也還不算嚴重,如果業務持續運作,但是導致數據污染,那會更加嚴重,到時候只能擦屁股了,有時候連屁股都沒辦法擦

其實並非除此。只要是連續崩潰,意味著這個錯誤是必須要被處理的。 想想看,連續崩潰肯定是某個 pattern 造成的,Erlang 里默認定義了檢測者5秒內重啟超於3次,就整個掛掉了,不會再重啟。假如是請求方發了x次還是掛了,肯定會有發現 的人。假如整個掛掉了,這個錯誤 就要處理。

簡單來說,不是頻繁出現的錯誤,或者是請求方感知到的錯誤,都不需要馬上修,都會存到 error log 里。想健壯些?error log 報警,一出現 error 馬上修,下次更新時帶上就好,不影響業務。

否則,就要馬上處理這個問題。


面對錯誤無腦重啟不是解決問題的最好辦法,大部分情況下你會得到一個不斷crash不斷重啟的死循環。軟體的錯誤無處不在,有軟體本身的bug,有外部邊界的問題,也有硬體本身的問題。作為一個健壯的軟體,要正確處理好錯誤絕不簡單,不是一個crash重啟就能解決的。

首先不是所有的錯誤軟體都可以處理,硬體造成的問題,軟體是無能為力的,所以面對不可恢復的 錯誤fail fast是必須的,但也不是簡單的直接crash,還需要的是做好記錄保留現場,方便後面進行排查。如果是面向最終用戶的軟體最好是能顯示友好的錯誤信息,讓用戶知道該如何應對。不好的例子就是windows的藍屏,顯示一堆對用戶來說毫無意義的錯誤碼,讓用戶不知所措,很多時候是掛在某個驅動模塊上,如果能友好的告知用戶,那很多時候是有辦法去解決的。

然後對於那些可恢復的錯誤,需要針對不同的錯誤類型制定不同的恢復策略。比如說網路連接中斷,這是有可能恢復的,所以策略應該是嘗試重連,但也可能碰到長時間無法連接的情況,那麼這就需要有一個超時策略,當重連的嘗試超時之後採取更進一步的措施。這些問題不是一個簡單的crash策略就能解決的。

最後,在網路服務中我們需要高的可用性,那就需要具有冗餘設計的高可用架構。在這樣的架構中我們擁有很多可以完成相同工作的節點,節點的總容量大大高於需要承載的服務量,當某個節點不可用時其他節點可以接替它的工作負載。而erlang就是為這個設計的,表面上看是fail fast,開發者在開發過程中不需要對大多數錯誤進行處理,但實際上這部分工作是被erlang的運行框架做掉了,另外也由於erlang語言的特殊性(例如無狀態的設計)使得let it crash成為可能。但這一切不是免費的,有些是付出了性能的代價有些是付出了靈活性的代價,如果對erlang整套框架背後的實現細節不甚了解的話很容易掉坑裡去。另外,如果脫離了erlang的框架,在其他語言和框架里無腦運用let it crash法則,那基本上就是作死了。


快速暴露問題,從未經歷過演練的災備系統相當於不存在的災備系統


當你的程序因為越界之類,莫名其妙崩潰的時候,你一定希望為什麼不一越界馬上就崩潰。


這種思想,在java的異常體系里比較常見,異常體系不再累述,在java 後端開發中,就我自己的使用經驗上來看,「就讓它崩潰」 這種描述,不完全準確,要想讓它發揮作用,需要一些相應的配套措施,保證這種思路的體驗。

(「就讓它崩潰」 下文用異常來代指)

首先,異常及錯誤返回碼,是兩種常見的系統執行錯誤後的處理方式,不能一味認為哪一種更好,一般來說,涉及到多系統的rpc調用,應當使用錯誤返回碼這種形式,將異常包裹在自己系統內部,不要傳導出去。而在同一個非大型系統內部,異常往往會更加靈活,提高開發效率(否則所有方法調用都定義合適的錯誤返回碼,沒有必要)。

那麼在系統內部,如果採用 異常這種來處理,以常見的spring+spring mvc為例,有幾個點需要注意:

1.事務控制:

異常會阻斷本次請求的後續流程,例如一筆轉賬,如果在付款方已經扣掉餘額,在收款方增加餘額時發生異常,必須將之前付款的數據回滾掉,那麼就需要用到事務控制,異常情況下,本次操作的所有內容回滾;

2.對異常統一切面處理:

異常會有很多類型,最常見的如NPE等 ,另外還可能有自定義的各種異常類型,不同類型的異常,拋出後,建議還是包裹一下(包裹為自定義的異常,一般建議 為 RuntimeException 的子類),統一處理(比如最常見的列印日誌)後,再包裝為統一格式的異常拋出的。

這個可以通過AOP 來做,將 service層的方法包裹起來,切面中進行異常捕獲、日誌列印,包裝為自定義的異常類型, 再進行拋出。

3.web 層用戶展示:

當異常傳遞到web層,實現自己的異常處理器 (實現 Spring ExceptionHandler介面),將異常信息封裝為用戶可理解的文案,暴露給用戶層面。

以上的三個方面,都做了之後,「就讓它崩潰」 這種思想,才能較為放心的使用。

比如常見的校驗邏輯(如「用戶密碼必須包含英文字母加數字等」),也可以放心使用異常來處理:

public static void notEmpty(Collection& collection, String message, Object... param) {
if (collection == null || collection.isEmpty()) {
throw new ZhiHuException(message, param);
}

校驗邏輯中拋出自定義異常,校驗失敗原因及一些參數等信息放到自定義異常的 message ,param中,前端會自動展示你的異常文案,伺服器也可自動記錄本次異常發生的日誌,這樣才能發揮出異常體系的優勢來。


貼一篇cowboy的作者的一篇blog,這個是作者在cowboy 在單機2million 連接的試驗下得出的一個結論。

Dont let it crash

22Jan

We have a specific mindset when writing Erlang programs. We focus on the normal execution of the program and don』t handle most of the errors that may occur. We sometimes call this normal execution the happy path.

The general pattern behind writing only for the happy path, letting the VM catch errors (writing them to a log for future consumption) and then having a supervisor restart the processes that failed from a clean state, has a name. We call it let it crash; and it drives many of our design decisions.

It』s a really great way to program and the results are fantastic compared to most other programming languages. And yet, let it crash barely convinced anyone that they should use Erlang. Why would that be?

You may already know that Cowboy is capable of handling at least 2 million Websocket connections on a single server. This is in large part thanks to the capabilities of the VM. Still, 2 million is good, much better than most other servers can do.

Cowboy is not just a Websocket server; it』s also an HTTP and HTTP/2 server, and it handles many related features like long polling or the parsing of most request headers.

Can you guess how large the Cowboy codebase is, without looking at the source?

Do make sure you have a clear answer in your mind before you go check.

Good, you are back. Now what were the results? If I am correct, you overestimated the size of Cowboy. Cowboy is in fact about five thousand lines of code. You probably thought it was at least ten thousand. About eighty percent of readers will have overestimated the size of Cowboy. And you did only because I mentioned it can handle millions of Websocket connections.

Numerous studies show this effect. Just mentioning the large number already prepared your mind to think in that direction. Repeating the number made you focus even more on it. Then the question asked for a number, which ended up larger than the reality.

The same effect can be applied to negotiation for example. You generally want to start by giving your offer (and not let the other party initiate) and you want to give a really large number first. You can also prepare your customer by mentioning an even larger number in the previous discussion.

And it』s not just numbers either. An experiment showed that just by looking at an image of clouds, customers of a pillow store were buying pillows more comfortable (and more expensive) than those who didn』t see that image.

This is the power of associations. It is covered in much larger detail in the books Influenceand Pre-suasion. I highly recommend reading those and applying what you learn to your daily life. I』m definitely not a professional psychologist so take this post with a grain of salt.

When selling Erlang, whether we are selling it to a customer or trying to convince a developer friend to start using it, we often talk about how Erlang lets you sleep at night, that it is auto healing and always gets fantastic uptimes.

And then we talk about let it crash.

And we describe what it means.

We might as well just say that Erlang crashes a lot and then take the door. It would have the same effect. It doesn』t even stop at programs crashing. You know what else crashes? Cars, planes, trains. Often with disastrous consequences. Is that really the message we want to convey?

They even printed it on a t-shirt! Keep calm and let it crash. It』s the kind of t-shirt you probably shouldn』t wear in an airport, and for good reasons. A few people did, then realized what they were wearing and were not too smug about it.

And yet this is how we sell Erlang.

A better way would be to focus on the positives, of course, but also to make sure that those positives are phrased in a way that prevents bad associations to be formed in people』s minds.

Instead of let it crash, you can say that Erlang has auto healing mechanisms. Healing is a good thing and accurately describes what happens in the system.

Should you need to go into more details, you will probably want to avoid recover from crashes and instead say recover from exceptions. Exceptions are a pretty neutral word and, should you explain what you mean by that, you can talk about exceptions that occur for reasons unrelated to Erlang, like hardware failure or network instability.

The trick is to always use positive words and phrases to describe Erlang, and to use external factors to explain how Erlang deals with failures. Never mention the failures internal to Erlang systems unless you are asked specifically, in which case you can say that the auto healing applies to all exceptions.

The let it crash philosophy is great when learning Erlang or when writing fault-tolerant systems. But it』s not going to convince anyone to use it unless they were already looking for it.

傳送門:Dont let it crash

update---------------------

談論問題要有方法論:

問題的來源,歷史依據,發展歷程,現在的狀況

學習一個開源項目最好的方式就是:

看GitHub上的歷史版本,從第一個commit開始,每個commit參看change log 和新的feature的提交。

具體到這個問題,題主可以查看erlang的歷史版本提交,在最初版本中是沒有otp這個架構的,查看崩潰機制這個feature的提交時間。change log 會寫明這個機制的來源!

尋本溯源!

update-----------------------

erlang的 崩潰機制 來源場景是當時愛立信的項目,當時的話務系統就是要求5個9的穩定性,然後崩潰機制也是當時的業務需求場景,所以Joe從prolog等學習參照寫erlang的時候,這是業務需求,然後才有erlang的這個特性,你們探討這個問題從erlang語言的機制來說忽略當時發明erlang語言的需求,本末倒置!

先有業務需求,然後才有實現!

erlang是門語言,但是也是業務驅動下的!

現在erlang成型了,確在用這個歷史場景來硬套現在新的業務需求。或者問怎麼理解這個問題,

你都不談問題來源,歷史發展狀況,確在談他的機制!

不要神話所謂OTP架構,現在新的場景是需要你自己閱讀OTP架構源碼,自己寫新場景下的東西,erlang的底層也是這些API的

寫代碼最好是把erlang的各個歷史版本,GitHub的提交歷史,code的commit change log 看一遍。

你都沒寫過erlang,沒在大量用戶,工業場景下使用過,你這不是照本宣科,紙上談兵么!

最後老大鎮樓!


Let it Crash 這是一種久經考驗的設計思想,也是血的教訓澆灌出的工程經驗。

在我的工程實踐經驗中,我認為它特別適合如下幾種場景:

1. 外部的請求由進程或者線程容器分配到各個子worker中處理。比如web服務中,對應的python uwsgi, php-fpm,java的tomcat容器。在這些子進程/線程中,對於非預期的異常和錯誤,可以放心地進行 Fail-Fast,讓當前請求快速掛掉。而主進程/線程會檢測到退出子進程/線程的崩潰狀態,會做一些清理工作,並重新拉起一個子worker準備接收新的請求。這樣做的好處:

  • 能夠終止當前業務流程,讓出寶貴的系統資源
  • 能夠在本地日誌系統中留下完整的異常棧信息
  • 配合旁路式的異常日誌監控收集系統,收集異常棧和上下文context,能迅速發出報警通知,提醒程序員進行處理,並方便地復現。(比如sentry)

2. 功能單一,或者本身是stateless的子系統,並且配合 nginx/lvs/haproxy 進行 load balance。這樣的系統中,前面有LB層存在,所以作為集群中的某個單點,可以花式作死,而不擔心過多影響全局服務。

當然,Let it Crash 並非適用於所有的業務場景。

1. 高並發,大流量的單點系統。進程的崩潰,即使有完善的Daemontools配合(systemd, supervisord),也需要至少幾百ms的時間,且不說服務本身還需要初始化配置,數據文件等。期間任何流量進來都是拒絕服務狀態的。這叫得不償失

2. 有複雜狀態的系統,比如關係型資料庫,nosql,cache等。即使要崩潰,這類軟體也需要做複雜的上下文保存和日誌備份,以便下一次啟動時恢複數據。(當然,不能說它不能 Let it Crash, 而是說對於絕大多數類型的錯誤和異常,都是有必要處理的,包括斷電啦,磁碟壞了等等)

3. 庫,客戶端軟體。

4. 不容錯,不鼓勵試錯的環境中。所謂上有所好,下必甚焉。我以前經歷過一些領導,對線上業務報警消息看的很緊。稍有風吹草動,對員工的關懷就到了。久而久之,員工寫的代碼裡面,到處充滿了大大的

try:
xxxx
except:
# nothing happened, all in control :)


見我四年前的貼吧掃盲帖:

https://tieba.baidu.com/p/2204006930?pn=0

匿了


首先要明確 "let it crash" 的 "it" 到底是指什麼?

如果指的是erlang的process,這裡同意 @沈萬馬 的觀點;"let it crash"前提是"stateless",只有"stateless"了,process帶著錯誤運行到crash再恢復才是無損的。

否則則必須要"fail fast",在工作流進入到錯誤狀態時及時終止;否則帶著這種錯誤運行到crash 造成了數據不一致(被破壞),外部狀態的不一致,再恢復就不簡單了。


推薦Fred先生的文章 《The Zen of Erlang》。完美解答這個問題。

文章鏈接:The Zen of Erlang


這是典型的通信系統的編程思想。想想誰開發了 erlang 就一點都不奇怪了。


忍不住咕嚕一句:

「潰了就潰了唄,既有重啟機制」, let it crash不應該與supervision強行關聯在一起


「快速失敗」原則吧 這個實踐中確實很有用


除非你能百分百確定異常發生原因,捕獲並可以合理處理,否則讓它崩潰。這樣有助於發現並解決問題。

這裡面包含兩點∶一是,有的異常發生是你知道會發生沒法阻止它發生,但可以在發生後做好保護。這種異常你需要捕獲並處理。比如某個函數返回值就可能為null,那你不對這個返回值進行防空保護,那程序永遠可能會掛。二是,你知道它可能會發生,你不知道為什麼會發生,或者你沒法處理髮生後的情況,這時候就讓它down。方便定位並解決問題。


異常本身就代表程序出錯了,既然已經出錯了並且無法處理,就沒繼續運行的必要,因為出錯的程序的結果就無法保證正確性,讓它崩就可以暴露問題儘快解決。

至於上線前還沒暴露,這是測試不到位的問題。


就像Windows的藍屏死機?


要有風險就別運行,跳到未定義區,基本上也就等於上帝擲骰子了


就是有問題就讓它及時爆發,不要掩蓋的意思吧。


方便你調試


崩潰的代碼好調試,debug工具能立刻定位崩潰位置。

如果try catch了,那麼引起問題的地方,就找不到了,只能翻log,可惜log也不如保留完整現場的core dump好用。


早發現早解決,完


死代碼不說謊


讓程序儘早崩掉對於發現和排查問題有積極作用,如果出現意外情況,最合理的情況是儘快讓問題曝光出來,保留線索然後退出,隱藏問題比忽略問題更加嚴重。

UNIX下就算崩掉也不怕,大不了重啟程序,所有狀態複位,還能繼續運行,系統別崩掉就行。

這個想法的重點在於讓問題在可控範圍內暴露出來,而不是勉強維持運行狀態,讓問題發展到無法失控後再一次爆發出來。


"Let it crash and fail gracefully" Goog SRE


今天正好碰到了,有個拷貝事物,需要給一個對象生成一份拷貝,存入資料庫,同時將一個特定目錄的文件拷到另外一個目錄。結果拷貝文件那裡catch了exception,導致對象拷貝成功,但是目錄沒有生成。

所以說將異常拋出,有助於別的業務回滾,不然有可能產生數據不一致的情況。

這只是其中一種情況,其他答主已經基本說明了。


簡單一句話,就是為了防止犯更大的錯!


在錯誤的第一現場崩潰,好發現問題。

等各種 == NULL 繞多了以後,定位問題會變得非常痛苦


你讓程序繼續運行,你知道後面會是什麼結果嗎?萬一段代碼以後會拋出類似於「全世界計算機集體自爆」的指令呢?

最可怕的不是錯誤,而是未知行為!因為你根本就不知道它接下來會幹什麼……


對於C/C++,善用ASSERT機制


我寫Java也是認同這點的,防禦式編程太腦殘了,多做了很多不必要的無聊工作


好多人提fail fast然後就開始「不僅是erlang」然後把C之類的加進來說一通,根本就不知道題主在說什麼…… erlang的let it crash是另一回事。

erlang擁有非常輕量的進程(非處理器或者系統進程,更輕量,你可以在你的筆記本跑上百萬個進程)。因為非常輕量,所以你在把狀態和處理單元分離之後,處理的東西就是stateless的,因此在beam (erlang vm)上簡單重啟就好了,不用寫任何concrete case的handle邏輯。

我們考慮一個簡單場景:

你有一個數組存作為model,每次有其他代碼調用的時候就要讀取這個數組。那你就把這個數組放在一個進程里。然後其他進程僅作為計算單元來給這個進程發消息,因為輸入一般都在這些計算單元這邊,所以不合法的輸入會讓這些進程掛掉。這時候用其他語言基本就要小心翼翼地case by case try catch或者反覆判斷null之類的無聊事情。用erlang的話直接讓他們掛掉就好了,因為它們反正也沒保存狀態,只要給model的輸入不會讓model掛掉就好了。


推薦閱讀:

Android - Spring Animation
面向對象中的依賴注入概念本質上是否與面向過程中的模塊導入一致的?
OO 的設計理念對 Python 來說是重點嗎?
Bug螺旋

TAG:編程語言 | 編程 | Erlang編程語言 |