Google Forbids Use of Exception in C++
Posted on July 6th, 2008 at 23:57 by fr3@K

Quote from Google C++ Style Guide:

We do not use C++ exceptions.

在極端狀況下, 我能想像在 no virtual function 或是 no templates 但仍能使用 STL 的環境寫 C++. 但完全禁止 user-defined classes/functions 以拋出 exception 的方式回報錯誤的環境呢? No, thanks. 即便有免費的食物與漂亮的辦公室, 這還是太折磨人了.



除了 vectorbasic_string 因為特殊考量 (主要是 performance) 只提供了 basic guarantee (e.g. no leak), 其他的 STL 的 container 與部份特別的 STL algorithm (如 uninitialized_copy), 基於 exception/RAII 建構出 commit or rollback 的 strong guarantee. 而在一個有高可靠需求的系統中, 如 DB, 狀態的一致性是不可或缺的特性. 為何不有效利用 C++ 語言與標準 library 所鼓勵的 commit or rollback 的 coding style?

在一個 non-trivial (具規模) 的軟體 code base, 在深不見底的 code stack 裏, 偵測到錯誤的 code 與知道該怎麼 recover 該錯誤或 pop-up 錯誤訊息給使用者的實質 handler 可能相距十萬八千里. 讓每一層都以 if/else/return 方式向上層回報是連 不支持 exception 的 C 標準函式庫都儘量避免的事情. 況且, 除了向上回報, 絕大部份中間的 code 根本不知道該拿這個錯誤如何是好.

禁止了 exception, RAII 當然也跟著沒了. 而少了 RAII 的 code 看起來可能就會像這樣:

int StringLength(const String& str)
{
  if(str.IsInitOk() == false)
    return ERROR_BAD_STRING;
  return strlen(str.GetInternalData());
}

在不盡理想的情況下, 很可能大部份直接/間接呼叫 StringLength 的 code 都需要 check/translate/report 這個 error condition (更別提某段中間的 code 忘了處理這個狀況). 或許在 StringLength 裏面用 assert 可將這個問題簡單化:

size_t StringLength(const String& str)
{
  assert(str.IsInitOk() == true);
  return strlen(str.GetInternalData());
}

看起來似乎好些了. 可是 StringLength 的使用者呢?

int foo()
{
  String name(”fr3@K”);
  if(name.IsInitOk() == false)
    return ERROR_BAD_STRING;

  size_t len = StringLength(name);
  // do things with `name’ and/or `len’…
  return 0;
}

Client code 的情況完全沒有改善!

那把 assert 放到 foo 裏面怎麼樣? 或者更進一步, 直接把 assert 放到 String 的 constructor 裏面 (下例中的第二個 assert):

String::String(const char* str)
{
  // Check for prerequisite, see here for detail.
  assert(str != 0);
  data_ = malloc(strlen(str) + 1);
  // Check for exceptional condition!
  assert(data_ != 0);
  strcpy(data_, str);
}

這樣的確避免了前面的層層錯誤回報. 但這個作法跟在 malloc 失敗後拋出 exception 有什麼差別? 在遇到例外狀況時, 拋出 exception VS. 觸發 assertion failure 有什麼不一樣?

String::String(const char* str)
{
  // Check for prerequisite, see here for detail.
  assert(str != 0);
  data_ = malloc(strlen(str) + 1);
  // Check for exceptional condition!
  if(data_ == 0)
    throw bad_alloc();
  strcpy(data_, str);
}

對於 class String 的 constructor 來說差異不大. 若上層的 code 能善用 resource handle (如 std::auto_ptr, Boost.Smart-Ptr這篇文章中的 example) 來管理 resource, 除了知道該拿這個例外狀況怎麼辦的最上層, 對其餘中間層的 code 來說只有省掉一種錯誤狀況的檢查, 除此幾乎沒有差別!

如果在這個時候拋出 exception, 由於 stack unwind, catch 到這 exception 的 (遙遠) 上層可能有足夠的 heap memory 顯示錯誤訊息. 又或者上層 reserve 了部份 memory 可以 release (只是要提示使用者 low memory) 進而 re-try. 就算只有在 main 有簡單的 try-catch, 而沒有任何其他的錯誤處理, 程式也有很好的機會可以乾淨地 (no leak) 退出. 這些都是 assert 做不到的.

