本文譯自Dojo Style Guide。
目錄
一般風格
快速參考
命名慣例
特別的命名慣例
文件
變量
佈局
文檔
本文檔遵循《Java Programming Conventions Guide》的基本框架。《Java Programming Conventions Guide》可以從http://geosoft.no/javastyle.html處獲得。小部件的作者除需要遵守本風格指南外,還要遵守Dojo Accessibility Design Requirements指南一文。
一般風格
爲了增進可讀性而違反本指南的做法是允許的。
本文的指導方針來自於Dojo核心開發人員的討論。那些會影響到外部開發人員如何與Dojo代碼與API交互的地方得到最優先考慮。象空白佔位符的規定這類對Dojo開發人員來講不太重要的規則,大部分也應該得到遵守,以便於增進開發人員之間的合作。
快速參考
核心API構件的命名規則表:
構件 | 慣例 | 註釋 |
模塊(module) | 小寫 | 從不使用多個單詞 |
類(c lass) | 駝峯法(CamelCase) | |
公共方法(public method) | 大小寫混合法(mixedCase) | 對類方法和實例方法都適用。lower_case()這樣的命名方法,如果是用在模仿其它API的時候,也是可以接受的。否則使用駝峯法。 |
公共變量(public var) | 大小寫混合法(mixedCase) | |
常量(constant) | 駝峯法或者全部字母大寫 |
不可見構件的命名規則見下表。因爲其不可見性,故對命名規則不做太強的約束。
構件 | 慣例 |
private method | _mixedCase |
private var | _mixedCase |
method args | _mixedCase, mixedCase |
local vars | _mixedCase, mixedCase |
命名慣例
1. 設計字符串ID或者ID前綴時,不要使用’dojo’, ‘dijit’, 或’dojox’。因爲我們現在允許在同一個頁面裏使用dojo的多個版本,所以使用_scopeName是十分重要的(dojo._scopeName, dijit_scopeName,dojox_scopeName)。<譯註:原文爲 When constructing string IDs or ID prefixes in the code, do not use "dojo", "dijit" or "dojox" in the names. Because we now allow multiple versions of dojo in a page, it is important you use _scopeName instead (dojo._scopeName, dijit._scopeName, dojox._scopeName). >
2. 代表模塊的名字必須使用全部小寫的單詞。
3. 代表類型(classes)的名字必須是名詞並且使用駝峯法:
Account, EventHandler
4. 常量必須置於爲專門存放這些常量而創建的對象之中,模擬枚舉類型。枚舉類型必須恰當命名,其成員必須或者使用駝峯法,或者使用大寫。參見下例:
var NodeTypes = {
Element : 1,
DOCUMENT: 2
}
5. 當縮略語(Abbreviation, acronyms)作爲名字的一部分時,不可以大寫。參見下例:
getInnerHtml(), getXml(), XmlDocument
6. 代表方法的名字必須是動詞或者動詞詞組,參見下例:
obj.getSomeValue()
7. 公共類變量必須使用mixedCase方法命名。
8. CSS變量名必須遵循公共類變量一樣的命名規則。
9. 私有類變量可以使用帶下劃線前綴的混合書寫法(mixedCase),參見下例:
var MyClass = function(){
var _buffer;
this.doSomething = function(){
};
}
10. 想要私有化的變量,如果沒有綁定爲閉包,必須加上下劃線前綴,參見下例:
this._somePrivateVariable = statement;
11. 一般變量必須使用它們的類型名做變量名,參見下例:
setTopic(topic) // where topic is of type Topic
12. 所有名字必須使用英文書寫。
13. 跨越較大範圍的變量必須使用全局無二義性的名字;可以通過使用模塊成員化的方法消除二義性。私有變量或者在小範圍內使用的變量可以使用較簡潔的名字。
14. 函數返回的對象名是不言自明的,因此不應該出現在方法名中,例如:
getHandler(); // NOT getEventHandler()
15. 公開可見的名字必須儘可能清晰,不得使用不明晰的簡化和縮寫,例如:
MouseEventHandler // NOT MseEvtHdlr
16. 類名字和構造函數名字可以根據它們的繼承模式來命名,並將基類名字放在最右邊,例如:
EventHandler
UIEventHandler
MouseEventHandler
如果子類的基類名字是不言自明的,也可以去掉,例如:
MouseEventHandler // as opposed to MouseUIEventHandler
特別的命名規範
1. get/set在字段可以直接訪問時不得使用,除非該變量從字面上看是私有的。<譯註:原文爲“The terms get/set SHOULD NOT used where a field is accessed, unless the variable being accessed is lexically private.”>
2. 對布爾型變量和方法應該使用“is”前綴。其它選擇包括“has”,“can”和“should”。
3. “compute”可以用在計算方法的命名上。
4. “find”可以用在查找方法的命名上。
5. “initialize”和“init”可以用在某個變量或者概念建立時。
6. UI控制變量名必須以該控制的名字結尾,例如:leftComboBox, topScrollPane。
7. 集合名字必須使用複數。
8. 當一個變量代表一個數字對象時,必須使用num前綴,或者count後綴。
9. 迭代變量必須用“i”, “j” “k”等。
10. 補語名必須只用於補足實體,例如:get/set, add/remove, create/destroy, start/stop, insert/delete, begin/end等。
11. 應該避免使用名字中的縮略語。
12. 避免使用否定的布爾變量,如"isNotError", "isNotFound", "unacceptable"等。
13. 異常類型必須以"Exception"或者"Error"結尾。<譯註:原文此處有一註釋:"FIXME(trt) not sure about this?">
14. 返回某個對象的方法可以用它們返回對象的名字來命名;不返回任何對象的方法在命名上要能體現方法的主要行爲。
文件
1. 是否使用每個文件只定義一個類或者對象的規則尚未確定。
2. 使用Tab鍵(設置爲4個空格)縮進,而不是空格本身。
3. 如果編輯器支持"file tags", 請在文件尾附加恰當的標籤,以使得其它人可以方便地遵守正確的縮進指示。如:
// vim:ts=4:noet:tw=0:
4. 行的分割必須明顯地標示出來,如:
var someExpression = Expression1
+ Expression2
+ Expression3;
var o = someObject.get(
Expression1,
Expression2,
Expression3
);
注意表達式中的縮進要相對於變量名,而函數參數的縮進則是相對於被調用的方法。注意括號的位置,必須和塊標識("{}"使用相近的格式。
變量
1. 變量必須在聲明時就進行初始始化。變量應該在儘可能小的範圍內聲明。將變量初始化爲null是可以允許的。
2. 變量不得具有二義性。
3. 相同類型且相關的變量可以在一個語句中聲明;不相關的變量必須在不同的語句中聲明。
4. 變量的生存期應該儘可能地短。
5. 循環/迭代聲明
1. 只有循環控制語句纔可以包含在"for"循環結構中。
2. 循環變量應該在循環之前立即初始化;在"for"循環中的變量可以在"for"循環控制中初始化。
3. 可以使用"do … while"(不同於Java)。
4. 不限制使用"break"和"continue"(不同於Java)。
6. 條件
1. 應該避免複雜的條件表達式;應該使用臨時布爾變量來代替。
2. 正常條件應該在"if"分支裏,而異常條件應該放在"else"分支裏。
3. 條件表達式應該避免可執行的聲明語句。
7. 雜項
1. 代碼應該避免使用魔術數字。它們應該使用常量定義。
2. 浮點數總是使用小數點並且至少一位小數。
3. 浮點常量在小數點前必須有至少一位數字。<譯註:Javascript裏有0.2 == .2>
佈局
1. 語句塊的佈局
1. 塊的佈局必須如下所示:
while(!isDone){
doSomething();
isDone = moreToDo();
}
2. "if"聲明必須遵守下面的格式:
if(someCondition){
statements;
}else if(someOtherCondition){
statements;
}else{
statements;
}
3. "for"聲明必須遵守下面的格式:
for(initialization; condition; update){
statements;
}
4. "while"聲明:
while(!isDone){
doSomething();
isDone = moreToDo();
}
5. do…while 語句:
do{
statements;
}while(condition);
6. switch語句:
switch(condition){
case ABC:
statements;
// fallthrough
case DEF:
statements;
break;
default:
statements;
// no break keyword on the last case -- it's redundant
}
7. try …catch…finally 語句:
try{
statements;
}catch(ex){
statements;
}finally{
statements;
}
8.單語句的if-else,while或for語句可以寫在同一行,但必須帶着括號。
if(condition){ statement; }
while(condition){ statement; }
for(intialization; condition; update){ statement; }
2. 空白符
1. 條件操作符可以在兩側附加空白字符(包括三元操作符)。
2. 下列保留字不可以在其後附加空格:
break
catch
continue
do
else
finally
for
function
if anonymous, ex.var foo = function(){};
if
return
switch
this
try
void
while
with
4. 逗號後面必須跟隨一個空格。
5. 冒號後面可以跟隨一個空格。
6. for語句中的分號後面必須跟隨一個空格。
7. 分號前面不得有空格。
8. 函數或方法調用後面不得跟有空格。例:doSomething(someParameter); 而不是doSomething (someParameter)。
9. 語句塊中的邏輯單元之間應該使用空行分開。
10. 可以使語句對齊,如果這樣做能增進可讀性的話。
3. 註釋
1. 語義不直觀的代碼必須重寫,而不是通過註釋來使其變得易懂。
2. 所有註釋必須使用英文書寫。
3. 註釋應根據它們對應代碼的位置做相應的縮進,並且放在代碼的前一行,或者放在代碼的右端。
4. 聲明一個集合類變量時,必須伴有對元素對象類型的聲明。
5. 語句塊應該含有註釋,以解釋該語句塊的要點。
6. 不應該對語句逐行註釋。
文檔
標註符號準則
如何使用關鍵字
當解析一個註釋塊時,我們給解析器一個‘關鍵字’列表。這個列表中有’summary’,'description’和’return’s。但是許多註釋塊也會含有列表中的字做爲變量和參數。
如果任一’關鍵字’出現在一行開頭,解析器都後將其後讀出的文字做爲那個’關鍵字’的內容,直到遇到另一’關鍵字’,或者一個空白行。這意味着你需要仔細選擇每個註釋行起頭的字。例如,如果一行文字的內容不是小結,則不應該使用’summary’來爲這一行註釋起頭。
使用’Markdown’
在摘要和代碼示例中我們使用‘Markdown’語法。
在’Markdown’語法中,使用4個空格或者1個’Tab’字符來縮進代碼塊,從而指示該段爲一個代碼塊。解析器將管道符’|'看成一行的開始,你必須使用一個管道符,再加上一個’Tab’字符,或者4個空格來表明這是一個代碼塊。
在’Markdown’語法中,要表明一個行內聯的代碼塊,需要將該代碼塊用一對’<div>’包含起來。
一般信息
下述關鍵字構成函數或者對象的摘要:
summary: 關於函數或者對象的用途的簡短描述。始終讀作純文本(html實體被轉義,Markdown只用作代碼轉義)。
description: 關於函數或者對象的完整摘要,將出現在’summary’部分(使用Markdown)。
tags: 空白符分隔的一列標籤,以指示這些方法將如何被使用(後面有更詳細的解釋)。
returns: 關於函數返回值的摘要(不包含類型信息,類型信息應該出現在函數裏)。
example: 示例。使用Markdown語法。本關鍵字可以多次出現。
Tags
方法缺省爲公有方法;如果方法名具有下劃線前綴,則缺省被認爲是受保護方法(protected)。這意味着只有一個方法沒有帶下劃線前綴,而你又並不想該方法被他人使用時,才應該加上’protected’標籤;如果根本不想他人沾任何一點該函數的邊,則你需要使用’private’標籤。
protected: 該方法可以被子類調用或者覆蓋,但不應該被用戶直接調用。如:
postCreate: function(){
// summary:
// Called after a widget's dom has been setup
// tags:
// protected
},
private: 該方法不被任何本類以外的方法調用。如
_attrToDom: function(/*String*/ attr, /*String*/ value){
// summary:
// Reflect a widget attribute (title, tabIndex, duration etc.) to
// the widget DOM, as specified in attributeMap.
// tags:
// private
...
}
方法專屬標籤
callback: 這個方法代表一個可以聯接的位置(即可以使用dojo.connect),以接收事件通知,比如用戶點擊一個按鈕,或者一段動畫結束之類的事件。如:
onClick: function(){
// summary:
// Called when the user clicks the widget
// tags:
// callback
...
}
extension: 不同於普通的受保護方法,如果基類的缺省方法的功能並不是我們想要的,則我們將子類的對應方法標記爲擴展(’extension’)。用於諸如生命期方法(如postCreate)或者子類期望改變基類的缺省行爲時(如buildRendering)。回調只強調在事件發生時被調用;而擴展意味着小部件代碼期待着某個特定的值被返回,或者一些特定動作被執行。以calendar爲例:
isDisabledDate: function(date){
// summary:
// Return true if the specified date should be disabled (i.e. grayed out and unclickable)
// description:
// Override this method to define special days to gray out, such as weekends
// or (for an airline) black-out days when discount fares aren't available.
// tags:
// extension
...
}
一般函數信息
Foo = function(){
// summary: Soon we will have enough treasure to rule all of New Jersey.
// description: Or we could just get a new roommate.
// Look, you go find him. He don't yell at you.
// All I ever try to do is make him smile and sing around
// him and dance around him and he just lays into me.
// He told me to get in the freezer 'cause there was a carnival in there.
// returns: Look, a Bananarama tape!
}
Foo.prototype.onSomethingNoLongerApplicable = function(){
// tags: callback deprecated
}
對象信息
不存在對於返回對象的描述。
var mcChris = {
// summary: Dingle, engage the rainbow machine!
// description:
// Tell you what, I wish I was--oh my g--that beam,
// coming up like that, the speed, you might wanna adjust that.
// It really did a number on my back, there. I mean, and I don't
// wanna say whiplash, just yet, cause that's a little too far,
// but, you're insured, right?
}
函數組裝信息(定義/聲明小部件)
如果聲明傳入了一個構造函數,則’summary’和’description’部分必須填寫。如果沒有傳入構造函數,則可以在混入(mixins)對象中創建註釋。例:
dojo.declare(
"steve",
null,
{
// summary:
// Phew, this sure is relaxing, Frylock.
// description:
// Thousands of years ago, before the dawn of
// man as we knew him, there was Sir Santa of Claus: an
// ape-like creature making crude and pointless toys out
// of dino-bones, hurling them at chimp-like creatures with
// crinkled hands regardless of how they behaved the
// previous year.
// returns:
// Unless Carl pays tribute to the Elfin Elders in space.
}
);
參數
簡單類型
類型最好(但非必須)出現在參數定義塊中,如:
function(/*String*/ foo, /*int*/ bar)...
類型修飾符
可以在類型後面附加這些修飾符:
‘?’表明該參數可省略
‘…’表明最後一個參數可以無限重複 <譯註:即不定參數函數>
‘[]‘表明該參數爲一個數組。
例:
function(/*String?*/ foo, /*int...*/ bar, /*String[]?*/ baz)...
完整的參數說明
如果你還想加上一個概要說明,你可以在函數最前面的註釋塊裏這樣做。如果已經在參數定義裏聲明瞭類型,則在此外不需要再次聲明。
一般信息的格式是:關鍵字 描述語句
參數和變量信息的格式是:關鍵字 類型 描述語句
此處關鍵字可以被非字母字符包圍。
function(foo, bar){
// foo: String
// used for being the first parameter
// bar: int
// used for being the second parameter
}
變量
實例變量,原型變量和外部變量都可以以同樣的方式定義。參見下例,一個變量可能會有多種賦值方式,將其定位在函數內部是一種較好的方式,這樣可以避免失去對變量的跟蹤管理,也避免了對變量意外地多次註釋。
function Foo(){
// myString: String
// times: int
// How many times to print myString
// separator: String
// What to print out in between myString*
this.myString = "placeholder text";
this.times = 5;
}
Foo.prototype.setString = function(myString){
this.myString = myString;
}
Foo.prototype.toString = function(){
for(int i = 0; i < this.times; i++){
dojo.debug(this.myString);
dojo.debug(foo.separator);
}
}
Foo.separator = "=====";
變量標註
可以用置於類型之前的放在一對’[]‘之中的標籤來標註一個變量。標籤與其它元素之間使用空白符分隔。可以使用的標籤除了前面提到的主要標籤之外,還有一些變量專用標籤:
const: 可用於配置的小部件屬性,但只能在初始化時賦值。該值其後不可改變。
// id: [const] String // A unique, opaque ID string that can be assigned by users... id: ""
readonly: 該屬性只讀,無論在初始化或者初始化以後都不可改變。
// domNode: [readonly] DomNode // This is our visible representation of the widget... domNode: null
對象中的變量註釋
解析器也處理對象值定義之間的註釋,並使用與初始化註釋塊一樣的規則。
{
// key: String
// A simple value
key: "value",
// key2: String
// Another simple value
}
返回值
因爲函數可能返回多類型,因此返回值類型必須聲明在返回語句的同一行,且必須在該行最右端出現。如果所有返回類型相同,解析器就使用該類型,否則,函數被認爲返回混合類型。
function(){
if(arguments.length){
return "You passed argument(s)"; // String
}else{
return false; // Boolean
}
}
注意: 返回類型必須註釋在與’return’語句同一行裏。下面的例子中,第一個示例是無效的,第二個示例纔是有效的。
function(){
return {
foo: "bar" // return Object
}
}
function(){
return { // return Object
foo: "bar"
}
}
專門爲文檔用的代碼
有時候對象的構造方式很難從源代碼中看出來;有時候我們傳入一個泛型對象的時候,會希望以中方式告知用戶他們可以將什麼樣的字段放入到這個對象當中。這時候有兩種解決方案:
內聯的註釋代碼
有些場合下我們希望某個對象或者函數出現在文檔中,但不出現在dojo中或者在我們的構建(build)當中。此時,你可以通過/*======開啓一個註釋塊,等號的個數可以是5個或者更多。
解析器簡單地將/*===== 和====*/替換爲空白符,因此你必須十分小心你的語法。
dojo.mixin(wwwizard, {
/*=====
// url: String
// The location of the file
url: "",
// mimeType: String
// text/html, text/xml, etc
mimeType: "",
=====*/
// somethingElse: Boolean
// Put something else here
somethingElse: "eskimo"
});
代碼出現在分開的文件中
這樣做的好處是,我們仍然可以利用編輯器的語法高亮,並且會較少擔心語法問題,因爲在函數dojo.provide的幫助下,並不比寫一個普通的JS文件複雜。
代價是難以維護純文檔文件。每個名字空間(即同一層目錄)下只有一個這樣的文件會好一點,我們稍後會討論一個例子。
給關鍵參數寫文檔
Dojo許多地方使用了關鍵字風格的參數。有時候很難描述該如何使用它們。一個選擇是提供僞對象來描述其行爲。例如我們可以創建module/_arg.js文件:
dojo.provide("module._arg");
module._arg.myFuncArgs = function(/*Object*/ kwArgs){
// url: String
// Location of the thing to use
// mimeType: String
// Mimetype to return data as
this.url = kwArgs.url;
this.mimeType = kwArgs.mimeType;
}
這樣描述了一個真正的對象來僞裝泛型對象的功能,但一樣提供了文檔來描述對象包含的字段以及它們的作用。要將這個對象和原來的函數關聯起來,可以這樣:
var myFunc = function(/*module._arg.myFuncArgs*/ kwArgs){
dojo.debug(kwArgs.url);
dojo.debug(kwArgs.mimeType);
}
由於我們並沒有對module._arg調用dojo.require,因此它並不會被包含起來。文檔解析器則會提供到module._arg的鏈接,因此用戶可以看到它的功能。這個僞對象也可以用/*===== =====*/語法內聯進來。文件dojo/_base/fx.js中的"dojo.__FadeArgs"僞代碼提供了一個內聯僞對象的示例,這個僞對象作爲文檔提供給函數dojo.fadeIn()和dojo.fadeOut()。
如何決定使用何種文檔語法?
在分開的文件中提供文檔減少了代碼打斷解析的錯誤機率。從這個角度看,應該儘可能多地使用單獨文件來提供文檔。但也有很多場合下無法這樣做,這時就應該使用內聯註釋的語法。有時候也存在當增加新的不可見字段時,文檔忘記同步的擔心。如果文檔同步很重要的話,你也可以使用內聯註釋語法。
在本地使用Doctool
如果您作爲一名開發人員,使用了這些語法對代碼做了標註,想通過測試來驗證是否正確的話,你可以在本地運行doctool。請檢查util/jsdoc文件中的INSTALL部分。此外util/docscript/_browse.php也是一個可以快速檢視解析結果的工具。