捕获未经测试的返回值

前面翻译的一篇文章《使用错误代码对象进行C++错误处理》中提到,作者的灵感来源于另一篇文件《捕获未经测试的返回值》,于是再把这篇文章翻译过来,做为对比,也算是形成一个系列的文章。

前言

函数返回值通常用于表函数是否在没有错误的情况下执行。但是,很难确保调用者适当地使用这些信息(指返回值)。也许一些商业工具可以完成这项工作,但你并不总是能获得购买许可,特别是在小型项目中。你很有可能听到过这样说:“我相信你,你不会犯这样的错误。”

我在这里提出的想法,是受到几周前我们在项目中出现的一个错误的启发。它只出现在一个生产环境中,花了几天的时间才发现,它来自于一个特定环境的初始化例程的失败。实际上,调用此例程的代码没有测试其返回代码。

添加责任标志

在我的经验中(但不是很长时间),我经常看到函数返会是按类型分组成一个枚举。如图1所示,调用者可以或多或少忽略此类返回值。为了控制返回值发生了什么,我不直接返回这些值,而是返回类ErrorCode的实例。它包含两个成员变量:值(enValue_)指示函数错误代码,责任标志(PboResp_):

class ErrorCode
{
private:
   ErrorCodeValue enValue_;
   bool * PboResp_;
public:
   // some code
}

责任标志的目的是表明是否需要对ErrorCode对象的值负责(指是否需要检查)。当ErrorCode对象使用拷贝构造函数或赋值函数拷贝对象时,值(enValue_)被拷贝,同时责任标志PboResp_中包含的责任需要“转移”。所谓“转移”,是指拷贝操作将责任从源实例转移到目标实例。复制后,源实例不再负责其内容(即调用者不再需要检查)。(实际上,拷贝构造函数的语义与赋值函数略有不同,但总体思路是相同的。下文详解。)

因为拷贝构造函数和赋值函数的参数使用const修饰符,我选择将责任标志PboResp_实现为一个指向布尔值的指针,而不是一个布尔值。这使拷贝函数能够修改作为参数传递进来的源实例的责任标志PboResp_。还有另一个适用于运算符=约束:如果参数ErrorCode对象的责任标志PboResp_是true,使之前值enValue_会丢失,必须要记录(指输出日志)。(此处描述的责任转移过程类似于发生在auto_ptr上的复制)

ErrorCode对象还要使用==和!=运算符。与从函数返回的临时ErrorCode对象比较时,需要这些操作符。这些临时ErrorCode对象已被构造成表示特定的错误状态(例如成功)。如您所料,这些操作符函数就是比较两个ErrorCode对象内部的值enValue_。而且,这些ErrorCode对象的责任标志PboResp_也要设置为false,这样就不会出现“未经测试的错误代码”的日志记录。如有需要,其他测试操作符(<, >)也可以实现。

最后,析构函数要检查实例是否仍然对错误代码值负责(责任标志PboResp_为true),如果存在这种情况就要记录它。

与现有代码的集成

在我看来,这项技术的成功取决于它能否很容易地集成到现有的程序中。图1展示了一种情况,这是一个很好的起点,可以演示如何进行集成。

首先,在类定义时,我使用之前定义的枚举的名称作为类名(即旧的枚举名是ErrorCode,则新的类名就是ErrorCode)。因此,所有之前返回枚举值的函数,现在都变成返回一个错误码对象,只要重新编译程序即可。我还将现在有枚举名ErrorCode改为ErrorCodeValue。要使这项技术真正有效,还必须定义一个ErrorCodeValue作为参数的构造函数。此构造函数主要在两种情况下会被隐式调用:第一,之前函数是返回ErrorCodeValue,而不是ErrorCode类的对象。第二,当ErrorCode对象和ErrorCodeValue进行比较(通过运算符==或!=)。在这种情况下,会用ErrorCodeValue构造出一个临时的ErrorCode对象,用作比较运算符的参数。如前所述,比较运算符还“关闭”两个对象的责任标志(责任标志PboResp_置为true)。

实施

图2显示错误处理的新实现,即使用ErrorCode类。ErrorCode类添加到现有文件ErrorCodes.h中。此外,还需要创建一个文件ErrorCodes.cpp,用于实现ErrorCode类的成员函数。

如上所述,拷贝构造函数和赋值操作符必须将错误代码的责任转移到它们的目标对象。但这还不够,函数必须始终ErrorCode对象,该对象负责其内容。因此,拷贝构造函数和接受ErrorCodeValue的构造函数,在构建对象时将责任标志PboResp_设置为true(他们无条件地这样做)。这就是rf构造函数与赋值操作符的不同之处:赋值操作符把责任标志从源对象复制到目标实例的,拷贝构造函数则要设置责任标志为true。

最后,默认构造函数与所有其他构造函数不同,因为它将责任标志初始化为false。默认构造的ErrorCode对象不表示未经测试的错误代码,它完全取决于创建它的程序员来决定如何处理它。

