【參考】assert 斷言

assert 斷言

C1 - C 斷言 nototon

  在程序設計中,斷言(assertion)是一種放在程序中的一階邏輯(如一個結果爲真或是假的邏輯判斷式),目的是爲了標示與驗證程序開發者預期的結果-當程序運行到斷言的位置時,對應的斷言應該爲真。若斷言不會真時,程序會中止運行,並出現錯誤信息。
  例如,以下的程序包括二個斷言:

x := 5;
{x > 0}
x := x + 1
{x > 1}

  x>0x>1,當程序運行到二個斷言對應的位置時,斷言的內容均爲真。程序設計者可以用斷言來標示程序,提供程序正確性的相關信息。例如在一段程序前加入斷言(先驗條件),說明這段程序運行前預期的狀態。或在一段程序後加入斷言(後驗條件),說明這段程序運行後預期的結果。
  以下的示例使用東尼·霍爾在1969年論文中提出的斷言標示方式:

x = 5;
x = x + 1;
// {x > 1}


  以上註解中的大括號表示爲斷言,不是一般的註解。現有的主流編程語言不支持上述的標示方式,不過程序設計者仍可以用註解的方式標示斷言,標示此時應該成立的條件,只是此斷言沒有檢查機能,不成立時程序不會中止運行。
  許多現代的編程語言已支持有檢查機能的斷言,可能是運行期或其他時間檢查的陳述。若在運行期中有斷言不成立,會出現斷言失敗,一般會停止程序的運行。程序設計者可以專注在斷言失敗,邏輯不一致的部份,並設法修正。
  斷言的使用有助於程序設計者設計、開發及理解程序。

用途

  在開發Eiffel語言的程序時,斷言是設計過程中的一部份。像C語言或Java等編程語言,主要在運行期檢查斷言是否正確,也可以用靜態斷言的方式,在編譯期檢查斷言。不論是哪一種情形,都可以檢查斷言的有效性,也可以關閉斷言檢查的機能。

契約式設計中的斷言

  斷言可以當做是一種軟件的文件:斷言可以描述程序運行前預期的情形(先驗條件)以及運行後的預期的結果(後驗條件)。斷言也可以描述類型中的不變量。Eiffel編程語言將斷言和程序集成,也可以自動將程序中的斷言抽出,形成程序的文件。這是契約式設計的重要特性。以下是一個Eiffel語言的程序,其中require及ensure分別標示程序的先驗條件及後驗條件。

 set_hour (a_hour: INTEGER)
            -- Set `hour' to `a_hour'
        require
            valid_argument: a_hour >= 0 and a_hour <= 23
        do
            hour := a_hour
        ensure
            hour_set: hour = a_hour
        end

  斷言可以用敘述的方式放在程序中,也可以用註解的方式標示。不過斷言若用註解的方式標示,在斷言和程序未同步更新時比較容易讓設計者發現問題。有斷言敘述的程序會可以在每次程序運行時,自動檢查斷言是否成立,若斷言不成立時,就會輸出錯誤信息。因此避免了程序和斷言未同步更新的問題。

運行期檢查的斷言

  在程序運行時,可以用斷言檢查程序開發時的假設,確認這些假設是否成立。考慮以下的Java程序:

 int total = countNumberOfUsers();
 if (total % 2 == 0) {
     // total is even
 } else {
     // total is odd and non-negative
     assert(total % 2 == 1);
 }

  在Java語言中,%爲餘數的運算符,若其第一個運算對象爲負,其結果也爲負值。程序設計者假設total爲非負值,因此除以2的餘數只可能是0或是1,使用斷言可以使這個假設變得明確,若countNumberOfUsers傳回負值,程序會出現錯誤
  此作法的最大好處是當程序出現錯誤時,程序立刻停止運行,而不會在較晚的時間才中止運行,而斷言錯誤時會回報錯誤代碼的位置,因此程序設人計者可以很快找到錯誤所在的位置。
  斷言有時也會放在程序未預期會運行到的部份。例如,若switch敘述中所有可能的狀態都有對應的case敘述,不會運行到default敘述,此時可以在default敘述中加入一個條件不會成立的斷言,當程序運行到default敘述時會立刻停止運行,而不會使程序繼續在一個錯誤的狀態下運行。
  Java語言在1.4版以後支持斷言機能,若程序運行時有設置對應旗標,斷言錯誤時會產生AssertionError,若未設置對應旗標,則會省略斷言敘述。C語言的斷言是在標準的開頭文件assert.h中定義,其斷言 assert (assertion) 是一個宏,若條件不成立時產生錯誤,並且中止程序運行。C++語言的斷言需配合開頭文件cassert,不過有些C++的庫也開頭文件assert.h。
  上述斷言的缺點是可能有改變存儲器數據或是線程時序的風險,因此需小心的處理斷言,確認斷言在程序中沒有其他的副作用。
  程序結構中的斷言有助於在不使用第三方程式庫的情形下,應用測試驅動開發的開發方式。

