Обнаружение, когда для любой переменной в большой программе JS установлено значение NaN

У меня большая и запутанная кодовая база JS. Иногда, когда приложение используется, переменной присваивается значение NaN. Поскольку x = 2 + NaN приводит к тому, что x устанавливается в NaN, NaN распространяется вирусно. В какой-то момент, после того, как он распространился довольно далеко, пользователь замечает, что NaN повсюду, и дерьмо, как правило, больше не работает. Из этого состояния мне очень трудно вернуться назад и определить источник NaN (и вполне может быть несколько источников).

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

Как я могу исправить эту ошибку? Любые идеи?

Я подумал о двух глупых идеях, которые могут оказаться неосуществимыми:

  1. Напишите какой-нибудь препроцессор, который вставляет isNaN проверок перед каждым использованием любой переменной и регистрирует первое появление NaN. Я не думаю, что это было сделано раньше, и я не знаю, насколько это будет сложно. Любой совет будет принят во внимание.

  2. Запустите мой код в движке JS, который может установить точку останова в любое время, когда для любой переменной установлено значение NaN. Я не думаю, что что-то делает это из коробки, но насколько сложно будет добавить его в Firefox или Chrome?

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


person dumbmatter    schedule 25.10.2014    source источник
comment
Как организован код? Это большая модульная JS-программа? или структурированное ООП? или что-то среднее?   -  person carlodurso    schedule 25.10.2014
comment
Он довольно хорошо организован в модули с использованием RequireJS.   -  person dumbmatter    schedule 25.10.2014
comment
Отладчик Chrome позволяет устанавливать условные точки останова.   -  person Cheery    schedule 28.10.2014
comment
если вы делаете то, что вы описали в 1), вы также можете поставить debugger; перед ним. Или if (isNaN(x)) {debugger;} - который будет работать по крайней мере в хроме, возможно, и в других браузерах. По сути, это условная точка останова.   -  person amenthes    schedule 28.10.2014
comment
@Cheery, позволяет ли это мне сделать что-то вроде break, когда для любой переменной установлено значение NaN?   -  person dumbmatter    schedule 28.10.2014
comment
@dumbmatter any - нет, но в самых посещаемых локациях можно поставить такое условие.   -  person Cheery    schedule 28.10.2014
comment
@Cheery Это не удовлетворяет моего любопытства и надежды более элегантно решить эту проблему :)   -  person dumbmatter    schedule 28.10.2014
comment
Возможно, это может вам помочь: stackoverflow.com/a/2762091/3225104   -  person Hristo    schedule 02.11.2014
comment
Поскольку вы находитесь в стране Javascript: ... выражение (x != x) является более надежным способом проверить, является ли переменная x NaN или нет ... ~ MDN на isNaN.   -  person musically_ut    schedule 03.11.2014


Ответы (7)


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

function deepNaNWatch(objectToWatch) {
  'use strict';

  // Setting this to true will check object literals for NaN
  // For example: obj.example = { myVar : NaN };
  // This will, however, cost even more performance
  var configCheckObjectLiterals = true;

  var observeAllChildren = function observeAllChildren(parentObject) {

    for (var key in parentObject) {
      if (parentObject.hasOwnProperty(key)) {
        var childObject = parentObject[key];

        examineObject(childObject);
      }
    }
  };

  var examineObject = function examineObject(obj) {
    var objectType = typeof obj;

    if (objectType === 'object' || objectType === 'function') {
      Object.observe(obj, recursiveWatcher);
      if (configCheckObjectLiterals) {
        observeAllChildren(obj);
      }
    } if (objectType === 'number' && isNaN(obj)) {
      console.log('A wild NaN appears!');
    }
  };

  var recursiveWatcher = function recursiveWatcher(changes) {
    var changeInfo = changes[0];
    var changedObject = changeInfo.object[changeInfo.name];

    examineObject(changedObject);
  };

  Object.observe(objectToWatch, recursiveWatcher);
}

Вызовите deepNaNWatch(parentObject) для каждого объекта/функции верхнего уровня, которые вы используете для вложения вещей, как только они будут созданы. Каждый раз, когда объект или функция создаются внутри наблюдаемого объекта/функции, они сами также становятся наблюдаемыми. Каждый раз, когда number создается или изменяется в отслеживаемом объекте — помните, что typeof NaN == 'number' — он проверяет, является ли он NaN, и если да, то запускает код в console.log('A wild NaN appears!');. Обязательно измените это на любой вид отладочного вывода, который, по вашему мнению, поможет.

Эта функция была бы более полезной, если бы кто-то мог найти способ принудительно применить ее к глобальному объекту, но каждая попытка, которую я предпринял, просто говорила мне, что я должен сидеть в перерыве и думать о том, что я сделал.

О, и если это не очевидно из вышесказанного, в крупномасштабном проекте эта функция обязательно сделает надоедливые функции, такие как «скорость» и «эффективность», делом прошлого.

