爲什麼投機是有益的

說到投機,許多人想到的就是罪惡的資本主義世界裏貪婪的商人形象,最容易與經濟泡沫、金融海嘯聯繫起來,直觀上感覺投機是百害而無一利的。許多人認爲投機就是參與一場賭博或者騙局,少數人暴富而多數人破產,所有投機者都試圖通過不勞而獲暴富,而損失讓別人承擔,因此堪稱是資本主義罪惡性的集中體現。共產主義國家都禁止任何投機行爲,中華人民共和國刑法中曾經就有「投機倒把罪」,一直到1997年纔廢除。

投機和投資

如果投機(Speculation)被認爲是有害的,那麼投資(Investment)呢?

許多人出於無知或者故意把這兩者混淆,其實投機和投資是根本不同的行爲,動機、方法、效果完全不同。投資是把一些資源(一筆錢或者一些時間)投入到某個項目,在未來的較長時間內持續不斷地獲取收益,如基礎設施建設、學習某項知識技能、購買房地產、股票。而投機就是在市場上買賣,以較低的價格買進並預期以較高的價格賣出,譬如買賣黃金、債券、期貨、房地產、股票,甚至古董、藝術品。很顯然黃金、古董這些物品不是自己生出錢來的,只能靠它自身價格增值獲取收益,如果沒有出讓其所有權就沒有收益,相比之下修建的橋樑、公路、學習獲得的技能即使不出讓其所有權,也能不斷獲取收益。 從英文字面上可以看出來,投機的英文是speculate,speculate的本意是「思索」、「推測」,故投機的重點在於「預期」,而投資的重點是「投入」。

之所以投資和投機容易被混淆,是因爲有些買賣行爲兼具投資和投機的可能,譬如房地產和股票。簡單來講,投資房地產的目標在於收取房租,因爲房租可以在較長時間內持續不斷地獲得,同理投資股票獲得的是公司利潤分紅的權利。而投機房地產或者股票和投機其他物品沒有區別,只是希望在較低價格買入而在較高價格拋出,靠其自身價格增值來獲取收益。

市場的效率

投資對個人和對整個社會經濟的的好處顯而易見,而投機是不是一種損人利己的行爲呢?

投機的目的是爲了獲取超額收益,也就是超過市場整體平均的收益。完美的市場是達到均衡狀態的市場,所有賣方都能將出售的商品以均衡價格賣出,而所有買方都能將需求的商品以均衡價格買入,價格完美地由供求決定。在這樣的市場上投機收益的期望就是市場平均的收益,這樣的市場被稱爲有效市場(Efficient Market)。有效市場是市場的最佳形態,買賣雙方的利益均達到了最大化,資源配置達到了帕累托最優。

有效市場有三個基本假設:

  1. 市場將立即反應新的信息,並調整至新的價位。
  2. 信息的出現是完全隨機的。
  3. 市場參與者數目衆多,而且總是理性地追求最大利潤。

這三個條件的假設十分強,在現實世界中完全滿足條件的有效市場是不存在的。但部分滿足有效市場假設的市場或許是存在的,因此又有了不同程度的有效市場的度量。

弱有效市場

市場信息是公開的,而且充分反映了全部的交易行爲。目前商品的價格已經完全反映了過去的各種市場信息。在弱有效市場下,通過商品價格和交易的歷史信息預測未來的價格走勢是不可能的(也就是技術分析是無效的)。

半強有效市場

商品的價格已經完全反映了商品相關的公開信息,譬如股票價格完全反映了公司的公開財報,以及宏觀經濟形勢、政治局勢。在半強有效市場下,通過基本分析試圖預測價格是不可能的。

強有效市場

商品的價格已經反映了全部的信息,包括未公開的信息,如公司機密、領導層決策。在強有效市場下,即使有內幕消息也無法獲得超額利益。

投機對社會的益處

投機行爲之所以能進行,是因爲市場沒有達到有效的狀態,理性的投機可以幫助市場變得更加有效。投機講究的是低買高賣,這樣會使得價格趨於均衡。譬如說,一種典型的投機是不同市場之間的套利(Arbitrage),有些商品由於流動性不足,會在不同的市場之間產生差價,在價格較低的市場買進並在價格較高市場賣出的過程可以賺取大量利潤。與此同時,市場之間的價格會趨於平衡,調節了商品的過剩和短缺,優化了資源配置。

