1. 程式人生 > >Java基礎系列(三十二):斷言 + 日誌入門

Java基礎系列(三十二):斷言 + 日誌入門

斷言

在Java語言中,給出了3種處理系統錯誤的機制:

  1. 丟擲一個異常
  2. 日誌
  3. 使用斷言

那我們應該在什麼情況下去使用斷言呢?

  1. 斷言失敗是致命的,不可恢復的錯誤
  2. 斷言失敗只用於開發測試階段。

不應該使用斷言向程式的其他部分通告發生了可恢復性的錯誤,或者,不應該作為程式向用戶通告問題的手段,斷言只應該用於在測試階段確定程式內部的錯誤資訊。

在一個具有自我保護能力的程式中,斷言很常用,假如確信某個屬性符合要求,並且程式碼的執行非常的依賴這個屬性,比如:

double a =
Math.sqrt(x);

我們在這裡確信x必須是一個正值,因為它是另一個計算的得出的非負結果,或者是某一個方法的引數,而這個方法要求它的呼叫者只能提供一個正整數。但是為了以防萬一,我們還是會對這個引數進行檢查:

if(x < 0) {
    throw new IllegalArgumentException("x < 0");
}

但是,有一個問題就是,這段程式碼會一直保留在程式中,即使測試完畢也不會自動的進行刪除,如果程式中含有大量的這樣的檢查,會嚴重的影響程式的執行速度。

而斷言機制允許在測試期間向程式碼中插入一些檢查語句。當代嗎釋出的時候,這些插入的檢測語句將會自動地移走。

在Java中,斷言有兩種語法形式:

assert 條件;
assert 條件:表示式;

這兩種形式都會對條件進行檢測,如果結果為false,就會丟擲一個AssertionError異常。在第二種形式中,表示式將會傳入AssertionError的構造器,並轉換成一個訊息字串。

在上述的程式中,如果我們想使用斷言:

assert x >= 0;
//或者將x的實際值傳給AssertionError物件
assert x >= : x;

但是在預設情況下,斷言是被禁用的,我們可以通過在執行程式的時候輸入引數來選擇啟用:

java -ea MyApp
//or
java -enableassertions MyApp

啟動和禁用斷言的時候不用重新編譯程式,它是類載入器的功能,當斷言被禁用的時候,類載入器將會跳過斷言程式碼,所以,不會降低程式的執行速度。

同樣的,我們也可以在某個類或整個包中使用斷言,比如:

java - ea:MyClass -ea:com.viyoung... MyApp

這個命令將會開啟MyClass類以及在com.viyoung包和它的子包中的所有類的斷言。
選項 -ea 將會開啟預設包中所有類的斷言。
也可以使用選項 -disableassertions 或 -da 禁用某個特定類或包的斷言:

java -ea: ... -da:MyClass MyApp

有些類不是由類載入器載入,而是直接由虛擬機器載入。可以使用這些開關有選擇的啟用或禁用那些類的斷言。

然而,啟用和禁用所有斷言的 -ea 和 -da 開關不能應用到那些沒有類載入器的“系統類”上,對於這些系統類來說,需要使用 -enablesystemassertions/-esa 開關啟用斷言。

斷言和日誌的區別在於,斷言是一種測試和除錯階段使用的戰術性工具;而日誌記錄是一種在程式的整個生命週期都可以使用的策略性工具

記錄日誌

