首页 文章

通过Google Apps脚本中的闭包搜索树节点

提问于
浏览
0

General problem I'm trying to solve

我正在尝试在Google Apps脚本中实施搜索树,按 pkgName 属性排序,最终目的是将软件项目中导入的元数据与包含类似数据的工作表进行比较 .

为了防止构造函数的命名空间被“私有”属性污染,我使用了闭包 .

Implementation

因此,我迄今为止的实施是:

SheetDataNode.gs

/**
 *  Constructor for a SheetDataNode. Takes one, three, or four arguments.
 *  @param { { package : string, files : { complexity : number, name : string, testingStatus : string }[], rowNumber : number } | string } line data or package name
 *  @param { string } filename : the files contained in package
 *  @param { number } complexity : the total number of branches in the file 
 *  @param { number } rowNumber : the row number as this appears in the spreadsheet it is being created from
 *  @param { string } [ testingStatus ] : the status on the testing of this file. Should be one of the following: NOT_TESTED, FULLY_TESTED, IN_PROGRESS, or PARTIAL
 *  @returns { SheetDataNode }
 *  @deprecated This function is not working right now 
 **/
function SheetDataNode(data, filename, complexity, rowNumber, testingStatus) { 
    var _pkgName = '';
    var _leftChild = null;
    var _rightChild = null;
    var _beenFound = false;
    var _rowNumber = rowNumber;
    var _files = [];

    // if there's only one argument, it better be an object, having the required fields
    if (arguments.length === 1) { 
      // it should have package field
      if ((data.package === undefined) || (data.package !== data.package.toString())) { 
        throw ReferenceError('only one argument was specified, but it is not an object that contains package');
      }
      // it should have files field
      if ((data.files === undefined) || (!Array.isArray(data.files))) { 
        throw ReferenceError('Called from the one-arg constructor, so files should be Array');
      }
      // that files field should itself be an object with the following fields: complexity and name
      for (var idx in data.files) { 
        if (data.files[idx].complexity !== parseInt(data.files[idx].complexity)) { 
          throw TypeError("complexity should be an integer");
        }
        if (data.files[idx].name !== data.files[idx].name.toString()) { 
          throw TypeError("name of file should be a string");
        }
      }

      // sort the array of files
      data.files.sort(fileSorter)

      // call the initialization function
      return SheetDataNode._init(data.package, data.files, parseInt(data.rowNumber));
    }
    // performing argument checking
    if (filename !== filename.toString()) throw TypeError("filename is supposed to be a String")
    if ((complexity !== undefined) && (complexity !== parseInt(complexity))) { 
      throw TypeError("complexity must be a number, or undefined")
    }

  // call the initialization function, constructing a single file object
  return SheetDataNode._init(data.toString(), [{
    complexity : complexity,
    name: filename, 
    testingStatus : testingStatus
  }])
}

// Helper private function that performs initialization
SheetDataNode._init = function(package, files, rowNumber) { 
  // bring in the variables
  var _pkgName = package;
  var _files = files;
  var _leftChild = null;
  var _rightChild = null;
  var _beenFound = false;
  var _rowNumber = rowNumber;

  // providing a function to add file
  _addFile = function(file) { 
    for (var f in _files) { 
      if (file.name < _files[f].name) { 
        _files.splice(f, 0, file)
        return 
      }
    }
    _files.push(file)
  }


  return {
    getRowNumber : function() { return _rowNumber; },
    getPackageName : function () { return _pkgName; },
    getFiles: function() { return _files; },
    addFile : _addFile,
    addFiles : function(files) { 
      if (!Array.isArray(files)) throw TypeError("files should be an Array")
      for (var idx in files) { 
        _addFile(files[idx])
      }
    },
    getLeftChild : function() { return _leftChild; },
    setLeftChild : function(node) { 
        _leftChild = node;
    },
    getRightChild : function() { return _rightChild; },
    setRightChild : function(node) { 
        _rightChild = node;
    },
    insertNode : function(node) { 
      // set the current node as the head node
      var currentNode = this;
      // while we are on a non-null node
      while (currentNode) { 
        // if the package of node is the same as that of currentNode
        if (currentNode.getPackageName() === node.getPackageName()) { 
          // simply add the files of node to currentNode._files
          currentNode.addFiles(node.getFiles())
          return
        }
        // if the package of node "comes before" that of currentNode, move to the left
        if (currentNode.getPackageName() > node.getPackageName()) { 
          // if the left child of node is defined, that becomes the current node
          if (currentNode.getLeftChild()) currentNode = currentNode.getLeftChild()
          // else construct it, and we're done
          else { 
            currentNode.setLeftChild(node)
            return
          }
        }
        // if the package of node "comes after" that of currentNode, move to the right
        if (currentNode.getPackageName() < node.getPackageName()) {
          // if the right child of node is defined, that becomes the current node
          if (currentNode.getRightChild()) currentNode = currentNode.getRightChild()
          // else construct it, and we're done
          else {
            currentNode.setRightChild(node)
            return 
          }
        }
        throw Error("Whoa, some infinite looping was about to happen!")
      }
    }
  }

}