在中國大陸改革開放初期,通過長途販運在不同市場之間的套利是一種被禁止的投機倒把行爲。因爲按照馬克思主義政治經濟學,價值等同於勞動生產(而不是效用交換),長途販運並沒有生產,因此對社會無用,所賺的錢是從剝削得來的。但這種看法很快就遭到了質疑,國際貿易也是一種長途販運,難道也是違法的嘛?如果是的話那還怎麼對外開放。

投機對社會的主要貢獻在於調劑時間和空間上的餘缺,幫助資源進行更加優化的配置。但是投機可能會失敗,譬如在一個價格較低的市場買入了商品以後到另一個市場卻發現價格變得更低了,那麼就沒有帶來資源優化配置,同時對投機者也有響應的懲罰。投機的另一個作用在於揭示商品未來的價格,譬如股票的價格事實上是人們對這個公司未來盈利的預期,事實上將許多消息都反映在了價格中,所以股市可以作爲經濟的「晴雨表」。

內幕交易對社會的益處

內幕交易指的是利用內幕消息進行的投機交易。譬如在一個政策出臺前夕,政府官員利用這個消息提前在市場上進行買賣,因爲他幾乎完全可以確定消息公開以後商品的價格的走勢,如果漲的話就提前買進,如果跌的話就提前賣出,這樣就可以穩賺不賠。有接觸到內幕消息的人還有公司高管、證券從業者、媒體記者、私人秘書等等。

內幕交易是一種很不公平的行爲,因此被多數人所憎恨,幾乎所有國家的法律都禁止內幕交易,具體措施是禁止證券從業者以個人身份買賣證券、對有內幕交易可能的交易者進行額外調查監管等等。但事實上儘管有嚴厲的法律制裁,內幕交易還是層出不窮,而且監管帶來的巨大的社會成本,不得不讓人重新考慮是否有禁止內部交易的必要。

學術界對內幕交易的研究主要有兩種理論,分別是代理理論(Agency Theory)和市場理論(Market Theory)。由於股票市場的存在,公司的所有權和經營權發生了分離,因此公司股東和公司管理者之間產生了潛在的利益衝突。內幕交易代理理論的支持者認爲內幕交易可以作爲公司管理者的一種報酬,從而鼓勵他們進行創新活動,提升公司價值。給高管的內幕交易的機會相當於給他額外的激勵,這種激勵比薪酬更有效率。而反對者認爲無法避免非決策人員利用內幕消息的機會,譬如秘書、打字員等。內幕交易市場理論支持者認爲內幕交易可以加速信息的流通,使得價格能夠更快收斂到新的均衡點。譬如一支股票目前的價格是100元,如果一個內幕消息公開會漲到110元,這說明了110元是當前市場對股票的正確定價,如果允許內幕交易會使價格更快趨於正確定價,既能反映尚未公開的消息,又能減少市場價格波動。反對者認爲這只對內部人有好處,使外部人信心受損而不願參與交易。

現實世界中,許多股票的價格變動趨勢在內幕消息公開前後差不多是一致的,這正反映了內幕交易的存在——因爲消息公佈之前價格就已經反映了這則消息要公佈了。公開允許內幕交易以後,市場的非強有效性的時段會縮短,因而有效性會大大提升,達到強有效市場。

非理性的投機

投機並不總是對社會有益的,因爲有非理性的投機行爲的存在。什麼是非理性的投機行爲呢?最常見的是羊群效應,跟風買賣,你買我買大家一起買,至於爲什麼要買並不清楚,只是「感覺」價格在未來會漲。由於交易的主體(直接或間接)是人,人總是容易被情緒控制,人們傾向於過度自信、厭惡損失,於是容易產生各種不理性的決策。行爲金融學專門研究人們非理性的投機行爲。