person KoratDragonDen    schedule 30.10.2014
comment
Это может многое поймать, но все равно может пропустить что-то, например. в литералах объекта: var o = {}; deepNaNWatch(o); o.o = {n: NaN}; не будет перехвачено, а более позднее o.o.n = NaN будет. - person Jake Cobb; 30.10.2014
comment
@JakeCobb Верно; Я пересмотрел код, чтобы учесть это. Теперь он должен иметь возможность перехватывать случаи, когда объект создается с литералом объекта. К сожалению, это неизбежно будет стоить еще большей производительности, но такова цена попытки создать всеведущую функцию. - person KoratDragonDen; 31.10.2014

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

  • Ранее было сказано, что отладчик Chrome предлагает условные точки останова. Но он также поддерживает просмотр выражений. В меню Watch-Expressions вы можете установить условие, которое прерывается всякий раз, когда переменной присваивается определенное значение.

  • Object.observe — это метод, отслеживающий изменения объекта. Вы можете прослушивать все изменения объекта и вызывать debug, когда для любой переменной установлено значение NaN. Например, вы можете наблюдать за всеми изменениями объекта окна. Всякий раз, когда любая переменная в объекте окна устанавливается в NaN, вы вызываете debug. Обратите внимание, что Object.observe является передовым и поддерживается не всеми браузерами (ознакомьтесь с polyfill). в таком случае).

  • Воспользуйтесь этой возможностью, чтобы написать тестовый пример для каждой функции в вашем коде. Выполните случайное тестирование и найдите строку кода, которая может создавать значения NaN.

Другая ваша проблема, вероятно, заключается в том, как воспроизвести эту ошибку. Перезагружать веб-страницу снова и снова не имеет особого смысла. Вы можете проверить так называемый безголовый браузер: он запускает экземпляр браузера, не отображая его. Его можно использовать для выполнения автоматических тестов на веб-сайте, нажатия некоторых кнопок, выполнения некоторых действий. Может быть, вы можете написать его таким образом, чтобы он, наконец, воспроизвел вашу ошибку. Это имеет то преимущество, что вам не нужно перезагружать веб-страницу сотни раз. Существует несколько реализаций безголовых браузеров. На мой взгляд, PhantomJS действительно хорош. Вы также можете запустить консоль отладки Chrome (вам понадобится плагин: удаленный отладчик ).

Кроме того, обратите внимание, что NaN никогда не равен NaN. Было бы жаль, если бы вы, наконец, смогли воспроизвести ошибку, но ваши точки останова не работают.

person dmonad    schedule 29.10.2014
comment
Я не видел JCheck раньше, спасибо за ссылку. Будет ли это достаточно сложно, чтобы определить, когда NaN может быть вызван состоянием гонки? Например, возможно, моя ошибка вызвана чем-то вроде... Пользователь инициирует действие, которое записывает два значения в двух транзакциях в два разных хранилища объектов IndexedDB. После завершения первого, но до завершения второго, он загружает ту же страницу в новом окне. Новый экземпляр видит первое значение и ожидает найти второе, но вместо этого он возвращается неопределенным и превращается в NaN при добавлении к другой переменной. - person dumbmatter; 29.10.2014
comment
Вероятно, нет решения для вашей проблемы, также известной как: break, всякий раз, когда для любой переменной установлено значение NaN. - почему нет? Я представляю синтаксический анализатор, который идентифицирует каждое присвоение переменной и сразу после него добавляет строку isNaN. Если это правда, запишите ошибку. Возможно, было бы немного медленнее, если бы повсюду были isNaN, но если я оставлю это в живых примерно на неделю, я смогу найти основную причину проблемы и исправить ее раз и навсегда... - person dumbmatter; 29.10.2014
comment
Хорошо, я должен перефразировать: для этого, вероятно, не существует существующего решения. Ваше решение, вероятно, сработает, но написание такой программы может быть очень сложным для правильной реализации. Хотя, согласен, можно. Когда дело доходит до JCheck, это что-то вроде изощренного способа создания случайных параметров для функций. Он не такой мощный. - person dmonad; 29.10.2014
comment
В моей версии Chrome вы не можете использовать Object.observe для объекта window, он дает TypeError сообщение о том, что наблюдение не может быть вызвано для глобального прокси-объекта. - person Jake Cobb; 30.10.2014

Ваш код взаимодействует с серверной частью или только с клиентской? Вы упоминаете, что это редкая проблема, поэтому она может возникнуть только в некоторых браузерах (или версиях браузеров) или в любой ситуации, которую трудно воспроизвести. Если мы предположим, что любое появление nan является проблемой, и что когда это происходит, пользователь замечает ошибку («во всем мире есть NaN»), тогда вместо отображения всплывающего окна с ошибкой ошибка должна содержать первое появление nan (тогда пользователи могут сообщить это «Несмотря на то, что сотни людей наблюдают за этим и сообщают мне об этом»). Или не показывать, а отправить на сервер. Для этого напишите простую функцию, которая принимает в качестве аргумента только одну переменную и проверяет, является ли переменная NaN. Поместите его в свой код в чувствительные места (чувствительные переменные). И это может указывать на проблематичный код. Я знаю, что это очень грязно, но это может помочь.

