良好程式碼的優點大同小異。
不好的程式碼的糙點卻各有巧妙之處。
很少人用過吧?
但是真正製作產品上,傳聞寫 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-else
try {
//正常
}
catch (...){
// 出錯
}
「當你內心充滿焦慮時,一定要正面對決問題」
這是人生的問題呀
[1]: 例外處理壞味道:將例外當作控制流程