2009-11-12 38 views
15

我正在開發一個JavaScript庫,供第三方開發人員使用。 API包括與此簽名的方法:我們應該在JavaScript API中驗證方法參數嗎?

函數doSomething的(ARG1,ARG2,選項)

  • ARG1,ARG2是 '必要' 簡單類型的參數。
  • 選項是一個包含可選參數的哈希對象。

您會建議驗證: - 參數類型是否有效? - 選項屬性是否正確?例如:開發者沒有錯誤地通過onSucces而不是onSuccess?

  • 爲什麼流行的庫如prototype.js不驗證?

回答

4

感謝您的詳細解答。

下面是我的解決方案 - 驗證實用程序對象,可以很容易地擴展到基本上驗證任何東西... 代碼仍然足夠短,以便我不需要在生產中解析它。

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'})]); 
+0

這是一個好主意。最重要的是,你甚至可以'裝飾'一個函數來驗證它的參數,並保留原始函數'clean'。 – xtofl

-6

不驗證。更多的代碼是用戶必須下載的更多代碼,因此這對用戶和生產系統來說是非常實際的成本。參數錯誤很容易被開發人員捕獲;不要給用戶帶來這樣的負擔。

+0

我不同意這種做法總是最好的。如果API足夠複雜,則驗證不會導致令人討厭的錯誤。例如,使用ExtJS庫的時候有很多次,我希望他們已經對傳入的參數進行了更多的驗證,節省了我幾個小時的頭痛。 – jvenema

+1

你更喜歡哪一個?頭痛的幾小時,或沒有用戶,因爲您的網站需要太長時間才能加載?選擇是顯而易見的。 –

+0

在這裏既不是一個選項。爲了讓您的壓縮器和/或構建過程有點「智能」,再多做一點工作,意味着您不會頭痛,並且您的用戶可以快速加載。 – jvenema

9

您有權決定是否進行「防禦」與「契約」API。在很多情況下,閱讀庫的手冊可以讓用戶明白,他應該提供符合這些和那些約束的這種或那種類型的參數。

如果你打算做一個非常直觀的,用戶友好的API,至少在調試模式下驗證你的參數是很好的。但是,驗證需要花費時間(並且源代碼=>空格),所以它可能也不錯。

這取決於你。

0

這取決於。這個圖書館有多大?據說類型化語言對於具有複雜API的大型項目更好。由於JS在一定程度上是混合的,你可以選擇。

關於驗證 - 我不喜歡防禦性編程,函數的用戶應該有義務傳遞有效的參數。並在JS代碼大小的問題。

7

儘可能地進行驗證並打印有用的錯誤消息,這些消息可幫助人們快速輕鬆地找到問題。

引用此驗證碼並附帶一些特殊註釋(如//+++VALIDATE//--VALIDATE),以便您可以使用高速壓縮生產版本的工具輕鬆刪除它。

+3

高速生產失敗? –

+2

在生產代碼中,由於大多數錯誤已得到修復,故障早期通常不再那麼重要。也就是說,如果您使用我的方法,您可以輕鬆創建較慢的驗證版本,並在需要時將其部署到生產環境中。 –

0

當我在過去開發類似這樣的API時,我已驗證任何我認爲是「主要」的要求 - 在您的示例中,我會驗證前兩個參數。

只要您指定了合理的默認值,對於您的用戶來說,確定「可選」參數沒有被正確指定是非常簡單的,因爲它不會對應用程序進行任何更改,但一切仍然可行正常。

如果API很複雜,我會建議遵循Aaron的建議 - 在驗證過程中添加可由壓縮器解析的註釋,以便開發人員獲得驗證的好處,但可以在推送代碼時提取額外的體重投入生產。

編輯:

我這裏還有一些我喜歡在驗證是必要的情況下做的例子。這個特例非常簡單;我可能不會爲它的驗證而煩惱,因爲它確實很微不足道。根據您的需要,有時試圖強制類型比驗證更好,如整數值所示。

假設延伸()是一個合併的對象的功能,並且所述的輔助函數存在:

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.') 
} 
+0

在某些情況下,可選參數很難檢測到,例如我注意到如果超時值是一個字符串'60' - 沒有設置超時值(該值傳遞給prototype.js ..)。 總是可以驗證可選屬性嗎?我注意到prototype.js有時會添加其他方法,那麼如何區分開發人員設置的錯誤命名方法? – Totach

+0

當然,他們肯定很難察覺;所有更多的理由來驗證。我已經更新了我的答案以舉例說明。 – jvenema

0

中間方法是在缺少必要的參數時返回合理的默認值(例如null)。這樣,用戶的代碼就會失敗,而不是你的。他們可能會更容易找出他們的代碼而不是你的代碼中的問題。

1

您可以通過合同同時驗證項目(參數)和出口(返程)點。這裏接受JSDoc語法合同一個lib https://www.npmjs.com/package/bycontract

/** 
* @param {number|string} sum 
* @param {Object.<string, string>} payload 
* @param {function} cb 
* @returns {HTMLElement} 
*/ 
function foo(sum, payload, cb) { 
    // Test if the contract is respected at entry point 
    byContract(arguments, [ "number|string", "Object.<string, string>", "function" ]); 
    // .. 
    var res = document.createElement("div"); 
    // Test if the contract is respected at exit point 
    return byContract(res, "HTMLElement"); 
} 
// Test it 
foo(100, { foo: "foo" }, function(){}); // ok 
foo(100, { foo: 100 }, function(){}); // exception 

但是我一直在開發/測試/開發環境的這種驗證,但跳過它活:

if (env === "production") { 
    byContract.isEnabled = false; 
} 
相關問題