在開發過程中使用斷言

  在軟件開發過程中,程序設計者一般會在斷言有效的情形下運行或測試程序。若出現斷言錯誤,程序設計者會立刻注意到此問題。許多斷言的實現方式會中止程序的運行,這功能方便程序設計者處理問題,若在錯誤出現後程序繼續運行,往往更不容易找到有問題程序的位置。斷言錯誤一般也會顯示一些信息,例如錯誤程序的位置,也許包括堆棧追蹤,若環境支持核心文件或是在調試工具中運行,可能還可以提供程序的完整狀態,程序設計者可以配合這些信息來修正程序的錯誤,是在除錯過程中很有利的工具。

靜態斷言

  只在編譯期間檢查的斷言稱爲靜態斷言,靜態斷言必需配合清楚的註解說明。
  在編譯期的模板元編程時,靜態斷言特別有用。靜態斷言也可以用在C語言的程序中,當斷言不成立時,產生有錯誤的代碼。例如以下就是一個定義靜態斷言的方法:

#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}

COMPILE_TIME_ASSERT( BOOLEAN CONDITION );


  若(BOOLEAN CONDITION)的成果不爲真,上述程序中會有二個相同的case標記,因此編譯器會出現錯誤。此處的布林表達式是要在編譯階段就可以確定的表達式,例如(sizeof(int)==4)等。此結構無法放在函數範圍以外,因此若要運行此斷言時,必需將此斷言放在一個函數以內。
  另一個常使用的靜態斷言方式如下:

static char const static_assertion[ (BOOLEAN CONDITION)
                                    ? 1 : -1
                                  ] = {'!'};

  若(BOOLEAN CONDITION)的成果不爲真,由於無法定義長度爲負值的數組,因此編譯器會出現錯誤。即使編譯器真的允許負值長度,後續的初始化字符應該也會使編譯器出現錯誤信息。此處的布林表達式是要在編譯階段就可以確定的表達式,例如(sizeof(int)==4)等。
  上述的方式都需要唯一不重複的名稱。現代的編譯器支持COUNTER的預處理器定義,在每次使用到此定義時,回傳一個每次會加一的數值,因此可以產生唯一不重複的名稱[3]。
  C11 (程序標準版本)及C++11可以用static_assert支持靜態斷言。  

關閉斷言機能

  大部份的編程語言可以用設置啓動或關閉所有的斷言,有時也可以啓動或關閉個別的斷言。一般會在開發過程中啓動斷言,在最後測試完成,要發佈正式版本給客戶時會關閉斷言。在不考慮斷言副作用的前提下,關閉斷言不作檢查可以節省評估斷言需要的代碼,在正常情形下程序仍有相同的結果。在異常的情形下,關閉斷言檢查會使得原來可能會停止的程序可以繼續運行,有時這會是比較可接受的結果。
  C語言及C++可以利用預處理器在編譯階段完全關閉斷言。Java語言若要啓動斷言,需要在運行期引擎中設置特定選項,若選項未設置,不會啓動斷言,不過斷言仍存在程序中,只有在用在運行期用JIT編譯器中進行優化,或是在編譯期使用if(false)的條件,才能排除斷言運行。

和錯誤處理的比較

  有必要去區分斷言和錯誤處理程序的差異。斷言只用在標示一些邏輯上不可能或不應該出現的情形,若上述情形真的出現時,表示有些基本架構已出現問題。這和錯誤處理不同,大部份錯誤的條件都是有可能出現的,只是實際上出現頻率很低。斷言不宜作爲通用的錯誤處理程序,因爲斷言一般會中止程序的運行,程序無法恢復到沒有錯誤發生前的情形,而且斷言顯示的信息只對程序設計者有幫助,對用戶的幫助不大。
  考慮以下用斷言處理錯誤的程序。

 int *ptr = malloc(sizeof(int) * 10);
  assert(ptr);
  // use ptr 
  ...

  操作系統不保證每一次運行malloc都會成功,若無法配置到存儲器,malloc會傳回NULL pointer,程序會立刻中止運行。
  比較理想的錯誤處理方式是配置到存儲器時另外由程序處理。例如服務器可能有數個客戶端,可能有一些資源保留,尚未釋放,也可能有一些修改尚未寫入數據庫。此時較好的處理方法只讓單一的交易失敗,出現錯誤信息,而不是直接中止程序的運行。

自我總結

幹什麼用

  斷言主要是用來判斷程序是否按照預想的邏輯運行,若不是,便終止程序,並顯示錯誤信息。

優缺點

  好處是當程序出現錯誤時,程序立即停止運行,而不會在較晚的時間才終止運行,而斷言錯誤時會回報錯誤代碼的位置,因此程序設計者可以很快找到錯誤所在的位置。
  缺點就是有可能會改變存儲器數據或是線程時序的風險,因此需要小心的處理斷言,確認斷言在程序中沒有其他的副作用。

使用

  1. 運行期檢查的斷言
  2. 開發過程中使用斷言
  3. 靜態斷言
  4. 關閉斷言技能
      大部分的編輯語言可以用設置啓動或關閉所有的斷言,有事也可以啓動或關閉個別斷言。
  5. 和錯誤處理的比較

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章