Код ведет себя по-разному в режиме выпуска и отладки

У нас есть несколько модульных тестов, которые не работают при запуске в режиме выпуска по сравнению с режимом отладки. Если я подключу отладчик в режиме выпуска, тесты пройдут. Здесь слишком много кода для публикации, поэтому я просто ищу передовые методы отладки проблем, связанных с режимом выпуска. Я проверил:

РЕШЕНИЕ: В данном случае это потому, что я сравнивал переменные с плавающей запятой на равенство. Я не мог изменить числа с плавающей запятой на десятичные без серьезного рефакторинга, поэтому я добавил метод расширения:

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;
    }
}

person Shaun Bowe    schedule 24.01.2011    source источник
comment
Пара вопросов. 1. Какие ошибки вы допускаете, чтобы придать этому вопросу некую «изюминку»? 2. Проверяли ли вы условные методы?   -  person Tim Lloyd    schedule 24.01.2011
comment
Основная проблема в том, что метод Equals возвращает false. Однако, если я буду рассматривать каждое утверждение по отдельности, все они вернут истину. Если я попытаюсь подключить отладчик, проблема исчезнет.   -  person Shaun Bowe    schedule 25.01.2011
comment
Связано ли это с плавающей запятой (тип данных double и т. Д.)?   -  person stefan    schedule 25.01.2011
comment
@Mark Byers Нет, приложение однопоточное   -  person Shaun Bowe    schedule 25.01.2011
comment
@stefan Я сравниваю некоторые значения с плавающей запятой в методе IsEqual.   -  person Shaun Bowe    schedule 25.01.2011
comment
Почему бы вам не опубликовать метод IsEqual?   -  person Gabe    schedule 25.01.2011


Ответы (6)


Поскольку кажется, что это связано с плавающей запятой, существует очень много вещей, которые могут пойти не так. См .: C # - Несогласованный результат математической операции на 32 -bit и 64-bit и проблемы с двойной точностью в .NET

Есть так много вещей, которые можно выбросить с плавающей запятой. А сравнивать числа с плавающей запятой на равенство вообще запрещено. Вы можете проверить разницу меньше, чем разумный эпсилон.

person stefan    schedule 24.01.2011
comment
См. Также stackoverflow.com/questions/2342396/ - person Eric Lippert; 25.01.2011

Одна вещь, которая может вызвать такое поведение, - это ошибка, которая вызывает состояние гонки. Присоединение отладчика может изменить синхронизацию кода, так что состояние гонки больше не запускается.

Чтобы исправить это, используйте синхронизацию соответствующим образом, когда у вас есть несколько потоков, обращающихся к данным.


Я сравниваю некоторые значения с плавающей запятой в методе IsEqual.

Звучит как очень плохая идея. Вы не должны сравнивать числа с плавающей запятой на равенство, потому что вычисления с плавающей запятой не являются точными на 100%, и вы можете получить ошибки представления и округления. Сравните, чтобы увидеть, достаточно ли они близко друг к другу. Для расчетов, связанных с деньгами, вы, вероятно, захотите использовать вместо этого тип decimal.

person Mark Byers    schedule 24.01.2011
comment
Спасибо за ответ, однако эта конкретная часть приложения является однопоточной. - person Shaun Bowe; 25.01.2011
comment
Это должны быть числа с плавающей запятой. Я внесу некоторые изменения и проверю. - person Shaun Bowe; 25.01.2011

Вопросы, которые вы должны задать себе -

  1. Является ли мой код многопоточным? Разница во времени повлияет на результат
  2. Кто-то вызывает Debug.Assert () с выражением, которое вызывает побочные эффекты?
  3. Какие объекты реализуют IDisposable (), а некоторые делают это таким образом, что меняют состояние?
  4. Вы выполняете P / вызов в неуправляемый код?

Номер 3 в данном случае, скорее всего, плохой мальчик. Сборка мусора может сильно отличаться при отладке и выпуске, и вы можете обнаружить, что сбор мусора для объекта влияет на результат более позднего модульного теста.

