Dave Abrahams定義了三種安全異常來保證程序:基本保護, 強保證,以及無拋出保證。Herb Sutter在他的Exceptional C++(Addison- Wesley, 2000)一書討論了這些保證。基本保證狀態是指沒有資源洩漏,而且所 有的對象在你的應用程序拋出異常後是可用的。強異常保證是創建在基本保證之 上的,而且添加了一個條件,就是在異常拋出後,程序的狀態不發生改變。無拋 出保證狀態是操作決對不發生失敗,也就是從在某個操作後決不會發生異常。強 異常保證在從異常中恢復和最簡單異常之間最平衡的一個。基本保證一般在 是.Net和C#裡以默認形式發生。運行環境處理托管內存。只有在一種情況下,你 可能會有資源洩漏,那就是在異常拋出時,你的程序占有一個實現了 IDisposable接口的資源對象。原則18解釋了如何在對面異常時避免資源洩漏。
強異常保證狀態是指,如果一個操作因為某個異常中斷,程序維持原狀 態不改變。不管操作是否完成,都不修改程序的狀態,這裡沒有折衷。強異常保 證的好處是,你可以在捕獲異常後更簡單的繼續執行程序,當然也是在你遵守了 強異常保證情況下。任何時候你捕獲了一個異常,不管操作意圖是否已經發生, 它都不應該開始了,而且也不應該做任何修改。這個狀態就像是你還沒有開始這 個操作行為一樣。
很多我所推薦的方法,可以更簡單的幫助你來確保進 行強異常保證。你程序使用的數據元素應該存為一個恆定的類型(參見原則6和原 則7)。如果你組並這兩個原則,對程序狀態進行的任何修改都可以在任何可能引 發異常的操作完成後簡單的發生。常規的原則是讓任何數據的修改都遵守下面的 原則:
1、對可能要修改的數據進行被動式的拷貝。
2、在拷貝的 數據上完成修改操作。這包括任何可能異常異常的操作。
3、把臨時的拷 貝數據與源數據進行交換。 這個操作決不能發生任何異常。
做為一個例 子,下面的代碼用被動的拷貝方式更新了一個雇員的標題和工資 :
public void PhysicalMove( string title, decimal newPay )
{
// Payroll data is a struct:
// ctor will throw an exception if fIElds aren't valid.
PayrollData d = new PayrollData( title, newPay,
this.payrollData.DateOfHire );
// if d was constructed properly, swap:
this.payrollData = d;
}
有些時候,這種強保證只是效率很低而不被支持,而 且有些時候,你不能支持不發生潛在BUG的強保證。開始的那個也是最簡單的那 個例子是一個循環構造。當上面的代碼在一個循環裡,而這個循環裡有可能引發 程序異常的修改,這時你就面臨一個困難的選擇:你要麼對循環裡的所有對象進 行拷貝,或者降低異常保證,只對基本保證提供支持。這裡沒有固定的或者更好 的規則,但在托管環境裡拷貝堆上分配的對象,並不是像在本地環境上那開銷昂 貴。在.Net裡,大量的時間都花在了內存優化上。我喜歡選擇支持強異常保證, 即使這意味要拷貝一個大的容器:獲得從錯誤中恢復的能力,比避免拷貝獲得小 的性能要劃算得多。在特殊情況下,不要做無意義的拷貝。如果某個異常在任何 情況下都要終止程序,這就沒有意義做強異常保證了。我們更關心的是交換引用 類型數據會讓程序產生錯誤。考慮這個例子:
private DataSet _data;
public IListSource MyCollection
{
get
{
return _data;
}
}
public void UpdateData( )
{
// make the defensive copy:
DataSet tmp = _data.Clone( ) as DataSet;
using ( SqlConnection myConnection =
new SqlConnection( connString ))
{
myConnection.Open();
SqlDataAdapter ad = new SqlDataAdapter( commandString,
myConnection );
// Store data in the copy
ad.Fill( tmp );
// it worked, make the swap:
_data = tmp;
}
}