非理性泡沫理論認爲泡沫總是由非理性的投機引起的,許多人買賣之前並不做任何分析(或者進行無效分析,譬如分析彩票走勢),只是靠自己的信念支撐,許多時候會把市場的噪音當成有效信息來買賣,這些人被稱爲噪音交易者。噪音交易者容易形成羊群效應,通過正反饋交易策略使得市場不斷偏離均衡狀態,直至泡沫崩潰。非理性的投機者還容易被操縱和利用,惡意機構可以通過散佈虛假消息、製造羊群效應等手段使這些人入彀,從而在泡沫破裂中獲利。

總而言之,投機並不是零和遊戲,它幫助了市場的運轉,優化了資源配置,創造了大量財富。理性分析下的投機是對個體和市場整體都有益(至少無害)的。投機無傷道德,更不是犯罪。

C++語法分析中最讓人頭疼的歧義

C++是個特別複雜的語言,其複雜性不僅體現在開發模式上,也體現在語法分析上。許多人都遇到過嵌套模板參數的歧義問題,如vector<vector<int>> v,在有些編譯器上會被解析爲vector < vector < int >> v,但新的編譯器都已經解決了。而最讓人頭疼的歧義則是Most vexing parse

class Timer {
 public:
  Timer() {}
};

class TimeKeeper {
 public:
  TimeKeeper(const Timer& t) {}
  int get_time() {return 0;}
};

int main() {
  TimeKeeper time_keeper(Timer());
  return time_keeper.get_time();
}

以上代碼中出現歧義的是TimeKeeper time_keeper(Timer());,因爲它有兩種理解方式:

  1. 定義一個TimeKeeper類型的對象,並用Timer()作爲初始化參數。
  2. 聲明一個名叫time_keeper的函數,它的返回值類型是TimeKeeper,參數是一個函數指針,這個函數指針指向的函數的返回值是Timer,無參數。

很明顯我們想要表達的是第一種意思,但很不幸編譯器會默認理解爲第二種。Clang++會給出以下錯誤:

timekeeper.cc:15:21: error: member reference base type 'TimeKeeper (Timer (*)())' is not a
      structure or union
  return time_keeper.get_time();
          ~~~~~~~~~~~^~~~~~~~~

之所以產生這種歧義,是因爲這幾個原因:

  1. C++的函數在使用前需要聲明,定義和聲明是可以分離的。
  2. C++的函數聲明的參數可以只有類型,沒有名稱,如int max(int, int);
  3. C++的函數聲明的參數名在類型名後可以加(),如int max(int (a), int())
  4. C++的函數聲明可以在函數體中。

最優美的解決方案是使用C++11的統一初始化語法:

TimeKeeper time_keeper{Timer()};

你不知道的東西正在傷害你——淺談信息不對稱

信息不對稱(Information Asymmetry)指的是市場上交易雙方所掌握的信息不相等,有時候賣方掌握得多,有時候買方掌握得多,因此可能會導致逆向選擇。同時,信息不對稱的存在產生了信息市場,幫助交易雙方相互瞭解的服務可以從此獲利,但如果產生了信息壟斷,則會導致尋租行爲。你不知道自己不知道什麼(You do not know what you do not know),而且你不知道的東西可能正在傷害你。

市場中的信息不對稱

多數情況下是賣方掌握更多的信息,譬如在二手汽車交易市場上,賣方很瞭解出售的汽車,但買方很難有效判斷哪個汽車是好的,哪個汽車是差的,因此賣方就存在着隱瞞二手汽車故障的動機。在這種前提下,對於賣方來說總是希望出售差的二手車,以獲取更高的收益,因而導致市場上充斥着各種差的二手車,好的二手車退出市場。於是買方購買二手車的意願就會降低,導致交易成本提高,雙方都受到損失。

當買方掌握了更多信息之後同樣會有問題,譬如醫療保險市場,賣方對投保者的真實身體狀況很難有效掌握,而買方對自己疾病的風險卻相對清楚。如果保險公司設定一個費率,那麼風險低於這個費率的投保者會不願意投保,投保者全部都是疾病高風險的人。這樣就會導致保險公司虧損,破產倒閉,交易無法進行。爲了逃出這個困境,保險公司總是花費鉅額成本對投保人的真實情況進行調查,帶來了很大的社會成本。