person Krzysztof Sztompka    schedule 27.10.2014
comment
Это чисто клиентское приложение. Ошибка была обнаружена в Firefox и Chrome, единственных двух браузерах со значительной пользовательской базой. Поместить это в свой код в чувствительных местах - сложная часть, я надеялся, что это будет систематическим способом. Если это невозможно, мне, возможно, придется сделать то, что вы предлагаете, но это будет медленный и болезненный процесс. Тем более, что NaN вполне может быть представлено в нескольких местах, и я хочу иметь возможность с уверенностью сказать, что исправил их все. - person dumbmatter; 28.10.2014
comment
да это боль и грязный путь. Но другие решения основаны на воспроизведении ошибки в вашем инструменте (если вы обнаружите эту ошибку в отладчике Chrome, это не означает, что в браузерах ваших клиентов нет других ошибок). Сколько раз вы воспроизводите эту ошибку? Проблема может быть не в коде, а в пользовательских данных, и ваш инструмент отладки при его использовании может не найти проблему. Что делает ваше приложение? Общается ли это на стороне сервера при запуске или в других местах? Пользователь заполняет какие-то формы, и ваше приложение работает с данными из него? - person Krzysztof Sztompka; 28.10.2014
comment
Лично я видел его всего несколько раз. Очень редко. У меня тысячи пользователей, и многие из них видели это, но очень редко. В моем приложении почти нет ввода пользовательских данных (это видеоигра, почти все данные генерируются случайным образом, нет серверного компонента). Я подозреваю, что конечным источником проблемы является состояние гонки из-за того, насколько редка и прерывиста эта ошибка. Я не хочу вручную проверять каждую переменную в 20 разных файлах, но если бы был автоматизированный способ анализа моего JS и вставки кода проверки перед назначением всех переменных, это было бы круто. - person dumbmatter; 28.10.2014

Одна из ваших математических функций не работает. Раньше я использовал число (переменная), чтобы исправить эту проблему. Вот пример:

test3 = Number(test2+test1), даже если test1 и test2 кажутся числами

person chran    schedule 28.10.2014
comment
Это не поможет. Number('a' + 4) это NaN. Я не хочу иметь NaN там, где у меня должен быть номер, вот в чем проблема. Кроме того, моя цель не скрыть ошибку, а определить ее источник. - person dumbmatter; 28.10.2014

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

Отладка исходного кода определенно будет способом справиться с этим.

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

Как выглядит ваш стек? Я не могу дать слишком много анализа, не глядя на ваш код, но, поскольку это javascript, вы должны иметь возможность использовать инструменты разработки браузера, как я полагаю?

person mattLummus    schedule 30.10.2014

Если вы знаете места, куда распространяются NaN, вы можете попробовать использовать нарезку программы, чтобы сузить другие операторы программы, влияющие на это значение (через зависимости управления и данных). Однако эти инструменты обычно нетривиальны в настройке, поэтому я бы сначала попробовал ответы в стиле Object.observe, которые дают другие.

Вы можете попробовать WALA от IBM. Он написан на Java, но имеет интерфейс JavaScript. Информацию о слайсере можно найти на вики.

По сути, если инструмент работает, вы дадите ему программную точку (оператор), и он даст вам набор операторов, от которых начальная точка (транзитивно) зависит от управления и/или данных. Если вы знаете несколько «зараженных» точек и подозреваете один источник, вы можете использовать пересечение их срезов, чтобы сузить список (срез программной точки часто может быть очень большим набором операторов).

person Jake Cobb    schedule 30.10.2014

(был слишком длинным для комментария)

Во время тестирования вы можете перезаписать ВСЕ функции Math, чтобы проверить, создается ли NaN.

Это не поймает

a = 'string' + 1;

но будет ловить такие вещи, как

a = Math.cos('string');
a = Math.cos(Infinity);
a = Math.sqrt(-1);
a = Math.max(NaN, 1);
...

Пример:

for(var n Object.getOwnPropertyNames(Math)){
    if (typeof Math[n] === 'function') Math[n] = wrap(Math[n]);
}
function wrap(fn){
    return function(){
        var res = fn.apply(this, arguments);
        if (isNaN(res)) throw new Error('NaN found!')/*or debugger*/;
        return res;
    };
}

Я не проверял, возможно, явный список «обернутых» методов лучше.

Кстати, вы не должны использовать это в рабочем коде.

person Prusse    schedule 31.10.2014