最近,我在写stm32的单片机程序,由于采用分模块编写方式,一个模块对应一对头文件和源文件,在头文件中使用宏定义或者枚举类型定义的方式编写该模块可能得错误码,然后在源文件中定义操纵函数,每一个函数都有返回值类型,对应错误码,也就是函数定义的返回值用于追踪函数在运行阶段运行的情况。
遇到的问题:我现在遇到的问题是这样的,假设某一个函数的代码太长了,我想要将函数内部某些具有特定含义的代码写成子函数,子函数定义的时候我如果也想要保留返回值类型,也就是和父函数一样返回值记录子函数运行情况,那么遇到的问题是,子函数的错误码与父函数的错误码是否是一套呢?
我当时遇到这个问题很犹豫,一是因为不确定子函数是否也按照父函数一样的方式带有返回值类型来定义,二是就算是按照父函数一样的方式编写子函数,子函数返回值对应的错误码组是否和父函数中已经定义的错误码组是属于同一套呢?因为子函数实现功能比较简单,因此错误码可能更倾向于描述子函数本身可能得错误,因此错误码的宏名或者枚举名更倾向于子函数本身的含义。
现在想想以上两点已经不是问题了。首先子函数定义的时候如果能写成返回值为错误码的方式,尽量在函数定义的时候带有返回值,此次,无论是子函数的错误码还是父函数的错误码都是定义在同一个模块内部源文件或者头文件中,因此他们属于同一套错误码,因此在使用宏定义编写错误码的时候,宏值要保证各不相同,枚举类型定义更不用说了,自己就对枚举值以不同整数值依次编码。
虽然子函数的错误码和父函数的错误码是同一套,但并不意味着着这两类错误码在含以上是同一层的,定义子函数的错误码的时候可以更体现第一层的特定,例如:
cpp
// error_codes.h
typedef enum {
ERR_OK = 0,
ERR_TIMEOUT,
ERR_INVALID_PARAM,
ERR_HW_BUSY,
// 针对拆分出的子模块扩展
ERR_SUB_PARSE_FAIL,
ERR_SUB_CRC_MISMATCH,
} ErrorCode_t;
ERR_SUB_PARSE_FAILE, ERR_SUB_CRC_MISMATCH在语义上更倾向于第一层,但是它们和上面的错误码都属于同一模块,都属于同一套。
子函数定义:
cpp
ErrorCode_t ParseSensorData(const uint8_t *raw_buf, SensorResult_t *out_result);
父函数定义,内部调用子函数
cpp
ErrorCode_t ProcessSensorFrame(const uint8_t *frame, uint16_t len)
{
ErrorCode_t ret; /*定义一个错误码类型变量,用于接受子函数调用的返回值*/
ParsedData_t data;
// 子函数1:校验
ret = ValidateFrame(frame, len);
if (ret != ERR_OK) {
return ret; // 直接透传,或转换为更上层的错误码
}
// 子函数2:解析
ret = ParseSensorData(frame, &data);
if (ret != ERR_OK) {
return ret;
}
// 子函数3:应用校准
ret = ApplyCalibration(&data);
if (ret != ERR_OK) {
return ret;
}
return ERR_OK;
}
当时我在父函数内部因为没有定义临时栈变量用于接受子函数调用返回错误码,而是直接使用了类似:
cpp
if (ApplyCalibration(&data) != ERR_OK)
{
return xxx;
}
我习惯这样写代码,卡在了return xxx;这行代码上了,因为我想要返回函数ApplyCalibration(&data)运行的子函数返回值,但是我不知道是什么,也就是说xxx是一个不确定的值,我们现在知道,不确定的值用变量去描述,而且该变量会被读至少两次以上,不能使用这种省略写法,因此需要定义个变量接收函数运行的返回值错误码,改成这样:
cpp
ErrorCode_t ret;
ret = ApplyCalibration(&data);
if (ret != ERR_OK)
{
return ret;
}
这样就完美解决同一模块下定义子函数和父函数错误码的统一关系问题。(吐槽,还是编写代码太少了,有时候不知道拐弯,什么时候需定义变量显示的接收函数的返回值,是有后面代码还对该变量会不会进行读操作有关)。