如何快速定位一个函数的返回点

如何快速定位一个函数的返回点,这对于一个比较短小精悍的函数来讲,从来就不是问题,但是假设我们有一个名为LongFunction的1000行长的函数, 调用如下:

1
2
bool bSuccess = LongFunction();
assert(bSuccess);

在运行中第二行弹出一个assert,我们知道肯定是LongFunction内部运行中出了什么问题导致其返回false。那么它内部出了什么问题,是在哪一行出错导致返回的?这恐怕不是一件容易的事,要知道这是一个1000行的函数,而且极有可能有很多的返回点。

我想这应该是我们日常工作中常见的问题,1000行的长函数在一些大型的系统中,老代码中应该还是不少见的。当然,有些朋友会强烈的认为这样的函数必须要重构,理由是~~~(此处略去500字)。的确,重构的好处是显而易见的,但很多时候由于资源,时间以及复杂度上的考虑,是不被采纳的。所以这里我们不考虑重构,只想找出一个能快速定位到函数返回点的方法。

我们先来分析一下可能的方案:

  • 单步执行
    这是最直接也是最浪费时间的方法,虽然你总能找到那个返回点,但显然,程序员是不会这么做的。

  • 搜索并打断点
    搜索函数中所有的"return"点,并在每一处设断点。这比第一点有效多了。虽然我们可以用正则表达式非常精确的定位到每一处真正的"return",但如果每次遇到这个情况都要搜一次,设一次,也比较麻烦。而且这种方法也有一点小瑕疵,假设以下代码:
    1
    if(a != b) return false;

    我们会在这一行设上断点,执行时也会break进去,但这不一定是真正的返回点。
  • 重定义return关键字
    1
    #define return TRACE(__LINE__); return;

    如果我们的代码内建了这种机制,我只要看一下输出窗口的打印出的行号,就知道在哪里返回了。但是不能处理这个情况:
    1
    if(bOK) return true;

  • 自定义宏替换return
    1
    #defineRETURN(value) {TRACE(__LINE__); return value;}

    的确,这是能工作的,但这要求我们修改所有现存的代码,更糟的是,以后编程也需要使用这个RETURN,让这个丑陋的RETURN成为guideline,我想大家都不愿意。
  • 返回时构造
    1
    2
    struct ReturnType{ ReturnType(bool){ } };
    ReturnType LongFunction();

    我们把LongFunction的返回类型改为ReturnType类,这个类的构造函数以返回类型为参数。这样,LongFunction返回时就会构造ReturnType,我们只要在其构造函数中设断点,在callstack中就能看到LongFunction是在哪里返回的。有的朋友可能会觉得这样对于不同的返回类型,就要写不同的构造函数,我们可以有两种方法来解决这个问题:
    1
    2
    3
    4
    5
    6
    7
    // 1. 变参
        struct ReturnType{ ReturnType(...){ } };
        // 2. 模板构造函数
        struct ReturnType
        {
        template < typename T > ReturnType(T t){}
        };
    这个方案的真正问题在于要求我们修改函数返回参数,这种接口的改动影响太大。
  • 返回时析构
    1
    2
    3
    4
    5
    6
    7
    8
    class ReturnMonitor
        {
        ~ReturnMonitor(){}
        };
        bool LongFunction()<br>     {
        ReturnMonitor mon;
        // Function body
        }
    在资源管理中我们经常会用这种方式(RAII),现在我们利用函数返回时会调用析构函数这个特性,在析构函数中设断点,就能在callstack中看到返回点。这还有一个优点就是在LongFunction调用过程中如果出现异常,也能被捕捉。

根据以上分析,我认为有两个方案只要稍加修饰,就能成为比较不错的候选方案:
第一个是"搜素并打断点",我们可以利用IDE的集成功能自动化这个步骤,比如说在Visual Studio中,我们可以写一个宏,或者写个插件来做这件事件,只要选中函数一点按钮,所有"return"自动打上断点。

第二个是"返回时析构",我们可以定义以下宏:

1
2
3
4
5
#ifdef _DEBUG
#define RETURN_MONITOR ReturnMonitor mon;
#else
#define RETURN_MONITOR
#endif

这样对于我们代码中比较长的,较难调试的长函数,就可以在函数开始加上RETURN_MONITOR,并且不影响release版本。

posted @   lzprgmr  阅读(1599)  评论(4编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述

黄将军

点击右上角即可分享
微信分享提示