C++ 原生支援三種錯誤回報機制; assertion failure, exception 與 error code . 三者都有各自恰當的使用時機. 沒有一種比其他的方式更具有絕對的優勢.

  • assert 適合拿來 (在 NDEBUG 沒有被 define 時) 檢驗程式的正確性. 例如檢查 module 內部的狀態完整性 (integrity) 與外部使用者操作的邏輯錯誤 (call sequence 順序與 pointer 參數不可為 NULL 等)
  • Exception 則更適合用來回報執行時期的特殊 (exceptional) 錯誤, 如資源請求失敗與物件起始時發生的錯誤 (i.e. in constructor) 等等
  • 其他時候, 使用傳統的 error code 可能更為適當

即便做出正確的選擇不總是件容易的事情, 完全地迴避任何一種 error reporting 機制都不會是明智的 C++ coding style.

del.icio.us:Google Forbids Use of Exception in C++ digg:Google Forbids Use of Exception in C++ spurl:Google Forbids Use of Exception in C++ newsvine:Google Forbids Use of Exception in C++ furl:Google Forbids Use of Exception in C++ Y!:Google Forbids Use of Exception in C++ 黑米共享書籤:Google Forbids Use of Exception in C++ 推推王:Google Forbids Use of Exception in C++
Previous Post
« Delicious Bookmarks Upgrade Breaks Firefox Tab Navigation «
Next Post
» C++0x, Near Feature-Complete »

4 Comments »

Comment #4515

基本赞同。应该有限制的使用异常。
google的风格必然导致两阶段构造对象法。
但是编写异常安全的代码比想象的要难一些。
像String这种属于基础库的设施,而对于普通的应用开发,如果没有特别的理由,都不应该使用异常。

Comment by eXile — July 10, 2008 @ 22:16


Comment #4516

的確, exception safety 不是容易達成的, 我也寫過一些相關的文字 (here, here and here). 小弟潛見, 我認為那正是專業與業餘 C++ 程序員的區別之一.

你的最後一句話我有不一樣的看法:

像String这种属于基础库的设施,而对于普通的应用开发,如果没有特别的理由,都不应该使用异常。

我的看法是愈是像 String 這種基礎設施, 常常, 愈有機會正確地使用 exception. 因為 call stack 的上上下下大家都在使用, 但能處理錯誤的人卻常不在附近. 回到我在這篇文字中舉的例子, 除了靠近最上層的程序可能有足夠的 knowledge 能 recover 或是對使用者提示錯誤狀況之外, 中間的 code 能拿 String 記憶體請求失敗怎麼辦?

換一個對象來看, 若是針對較特殊應用的”零件”, 像是設置檔的讀寫, 由於被使用/參考的範圍較小, 很容易在接近錯誤出現的地方就能對錯誤加以處理. 例如在讀設置檔時找不到某個欄位的資料 (例如讀不到上一次關閉時視窗的座標) 時, 使用 exception 來回報錯誤就很可能是小題大作了.

我絕對不是在鼓吹 exception 有多好, 即便是用在 String 身上. 例如在這樣的 function 就不適合:

some_integral_type String::FindSubString(const String& sub);

大多數的情況下, 字符串的內容是動態的 (外部輸入的), 找不到一段 sub-string 是執行時期的 input validation 錯誤, 以 error code 方式回報可能更為恰當.

Comment by fr3@K — July 11, 2008 @ 13:26


Comment #4526

呵呵, 我没有表达清楚,我的意思是,像string这种基础设施可以使用异常,但在业务比较复杂的应用开发中,还是不要使用异常为好。

Comment by eXile — July 15, 2008 @ 23:53


Comment #4530

看起來其實好像也沒那麼嚴重?

Google 列出的 Cons (of using exception) 第一條和 Joel 的 http://www.joelonsoftware.com/items/2003/10/13.html 差不多意思,而且您先前的文章也包含了相關的部分。

正因為那需要專業程度,換句話說就是犯錯的機會增加了,即使是最優秀的工程師都有可能在狀況差的時候對這些事有所疏忽。於是我的推測是,正如 Google 在文件上寫的,它不想承擔這類型的成本。

另外我也猜測,Google 或許用 code review 和 unit test 從外部來取代某些語言內部機制提供的能力。

Comment by augustinus — July 17, 2008 @ 19:15


Comments RSS TrackBack URI

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>