检测大型JS程序中的任何变量何时设置为NaN



我有一个庞大而混乱的JS代码库。有时,在使用应用程序时,会将一个变量设置为NaN。因为x = 2 + NaN导致x被设置为NaN,所以NaN会病毒传播。在某种程度上,在它传播得相当远之后,用户注意到到处都是NaN,而且大便通常不再起作用。从这种状态来看,我很难回溯并确定NaN的来源(很可能有多个来源)。

NaN错误也不容易重现。尽管有数百人观察并向我报告,但没有人能告诉我导致NaN出现的一系列步骤。也许这是一种罕见的种族状况或其他什么。但它绝对是罕见的,而且起源不明。

如何修复此错误?有什么想法吗?

我想到了两个可能不可行的愚蠢想法:

  1. 编写某种预处理器,在每次使用任何变量之前插入isNaN检查,并记录NaN的首次出现。我想以前没有这样做过,也不知道会有多难。任何建议都将不胜感激。

  2. 在JS引擎中运行我的代码,该引擎能够在任何变量设置为NaN时设置断点。我不认为有什么能开箱即用,但把它添加到Firefox或Chrome有多难?

我觉得我一定不是第一个遇到这种问题的人,但我找不到其他人在谈论它。

你的问题可能没有解决方案,也就是:break,每当任何变量设置为NaN时。相反,你可以试着这样观察你的变量:

  • 前面说过,Chrome调试器提供了条件断点。但是,它也支持观看表情。在"监视表达式"菜单中,只要变量设置为特定值,就可以设置要中断的条件。

  • Object.observe是一种观察对象变化的方法。您可以监听对象上的所有更改,并在任何变量设置为NaN时调用debug。例如,您可以观察到窗口对象上的所有更改。只要窗口对象上的任何变量设置为NaN,就可以调用debug。请注意,Object.observe非常先进,并非所有浏览器都支持(在这种情况下,请查看polyfill)。

  • 利用这个机会为代码中的每个函数编写一个测试用例。执行随机测试并找到可以创建NaN值的代码行。

你的另一个问题可能是如何重现这个错误。一遍又一遍地重新加载你的网页没有多大意义。你可以查看一个所谓的无头浏览器:它启动一个浏览器实例而不显示它。它可以用来在网站上执行自动测试,点击一些按钮,做一些事情。也许你可以用这样一种方式编写它,它最终会重现你的错误。这样做的好处是你不必重新加载网页数百次。无头浏览器有几种实现。PhantomJS在我看来真的很不错。你也可以用它启动Chrome调试控制台(你需要一些插件:远程调试器)。

此外,请注意,NaN永远不等于NaN。如果您最终能够重现错误,但断点不起作用,那将是一件很遗憾的事情。

如果你能很好地将事物与全局名称空间隔离开来,并在对象中嵌套,这可能会有所帮助。在这之前,我会说这绝不是一个完全完整的解决方案,但至少,这应该有助于你的搜索。

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!');上运行代码。确保将其更改为您认为有帮助的任何类型的调试输出。

如果有人能找到一种方法将它强制到全局对象上,这个函数会更有帮助,但我每次尝试都只是告诉我,我应该坐下来,思考我做了什么。

哦,如果从上面看不清楚的话,在一个大型项目中,这个功能势必会让"速度"one_answers"效率"等讨厌的功能成为过去。

您的代码是与服务器端通信,还是仅与客户端通信?您提到这是一个罕见的问题,因此它可能只发生在某些浏览器(或浏览器版本)中,或者发生在任何可能难以复制的情况下。如果我们假设nan的任何出现都是问题,并且当它发生时,用户注意到错误("到处都是nan"),那么显示错误的弹出窗口,错误应该包含nan的第一次出现(然后用户可能会重新输入"尽管有数百人观察到它并向我报告")。或者不显示,而是将其发送到服务器。要做到这一点,请编写一个简单的函数,该函数只接受一个变量,并检查变量是否为NaN,。把它放在代码中的敏感位置(敏感变量)。而这个raports也许可以安慰有问题的代码。我知道这很脏,但它会有所帮助。

您的一个数学函数失败了。我以前用过数字(变量)来纠正这个问题。这里有一个例子:

test3=数字(test2+test1)即使test1和test2看起来是数字

对源代码进行调试肯定是解决这一问题的方法。

我的建议是设置一些功能测试,重点关注这些测试在哪里被复制,设置一些不同超时的测试条件,然后重新运行它,直到它捕捉到它。如果可能的话,设置一些日志记录过程来查看回溯。

你的堆栈是什么样子的?我不能在不查看代码的情况下进行太多分析,但既然它是javascript,我想你应该能够使用浏览器的开发工具吗?

如果您知道NaN的传播位置,您可以尝试使用程序切片来缩小影响该值的其他程序语句(通过控制和数据依赖性)。然而,这些工具的设置通常很简单,所以我会尝试其他人首先给出的Object.observe风格的答案。

你可以试试IBM的WALA。它是用Java编写的,但有一个Javascript前端。你可以在维基上找到关于切片器的信息。

基本上,如果工具正在工作,你会给它一个程序点(语句),它会给你一组语句,这些语句的起点是(过渡的)控制和/或数据依赖的。如果你知道多个"感染"点,并怀疑是单一来源,您可以使用它们切片的交集来缩小列表(程序点的切片通常是一组非常大的语句)。

(对于注释来说太长)

测试时,您可以覆盖所有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;
    };
}

我并没有测试,也许一个明确的"包装"方法列表会更好。

BTW,您不应该将其放入生产代码中

最新更新