良好程式碼的優點大同小異。
不好的程式碼的糙點卻各有巧妙之處。
很少人用過吧?
但是真正製作產品上,傳聞寫 try-catch 的 code 比寫正常邏輯還多是正常的。
先介紹 throw 和 Error 的用法
function willHappenError() {
  //...
  throw Error("this is my error")
  //...
}
willHappenError ();
執行順序
當發生 exception (Error) 時,執行順序,就不是我們一般熟知那樣的順序,而是有點像是「有某種特定規律的 goto() 」的執行方式。

willHappenError 後,就執行第 7 行willHappenError function 定義willHappenError function 內容throw 將即將 throw 的物件產生並且 throw 出目前的 function scope。由於呼叫 willHappenError 的 scope (global)沒有任何補捉機制,所以會自動再丟往 global 通知瀏覽器強制停止、顯示錯誤。
執行結果

注意
在 C++ 這種沒有垃圾搜集機制的語言中。
拋出來的會是 Exception 物件,而不是 Exception 指標。
function happenError() {
  console.log(2)
  throw Error("this is my error")
  console.log(3)
}
try {
    console.log(1)
    happenError ();
    console.log(4)
}
catch(e) {
  console.log(5)
  console.log(e.message)
  console.log(6)
}
finally {
  console.log(7)
}
console.log(8)
執行順序

willHappenError 後,就執行第 7 行willHappenError function 定義willHappenError function 內容throw 將即將 throw 的物件產生並且 throw 出目前的 function scope。willHappenError 的 scope (global) 有補捉機制 try-catch 所以會跳到 catch 並且執行裡面的 code執行結果

看到了嗎?這次就 safe 多了,程式都有執行,並且沒有出現紅字。
只要在 try 區塊中,發生 exception 就會執行 finally block 裡的 code。
通常順序會是 catch 區塊執行結束,才會執行 finally 而且一定會搭配使用。
記得 過度依賴前置處理器 提過的 MACRO (前置處理器) 的寫法嗎?
絕對不要拿來裝 try-catch 原因如下
這些建議不適用於 C 語言
絕對不要用 MARCO 把 try 和 catch 分開裝起來。
設計
#define START()\
  try {\
#define END()\
  }\
    catch(exception& e) {\
    log("Occur exception");\
    return ;\
  }\
    catch(...) {\
    log("Occur exception");\
    return ;\
  }\
