Debugging 如何处理间歇性错误? 脚本

Debugging 如何处理间歇性错误? 脚本,debugging,validation,Debugging,Validation,您有几个bug报告都显示了相同的问题。他们都很神秘,关于这个问题是如何发生的。您按照这些步骤操作,但它不能可靠地再现问题。经过一些调查和网络搜索,你怀疑可能发生了什么,你很确定你可以修复它 问题 不幸的是,如果没有可靠的方法重现原始问题,您就无法验证它是否确实解决了问题,而不是根本没有效果,或者加剧并掩盖了真正的问题。在每次都可以复制之前,您无法修复它,但这是一个大错误,不修复它会给您的用户带来很多其他问题 问题: 您如何验证您的更改 我认为这对于任何一个设计过软件的人来说都是一个非常熟悉的场景

您有几个bug报告都显示了相同的问题。他们都很神秘,关于这个问题是如何发生的。您按照这些步骤操作,但它不能可靠地再现问题。经过一些调查和网络搜索,你怀疑可能发生了什么,你很确定你可以修复它

问题 不幸的是,如果没有可靠的方法重现原始问题,您就无法验证它是否确实解决了问题,而不是根本没有效果,或者加剧并掩盖了真正的问题。在每次都可以复制之前,您无法修复它,但这是一个大错误,不修复它会给您的用户带来很多其他问题

问题: 您如何验证您的更改


我认为这对于任何一个设计过软件的人来说都是一个非常熟悉的场景,所以我确信有很多方法和最佳实践来解决这样的bug。我们目前正在研究项目中的其中一个问题,我花了一些时间确定问题,但无法证实我的怀疑。一位同事正在测试我的修复方案,希望“一天没有崩溃的跑步”等同于“修复了”。然而,我更喜欢一种更可靠的方法,我认为在这方面有丰富的经验。

这个问题没有一个答案。有时,您找到的解决方案可以帮助您找出重现问题的场景,在这种情况下,您可以在修复之前和之后测试该场景。不过,有时候,你找到的解决方案只解决了其中一个问题,而不是所有问题,或者就像你说的,掩盖了一个更深层次的问题。我希望我可以说“做到这一点,它每次都能工作”,但没有一个“这一点”适合这种情况。

使用更广泛(可能是可选的)日志记录和数据保存来指导构建,允许准确再现用户在崩溃发生前采取的可变UI步骤。 如果这些数据不能可靠地让您重现问题,那么您就缩小了bug的范围。是时候看看随机行为的来源了,比如系统配置的变化、指针比较、未初始化的数据等。


有时,您“知道”(或者更确切地说,感觉到)您可以在不进行大量测试或单元测试的情况下修复问题,因为您真正理解这个问题。但是,如果你不这样做,它通常会归结为“我们运行了100次,错误不再发生,所以我们会认为它是固定的,直到下一次它被报告。”

< p>你将永远无法验证这个修复,而不确定根本原因,并想出一个可靠的方法来重现bug。 用于确定根本原因:如果您的平台允许,请将一些事后调试挂接到问题中

例如,在Windows上,让您的代码在遇到此问题时创建一个小型转储文件(Unix上的核心转储)。然后,您可以让客户(或Windows上的WinQual)向您发送此文件。这将为您提供更多有关生产系统上的代码是如何出错的信息

但是如果没有这些,您仍然需要找到一种可靠的方法来复制错误。否则,您将无法验证它是否已修复


即使有了所有这些信息,您也可能最终修复了一个看起来像但不是客户看到的bug。

难以重现的bug是最难解决的bug。您需要确保找到问题的根源,即使问题本身无法成功重现

最常见的间歇性错误是由比赛条件引起的-通过消除比赛,或确保一方始终获胜,您已经消除了问题的根源,即使您无法通过测试结果成功确认问题。你唯一能测试的是,原因确实需要重复


有时,解决被视为根源的问题确实解决了问题,但不是正确的问题——无法避免。避免间歇性错误的最佳方法是对系统设计和体系结构进行仔细和系统化。

这些类型的错误非常令人沮丧。将其外推到不同的机器上,这些机器中可能有不同类型的定制硬件(比如在我的公司),天啊,天啊,这真是一场噩梦。目前我的工作中有几个类似的bug

我的经验法则是:除非我能自己复制它,或者我看到一个日志清楚地显示出错误,否则我不会修复它。否则,我无法验证我的更改,也无法验证我的更改没有破坏任何其他内容。当然,这只是一条经验法则——我也有例外


我认为你关心你的同事的做法是对的。

这些问题总是由以下原因造成的:

  • 记忆问题
  • 线程问题
  • 要解决此问题,您应该:

    • 插入代码(添加日志语句)
    • 代码审查线程
    • 代码检查内存分配/取消引用
    只有当代码审查是一个优先事项,或者您对多个bug报告共享的代码有强烈怀疑时,代码审查才最有可能发生。如果是线程问题,那么检查线程安全性-确保两个线程都可以访问的变量受到保护。如果是内存问题,那么请检查您的分配和取消引用,尤其要怀疑分配和返回内存的代码,或者可能正在释放内存的其他人使用内存分配的代码。

    我使用我称之为的“重式防御编程”在所有似乎与问题相关的模块中添加断言。我的意思是,添加大量断言,断言证据,断言所有成员中对象的状态,断言“环境”状态,等等

    断言有助于识别代码t
    const int NITER = 1000;
    int thread_unsafe_count = 0;
    int thread_unsafe_tracker = 0;
    
    void* thread_unsafe_plus(void *a){
      int i, local;
      thread_unsafe_tracker++;
      for (i=0; i<NITER; i++){
        local = thread_unsafe_count;
        local++;
        thread_unsafe_count+=local;
      };
    }
    void* thread_unsafe_minus(void *a){
      int i, local;
      thread_unsafe_tracker--;
      for (i=0; i<NITER; i++){
        local = thread_unsafe_count;
        local--;
        thread_unsafe_count+=local;
      };
    }
    
    pthread_t th1, th2;
    pthread_create(&th1,NULL,&thread_unsafe_plus,NULL);
    pthread_create(&th2,NULL,&thread_unsafe_minus,NULL);
    pthread_join(th1,NULL);
    pthread_join(th2,NULL);
    if (thread_unsafe_count != 0) {
      printf("Ah ha!\n");
    }