說起日誌,大家可能會有點陌生,尤其是剛剛接觸Java不久的初級程式設計師,我們在學習初期進行除錯程式的時候回插入一些System.out.println方法來幫助我們對程式的執行狀況進行一個把控和分析,但是如果說,我們解決了這個問題,就需要把這些語句從我們的程式碼中及時的刪除,當遇到其他問題的時候,則需要再次新增,然後解決後再刪除,Java中內建了一個包叫做:java.util.logging包中,在這個包中提供了一系列的API來解決我們的痛點,這些API的優點有許多:

  1. 可以很容易地取消全部日誌記錄,或者僅僅取消某個級別的日誌,而且開啟和關閉這個操作也是很容易的。
  2. 可以很簡單地禁止日誌記錄的輸出,因此,將這些日誌程式碼留在程式的開銷很小。
  3. 日誌記錄可以被定向到不同的處理器,用於在控制檯中顯示,用於儲存在檔案中等。
  4. 日誌記錄器和處理器都可以對記錄進行過濾。過濾器可以根據過濾實現器制定的標準丟棄那些無用的記錄項。
  5. 日誌記錄可以採用不同的方式格式化,例如,純文字或XML。
  6. 應用程式可以使用多個日誌記錄器,它們使用類似包名的這種具有層次結構的名字,例如,com.viyoung.myapp。
  7. 在預設情況下,日誌系統的配置由配置檔案控制。如果需要的話,應用程式可以替換這個配置。

基礎日誌

如果只是想生成一個簡單的日誌記錄,可以使用全域性日誌記錄器(global logger)並呼叫其info方法:

Logger.getGloabal().info("This is a Logger Info");

他會在控制檯上打印出:

INFO:This is a Logger Info

如果在適當的地方呼叫

Logger.getGlobal().setLevel(Level.OFF)

會取消所有的日誌。

高階日誌

上面的日誌在我們日常的開發中是不常見的,在一個專業的應用程式中,不要講所有的日誌都記錄到一個全域性日誌記錄器中,而是可以自定義日誌記錄器。

可以呼叫Logger類的getLogger()方法獲取記錄器:

private static final Logger myLogger = Logger.getLogger("com.viyoung.myapp");

未被任何變數引用的日誌記錄器都可能會被垃圾回收,為了防止這種情況的發生,所以要用一個靜態變數儲存日誌記錄器的一個引用。

與包名類似,日誌記錄器名也具有層次結構,而且與包名相比,日誌記錄器的層次結構更強,如果你對某個包設定了日誌級別,那麼它的子記錄器會去繼承這個級別。

通常來說,存在以下7個日誌記錄器級別:

1. SEVERE
2. WARINING
3. INFO
4. CONFIG
5. FINE
6. FINER
7. FINEST

通常來說,只會記錄前三個級別,但是也可以設定其他的級別。比如:

logger.setLevel(Level.FINE);

當然,我們還可以使用Level.ALL開啟所有級別的記錄,或者使用Level.OFF關閉所有級別的記錄。

對於所有級別有下面幾種記錄方法:

logger.warning(message);
logger.fine(message);

同時,還可以使用log方法指定級別,例如:

logger.log(Level.FINE, message);

預設的日誌配置記錄了INFO或更高級別的所有記錄,因此,應該使用CONFIG、FINE、FINER和FINESET級別來記錄那些有助於診斷,但對於程式設計師又沒有太大意義的除錯資訊。

預設的日誌記錄將顯示包含日誌呼叫的類名和方法名,如同堆疊所顯示的那樣,但是如果虛擬機器對執行過程進行了優化,就會導致獲取不到準確的呼叫資訊,這時我們可以使用logp方法獲得呼叫類和方法的確切位置:

void logp(Level l, String className, String MethodName, String message);

以及有一些用來追蹤執行流的方法:

// 記錄一個方法條目
void entering(String className, String methodName);
//記錄一個方法條目,帶有一個引數。
void entering(String sourceClass, String sourceMethod, Object param1) 
// 記錄一個方法條目,帶有一組引數。
void entering(String sourceClass, String sourceMethod, Object[] params) 
//記錄一個方法返回。
void exiting(String sourceClass, String sourceMethod) 
// 記錄一個方法返回,帶有結果物件。         
void exiting(String sourceClass, String sourceMethod, Object result)     

記錄日誌的常見用途是記錄那些不可預測的異常,可以使用下面的兩個方法提供日誌記錄中包含的異常描述內容:

//正丟擲異常的記錄。
 void throwing(String sourceClass, String sourceMethod, Throwable thrown) 
//記錄帶有相關的可丟擲資訊的訊息。
void log(Level level, String msg, Throwable thrown)         

公眾號

掃碼或微信搜尋 Vi的技術部落格,關注公眾號,不定期送書活動各種福利~

image