kingofark's Ineffective C/C++:自白1:返回值的運用 (轉)

worldblog發表於2007-12-12
kingofark's Ineffective C/C++:自白1:返回值的運用 (轉)[@more@]

Ineffective C/C++ : The Confession of A Novice
《Ineffective C/C++ :一個低手的自白》

by K ][ N G of @ R K

[宣告:kingofark並非高手,在論述中所舉的例子未必就可取,也未必就是很好的做法。所有例子僅僅是為了說明某些問題,並不具有代表性。kingofark自知才淺,歡迎大家提出批評,指出錯誤。]

Item 1: Return Values
自白1:返回值的運用


[問題]:試評價如下程式碼碎片。

/////////////////////////////////////////////////
// ... (在外部定義中)
// 有一組屬於同一個,都以 OK 或者 NG 作為返回值
#define OK  0
#define NG  -1
int AFunction();  //返回OK或者NG

// ... (在某個函式中)
int Rtn;  //存放子函式的返回值
Rtn = OK;  //初始化
// ...
Rtn = AFunction();  //呼叫一個函式
if (Rtn == NG)
{
  //...(這裡做一些錯誤處理)
  return Rtn;  //只要錯了就向外退
}
// ...
return Rtn;  //s! 當前函式返回
/////////////////////////////////////////////////


[kingofark的看法]:

首先需要說明的是,把返回值定成 OK、NG,以及讓一組函式具有相同返回方式,這本身並非一個好主意(事實上,筆者認為這個設計很拙劣——但是,你知道,當你是一個工人的時候,並非什麼東西都是你自己設計的),但這不是本條款所要描述的重點,因此對這些外部的背景條件我們不予討論。

這個程式碼碎片至少存在三個問題:

1) 函式返回值的判斷方式

/////////////////////////////////////////////////
if (Rtn == NG)
/////////////////////////////////////////////////

"if (Rtn == NG)"意味著:只要返回值不是NG,一律認為是成功。
而"if (Rtn != OK)"意味著:只要返回值不是OK,一律認為是失敗。

前者意味著在子函式存在邏輯錯誤或者發生不可預料的錯誤時,子函式內部並未陷入程式碼編寫者編寫的失敗路線導致返回NG,而這個子函式呼叫仍然被認為是成功的——因為此時子函式返回的很可能是OK,甚至是一個垃圾值(如果子函式內部用一個內部變數作為返回值的話),但它不等於NG!

後者附和了函式返回的意義,即只有在子函式內部確實走過了預想的正確執行路線,導致返回OK的情況下才承認呼叫的正確性,其它所有情況(包括函式失敗返回NG以及其它任何不可預料的錯誤)都認為是失敗——這樣做的好處是:

  a) 便於:只要在函式內部沒走到過預想的執行路線,就不返回OK,於是函式呼叫就被認為是失敗的。由此往往能夠發現一些函式內部的低階邏輯錯誤;

  b) 促使函式編寫者考慮得儘量周全:如果真的是希望函式具有較高的容錯性,請小心安排好返回OK。

2) 初始化的方式

/////////////////////////////////////////////////
Rtn = OK;  //初始化
/////////////////////////////////////////////////

把存放函式返回值的變數初始化為成功的返回值,意味著:

  a) 如果在後面的程式碼中沒用到過這個變數,而又不小心用 "return Rtn;" 返回(從而使也不會警告你這個變數沒使用過),那麼這個函式就會認為是成功呼叫的而不管其中發生了什麼不可思議的事情,從而使得除錯人員很難發現這個函式的問題(除錯人員必須很仔細的考察與這個函式相關聯的值和該函式產生的效果)。

  b) 編寫程式碼者可能是個樂觀主義者。樂觀是好事,但並不是說樂觀就不會犯錯——我們要“嚴肅活潑”:-)

似乎寫 "Rtn = NG;" 更好一些,因為這意味著:在函式中,只要沒有將 Rtn 賦值為OK,那麼在後面使用 "return Rtn;" 返回時,會返回NG從而引起你的注意,看看是不是什麼地方編寫錯了(比如忘記使用這個變數)。

3) 返回的方式

/////////////////////////////////////////////////
return Rtn;  //oops! 當前函式返回
/////////////////////////////////////////////////

如果函式沒有錯誤系統(即返回值涵蓋了一系列設計好的錯誤碼),似乎應該明確以 OK 或者 NG 返回,從而避免因為某種原因而返回垃圾值,比如

Rtn = Fun();  //誤用:倒黴!Fun()恰好是一個可能返回多種錯誤碼的函式
return Rtn;  //注意:其實這樣返回本身就不好!

或者

//注意:這個寫法太詭異,絕對不推薦使用!僅作為舉例。
return (Rtn || Flag); //筆誤:慘!本來想寫 | 來著(其中flag = -1)
 

另外,還有一個需要注意的問題:一定要根據函式的返回值判斷函式呼叫的情況!!

這好像是一句廢話,但是就有人這麼做:

/////////////////////////////////////////////////
//... (在與前面相同的系統中)
int myfun()
{
  if (...)
  {
  return 1; //一種返回情況
  }
  //...
  return 0;  //另一種返回情況
  //...
}

//...(在另外一個地方的程式碼中)
Rtn = myfun();
if (Rtn != OK)  //甚或是 if (Rtn == NG)
{
  //...
}
/////////////////////////////////////////////////

是的,一般來說,我們會把諸如OK這樣的東西定義為0,但如果萬一不是呢?如果後來系統改版,另一個高手決定OK應該等於100呢?

上面的程式碼中,或許myfun()的編寫者僅僅只是忘記了在註釋和文件中明確說明“0與該系統中的OK對應;-1與該系統中的NG對應”並且把 "return -1;" 誤寫為 "return 1;",但是,請銘記:永遠只使用函式明確說明的返回值進行返回值判斷,無論這個值是否“(應該)實際上與某個外部量相對應”。

只要你堅持使用函式明確說明的返回值進行返回值判斷,那麼無論是該函式的編寫者忘記了說明,還是什麼其它瑣事,你的做法都是對的。如果你一看,心裡想,哦,應該是編寫者忘了說明,“本來應該”是使用OK/NG的,所以你就這樣做了,那麼當OK很不幸的被重新定義為100而該函式又沒有做任何更改時,寫錯程式碼的就是你。

[kingofark的收穫]:

1) 寧可錯殺一千,不可放過一個。

2) 只寫對的,不玩鬼的。

3) 理想與現實的差距在於:理想中的錯誤是你想得到的,而現實中的錯誤有些是你想不到的。

4) 當你調調不通的時候,請把你自己認為最不可能出錯的地方羅列出來,然後逐個仔細檢查。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-991995/,如需轉載,請註明出處,否則將追究法律責任。

相關文章