本文译自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也是一个可以快速检视解析结果的工具。