增强版的ErrorCode类,可以在析构函数和赋值操作符函数中增加断言,以便在开发和测试阶段拦截“错误泄漏”(即知道有哪些错误码没有检查)。

把新的错误码类在图1程序中实施,运行后将在标准错误输出上输出以下消息:

Destruction of untested error code:
value 1
Untested error code (value 0) erased by
new value 2

结语

我相信这个编码技巧对于检测某种类型的错误是非常有帮助的。请注意,此方法没有消除任何错误,只是在问题发生后报告未经测试的返回代码。我相信即使在现有的项目中也很容易实现。开始时,你可能会得到一个日志文件,其中包含大量未经测试的返回代码。因此,在我们的项目中,我们不得不稍微清理一下代码。

欢迎关注我的公众号【林哥哥的编程札记】,也欢迎赞赏,谢谢!

原文地址:https://www.cnblogs.com/qinwanlin/p/12681178.html

时间: 2024-08-29 15:18:16

捕获未经测试的返回值的相关文章

接口API测试和返回值JSON解析的插件

火狐插件1.   HttpRequest作用:接口API测试例子:http://192.168.10.61:8080/ZHCS/user/loginApp.do?phone=admin&pwd=admin接口以?作为结束, 连接用& 2.   JSON-handle作用:对json进行解析

js 中 函数的返回值问题

var result=''; function searchByStationName( address ) { // map.clearOverlays();//清空原来的标注 var keyword = address ; localSearch.setSearchCompleteCallback(function (searchResult) { var poi = searchResult.getPoi(0); result = poi.point.lng + "," + po

利用SQLServer查询分析器获取存储过程的返回值,检查测试存储过程

1.存储过程没有返回值的情况(即存储过程语句中没有return之类的语句)用方法 int count = ExecuteNonQuery(..)执行存储过程其返回值只有两种情况(1)如果通过查询分析器执行该存储过程,在显示栏中如果有影响的行数,则影响几行count就是几(2)如果通过查询分析器执行该存储过程,在显示栏中如果显示'命令已成功完成.'则count = -1;在显示栏中如果有查询结果,则count = -1总结:A.ExecuteNonQuery()该方法只返回影响的行数,如果没有影响

03_MyBatis基本查询,mapper文件的定义,测试代码的编写,resultMap配置返回值,sql片段配置,select标签标签中的内容介绍,配置使用二级缓存,使用别名的数据类型,条件查询ma

 1 PersonTestMapper.xml中的内容如下: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace:命名空间

测试 多线程 实现 callable 带返回值

1 package threadTest; 2 3 import java.util.ArrayList; 4 import java.util.Date; 5 import java.util.concurrent.Callable; 6 import java.util.concurrent.ExecutionException; 7 import java.util.concurrent.ExecutorService; 8 import java.util.concurrent.Exec

有的函数有返回值 有的没有 当测试输出为为空时,应该是不需要接收的函数你给接收了

replace函数有返回值  需要变量接收(大部分都需要变量接收) s='abc' s=s.replace('a','x',1) 列表remove函数无返回值(非常特殊) 不需要变量接收 li=[1,2,3]  li.remove(1) 列表extend函数无返回值 不需要变量接收

大开测试:性能- 如何理解Return的返回值(连载14)

7.14  如何理解Return的返回值 1.问题提出 在创建和录制脚本的时候,发现在脚本vuser_init.Action.vuser_end三部分中都会有一条"return 0;"语句,那么平时在编写脚本时如何应用return语句,return不同的返回值又有什么含义呢? 2.问题解答 Return表示一个过程的结束,在LoadRunner中用return根据脚本不同的返回值,表示脚本的成功或者失败."return +大于等于零的数字;"表示成功,反之,则表示失

SubSonic3.0使用存储过程查询时,不能使用output参数返回值的问题修改

有个群友问SubSonic3.0执行存储过程时能不能使用output参数返回值,说测试过后获取不到返回值,早上有些时间所以就尝试修改了一下 首先在数据库中创建一个存储过程 1 CREATE PROCEDURE [OutValue] 2 @a int, 3 @b int, 4 @c int output 5 AS 6 Set @c = @a + @b 7 GO 打开Settings.ttinclude模板,找到SPParam类,修改为下面代码 1 public class SPParam{ 2 p

如何在Shell 中正确的传递函数返回值

Debug 的过成比较无聊,所以这里先上结论和示例,Debug 的笔记看不看并没有什么乱用. 结论 在shell 中使用返回值,唯一具有通用性的方法是使用全局变量,或者使用echo 并在父进程中接收. return 语句不能用来传递计算结果--return 语句是用来传递函数退出状态的,在几乎所有情况下,你的计算结果都不会是退出状态! 任何违反上面规则的shell 脚本,都不具有通用性. 示例 #!/usr/bin/env bash # =============================