優雅簡潔但是錯誤的程式碼

不能僅僅因為看不到錯誤處理流程,就認為錯誤不存在。

有一本C#程式設計的書中對異常的進行了”高度評價”,下面是該書中的例子程式碼,我們來看看:

優雅簡潔但是錯誤的程式碼

點選載入圖片

瞧瞧,這程式碼是多麼優雅和簡潔,異常確實是個好東西。

嗯,的確是非常簡潔,也非常優雅,但是它是錯誤的。

我們假設在CreateIndexes中出現了異常。GenerateDatabase函式不會捕獲這個異常,接下里這個異常會向上層傳遞。

但是當異常離開GenerateDatabase函式時,重要資訊丟失了:

資料庫

建立狀態。捕獲異常的程式碼不知道資料庫建立的哪一步失敗。是否需要

刪除

索引?是否需要刪除表?是否需要刪除物理資料庫?它啥也不知道。

因此,如果呼叫CreateIndexes時遇到問題,則會永久洩漏物理資料庫檔案和表。(由於這些檔案大概是磁碟上的檔案,因此它們會無限期地被系統掛起。)

從某種意義上說,在異常模型中編寫正確的程式碼比在錯誤程式碼模型中編寫困難,因為任何事情都可能失敗,並且你必須為此做好準備。在錯誤程式碼模型中,當你必須檢查錯誤時很明顯:當你獲得錯誤程式碼時。在異常模型中,你只需要知道錯誤可能在任何地方發生。

換句話說,在錯誤程式碼模型中,很明顯有人無法處理錯誤:他們沒有檢查錯誤程式碼。但是在丟擲異常的模型中,不太容易看出是否有人處理了錯誤,因為異常不是顯式的。

讓我們看看下面的例子程式碼:

優雅簡潔但是錯誤的程式碼

點選載入圖片

在程式碼中,我們建立了一個Guy物件,然後將他新增到參賽聯盟,然後隨機地將他加入到一個參賽組中。我們如何簡化這段程式碼呢?

記得:每一行程式碼都可能發生錯誤

假設執行”new Guy(name)”時丟擲異常?

丟擲這個異常時,我們還沒有做其他的動作,所以,問題不大。

假設執行”AddToLeague(guy)”時丟擲異常?

我們建立的guy物件會被拋棄,而稍後垃圾回收(GC)程式會清理這個物件。

假設執行”guy。Team = ChooseRandomTeam”時丟擲異常?

呃,現在我們有麻煩了。我們已經將該人加入了聯盟。如果有人捕獲了這個異常,他們將在聯盟中找到一個不屬於任何球隊的人。如果某些程式碼遍歷了聯盟的所有成員並使用了guy。Team成員,則由於guy。Team尚未初始化,他們將收到NullReferenceException異常。

在編寫程式碼時,如果每行程式碼都引發異常會帶來什麼後果?如果你打算編寫正確的程式碼,則必須執行以下操作。

我們看看下面的程式碼,透過重新排列呼叫的順序來解決問題:

優雅簡潔但是錯誤的程式碼

點選載入圖片

這種看似微不足道的更改對錯誤恢復有很大影響。透過延遲操作(將人員加入聯盟),在人員構造期間採取的任何異常都不會產生任何持久影響。所有發生的事情是,一個部分構造的物件被拋棄了,最終被GC清理了。

一般設計原則:在資料準備就緒之前,不要提交資料

當然,此示例非常簡單,因為設定該人員的步驟沒有副作用。如果在設定過程中出現問題,我們可以拋棄這個物件,讓GC進行清理。

在真實世界中,事情會變得複雜起來。我們看看下面的程式碼:

優雅簡潔但是錯誤的程式碼

點選載入圖片

以上程式碼和我們修正過的版本是一樣的,唯一的不同時,有人認為,如果每個團隊保留一份成員列表,效率會更高,因此你必須將自己新增到要加入的團隊中。這對功能的正確性有什麼影響?

總結

透過返回錯誤程式碼的方式來表達一項操作失敗了,是比較簡單和直觀的,但有些朋友可能會忘記檢查每個呼叫的返回值。

所以,我還是比較喜歡異常這種編碼模型,因為你很難忽略它。

最後

Raymond Chen的《The Old New Thing》是我非常喜歡的部落格之一,裡面有很多關於Windows的小知識,對於廣大Windows平臺開發者來說,確實十分有幫助。

本文來自:《Cleaner, more elegant, and wrong》

優雅簡潔但是錯誤的程式碼

點選載入圖片

相關文章