Qomo项目中代码的一般性约定

一、变量声明的一般性方法

Qomo约定在全局空间进行变量声明时,应用var关键字来声明,而不应当省略(尽管语法上讲能这样做)。声明时的代码格式尽可能使用如下形式:

var
  v1 = 1234;
  v2 = 'abcd';

或为每行声明都保留var关键字,例如:

var v1 = 1234;
var v2 = 'abcd';

Qomo约定在函数内部声明局部变量进,使用var关键字时可以用如下省写形式:

var
  v1 = 1234,      // <-- 注意行未是逗号
  v2 = 'abcd';

或为每行声明保留var关键字。

Qomo反对在函数内部声明全局变量。

二、Qomo中一些保留的命名

Qomo会将一些实现特殊的、具有“关键字”性质的功能声明为全局函数。它们通常具有特殊的名字,而且是以大写字符开始的(这看起来有点象构造器,但并不是)。这些函数名部分看起来象JavaScript的保留字,但由于javascript的命名是大小写区分的,且保留字都使用小写字符,因此这并不冲突。这些全局函数有:

  Class()        : 注册类
  Attribute()    : 快速属性声明
  Interface()    : 接口声明
  Aggregate()    : 接口聚合
  Abstract()     : 抽象方法
  NullFunction() : 空函数/空方法

Qomo为构建系统框架,使用标准的JS原型继承的方式在全局范围内声明了一些类(构造器),用户代码应该避免覆盖这些名称。这些命名包括:

  Url()             : 分析一个Url地址
  Ajax()            : Ajax异步方式
  MuEvent()         : 多投事件
  CustomArguments() : 标识函数入口使用了定制参数
  BreakEventCast()  : 标识事件投送列表被中止
  JoPoint()         : 切面系统中的连接点
  JoPoints()        : 切面系统中的连接点列表
  Profilers()       : 创建系统分析对象

作为习惯,Qomo保留了一些(少量的)全局的工具函数,例如:

  format()             : 指向String.format();
  RegisterInterface()  : 指向Interface.RegisterInterface()
  QueryInterface()     : 指向Interface.QueryInterface()

  isAlias()            : 检测一个命名空间是否是别名
  isNamespace()        : 检测一个变量是否是命名空间声明

  getAllFunctions()    : (用于调试与性能分析,) 列出一个代码块中调用过的函数与方法
  showProfiler()       : (用于调试与性能分析,) 调用$debug来显示性能分析报告

除了上述三种情况,Qomo中将尽可能少的占用全局的命名,例如函数或变量。

为此,Qomo为开发人员准备了一份资料文档,用于通告哪那些全局函数、类与命名空间被使用了,开发人员应尽可能避免出现相冲突的命名。

三、Qomo中的一些命名约定

1. 系统的特殊声明

Qomo约定以$开始的变量为系统特殊的声明,它通常是关键的函数或可能在系统编译时被特殊处理的函数。这些函数主要包括:

  $import() : 导入文件或包
  $inline() : 内联代码块
  $assert() : 断言
  $debug()  : 调试输出

  $map()    : 创建命名与路径的映射
  $mapx()   : 展开命名空间
  $n2p()    : 命名空间到路径的转换
  $p2n()    : 路径到命名空间的转换

  $profilers : (对象)全局的性能分析器

  $Q() : 等同于$QomoConfig
  $QomoConfig() : 查询Qomo装载时或编译时的配置信息
  $QomoCoreFunction : 简单地返回一个函数用来替换Function对象的toString()方法,以保护代码

此外,一些函数内的局部变量也使用$来表明它的特殊性。

Qomo约定以_开始的函数具有使用的上下文环境限制。主要包括:

// 以下函数仅能在类声明中使用
//  - 注: Attribute()也仅能在类声明使用
  _cls : 取类引用
  _get : 类内部信息的getter
  _set : 类内部信息的setter

此外,一些函数内的局部变量也使用_来表明它的特殊性,或与一个没有该前缀的同名函数存在关联。

Qomo约定全大写的变量是系统保留的,或有特殊的含义的变量,该规则并不限于在函数内/外使用。例如:

  _RTLOBJECT : 在Qomo替换之前的Object()构造器
  _RTLERROR  : 在Qomo替换之前的Error()构造器

2. 异常

Qomo约定以字符E开始并紧邻一个大写字母表明一个异常。异常可以用数组形式来表示,例如:

  ECallClassBadArguments = [8101, 'Arguments error  for Class().'];
  ERegClassNoname = [8102, 'With call Class(), lost class name .'];
  EAccessSafeArea = [8104, 'Try Access Safe area.'];

3. 类与类类型注册

Qomo约定以大写字母开始的函数通常是类构造器,而以小写字母开始的函数是普通函数或方法。这一点是JavaScript惯用的代码风格。例如:

  // 类构造器
  function MyObject() {
    // ..
  }

  // 方法或函数
  function callSafeMethod() {
    // ..
  }

Qomo约定应当以字符T开始,后面紧跟构造器函数名的格式对一个类进行类注册。Qomo的OOP系统和命名空间系统依赖于类注册。类注册使用Class()函数。例如:

  // 类构造器声明
  function MyObject() {
    // ...
  }

  // 类注册
  TMyObject = Class(Object, 'MyObject');

4. 事件、事件句柄与事件类型

