CSDN博客

img kingofark

kingofark's Ineffective C/C++:自白1:返回值的运用

发表于2002/9/24 9:31:00  1289人阅读

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;  //oops! 当前函数返回
/////////////////////////////////////////////////


[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) 当你调程序调不通的时候,请把你自己认为最不可能出错的地方罗列出来,然后逐个仔细检查。

0 0

相关博文

我的热门文章

img
取 消
img