2009年12月2日 星期三

assert(I'm loving it);

我很推崇在code裡加assert,可以讓問題及早被發現,也可以透過assert與client溝通。
廢話不多說,讓我先舉幾個assert常見的用法。

參數檢查

例如有個設定月份的method,所以月份的範圍理應是1到12。所以assert可以用來幫忙檢查傳進來的參數是否合理,如List 1
List 1 - Checks parameter
void setMonth(unsigned char month) 
{
 assert(month > 0);
 assert(month <= 12);
 if (month > 0 && month <= 12) {
  m_month = month;
 }
}
這裡有幾個重點要提醒,有人會想到,既然月份的範圍是1到12,那為何不寫成assert(month > 0 && month <= 12); 一行就好?我是不建議這樣寫,原因是當assertion fail的時候debugger通常會停在該行,但像這樣混在一起寫,你就比較難分出到底是那一條件不成立,當然還是有辦法,只不過要多一些步驟,所以我建議還是分開寫,比較清楚。
另外,還有人會認為既然有加assert了,為何還要加if (month > 0 && month <= 12)? 這裡有個觀念很重要,通常assert是debug版本才會有作用(當然這要看語言與環境,不過通常都是這樣),所以List 1中 line 3 與 line 4 在release版根本是空白的,如果沒有if的保護,程式還是有可能落入一個不預期的狀態。

Switch Case

假設有個function在處理command,然而"目前"只有三個command定義如List 2
List 2 - Command ID
typedef enum _CommandID_ {
  command_attack = 0,
  command_heal,
  command_run
} CommandID;
如果Command ID是由我定義,而處理command的function(如List 3)是你寫的,你可以在switch case的地方加個assert去做個例外的警告。所以當我多加一個command ID時,而你因某種原因忘記多加一個case去處理他,這時候assert就可以發生作用,讓我們知道這裡有一個例外發生了,不用多花時間去trace。
List 3 - Switch case
void processCommand()
{
 CommandID cmd; 
 while (_commandQueue.count() > 0) {
  cmd = _commandQueue[0];
  switch (cmd) {
   case command_attack:
   // attack
   break;
   case command_heal:
   // heal
   break;
   case command_run:
   // run
   break;
   default:
    assert(!"unknown command ID");
   break;
  }
  _commandQueue.erase(_commandQueue.begin());
 }
}
時間的關係,這篇先到此,有機會再多舉幾個例子。

沒有留言:

張貼留言