【问题标题】:Should we validate method arguments in JavaScript API's?我们应该验证 JavaScript API 中的方法参数吗?
【发布时间】:2010-12-15 22:28:36
【问题描述】:

我正在开发一个供 3rd 方开发人员使用的 JavaScript 库。 API 包含具有此签名的方法:

函数doSomething(arg1,arg2,选项)

  • arg1、arg2 是“必需的”简单类型参数。
  • options 是一个包含可选参数的哈希对象。

您是否建议验证: - 参数类型是否有效? - 选项属性是否正确?比如:开发者没有误传onSucces而不是onSuccess?

  • 为什么像prototype.js 这样的流行库没有验证?

【问题讨论】:

    标签: javascript validation


    【解决方案1】:

    不验证。更多代码是用户必须下载的更多代码,因此这对用户和生产系统来说是非常实际的成本。参数错误很容易被开发人员捕获;不要给用户带来这样的负担。

    【讨论】:

    • 我不同意这种方法总是最好的。如果 API 足够复杂,任何验证都不会导致烦人的错误。例如,有很多次在使用 ExtJS 库时,我希望他们对传入的参数进行更多的验证,从而节省我数小时的头痛。
    • 您更希望拥有哪个?数小时的头痛,或者因为您的网站加载时间过长而没有用户?选择是显而易见的。
    • 两者都不是这里的选择。多做一些前期工作以使您的压缩器和/或构建过程有点“智能”,这意味着您不会感到头疼,并为您的用户提供快速的加载时间。
    • @StefanKendall 我完全不同意你的看法。向您的网络应用程序添加几行代码不会使其速度慢到足以阻止人们使用它。哎呀,它肯定甚至无法测量。开发人员已经在他们的页面上引用了大量的库,他们只使用了一部分(jquery、prototype、backbone、knout),并且由于性能原因,它并没有真正让任何人破产。你的回答只是个坏建议。
    • 我同意 Keivan,这是个坏建议。
    【解决方案2】:

    您有权决定是制作“防御性”还是“合同性”API。在许多情况下,阅读库的手册可以让用户清楚地知道他应该提供遵守这些和那些约束的这种或那种类型的参数。

    如果您打算制作一个非常直观、用户友好的 API,最好验证您的参数,至少在调试模式下是这样。但是,验证会耗费时间(和源代码 => 空间),因此最好将其省略。

    这取决于你。

    【讨论】:

      【解决方案3】:

      这取决于。这个图书馆有多大?据说类型语言更适合 API 复杂的大型项目。由于JS在某种程度上是混合的,你可以选择。

      关于验证 - 我不喜欢防御性编程,函数的用户必须传递有效的参数。在 JS 中,代码的大小很重要。

      【讨论】:

        【解决方案4】:

        尽可能多地验证并打印有用的错误消息,帮助人们快速轻松地追踪问题。

        使用一些特殊的 cmets(如 //+++VALIDATE//--VALIDATE)引用此验证代码,以便您可以使用高速压缩生产版本的工具轻松删除它。

        【讨论】:

        • 生产中高速到故障?
        • 在生产代码中,早期失败通常不再那么重要,因为大多数错误已得到修复。也就是说,如果您使用我的方法,您可以轻松创建一个速度较慢的验证版本,并在需要时将其部署到生产环境中。
        【解决方案5】:

        当我过去开发此类 API 时,我已经验证了我认为是“主要”要求的任何内容 - 在您的示例中,我将验证前两个参数。

        只要您指定合理的默认值,您的用户应该很容易确定“可选”参数未正确指定,因为它不会对应用程序进行任何更改,但一切仍将正常工作.

        如果 API 很复杂,我建议遵循 Aaron 的建议 - 在验证过程中添加可由压缩器解析的 cmets,以便开发人员从验证中受益,但在将代码推送到生产。

        编辑:

        下面是一些我喜欢在需要验证的情况下做的事情的例子。这个特殊情况非常简单;我可能不会费心验证它,因为它确实是微不足道的。根据您的需要,有时尝试强制类型会比验证更好,如整数值所示。

        假设extend()是一个合并对象的函数,辅助函数存在:

            var f = function(args){
              args = extend({
                foo: 1,
                bar: function(){},
                biz: 'hello'
              }, args || {});
        
              // ensure foo is an int.
              args.foo = parseInt(args.foo);
        
              //<validation>
              if(!isNumeric(args.foo) || args.foo > 10 || args.foo < 0){
                throw new Error('foo must be a number between 0 and 10');
              }
        
              if(!isFunction(args.bar)){
                throw new Error('bar must be a valid function');
              }
        
              if(!isString(args.biz) || args.biz.length == 0){
                throw new Error('biz must be a string, and cannot be empty');
              }
              //</validation>
            };
        

        编辑 2:

        如果您想避免常见的拼写错误,您可以 1) 接受并重新分配它们或 2) 验证参数计数。选项 1 很简单,选项 2 可以这样完成,尽管我肯定会将其重构为自己的方法,例如 Object.extendStrict() (示例代码适用于原型):

        var args = {
          ar: ''
        };
        var base = {
          foo: 1,
          bar: function(){},
          biz: 'hello'
        };
        // save the original length
        var length = Object.keys(base).length;
        // extend
        args = Object.extend(base, args || {});
        // detect if there're any extras
        if(Object.keys(args).length != length){
          throw new Error('Invalid argument specified. Please check the options.')
        }
        

        【讨论】:

        • 在某些情况下可选参数更难检测,例如我注意到如果超时值是字符串'60' - 没有设置超时(该值被传递给prototype.js.. )。是否总是可以验证可选属性?我注意到prototype.js有时会添加额外的方法,那么如何区分开发者设置的命名错误的方法呢?
        • 当然,它们肯定更难被发现;更有理由验证。我已经更新了我的答案以提供一些示例。
        【解决方案6】:

        当所需参数缺失时,一种中间方法是返回一个合理的默认值(例如 null)。这样,用户的代码会失败,而不是你的。他们可能更容易找出他们的代码中的问题,而不是你的。

        【讨论】:

          【解决方案7】:

          感谢您的详细解答。

          以下是我的解决方案 - 一个用于验证的实用程序对象,可以轻松扩展以验证基本上任何内容... 代码仍然足够短,所以我不需要在生产中解析它。

          WL.Validators = {
          
          /*
           * Validates each argument in the array with the matching validator.
           * @Param array - a JavaScript array.
           * @Param validators - an array of validators - a validator can be a function or 
           *                     a simple JavaScript type (string).
           */
          validateArray : function (array, validators){
              if (! WL.Utils.isDevelopmentMode()){
                  return;
              }
              for (var i = 0; i < array.length; ++i ){            
                  WL.Validators.validateArgument(array[i], validators[i]);
              }
          },
          
          /*
           * Validates a single argument.
           * @Param arg - an argument of any type.
           * @Param validator - a function or a simple JavaScript type (string).
           */
          validateArgument : function (arg, validator){
              switch (typeof validator){
                  // Case validation function.
                  case 'function':
                      validator.call(this, arg);
                      break;              
                  // Case direct type. 
                  case 'string':
                      if (typeof arg !== validator){
                          throw new Error("Invalid argument '" + Object.toJSON(arg) + "' expected type " + validator);
                      }
                      break;
              }           
          }, 
          
          /*
           * Validates that each option attribute in the given options has a valid name and type.
           * @Param options - the options to validate.
           * @Param validOptions - the valid options hash with their validators:
           * validOptions = {
           *     onSuccess : 'function',
           *     timeout : function(value){...}
           * }
           */
          validateOptions : function (validOptions, options){
              if (! WL.Utils.isDevelopmentMode() || typeof options === 'undefined'){
                  return;
              }
              for (var att in options){
                  if (! validOptions[att]){
                      throw new Error("Invalid options attribute '" + att + "', valid attributes: " + Object.toJSON(validOptions));
                  }
                  try {
                      WL.Validators.validateArgument(options[att], validOptions[att]);
                  }
                  catch (e){
                      throw new Error("Invalid options attribute '" + att + "'");
                  }
              }   
          },
          

          };

          以下是我如何使用它的几个示例:

          isUserAuthenticated : function(realm) {
          WL.Validators.validateArgument(realm, 'string');
          
          
          
          getLocation: function(options) {            
              WL.Validators.validateOptions{
                  onSuccess: 'function', 
                  onFailure: 'function'}, options);
          
          
          makeRequest : function(url, options) {
              WL.Validators.validateArray(arguments, ['string', 
                  WL.Validators.validateOptions.carry({
                  onSuccess : 'function', 
                  onFailure : 'function',
                  timeout   : 'number'})]);
          

          【讨论】:

          • 这是个好主意。最重要的是,您甚至可以“装饰”一个函数来验证它的参数,并让原始函数保持“干净”。
          【解决方案8】:

          我们必须尽快发现并消除问题。如果不使用 TypeScript 或 Flow,您宁愿使用验证库。它将帮助您避免花费数小时寻找由作为参数给出的无效类型引起的模糊错误。看起来很多人都认真对待 - https://www.npmjs.com/package/aproba 目前每周获得 9M(!) 次下载。

          对我来说它不适合,在这里解释http://dsheiko.com/weblog/validating-arguments-in-javascript-like-a-boss 我选择基于 JSDoc 表达式的https://www.npmjs.com/package/bycontract

          import { validate } from "bycontract";
          
          const PdfOptionsType = {
            scale: "?number"
          }
          
          function pdf( path, w, h, options, callback ) {
            validate( arguments, [
              "string",
              "!number",
              "!number",
              PdfOptionsType,
              "function=" ] );
            //...
            return validate( returnValue, "Promise" );
          }
          
          pdf( "/tmp/test.pdf", 1, 1, { scale: 1 } ); // ok
          pdf( "/tmp/test.pdf", "1", 1, { scale: 1 } ); // ByContractError: Argument #1: expected non-nullable but got string
          

          在方法上,您可以重用现有的 JSDoc 注释块:

          import { validateJsdoc, typedef } from "bycontract";
          
          typedef("#PdfOptionsType", {
            scale: "number"
          });
          
          class Page {
            @validateJsdoc(`
              @param {string}          path
              @param {!number}         w
              @param {!number}         h
              @param {#PdfOptionsType} options
              @param {function=}       callback
              @returns {Promise}
            `)
            pdf( path, w, h, options, callback ) {
              return Promise.resolve();
            }
          }
          

          但是,我在开发/测试环境中保留此验证,但在现场跳过它:

          import { config } from "bycontract";
          if ( process.env.NODE_ENV === "production" ) {
            config({ enable: false });
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-02-17
            • 1970-01-01
            • 2015-12-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-11-21
            • 2011-10-13
            相关资源
            最近更新 更多