信息不對稱的存在還催生了廣告產業,由於買家對賣家銷售的商品不瞭解、不信任,賣家不得不花錢做廣告。廣告最初是幫助買賣雙方互相瞭解,但最終到達不做廣告就完全銷售不出去的地步。廣告產業一方面消除了信息不對稱,另一方面還造成了信息壟斷,使得賣方可以利用信息不對稱給買方帶來虛假信息,從中攫取經濟租值

戀愛中的信息不對稱

年輕男女相互求偶是一個很特殊的市場。請允許我將它抽象爲市場,戀愛成功視爲交易達成,雖然不涉及金錢交易,卻消耗了機會成本,而交易完成以後又給雙方帶來收益。

在戀愛市場中,男性一般是主動者,女性一般是被動者。優秀的女性會有大量追求者,而其他女性追求者寥寥。由於信息不對稱,優秀的女性儘管選擇較多,但很難從追求者中鑑別出最優秀的(最適合的)。而男性爲了使自己勝出,會使用各種方法取悅甚至欺騙女性(相當於虛假廣告的作用),結果總是造成「爛蛤蟆喫天鵝肉」或者「鮮花插在牛糞上」的結果。

信息不對稱還導致了「剩女」的問題。剩女有兩種,一種是不夠優秀,無人追求(第一類剩女),一種是有追求者,但自己總是不滿意(第二類剩女)。如果信息對所有人對稱,那麼前者當且僅當女性人數多於男性時纔會產生第一類剩女。第二類剩女只有在不理性時纔不會產生的,因爲所有人都知道自己能匹配到的最好的交易,預期超過最優值是不理性的。(當然,不理性的女人總是存在的。)

對於男性來說,信息不對稱時其取悅表現(廣告)帶來收益有可能超過了自身(價值)能夠達到的最好交易,這樣的交易是損人利己的交易,帶來的是負面的社會代價。因爲這樣的戀愛不是對等的,潛藏了破裂的危機。相當於購買者聽信虛假廣告購買了一個次品,會產生怨氣,不利於社會效率。信息不對稱給優秀但內向的男性帶來的個體損失是最大的,相當於優秀的產品因爲沒有在廣告上投資而錯失市場。

在完全信息對稱的戀愛市場中,求偶就簡化爲了穩定匹配問題,所有主動者都能找到自己能找到的最好的選擇,資源配置達到了帕累托最優(Pareto Optimality)。

信任可以消除信息不對稱

博弈論中經典的囚徒困境問題的根源也是信息不對稱導致的,在信息不對稱的前提下,人們傾向於自私自利,而不是與人合作,因爲與不誠信的人合作是要付出單方面的代價的。因此解決信息不對稱最有效的方法就是相互信任。信任可以顯著降低交易成本,給全社會帶來收益,一個發達的社會必然是相互信任程度較高的社會。

信息市場

主動給受信任的一方披露信息(信號)會給交易雙方帶來收益。譬如求職招聘市場的信息不對稱給獵頭公司帶來了獲利的機會,但LinkedIn這樣的網站正在蠶食獵頭行業的市場。LinkedIn建立了基於職業技能信息的社交網絡,每個人都可以把自己的簡歷公開,使得招聘的成本大大降低。無論是對於求職者還是招聘者,都從信息透明度增加中獲得好處。

同樣,Facebook的價值體現在它給全世界數億人提供了選擇性披露自己個人信息的機會,讓每個人都能迅速瞭解到自己社交圈內的每個人。Facebook最早之所以能在美國校園中爆發,是因爲它解決了社交圈內的信息需求,尤其是年輕人之間的相互吸引。在社交網絡誕生之前,美國年輕人一般都會去酒吧、俱樂部尋求豔遇,但在這種場合下遇到合適的人的效率是很低的,獲得超額利益的人往往是長期混跡於酒吧的人,其個人價值並不一定高,而高價值的人卻不被人知道。Facebook有效地解決了這一問題。最近一個基於Facebook社交圈的約炮網站Bang with friends也迅速走紅,它的價值也是消除了信息不對稱,用戶可以選擇性地將自己的意願披露給特定的朋友,不會泄漏隱私。

