发布模式和调试模式下代码表现不同

10
我们有一些单元测试在Release模式下运行时失败,而在debug模式下则通过。如果我在release模式下附加调试器,则测试将通过。这里有太多的代码需要发布,因此我真的只是寻找在调试Release模式问题方面的最佳实践。我已经检查了以下内容:
  • DEBUG和RELEASE预处理指令,但我没有找到任何。
  • 条件方法
解决方案:在这种情况下,原因是我比较浮点变量是否相等。我不能将浮点数更改为十进制数,而不进行重大重构,因此我添加了一个扩展方法:
public static class FloatExtension
{
    public static bool AlmostEquals(this float f1, float f2, float precision)
    {
        return (Math.Abs(f1 - f2) <= precision);
    }

    public static bool AlmostEquals(this float f1, float f2)
    {
        return AlmostEquals(f1, f2, .00001f);
    }

    public static bool AlmostEquals(this float? f1, float? f2)
    {
        if (f1.HasValue && f2.HasValue)
        {
            return AlmostEquals(f1.Value, f2.Value);
        }
        else if (f1 == null && f2 == null)
        {
            return true;
        }
        return false;
    }
}

1
几个问题。1. 你遇到了什么样的故障,可以让问题更具“风味”?2. 你是否检查过条件方法? - Tim Lloyd
主要问题是Equals方法返回false。然而,如果我逐个检查每个语句,它们都返回true。如果我尝试附加调试器,则问题消失。 - Shaun Bowe
这是否与浮点数有关(数据类型为double等)? - stefan
@Mark Byers 不,这个应用程序是单线程的。 - Shaun Bowe
@stefan 我正在 IsEqual 方法中比较一些浮点数值。 - Shaun Bowe
为什么您不发布“IsEqual”方法? - Gabe
6个回答

8

您看到的行为可能是由于导致竞态条件的错误引起的。附加调试器可以改变代码的时间,使得不再触发竞态条件。

要解决此问题,请在多个线程访问数据时适当使用同步。


我正在比较一些浮点数值在IsEqual方法中。

听起来不是一个好主意。你不应该比较浮点数的相等性,因为浮点计算不是100%精确的,你可能会得到表示和舍入误差。相反,比较它们是否足够接近。对于涉及货币的计算,您可能想使用 decimal 类型。


感谢您的回答,但是这个应用程序的这部分是单线程的。 - Shaun Bowe
必须是浮点数。我会进行一些更改并验证。 - Shaun Bowe

4

请参见https://dev59.com/zkzSa4cB1Zd3GeqPkjvl#2343351。 - Eric Lippert

3

你需要问自己以下问题:

  1. 我的代码是线程化的吗?时间差异会影响输出。
  2. 有人使用具有副作用表达式的Debug.Assert()吗?
  3. 哪些对象实现了IDisposable(),并且是否有一些以改变状态的方式实现了该接口?
  4. 你正在P/Invoke调用非托管代码吗?

在这种情况下,第3个问题很可能是一个坏点。 在调试和发布模式下,垃圾回收可能非常不同,你可能会发现当一个对象被垃圾回收时,它会影响后面的单元测试结果。

另外提醒一下,如果你正在使用NUnit和TestDriven.NET - 两者以不同的顺序运行测试。


在编程中,以下是关于垃圾回收的差异:在调试模式下,对象往往会活得更久(例如直到方法结束)以支持调试,而在发布模式下,对象通常会更早地被回收。+1 提到了垃圾回收的差异。 - stakx - no longer contributing

3
这通常是因为调试版本默认情况下未经过优化,即使您启用了它,调试时的行为也会有很大不同。您可以在“属性”->“生成”选项卡上的项目设置中禁用所有程序集的“优化代码”。

还有一些其他更改可能会导致差异,比如您提到的条件方法。对我来说,我发现这些很少引起问题,几乎总是优化器的问题。
优化器的经典陷阱包括被“内联”的方法,以至于它们无法出现在调用堆栈中。当使用System.Diagnostics.StackFrame类确定当前执行点时,这会导致问题。同样,这将影响MethodBase.GetCurrentMethod或依赖于执行方法的其他函数/行为的结果。
当然,优化器所做的许多事情我根本无法解释。其中一个例子已经在“HashDerivedBytes - replacing Rfc2898DeriveBytes, but why?”中得到记录和讨论了,但是我从未解决这个谜团。我只知道优化器在生成一系列派生字节时,使Rfc2898DeriveBytes出现了问题。奇怪的是,这仅在生成的字节数不能被哈希算法的大小(20)整除时才会出问题,且仅在前20个字节之后产生不正确的结果。
事实上,优化对代码的不利影响并非编译器上的新事物。大多数老派C++开发人员会立刻告诉你这一点,然后像我一样,会讲述一些长而详细的故事,说明他们是如何克服这个问题的。

1
如果在调试器下启动了一个开启了优化的构建,是否会抑制一些优化,可能导致原始问题中描述的行为?一个例子会非常有帮助。 - Govert
12年后,问题出在StackFrame上。 - ScoMo

0

正如Mark所建议的那样,这通常是由于时间相关问题引起的,往往是竞争条件或同步问题。

处理这种问题的一种常见方法是在受影响的区域使用“打印”语句来显示正在发生的情况。如果打印语句(Console.WriteLineResponse.Write、日志记录或其他)解决了问题,那么将值存储在全局变量中,并在问题出现后打印全局变量。

我最近遇到这种情况是在读取串口数据的代码中。调试活动导致时间上的微小变化足以影响串口数据的缓冲方式,从而改变了缓冲区的解析方式。由于打印语句改变了时间,因此我必须将数据存储起来以便稍后输出。


0

仅仅是想补充一下,最近我发现在一个 SQL 过程中有一个日期比较被测试调用了。这些日期都是在测试过程中自动生成的,并且将值插入到数据库中,因此偶尔它们会完全相同(当使用 RunTests 时),导致在表连接上返回 null。这不是我期望的结果。显然,在调试模式下,由于我正在缓慢地进行,自动生成的时间会有所不同,这意味着我从未遇到过这个错误。我通过插入解决了这个问题。

Threading.Thread.Sleep(520)

无论何时在操作之间都会有延迟。问题已解决。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接