Qomo约定以On开始的属性为事件,而以do开始的函数通常用作事件句柄。例如:

  function doCall() {
    // ...
  }

  function MyObject() {
    this.OnCall = NullFunction;
  }

  var obj = new MyObject();
  obj.OnCall.add(doCall);

Qomo约定事件可以用一个函数声明来描述事件的接口,但这个声明除了便于开发人员使用之外,并没有任何价值。一般情况下,该声明应该以字符T起始,紧接事件名,最后可以用Event结束。例如:

  TOnCall = function(item, index) { }
  TOnLoadEvent = function(filename, filenum) { }

  function MyObject() {
    this.OnCall = TOnCall;
    this.OnLoad = TOnLoadEvent;
  }

对于上面的声明来说,OnCallOnLoad后面是一个空函数(NullFunction)还是一个事件类型的声明并没有关系。上面这样做,只是便于开发人员理解:当事件发生时,事件句柄可以得到怎样的入口数据。

由于“事件类型”只有声明和描述的价值,它事实上从来不会被执行。因此,可以在不同的文件中,或者相同文件的不同类、不同位置重复的声明相同名字的“事件类型”。这对程序执行不会有负面的影响。

5. 接口

Qomo约定接口是以I字符开始来声明一个函数。接口的方法应声明为Abstract。例如:

  IInterface = function() {
    this.QueryInterface = Abstract;
  }

6. 切面

Qomo约定应尽量以aspasp_为前缀来声明切面。例如:

  var asp_OnTimer = new ObjectAspect(T2, 'OnTimer', 'Event', x0);
  var asp_getTopoString = new FunctionAspect(getTopoString, 'getTopoString', 'Function');

7. 内联代码

使用$inline()可以将一个代码片断内联到当前位置执行。Qomo约定被联接的代码块应该是一个独立的文件,文件中可以包括注释,其代码体应该被包含在一个标签声明当中。这个标签声明只用于表现该代码块的特殊性,实际上没有执行价值。

该标签声明应当以_inline_为前缀。例如:

/**
 *
 * 内联代码可以按JS的语言规范任意包含注释
 *
 */
_inline_object_regAllInterfaceForClass: {

    // register interfaces for Class's all instnaces
    if (arguments.length > 2) {
      Interface.RegisterInterface.apply(cls, [cls].concat(
        Array.prototype.slice.call(arguments, 2)
      ))
    }

}

8. 字符串、数组、正则等使用中的一般性规则

Qomo约定尽可能以单引号来声明字符串,而将双引号留给HTML中的属性来使用。

Qomo约定以如果在全局使用正则表达式,应该以_r_为前缀声明,在函数局部使用正则表达式,应尽可能使用该规则。例如:

  _r_event = /^On.+/;
  _r_attribute = /^([gs]et)(.+)/;

JavaScript中常用数组来表达一张表格,Qomo建议这种情况下的代码应该写成如下格式:

var aTable = [
 [v1, v2, v3,                   v4],
 [v1, v2, v3,                   v4],
 [v1, v2, this_is_long_variant, v4],
 [v1, v2, v3,                   v4]   // <-- 注意最后一行没有","号
];

尽可能用对齐的方式来避免表格修改时导致的出错。

四、Qomo中的一些特殊使用法

1. 对象(Object)的特殊用法

Qomo直接替换了构造器Object(),因此使用new Object()的方式会构造出一个Qomo格式的对象实例。但用户代码中仍然可以通过直接量声明来得到一个“原生的”JavaScript对象。例如:

var
  obj_1 = new Object();   // Qomo对象
  obj_2 = { };            // JavaScript原生的、直接量声明的对象

二者的区别在于,obj_1因为是Qomo对象,因此必然具有如下方法和属性:

  obj_1.ClassInfo
  obj_1.get()
  obj_1.set()
  obj_1.inherited()

而obj_2由于是原生的JavaScript对象,因此使用for .. in来列举时,不会看到任何“显式的”属性与方法。

Qomo约定用户代码不应当修改直接量形式所得到的Object的原型(prototype)。由于这种对象没有任何“显式的”属性与方法,因此它可以用来做for .. in做一些高速的列表处理。部分内核代码使用
了这种特性。

——在firefox等mozilla引擎中,对替换后的Object()进行原型修改会污染(我并不确定这是否更加规范)原生的、直接量声明的对象。因此本项“特殊用法”应当谨慎使用。

10. 匿名函数的特殊使用法

为了避免一段代码占用全局空间中的名称(命名污染),通常会使用匿名函数来执行一段代码。Qomo建议这种情况下使用void来触发该匿名函数的执行。——而不是使用一对括号来做相同的事。例如:

// 推荐用法
void function() {
  // 你的代码...
}();

// 不推荐用法
(function() {
  // 你的代码...
})();

如果执行的结果会返回值,例如声明一个变量。则建议使用如下的结构:

var myFunc = function() {
  // 一些私有变量的声明
  var ...

  // 你的函数
  var _myFunc = function() {
    //...
  }

  // 返回
  return _myFunc;
}();

如果不对上述的_myFunc做特殊处理(例如添加属性),那么上述的代码可以简化至:

var myFunc = function() {
  // 一些私有变量的声明
  var ...

  // 返回你的函数
  return function() {
    //...
  }
}();