互聯網誕生以來的最大功績就是大規模擴大了信息市場,使得信息的流通更加暢通無阻,交易的成本戲劇化地降低,移動互聯網的爆發更是加強了這一點。不過迄今爲止,信息市場的潛力還遠遠沒有被挖掘窮盡,我可以斷言幾年以內還會有成百上千家利用互聯網解決信息不對稱的創業公司誕生。隨着量變的積累終將會發生質變,我們即將進入一個前所未有的信息通暢的時代。

如何處理C++構造函數中的錯誤——兼談不同語言的錯誤處理

用C++寫代碼的時候總是避免不了處理錯誤,一般來說有兩種方式,通過函數的返回值或者拋出異常。C語言的錯誤處理一律是通過函數的返回值來判斷的,一般是返回0NULL或者-1表示錯誤,或者直接返回錯誤代碼,具體是哪種方式沒有統一的規定,各種API也各有各的偏好。譬如fopen函數,當成功時返回文件指針,失敗時返回NULL,而POSIX標準的open函數則在成功時返回0或者正數,失敗時返回-1,然後需要再通過全局變量errno來判斷具體錯誤是什麼,配套的還有一系列perrorstrerror這樣的函數。

C++的錯誤處理方式

C++號稱向下兼容C語言,於是就將C語言通過返回值的錯誤處理方式也搬了進來。但C++最大的不同是引入了異常機制,可以用throw產生一個異常,並通過trycatch來捕獲。於是就混亂了,到底是什麼時候使用返回值表示錯誤,什麼時候使用異常呢?首先簡單談論一下異常和返回值的特點。

異常的優點

  1. 錯誤信息豐富,便於獲得錯誤現場
  2. 代碼相對簡短,不需要判斷每個函數的返回值

異常的缺點

  1. 使控制流變得複雜,難以追蹤
  2. 開銷相對較大

返回值的優點

  1. 性能開銷相對小
  2. 避免定義異常類

