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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

    #ifdef _DEBUG
    #define RETURN_MONITOR ReturnMonitor mon;
    #else
    #define RETURN_MONITOR
    #endif
    

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

  • 相关阅读:
    senium
    学习记录
    方法参数化
    洛谷1892 团伙
    洛谷2661 信息传递
    洛谷2661 信息传递
    洛谷1576最小花费
    洛谷1576最小花费
    最短路 Dijkstra模板
    堆排(模板)
  • 原文地址:https://www.cnblogs.com/baiyanhuang/p/1730731.html
Copyright © 2020-2023  润新知