使用斷言可以創建更穩定,品質更好且不易於出錯的代碼。當需要在一個值為FALSE時中斷當前操作的話,可以使用斷言。
單元測試必須使用斷言(Junit/JunitX)。
除了類型檢查和單元測試外,斷言還提供了一種確定各種特性是否在程式中得到維護的極好的方法。
使用斷言使我們向按契約式設計更近了一步。
斷言特性
前置條件斷言:代碼執行之前必須具備的特性
後置條件斷言:代碼執行之後必須具備的特性
前後不變斷言:代碼執行前後不能變化的特性
使用方式
斷言可以有兩種形式
1.assert Expression1
2.assert Expression1:Expression2
其中Expression1應該總是一個布爾值,Expression2是斷言失敗時輸出的失敗訊息的字元串。如果Expression1為假,則拋出一個 AssertionError,這是一個錯誤,而不是一個異常,也就是說是一個不可控制異常(unchecked Exception),AssertionError由於是錯誤,所以可以不捕獲,但不推薦這樣做,因為那樣會使你的系統進入不穩定狀態。
java斷言
斷言在默認情況下是關閉的,要在編譯時啟用斷言,需要使用source1.4標記 即javac source1.4 Test.java ,在運行時啟用斷言需要使用 -ea參數 。要在系統類中啟用和禁用斷言可以使用 -ea和 -dsa參數。
例如:
public class AssertExampleOne{ public AssertExampleOne(){} public static void main(String args[]){ int x=10; System.out.println("Testing Assertion that x==100"); assert x==100:"Out assertion failed!"; System.out.println("Test passed!"); }}
如果編譯時未加 -source1.4,則編譯通不過
在執行時未加 -ea 時輸出為
Testing Assertion that x==100
Test passed!
jre忽略了斷言的舊代碼,而使用了該參數就會輸出為
Testing Assertion that x==100
Exception in thread "main" java.lang.AssertionError: Out assertion failed!
at AssertExampleOne.main(AssertExampleOne.java:6)
斷言的副作用
由於程式設計師的問題,斷言的使用可能會帶來副作用 ,例如:
boolean isEnable=false;
assert isEnable=true;
這個斷言的副作用是因為它修改了程式中變數的值並且未拋出錯誤,這樣的錯誤如果不細心的檢查是很難發現的。但是同時我們可以根據以上的副作用得到一個有用的特性,根據它來測試斷言是否打開。
public class AssertExampleTwo{ public static void main(String args[]){ boolean isEnable=false; assert isEnable=true; if(isEnable==false){ throw newRuntimeException("Assertion should be enable!"); } }}
斷言常見使用方式
1.可以在預計正常情況下程式不會到達的地方放置斷言 :assert false
2.斷言可以用於檢查傳遞給私有方法的參數。(對於公有方法,因為是提供給外部的接口,所以必須在方法中有相應的參數檢驗才能保證代碼的健壯性)
3.使用斷言測試方法執行的前置條件和後置條件
4.使用斷言檢查類的不變狀態,確保任何情況下,某個變數的狀態必須滿足。(如age屬性應大於0小於某個合適值)
不用斷言
斷言語句不是永遠會執行,可以禁止也可以啟用
因此:
1.不要使用斷言作為公共方法的參數檢查,公共方法的參數永遠都要執行
2.斷言語句不可以有任何邊界效應,不要使用斷言語句去修改變數和改變方法的返回值.
C里的宏
宏名: assert
功 能: 測試一個條件並可能使程式終止
用 法: void assert(int test);
程式例:
#include<assert.h>#include<stdio.h>#include<stdlib.h>struct ITEM{ int key; int value;};/*add item to list,make sure list is not null*/void additem(struct ITEM* itemptr){ assert(itemptr!=NULL); /*additemtolist*/}int main(void){ additem(NULL); return 0;}
assert() 宏用法
assert宏的原型定義在<assert.h>中,其作用是如果它的條件返回錯誤,則終止程式執行,原型定義:
#defineassert(expr)\((expr)\?__ASSERT_VOID_CAST(0)\:__assert_fail(__STRING(expr),__FILE__,__LINE__,__ASSERT_FUNCTION))/*DefinedInGlibc2.15*/
assert的作用是先計算表達式
expr,如果其值為假(即為0),那么它會列印出來assert的內容和__FILE__, __LINE__, __ASSERT_FUNCTION,然後執行abort()函式使kernel殺掉自己並coredump(是否生成coredump檔案,取決於系統配置);否則,assert()無任何作用。宏assert()一般用於確認程式的正常操作,其中表達式構造無錯時才為真值。完成調試後,不必從原始碼中刪除assert()語句,因為宏NDEBUG有定義時,宏assert()的定義為空。
請看下面的程式清單badptr.c:
#include<stdio.h>#include<assert.h>#include<stdlib.h>int main(void){ FILE* fp; fp=fopen("test.txt","w");//以可寫的方式打開一個檔案,如果不存在就創建一個同名檔案 assert(fp);//所以這裡不會出錯 fclose(fp); fp=fopen("noexitfile.txt","r");//以唯讀的方式打開一個檔案,如果不存在就打開檔案失敗 assert(fp);//所以這裡出錯 fclose(fp);//程式永遠都執行不到這裡來 return 0;}
[root@localhost error_process]# gcc badptr.c
[root@localhost error_process]# ./a.out
a.out: badptr.c:14: main: Assertion `fp' failed.
如果使用動態連結libc,那么除了__FILE__, __LINE__, __ASSERT_FUNCTION會讓目標變的稍稍大了一點,並不會因為多次使用assert()增加目標很多。不過好處也很明顯,就是會在assert的地方會列印出來檔案名稱,行數,和函式名。另外,要注意用assert()的錯誤程度。如果assert()的條件fail了,那么會調用abort()函式讓kernel殺掉自己,哪怕用戶自己重新註冊了SIGABRT信號的行為(abort()會先向自己傳送信號SIGABRT保證用戶的handler正確執行,然後修改SIGABRT信號的行為為默認行為coredump,再次像自己傳送SIGABRT,coredump)。
在調試結束後,可以通過在包含#include <assert.h>的語句之前插入 #define NDEBUG 來禁用assert調用,示例代碼如下:
#include <stdio.h>
#define NDEBUG
#include <assert.h>
用法總結與注意事項:
1)在函式開始處檢驗傳入參數的合法性
如:
int resetBufferSize(int nNewSize)
{
//功能:改變緩衝區大小,
//參數:nNewSize緩衝區新長度
//返回值:緩衝區當前長度
//說明:保持原信息內容不變nNewSize<=0表示清除緩衝區
assert(nNewSize >= 0);
assert(nNewSize <= MAX_BUFFER_SIZE);
}
2)每個assert只檢驗一個條件,因為同時檢驗多個條件時,如果斷言失敗,無法直觀的判斷是哪個條件失敗
/***不好***/assert(nOffset>=0 && nOffset+nSize<=m_nInfomationSize);
/****好****/assert(nOffset >= 0);
assert(nOffset+nSize <= m_nInfomationSize);
3)不能使用改變環境的語句,因為assert只在DEBUG生效,如果這么做,會使用程式在真正運行時遇到問題
錯誤: assert(i++ < 100);
這是因為如果出錯,比如在執行之前i=100,那么這條語句就不會執行,那么i++這條命令就沒有執行。
正確: assert(i < 100);
i++;
4)assert和後面的語句應空一行,以形成邏輯和視覺上的一致感
5)有的地方,assert不能代替條件過濾
注意:當對於浮點數:
#include<assert.h>
float pi=3.14f;
assert (pi==3.14f);
在switch語句中總是要有default子句來顯示信息(Assert)。
int number = SomeMethod();
switch(number){
case 1: Trace.WriteLine("Case 1:");
break;
case 2: Trace.WriteLine("Case 2:");
break;
default : Debug.Assert(false);
break;
}