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

来源:互联网 发布:wifi认证软件 编辑:程序博客网 时间:2024/05/21 09:40

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




原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 有上海居住证积分怎么办医保 未办理居住证积分新生儿医保怎么办 厦门中考居住证没满三年怎么办 海归落户过了两年期怎么办 借呗学历填错了怎么办 上海落户应届生分不够怎么办 应届生落户分数不够72分怎么办? 上海应届生落户时间延误怎么办 南京市区户口签江宁怎么办 深圳公司集体户口离职后怎么办 济南本地户口不符合入学条件怎么办 上海住亲戚家怎么办居住证 政府卖非农户口怎么办 90年代买了户口怎么办 上海应届大学生积分不够怎么办 广州居住证回执单丢了怎么办 惠阳居住证回执单丢了怎么办 南京居住证换地方了怎么办 买家退回的商品有问题怎么办 农转农户口手续怎么办 原房东不迁户口我怎么办 户主信息页掉了怎么办 户主变了户口本首页怎么办 大人户口迁走小孩户口怎么办 网银转账处理中怎么办 教育部学籍在线验证报告有错怎么办 验证码连续输入三次错误怎么办 交通运输监察大队截车了怎么办 平安安康续保没成功怎么办 危险品经营许可证到期了怎么办 郑万350渝万怎么办 厂里饭堂的饭好难吃怎么办 学校的食堂饭菜不好不卫生怎么办 亲戚借钱我真没有怎么办 榴莲肉酸了吃了怎么办 亲戚赖在家里住怎么办 食堂饭菜味道差该怎么办 被监视居住公安打电话睡着了怎么办 鱼缺氧浮上水面怎么办 车载低音炮有电流声怎么办 925纯银变黑了怎么办