И к вашему сведению, если вы используете NUnit и TestDriven.NET - эти два теста запускают тесты в разных порядках.

person plinth    schedule 24.01.2011
comment
+1 за упоминание различий в сборке мусора. В режиме отладки объекты имеют тенденцию оставаться в живых дольше (например, до конца метода), чтобы поддерживать отладку, в то время как в режиме выпуска объекты часто собираются раньше. - person stakx - no longer contributing; 25.01.2011

Это часто имеет место, поскольку отладочная сборка не оптимизирована по умолчанию, и даже если вы включите ее, поведение при отладке будет совсем другим. Вы можете отключить «Оптимизировать код» в настройках проекта для всех сборок на вкладке «Свойства» -> «Сборка».

Конечно, есть и другие изменения, которые могут вызвать различия, например, вы упомянули, что условные методы являются одними из них. Я обнаружил, что они редко являются причиной проблем, для меня это почти всегда оптимизатор.

Классические хитрости оптимизатора включают методы, которые становятся «встроенными», так что они не отображаются в стеке вызовов. Это вызывает проблемы при использовании классов System.Diagnostics.StackFrame для определения текущей точки выполнения. Точно так же это повлияет на результат MethodBase.GetCurrentMethod или других функций / поведения, которые зависят от выполняемого метода.

Кроме того, я, конечно, видел много вещей, которые делал оптимизатор, которые я просто не могу объяснить. Один такой пример был задокументирован и обсужден в сообщении «HashDerivedBytes - замена Rfc2898DeriveBytes, но почему?», но я никогда не разгадывал тайну. Я только знаю, что оптимизатор просто сломал Rfc2898DeriveBytes при использовании для генерации серии производных байтов. Как ни странно, это сломалось только тогда, когда сгенерированные байты не были равномерно разделены на размер используемого хеш-алгоритма (20) и дали неверные результаты только после первых 20 байтов.

Дело в том, что оптимизации, отрицательно влияющие на код, не новость для компиляторов. Большинство разработчиков C ++ старой школы скажут вам это сразу, а затем, как и я, расскажут о том, как они работали над этим;)

person csharptest.net    schedule 24.01.2011
comment
Итак, если сборка с включенной оптимизацией запускается под отладчиком, будет ли подавлена ​​некоторая оптимизация, что может вызвать поведение, описанное в исходном вопросе? Пример этого был бы очень полезен. - person Govert; 09.05.2012

Как предполагает Марк, это обычно является результатом проблемы, связанной с синхронизацией, часто из-за состояния гонки или проблемы с синхронизацией.

Один из распространенных способов решения такой проблемы - использовать операторы печати в затронутых областях, чтобы показать вам, что происходит. Если операторы печати (Console.WriteLine, Response.Write, ведение журнала или что-то еще) устраняют проблему, сохраните значения в глобальных переменных и распечатайте глобальные переменные, как только проблема обнаружится.

В последний раз это случилось со мной в коде, который читал из последовательного порта. Действия по отладке вызвали изменение времени, достаточное для того, чтобы повлиять на то, как байты из последовательного порта буферизовались, что изменило способ анализа буфера. Поскольку операторы печати изменили время, мне пришлось сохранить данные для вывода позже.

person Gabe    schedule 24.01.2011

Чтобы добавить к этому свои два цента, я недавно обнаружил, что у меня есть сравнение дат в процедуре sql, которую вызвало тестирование. Все даты были автоматически сгенерированы до процедуры тестирования, а значения были вставлены в базу данных, поэтому иногда они были точно такими же (при использовании RunTests), что приводило к возврату null при объединении таблицы. Не то, что я ожидал. Очевидно, что в режиме отладки, поскольку я медленно продвигаюсь через это, будет разница в автоматически сгенерированном времени, что означает, что я никогда не сталкивался с ошибкой. Я решил это, вставив

Заправка. Резьба. Сон (520)

везде, где точно была бы задержка между действиями. Проблема исправлена.

person user676767    schedule 25.10.2011