返回值的缺點

  1. 程序員經常「忘記」處理錯誤返回值
  2. 每個可能產生錯誤的函數在調用後都需要判斷是否有錯誤
  3. 與「真正的」返回值混用,需要規定一個錯誤代碼(通常是0-1NULL

使用異常還是返回值

我的觀點是,用異常來表示真正的、而且不太可能發生的錯誤。所謂不太可能發生的錯誤,指的是真正難以預料,但發生了卻又不得不單獨處理的,譬如內存耗盡、讀文件發生故障。而在一個字符串中查找一個子串,如果沒有找到顯然應該是用一個特殊的返回值(如-1),而不應該拋出一個異常。

一句話來概況就是不要用異常代替正常的控制流,只有當程序真的「不正常」的時候,纔使用異常。反過來說,當程序真正發生錯誤了,一定要使用異常而不是返回一個錯誤代碼,因爲錯誤代碼總是傾向於被忽略。如果要保證一個以返回值來表示錯誤代碼的函數的錯誤正確地向上傳遞,需要在每個調用了可能產生錯誤的函數後面都判斷一下是否發生了錯誤,一旦發生了不可解決的錯誤,就要終止當前函數(並釋放當前函數申請的資源),然後向上傳遞錯誤。這樣一來錯誤處理代碼會被重複地寫好幾遍,十分冗雜,譬如下面代碼:

int func(int n) {
  int fd = open("path/to/file", O_RDONLY);
  if (fd == -1) {
     return ERROR_OPEN;
  }
  int* array = new[n];
  int err;
  err = do_something(fd, array);
  if (err != SUCCESS) {
     delete[] array;
     return err;
  }
  err = do_other_thing();
  if (err != SUCCESS) {
     delete[] array;
     return err;
  }
  err = do_more_thing();
  if (err != SUCCESS) {
     delete[] array;
     return err;
  }
  delete[] array;
  return SUCCESS;
}

對使用異常容易增加函數出口的指控其實是不成立的,因爲即使使用返回值,這些出口也是免不了的,除非程序員有意或無意忽略掉,但異常是不可忽略的。如果你認爲可以把判斷錯誤的if語句縮寫到一行使代碼變得「更清晰」,那麼我只能說是自欺欺人。

有些錯誤幾乎總是可以被立即恢復(譬如前面所說的查找一個字符串不存在的子串,甚至都不能說這是一個「錯誤」),而且返回值本身就傳遞一定信息,就不需要使用異常了。

鑑於C++沒有統一的ABI,並不建議在模塊的接口上使用異常。如果要使用,就要把可能曝露給用戶的異常全部聲明出來,不要把其他類型的異常丟給用戶去處理,尤其是內部狀態——模塊的使用者通常也不會關心模塊內部具體是哪條語句發生錯誤了。

構造函數中的錯誤

有一個相當實際的問題是,如何處理構造函數的錯誤?我們都知道構造函數是沒有返回值的,怎麼辦呢?通常有三種常見的處理方法,標記錯誤狀態使用一個額外的initialize函數來初始化,或者直接拋出異常

合格的C++程序員都知道C++的析構函數中不應該拋出異常,一旦析構函數中的異常沒有被捕獲,整個程序都要被中止掉。於是許多人就對在構造函數中拋出異常也產生了對等的恐懼,寧可使用一個額外的初始化函數在裏面初始化對象的狀態並拋出異常(或者返回錯誤代碼)。這樣做違背了對象產生和初始化要在一起的原則,強迫用戶記住調用一個額外的初始化函數,一旦沒有調用直接使用了其他函數,其行爲很可能是未定義的。

使用初始化函數的惟一好處可能是避免了手動釋放資源(釋放資源的操作交給析構函數來做),因爲C++的一個特點是構造函數拋出異常以後析構函數是不會被調用的,所以如果你在構造函數裏面申請了內存或者打開了資源,需要在異常產生時關閉。但想想看其實並不能完全避免,因爲有些資源可能是要在可能產生錯誤的函數調用過後纔被申請的,還是無法完全避免手工的釋放。

標記錯誤狀態也是一種常見的形式,譬如STL中的ifstream類,當構造時傳入一個無法訪問的文件作爲參數,它不會返回任何錯誤,而是標記的內部狀態爲不可用,用戶需要手工通過is_open()函數來判斷是否打開成功了。同時它還有good()fail()兩個函數,同時也重載了bool類型轉換運算符用於在if語句中判斷。標記狀態的方法在實踐中相當醜陋,因爲在使用前總是需要判斷它是否「真的創建成功了」。

最直接的方法還是在構造函數中拋出異常,它並不會向析構函數中拋出異常那樣有嚴重的後果,只是需要注意的是拋出異常以後對象沒有被創建成功,析構函數也不會被調用,所以應該自行把申請的資源全部都釋放掉。

如何在構造函數中捕獲異常

構造函數與普通函數有一個很不一樣特性,就是構造函數可以有初始化列表,例如下面的代碼:

class B {
 public:
  B(int val) : val_(val * val) {
  }
 private:
  int val_;
};

class A {
 public:
  A(int val) : b_(val) {
    a_ = val;
  }
 private:
  int a_;
  B b_;
};

以上的代碼中A的構造函數的函數體的語句在執行之前會先調用B的構造函數,這時候問題在於,如果B的構造函數拋出了異常,A該如何捕獲呢?一個迂迴的做法是在A中把B的實例聲明爲指針,在構造函數和析構函數中分別創建和刪除,這樣就能捕獲到異常了。不過,實際上是有更簡單的做法的。下面我要介紹一個C++的很不常見的語法:函數作用域級別的異常捕獲。

class B {
 public:
  B(int val) : val_(val * val) {
    throw runtime_error("wtf from B");
  }
 private:
  int val_;
};

class A {
 public:
  A(int val) try : b_(val) {
    a_ = val;
  } catch (runtime_error& e) {
    cerr << e.what() << endl;
    throw runtime_error("wtf from A");
  }
 private:
  int a_;
  B b_;
};

注意上面A的構造函數,在參數列表後和初始化列表前增加了try關鍵字,然後構造函數就被分割爲了兩部分,前面是初始化,後面是初始化時的錯誤處理。需要指出的是,catch塊裏面捕獲到的異常不能被忽略,即catch塊中必須有一個throw語句重新拋出異常,如果沒有,則默認會將原來捕獲到的異常重新拋出,這和一般的行爲是不同的。例如下面代碼運行可以發現A會將捕獲到的異常原封不動拋出:

class A {
 public:
  A(int val) try : b_(val) {
    a_ = val;
  } catch (runtime_error& e) {
    cerr << e.what() << endl;
  }
 private:
  int a_;
  B b_;
};

這種語法是C++的標準,而且目前已經被所有的主流C++編譯器支持(VS2010、g++ 4.2、clang 3.1),所以幾乎不存在兼容性問題,大可放心使用。

其他語言中的錯誤處理

Java傾向於大量使用異常,而且還把異常分爲了兩類分別是檢查型異常(Checked Exception)和非檢查型異常(Unchecked Exception),檢查型異常就是java.lang.Exception的子類,用於報告需要檢查的錯誤,也就是正常的業務邏輯,錯誤主要是由用戶產生的,方便恢復或給出提示,譬如打開不存在的文件。而非檢查型異常則是真正的系統異常,通常由軟件缺陷導致,如數組下標越界、錯誤的類型轉換等,這類異常繼承於java.lang.RuntimeExceptionjava.lang.Error

Python和Java一樣也傾向於使用異常,並不一定真的發生故障纔拋出異常,譬如字符串轉換爲整數,如果字符串不合法,Python會拋出一個ValueError異常。甚至Python的迭代器在調用next()時沒有更多的結果時會拋出StopIteration異常。這是典型的用異常來處理正常控制流的方法,在Python中被廣泛使用。按照優秀C++代碼的標準來看,這是典型的對異常的濫用,既複雜又有額外開銷,不推薦使用,但在Python中這是一個廣泛遵循的約定。

相較於Java和Python,Go的錯誤處理是另一個極端,Go語言則根本沒有異常的概念,而是普遍採用返回值的方式來表示錯誤,同時還提供了panicrecover語法。由於Go有多返回值的特性,避免了錯誤代碼佔用返回結果的弊端,所以你可以經常看到函數的最後一個返回值是error類型。由於總是用返回值傳遞錯誤,你可以看到Go代碼中耦合了大量的錯誤處理,幾乎再每條函數調用語句之後都有一個判斷錯誤是否發生的語句。panicrecover機制十分類似於異常,程序在遇到panic時會一層一層退出調用棧,直到遇到recover。不過recover只在defer中定義,相當於一個函數只有一個recover,而且被recover恢復後會回到錯誤發生處繼續向下執行代碼。Go語言傾向於把一般錯誤都作爲返回值傳遞,除非是非常可怕的、除了重置狀態幾乎無法恢復錯誤纔會被panic語句拋出。

Go語言的recover機制和異常比起來,反倒更像Visual Basic語言中的On Error GoTo labelResume語法。這是一種非結構化的錯誤處理方式,具體是當聲明有On Error GoTo label的函數發生錯誤以後,會調轉到對應的行號,如果再遇到了Resume語句就會返回發生錯誤的語句後面的一條繼續執行,例如下面這段代碼:

Sub ErrorDemo
    On Error GoTo ErrorHandler
    Dim a as Integer
    a = 1/0 ' An error occurs.
    Print a ' Go back here
    Exit Sub

ErrorHandler:
    ' Code that handles errors.
    Resume
End Sub

Visual Basic中還有On Error Resume Next這樣的萬能錯誤處理語句,即遇到錯誤以後直接忽略並繼續執行,這是一種非常危險而且不負責任的做法,但卻可以在早期的Visual Basic代碼中到處看到。事實上用返回值傳遞錯誤代碼的時候許多人也並不處理而是直接忽略,這跟On Error Resume Next本質上沒有什麼區別,卻比On Error Resume Next危害更大——因爲On Error Resume Next至少還有個標記說明「老子就是這麼不負責任」,但忽略錯誤返回值就難以被一眼發現了。

參考閱讀