UtilityFunctions.gs

/**
 *  Sorts file objects by their name property, alphabetically
 *  @param { { name : string } } lvalue
 *  @param { { name : string } } rvalue
 *  @returns { boolean } the lexical comparison of lvalue.name,rvalue.name
 **/ 
function fileSorter(lvalue, rvalue) {
  if (lvalue.name > rvalue.name) return 1;
  return (lvalue.name < rvalue.name) ? -1 : 0;
}

Problem

我正在对代码进行单元测试,失败的测试用例包括以下步骤:

  • 构造 SheetDataNode node

  • 构造另一个 SheetDataNode otherNode ,其包名与第一个文件名相同,但文件名不同

  • otherNode 插入 node

  • 期望:它现在有两个文件

  • 实际:它只有一个:原件 .

  • 期望:此操作未设置左子节点或右子节点

  • actual:此操作未设置左子节点或右子节点

执行上述操作的代码如下所示:

QUnit.test("inserting a node having the same package as the node it is assigned to",
             function() { 
               // create the base node
               var node = SheetDataNode("example", "main.go", 3, 1)
               // insert an other node, with identical package name
               var otherNode = SheetDataNode(node.getPackageName(), "logUtility.go", 12, 3)
               node.insertNode(otherNode)
               // node should contain two files, and neither a left child nor a right child
               deepEqual(node.getFiles().map(function(val) { 
                 return val.name
               }), 
                         ["logUtility.go", "main.go"], 
                         "node contains the right file names")
               equal(node.getFiles().length, 2, "A package got added to the node")
               ok(!node.getLeftChild(), "leftChild still unset")
               ok(!node.getRightChild(), "rightChild still unset")
             })

以下是失败断言的屏幕截图:

请记住,测试中的方法是这样的:

insertNode : function(node) { 
      // set the current node as the head node
      var currentNode = this;
      // while we are on a non-null node
      while (currentNode) { 
        // if the package of node is the same as that of currentNode
        if (currentNode.getPackageName() === node.getPackageName()) { 
          // simply add the files of node to currentNode._files
          currentNode.addFiles(node.getFiles())
          return
        }
        // if the package of node "comes before" that of currentNode, move to the left
        if (currentNode.getPackageName() > node.getPackageName()) { 
          // if the left child of node is defined, that becomes the current node
          if (currentNode.getLeftChild()) currentNode = currentNode.getLeftChild()
          // else construct it, and we're done
          else { 
            currentNode.setLeftChild(node)
            return
          }
        }
        // if the package of node "comes after" that of currentNode, move to the right
        if (currentNode.getPackageName() < node.getPackageName()) {
          // if the right child of node is defined, that becomes the current node
          if (currentNode.getRightChild()) currentNode = currentNode.getRightChild()
          // else construct it, and we're done
          else {
            currentNode.setRightChild(node)
            return 
          }
        }
        throw Error("Whoa, some infinite looping was about to happen!")
      }

针对方法 addFiles 的测试,其中包含以下代码:

QUnit.test("testing method addFiles",
             function() { 
               // create the base node
               var node = SheetDataNode("example", "main.go", 3, 1)
               // create an array of files to add
               const filesToAdd = [{
                 name : 'aFile.go',
                 complexity : 10
               }, {
                 name : 'anotherFile.go',
                 complexity : 10
               }, {
                 name : 'yetAnotherFile.go',
                 complexity : 10
               }]
               // is node.getFiles() an array?!
               ok(Array.isArray(node.getFiles()), "node.getFiles() is an array")

               // add the files
               node.addFiles(filesToAdd)
               Logger.log(node.getFiles())
               // node.getFiles() should be an Array
               ok(Array.isArray(node.getFiles()), "node.getFiles() is still an array")
               // node.getFiles should now contain filesToAdd
               equal(node.getFiles().length, 1 + filesToAdd.length, "node.getFiles().length increased by the length of the files to add")
             })

经过:

,对于 insertNode 的其他测试也是如此,这意味着问题可能存在于我们如何尝试在 insertNode 中引用 currentNode 以进行数组属性修改 . If so, I have no idea how else to reference, in Google Apps Script, the SheetDataNode to undergo state change

1 回答

  • 0

    通过更改私有函数属性声明,我能够从the MDN docs on closures中获得灵感来解决问题:

    _addFile = function(file) { 
        for (var f in _files) { 
          if (file.name < _files[f].name) { 
            _files.splice(f, 0, file)
            return 
          }
        }
        _files.push(file)
      }
    

    function _addFile(file) { 
        for (var f in _files) { 
          if (file.name < _files[f].name) { 
            _files.splice(f, 0, file)
            return 
          }
        }
        _files.push(file)
      }
    

    idk为什么会这样,因为我忘记了声明方法之间的区别,比如函数变量(我正在做什么),并且在方法的名称前面加上 function 就像它's any other function. I' ll必须(重新)学习...

相关问题