實際使用
使用上其實和一般使用的習慣不同。
void function () {
  START()
  // do something...
  END()
}
要加分號嗎?
加了是不是算結束的語意?
void function () {
  START();
  // do something...
  END();
}
有一本書,針對例外處理的程式碼該如何編排做了相當的研究,不得不大推一下 《笑談軟體工程:例外處理設計的逆襲》
同作者的部落格中也有介紹幾個 try-catch 的壞味道[1]
- Return Code(回傳碼)
- Ignored Checked Exception(忽略受檢例外)
- Ignored Exception(忽略例外)
- Unprotected Main Program(未被保護的主程式)
- Dummy Handler(虛設的處理者)
- Nested Try Statement(巢狀Try敘述)
- Spare Handler(備胎)
- Careless Cleanup(粗心的資源釋放)
非常棒的書。推薦買來認識「不糙的例外處理,如何寫」
這篇就介紹一下我自己遇到的案例與正確的認識吧。
再拿 過度依賴前置處理器 提過的 code
設計
#define BEGIN_TRANSACTION(transaction, status_code)\
  int status_code = ERROR_SUCCESS;\
  Transaction* transaction = 0;\
  try\
  {\
    transaction = new Transaction();\
    try\
    {\
#define END_TRANSACTION(transaction, status_code)\
  }\
  catch(...)\
  {\
      status_code = TRANSACTION_ERROR;\
      log("END_TRANSACTION Error");\
  }\
}\
catch(...)\
{\
}\
{\
  try\
  {\
      if( !transaction->Commit() )\
      {\
          if( status_code == SUCCESS )\
          {\
              status_code = TRANSACTION_ERROR;\
              log("END_TRANSACTION Error");\
              transaction->Rollback();\
          }\
      }\
  }\
  catch(...)\
  {\
      status_code = TRANSACTION_ERROR;\
      log("END_TRANSACTION Error");\
  }\
  \
  try\
  {\
      delete transaction;\
  }\
  catch(...)\
  {\
      status_code = TRANSACTION_ERROR;\
      log("END_TRANSACTION Error");\
  }\
}
先把 MARCO 和 try-catch 拿掉看看
int status_code = ERROR_SUCCESS;
Transaction *transaction = 0;
transaction = new Transaction();
// do something
if (!transaction->Commit()) {
  if (status_code == SUCCESS) {
    status_code = TRANSACTION_ERROR;
    log("END_TRANSACTION Error");
    transaction->Rollback();
  }
}
delete transaction;
這樣的 code 是不是擺在一起就好看多了?
「難道不能這樣寫 code 嗎?」
客戶產品上線,怕出現「意料之外」的情況。
造成無法回頭的問題。
「告訴你!你愈這樣,就愈容易遇到」
而且,還是可以加 try-catch
try {
  int status_code = ERROR_SUCCESS;
  Transaction *transaction = 0;
  transaction = new Transaction();
  
  // do something
  
  if (!transaction->Commit() && status_code == SUCCESS) {
    status_code = TRANSACTION_ERROR;
    log("END_TRANSACTION Error");
    transaction->Rollback();
  }
  delete transaction;
}
catch (...) {
  status_code = TRANSACTION_ERROR;
  log("END_TRANSACTION Error");
}
「難道你就不能...」(好了好了...)
再拿剛剛那一段 MARCO 裡的 END_TRANSACTION()
不知道你有沒有仔細看裡面有一段
設計
#define END_TRANSACTION(transaction, status_code)\
//... code
catch(...)\
{\
  <- 空白的 catch 
}\
  <- 這裡沒有東西 單純用大括號括出 scope
{\
  //...other code
}
糙 code 讓你眼花瞭亂呀。
無法一眼看出來的就是糙!!
不知道出現什麼錯誤,所以使用 catch(...)
不知道要怎麼處理,所以使用空白的 catch block
如果非要用空白的 catch 不可,請加上註解!!!
另外,單純用大括號括出 scope ,我也不懂就什麼會寫出這種東西。
如果寫程式需要這麼害怕和焦慮,請務必再重新熟悉語法,而不是依賴 try-catch 讓你的程式碼不顯示錯誤 (不是不出錯)
C++ 的捕捉任意 exception catch(...) 不要濫用
產品在 release mode 後要容錯,debug mode 時要突顯錯誤。
debug 就是要解決出錯的原因。但是這樣的程式碼還出現在 debug mode 要如何找到錯誤的原因呢?
還遇過,初始化由 try-catch 巧妙的容錯,造成無法好好修改的邏輯,真是可怕的回憶
C++ 使用 MARCO 進行條件編譯
catch (Exception e) {
  status_code = TRANSACTION_ERROR;
  log("END_TRANSACTION Error");
}
#ifndef _DEBUG  // release mode 再加上去
catch (...) {
  status_code = TRANSACTION_ERROR;
  log("END_TRANSACTION Error");
}
#endif
不要巢狀 try-catch
你有用過 goto() 嗎?就是發明巢狀後,才可以大幅順利的不使用它,你知道嗎?
這樣的結構,會讓你不知道要如何處理垃圾收集
try {
  try {
  } catch {
  
  }
} catch {
  try {
  } catch {
  
  }
}
如果一定要,就用 function 包起來,給它一個好命名。
try-catch 要如何使用?
try-catch ≠ if-elsetry {
  //正常
}
catch (...){
  // 出錯
}
「當你內心充滿焦慮時,一定要正面對決問題」
這是人生的問題呀
[1]: 例外處理壞味道:將例外當作控制流程