二 、错误传递
2.1 返回值和回传参数C语言通常使用返回值来标志函数是否执行成功,调用者通过if等语句检查该返回值以判断函数执行情况。常见的几种调用形式如下: if((p = malloc(100)) == NULL)
//...
if((c = getchar()) == EOF)
//...
if((ticks = clock()) < 0)
//...
Unix系统调用级函数(和一些老的Posix函数)的返回值有时既包括错误代码也包括有用结果。因此,上述调用形式可在同一条语句中接收返回值并检查错误(当执行成功时返回合法的数据值)。 返回值方式的好处是简便和高效,但仍存在较多问题: 没有返回值的函数是不可靠的。但若每个函数都具有返回值,为保持程序健壮性,就必须对每个函数进行正确性验证,即调用时检查其返回值。这样,代码中很大一部分可能花费在错误处理上,且排错代码和正常流程代码搅在一起,比较混乱。 条件语句相比其他类型的语句潜藏更多的错误。不必要的条件语句会增加排障和白盒测试的工作量。 通过返回值只能返回一个值,因此一般只能简单地标志成功或失败,而无法作为获知具体错误信息的手段。通过按位编码可变通地返回多个值,但并不常用。 字符串处理函数可参考IntToAscii()来返回具体的错误原因,并支持链式表达: char *IntToAscii(int dwVal, char *pszRes, int dwRadix)
{
if(NULL == pszRes)
return "Arg2Null";
if((dwRadix < 2) || (dwRadix > 36))
return "Arg3OutOfRange";
//...
return pszRes;
}
不同函数在成功和失败时返回值的取值规则可能不同。例如,Unix系统调用级函数返回0代表成功,-1代表失败;新的Posix函数返回0代表成功,非0代表失败;标准C库中isxxx函数返回1表示成功,0表示失败。 调用者可以忽略和丢弃返回值。未检查和处理返回值时,程序仍然能够运行,但结果不可预知。 新的Posix函数返回值只携带状态和异常信息,并通过参数列表中的指针回传有用的结果。回传参数绑定到相应的实参上,因此调用者不可能完全忽略它们。通过回传参数(如结构体指针)可返回多个值,也可携带更多的信息。 综合返回值和回传参数的优点,可对Get类函数采用返回值(含有用结果)方式,而对Set类函数采用返回值+回传参数方式。 对于纯粹的返回值,可按需提供如下解析接口: typedef enum{
S_OK, //成功
S_ERROR, //失败(原因未明确),通用状态
S_NULL_POINTER, //入参指针为NULL
S_ILLEGAL_PARAM, //参数值非法,通用
S_OUT_OF_RANGE, //参数值越限
S_MAX_STATUS //不可作为返回值状态,仅作枚举最值使用
}FUNC_STATUS;
#define RC_NAME(eRetCode) \
((eRetCode) == S_OK ? "Success" : \
((eRetCode) == S_ERROR ? "Failure" : \
((eRetCode) == S_NULL_POINTER ? "NullPointer" : \
((eRetCode) == S_ILLEGAL_PARAM ? "IllegalParas" : \
((eRetCode) == S_OUT_OF_RANGE ? "OutOfRange" : \
"Unknown")))))
当返回值错误码来自下游模块时,可能与本模块错误码冲突。此时,建议不要将下游错误码直接向上传递,以免引起混乱。若允许向终端或文件输出错误信息,则可详细记录出错现场(如函数名、错误描述、参数取值等),并转换为本模块定义的错误码再向上传递。
|