Wiki
Clone wikiqforce.bitbucket.org / 编码规范
所有语言
子项目命名
C#项目应该采用大写驼峰命名,例如CSharpBcpRpc
。其他语言应该用小写字母和连字符命名,例如scala-bcp-rpc
、json-stream
。如果使用第三方库,应依照第三方库自己的命名建立目录结构,不必强制使用我们的命名规范,例如spine-csharp
。
全局变量
- 不得使用全局变量或静态变量。
- 可以使用全局函数或静态函数。
- 可以使用初始化后就不再修改其内容的全局常量或静态常量。
final
默认情况下,每个class
都应该标为final class
或@:final class
。
// Good! final class MyScalaClass { }
个别情况下,可以定义抽象类(即abstract class
或@:abstract class
)。但每处定义抽象类时,必须增加注释,解释为什么没用final class
、interface
或trait
代替。
// Good! abstract class BcpClient { // 本类为抽象类而不是trait,是为了禁止用户编写某个类同时从BcpServer.Session和BcpClient中派生 }
所有类都必须要么不可继承,要么就属于抽象类。禁止出现既允许继承又允许直接创建的类。
// Bad! class NotFinalAndNotAbstract { }
每个非抽象的public
和protected
方法都必须标为final
或@:final
。Scala的private[packageName]
或Java的internal
方法,也必须标为final
。
final class MyClass { // Good! final def foo():Unit = { println("Hello, World!"); } }
final class MyClass { // Bad! def foo():Unit = { println("Hello, World!"); } }
例外:常量属性
如果某个属性getter函数体是无副作用的常量,那么可以不加final
或者标为virtual
。
// Good! protected def heartBeatDelay:Duration = 3.seconds
var _heartBeatDelay:Duration = 3.secods // Bad! protected def heartBeatDelay:Duration = _heartBeatDelay // _heartBeatDelay受副作用影响,不是常量
// Good! protected virtual int MaxConnectionsPerSession { get { return 5; } }
构造函数的副作用
严禁在一切抽象类和trait
的构造函数中执行有副作用的操作1。
其他类的构造函数一般也不应包含有副作用的操作。确有必要时,可以在final class
的构造函数中执行有副作用的操作,但是必须写注释说明原因。
scala.App
此外,继承scala.App
的用法也类似于在构造函数中执行副作用操作,因此应避免使用scala.App
,而改用普通的def main(arguments: Array[String]):Unit
创建入口类。
// Good! object MyMain { def main(arguments: Array[String]):Unit = { // 副作用操作! doSomething() } }
// Bad! object MyMain extends App { // 副作用操作! doSomething() }
注释的语言
开源项目中的代码应当用英文注释,其他代码应当用中文注释。
命名
函数、类和成员变量的命名应当使用完整英文单词,通常不得使用简写单词(abbreviations)。局部变量一般使用完整单词,但作用域非常短的局部变量,也可以使用单字母命名。例如:
// Good! class AsynchronousInputStream { // Good! def available: Int }
// Bad! class AsyncInStr { // Bad! def avbl: Int }
def swap(left: Array[Int], right: Array[Int]): Unit = { // Good! val t = left(0) left(0) = right(0) right(0) = t }
此外,可以使用常见的首字母缩写,见 http://sourceforge.net/adobe/flexsdk/wiki/Coding%20Conventions/#acronyms ,这些缩写可用于任何语言。
空代码块
必须在空代码块中写上注释或日志,说明为什么为空。例如:
// Good! if (condition) { // 因为某某原因,某某条件下不需要处理,直接忽略 } else { doSomething(); }
// Good! xxxOption match { case Some(xxx) => { doSomething(); } case None => { logger.fine("Skip xxx because of yyy") } }
// Bad! if (condition) { } else { doSomething(); }
// Bad! xxxOption match { case Some(xxx) => { doSomething(); } case None => { } }
单个字母的小写驼峰和大写驼峰命名
如果某个变量以单个字母的单词开头,那么用于小写驼峰命名时,前两个字母都小写,用于大写驼峰命名时,前两个字母都大写,例如:
// Good! emailAddress in CSharpConfiguration := "user@gmail.com"
// Bad! eMailAddress in CsharpConfiguration := "user@gmail.com"
override
实现抽象方法时,不得省略override
或@Override
修饰符:
final class AbstractBase extends Function1[Int, Int] { // Good! override final def apply(i:Int):Int = i }
final class AbstractBase extends Function1[Int, Int] { // Bad! final def apply(i:Int):Int = i }
循环依赖
禁止多个包相互依赖(依赖指在源代码中通过import
或别的语句涉及其他包)。
package com.qifun.paraiso.foo import com.qifun.paraiso.bar.Bar class Foo
package com.qifun.paraiso.bar import com.qifun.paraiso.baz.Baz class Bar
package com.qifun.paraiso.baz object Baz { // Bad! val foo = new com.qifun.paraiso.foo.Foo } class Baz { // Bad! import com.qifun.paraiso.foo.Foo }
时间
绝对时间
在同一种语言内部,应当使用System.DateTime
、java.util.Date
等静态类型表示时间。
在需要跨平台传递时或者需要优化内存占用时,使用64位有符号整数表示绝对时间,数值是1970年1月1日以后的毫秒数。
相对时间
在同一种语言内部,应当使用scala.concurrent.duration.Duration
等静态类型表示时间间隔。
在需要跨平台传递时或者需要优化内存占用时,应根据数值的取值范围选择使用Int
、Long
、Float
、Double
表示相对时间。这种情况下,变量名必须加上秒、毫秒等时间单位,例如:
// Good! var coolDownMilliseconds(default, null):Int; // Good! var timeOutSeconds(default, null):Float;
// Bad! var coolDownTime(default, null):Int; // Bad! var timeOut(default, null):Float;
Scala
遵守Scala Style Guide,除非本文特别提及。
Scala IDE自动Format格式
当Scala IDE自动Format格式与 Scala Style Guide 发生冲突时,以Scala IDE自动Format格式为准。
包括且不限于以下情况:
主构造器参数换行时,使用2个空格缩进,而不是4个空格,例如:
// Good! class Person( name: String, age: Int) extends Entity { }
// Bad! class Person( name: String, age: Int) extends Entity { }
单行字数限制
参见 http://docs.scala-lang.org/style/indentation.html#line-wrapping 的换行规则。但单行宽度限制应改为120个半角字符,而不是80字符。
Continuation
不得使用Continuation插件。
new
如果创建对象时,构造函数没有参数,那么应该省略括号。例如:
// Good! throw new IllegalContentTypeException
// Bad! throw new IllegalContentTypeException();
scala.Enumeration
不应使用scala.Enumeration
,应当用sealed trait
、case object
、case class
代替。
object 中的 val
参见 http://stackoverflow.com/questions/9745488/naming-convention-for-scala-constants
使用大写驼峰命名,例如:
object Int { // Good! val MaxValue = 2147483647 }
不要使用小写驼峰:
object Int { // Bad! val maxValue = 2147483647 }
大写下划线是Java、ActionScript、Haxe的惯例,但并不是Scala的惯例,也不要使用:
object Int { // Bad! val MAX_VALUE = 2147483647 }
首字母缩写
在类名或变量名中如果出现首字母缩写,应该用驼峰命名法,例如:
// Good! case class PublicId // Good! class JsonObject // Good! trait XmlEvent // Good! object UiElement object Codec { // Good! val Utf8: Charset = ... // Good! def fromUtf8 (bytes: Array[Byte]): Array[Char] = ... }
而不要像Flex编码规范那样全部字母大写:
// Bad! case class PublicID // Bad! class JSONObject // Bad! trait XMLEvent // Bad! object UIElement object Codec { // Bad! val UTF8: Charset = ... // Bad! def fromUTF8 (bytes: Array[Byte]): Array[Char] = ... }
测试用例的命名
- 测试用例的类名应该叫
XxxTest
,其中,Xxx
是被测试的类名。 - 测试方法的名称应该是完整的句子或者短语,不开源的测试用例可以用中文。
例如:
// Good final class XxxTest { @Test final def `Xxx.foo should equals to Xxx.bar`():Unit = { assertEquals(Xxx.foo, Xxx.bar) } }
导入Scala容器
- 导入
scala.collection.mutable
包中的类型时,如果这个类型在scala.collection.immutable
存在同名类型,那么需要改名以增加Mutable
前缀。 - 导入
scala.collection.immutable
包中的类型时不需要改名。 - 除了Scala默认已经放入
scala
包的Seq
、Iterator
、Iterable
,尽量避免导入其他scala.collection
中的类型。
// Good! import scala.collection.immutable.SortedSet // Good! import scala.collection.mutable.{ SortedSet => MutableSortedSet } // Good! import scala.collection.mutable.LinkedList // Bad! import scala.collection.mutable.SortedSet // Bad! import scala.collection.SortedSet // Bad! import scala.collection.mutable.{ LinkedList => MutableLinkedList }
配置表
在Scala代码中涉及用haxe-import-csv导入的配置表的工作表名时,要么使用完全限定名,要么用import
语句添加Hic
前缀。相应的变量名,必须添加hic
前缀。例如:
// Bad! import com.qifun.paraiso.xlsx.Server val server:Server = ???
// Good! val hicServer:com.qifun.paraiso.xlsx.Server = ???
// Good! import com.qifun.paraiso.xlsx.{ Server => HicServer } val hicServer:HicServer = ???
zero-log配置文件
zero-log的配置文件应当命名为zero-log.config.scala
。不用遵守http://docs.scala-lang.org/style/files.html
没有yield的for循环超过120个字符时应该改用多重嵌套
// Good! for (x <- veryLongBoard.rows) { for (y <- veryLongBoard.files) { if (veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongCondition) { printf("(%d, %d)", x, y) } } } // Bad! for (x <- veryLongBoard.rows; y <- veryLongBoard.files if veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongCondition) { printf("(%d, %d)", x, y) }
null
新编写的Scala原生代码一般不允许使用null
,应该改用None
、Option
、Some
:
// Good! def getXxx():Option[String] = { if (condition) { Some(result) } else { None } }
// Bad! def getXxx():String = { if (condition) { result } else { null } }
例外
以下情况可以使用null
:
- Java标准库、Haxe生成的代码有时会返回
null
。在Scala中应当在调用这些API后立即检查返回值是否是null
。 - 在容器中大量保存的类的成员变量,如果在性能关键的场合,可以使用
null
来节省内存。
class Foo { // 首选的写法 val bar: AtomicReference[Option[String]] = new AtomicReference(None) } type FooSeq = Seq[Foo]
class Foo { // 为了优化性能时,可以接受的写法 val bar: AtomicReference[String] = new AtomicReference(null) } type FooSeq = Seq[Foo]
return
一般情况下不得使用return
关键字。
如果有迫不得已的原因要用return
,必须写上行注释,解释为什么这么写。
// Bad! if (condition) { foo() return } bar()
// Good! if (condition) { foo() } else { bar() }
break
一般情况下不得使用scala.util.control.Breaks.break
。
如果有迫不得已的原因要用break
,必须写上行注释,解释为什么这么写。
// Bad! import scala.util.control.Breaks._ var seq = 10000 until 20000 breakable { for (i <- seq) { if (i % 7 != 0) { println(i) } else { break } } }
// Good! var seq = 10000 until 20000 for (i <- seq.view.takeWhile(_ % 7 != 0)) { println(i) }
var
一般情况下不得使用var
关键字。
如果有迫不得已的原因要用var
,必须写上行注释,解释为什么这么写。
// Bad! var i = 0 while (i < 10) { println(i) i += 1 }
// Good! for (i <- 0 until 10) { println(i) }
scala-stm
在私有函数中使用STM
类或包内部使用STM的private
或internal
函数,如果可能被其他atomic
块调用,那么应该增加(implicit txn:InTxn)
参数:
object MyClass { private val intRef = Ref.make[Int] // Bad! private def foo(n: Int): Unit = { atomic { implicit txn => intRef() = intRef() + n } } def main(arguments: Array[String]): Unit = { atomic { implicit txn => foo(100) } } }
object MyClass { private val intRef = Ref.make[Int] // Good! private def foo(n: Int)(implicit txn:InTxn): Unit = { intRef() = intRef() + n } def main(arguments: Array[String]): Unit = { atomic { implicit txn => foo(100) } } }
嵌套atomic
禁止在atomic
块内嵌套atomic
块:
// Bad! atomic { implicit txn => atomic { implicit txn => } }
// Bad! def foo()(implicit txn: InTxn):Unit = { atomic { implicit txn => } }
atomic中的副作用操作
禁止在Scala STM的atomic
块内执行有副作用的操作1:
// Bad! atomic { implicit txn => socketRef().send(???) }
// Good! val socket = atomic { implicit txn => socketRef() } socket.send(???)
atomic中的内部类
禁止在Scala STM的atomic
块内以及atomic
的钩子函数内定义内部类:
// Bad! atomic { implicit txn => Txn.afterCommit { _ => // Bad! executor.schedule(new Runnable { override final def run() = ??? }) } }
// Good! class MyRunnable extends Runnable { override final def run() = ??? } atomic { implicit txn => Txn.afterCommit { _ => executor.schedule(new MyRunnable) } }
atomic中的内部函数
一般禁止在Scala STM的atomic
块内定义内部函数。
// Bad! atomic { implicit txn => future.onComplete { result => ??? } }
// Bad! atomic { implicit txn => Txn.afterCommit { _ => future.onComplete { result => ??? } } }
有两种情况是例外,可以用内部函数:
用于Scala容器操作的回调函数
// Good! atomic { implicit txn => intRef() = seqRef().find { _ > 5 } }
Txn钩子
允许创建不超过3行代码的内部函数,用于注册Txn.afterCommit
等Txn
钩子。
// Good! atomic { implicit txn => Txn.afterCommit { _ => shutedDown() } }
注意,如果创建超过3行代码的钩子,仍然必须定义在atomic
之外
// Bad! atomic { implicit txn => Txn.afterCommit { _ => shutedDown() socket.send(data1) socket.send(data2) socket.send(data3) } }
// Good! def onAfterCommit() = { shutedDown() socket.send(data1) socket.send(data2) socket.send(data3) } atomic { implicit txn => Txn.afterCommit { _ => onAfterCommit() } }
ActionScript 和 Haxe
除了本文特别提及之处,应遵守Flex 编码规范。
单行长度
单行长度通常不得超过120字符。
包命名
使用com.qifun.项目.模块.子模块
,其中如果出现连续的单词,用小写驼峰命名,例如:
package com.qifun.helloWorld.logging;
其中属于QForce引擎,但是不开源的部分,应当放在com.qifun.qforce.子项目
包中。
首字母缩写
不要像Flex编码规范那样的全部字母大写,应当像普通单词一样用驼峰命名。
缩进
使用 2 个空格缩进而不是 4 个空格。
Haxe
注释
类型及其成员的注释应当用Dox语法。
如果某些代码只为生成文档专用,应当放在#if doc_gen
中。例如:
#if doc_gen /** 定义了所有内置插件的模块。 `using com.qifun.jsonStream.Plugins;`将启用`builderPlugin`包、`deserializerPlugin`包和`serializerPlugin`包中的所有插件。 **/ @:final extern class Plugins{} #end
预编译变量
应使用小写字母加下划线命名。例如:
# Good!
haxe -D avatar_shader_render
// Good! #if avatar_shader_render var shader = new Shader(); #end
return
一般情况下Haxe中return
关键字写在函数声明的末尾,而不是在过程代码中。在函数返回值为Void
时例外。
如果有迫不得已的原因要在过程代码中用return
,必须写上行注释,解释为什么这么写。
// good! function foo1():Int return { var a:Int = 5; //do something a; } // good! function foo2():Void { //do something } // bad! function foo3():Int { var a:Int = 5; //do something return a; }
enum
枚举类型用大写驼峰命名,枚举值用大写加下划线命名(类似常量)。
// Good! enum DockDirection { LEFT; RIGHT; TOP; BOTTOM; ABSOLUTE(x:Float, y:Float); }
switch
不遵守Flex编码规范。
- 每一个case都必须省略
break;
语句。 - case之间也不必要额外的换行。
- case后强制使用花括号。
例如:
// Good! switch(event.type) { case Event.COMPLETE, IOErrorEvent.IO_ERROR: { foo(); bar(); } case Event.INIT: { baz(); } default: { throw "Unexpected event type"; } }
null
原则上不使用null
。
// Bad! var foo:MyType = null;
如果某处代码一定要使用null
,应该把可能为null
的变量类型标记为Null<MyType>
,例如:
// Good! static function depth(state:Null<State<Dynamic>>):Int { if (state != null) { return depth(state.master) + 1; } else { return 0; } }
对于允许传null
的函数参数,标记为可选参数亦可,例如:
// Good! static function depth(?state:State<Dynamic>):Int { if (state != null) { return depth(state.master) + 1; } else { return 0; } }
Interface
Interface的命名通常是名词或形容词,且以I
为开头。
在编写RPC相关接口时,如果接口内容为服务器端协议,则必须以-Service来结尾;如果接口内容为客户端协议,则必须以-Notification来结尾
// Interface for RPC // Good! interface ILoginService { } // Good! interface IMessageNotification { } // Bad! interface Login { }
哈希表
- 键类型如果是字符串,应使用StringMap<MyValueType>而不是Dynamic<MyValueType>。
- 键类型如果是整数,应使用IntMap<MyValueType>而不是Array<MyValueType>。
- 键类型如果是引用,应使用ObjectMap<MyValueType>。
// Good! var fooByString = new StringMap<Foo>(); // Bad! var fooByString = new Dynamic<Foo>(); // Bad! var fooByString:Dynamic<Foo> = {}; // Good! var fooByID = new IntMap<Foo>(); // Bad! var fooByID = new Array<Foo>(); // Bad! var fooByID:Array<Foo> = []; // Good var idsOfFoo = new ObjectMap<Foo, Int>();
using
对于同一个公有静态函数,在定义这个静态函数的文件以外,每处调用这个静态函数的语法必须相同。即,要么每处都以using
语法调用,要么每处都以普通语法调用。
如果某个公有静态函数的第一个参数名为self
,那么必须以using
语法调用这个静态函数。
例如:
class FooHelper { // Good! public static function mustUsing(self:Foo):Void { } }
using FooHelper; class Main { public static function main():Void { // Good! new Foo().mustUsing(); // Bad! FooHelper.mustUsing(new Foo()); } }
@:noUsing
而如果一个公有静态函数有参数,且第一个参数名不是self
,那么必须把这个公有静态函数标记为@:noUsing
。例如:
class FooHelper { // Good! @:noUsing public static function mustNotUsing(foo:Foo):Void { } }
class FooHelper { // Bad! public static function mustNotUsing(foo:Foo):Void { } }
第三方库
标准库和第三方库中存在许多静态函数不符合本编码规范。这些静态函数的第一个参数名不是self
,同时也没有被标记@:noUsing
。当调用这些静态函数时,除非本编码规范中特别提及,否则不应该使用using
语法。例如:
// Bad! using Reflect; class Main { public static function main():Void { var foo:Dynamic = {}; // Good! Reflect.setField(foo, "fieldName", "fieldValue"); // Bad! foo.setField("fieldName", "fieldValue"); } }
Lambda
和StringTools
作为上一条的例外,必须以using
语法调用标准库的Lambda
中的所有函数,以及StringTools
中除了isEof
以外的其他所有函数。例如:
using StringTools; using Lambda; class Main { public static function main():Void { // Good! [ 1, 2, 3 ].has(2); // Bad! Lambda.has([ 1, 2, 3 ], 2); // Good! " foo".ltrim(); // Bad! StringTools.ltrim(" foo"); } }
定义跨平台使用数据结构
haxe中所有跨平台使用的数据结构中不可以出现可变成员变量,一定需要可变成员变量需使用Stm的数据结构。
如果需要跨平台使用的数据结构的构造函数是空构造函数,则其成员变量都要赋初值。通常情况每一个成员都使用Stm的数据结构。
如果需要跨平台使用的数据结构的构造函数是非空构造函数,则模仿scala的case class
,构造函数要给每一个成员变量赋初值,构造函数参数和成员变量一一对应,且命名一致。其成员变量是不可变的,通常不使用Stm的数据结构,但特殊情况允许使用,需要用注释注明使用原因。
// Good! class User { public function new(userName:String, password:String):Void { this.userName = userName; this.password = password; } var userName(default, null):String; var password(default, null):String; } // Bad! class User { var userName:String; var password:String; } // Good! class User { var userName:StmRef<String> = StmRef.empty(); var password:StmRef<String> = StmRef.empty(); }
常量
编译时常量
编译时常量应该使用inline var
,常量名所有字母都大写,单词之间用下划线隔开,例如:
class Foo { public inline var MY_INLINE_VAR = 5678; }
初始化后就不修改的对象成员变量
某些成员变量只会初始化一次,初始化后就不会修改,这类变量在类中定义时应该使用(default, null)
修饰符,例如:
class Foo { public var myReadOnlyVar(default, null):Int; public function new(myReadOnlyVar:Int) { this.myReadOnlyVar = myReadOnlyVar; } }
如果这个变量的初始值是常量,也不需要访问this,那么应该直接用=
来初始化,例如:
class Foo { public var myReadOnlyVar(default, null):Int = 123; }
初始化后就不修改的静态成员变量
某些成员变量只会初始化一次,初始化后就不会修改,这类变量在类中应该使用(default, never)
修饰符,变量名则是大写下划线风格,例如:
class Foo { public var MY_READ_ONLY_VAR(default, never):Array<Int> = [ 1, 2, 3 ]; }
结构化类型中的只读变量
结构化类型中的只读变量应该使用(default, never)
修饰符,例如:
typedef Foo = { var myReadOnlyVar(default, never):Int; }
@:dox(hide)
有的类、宏和运行时函数仅用于模块内部使用,但是因为某些原因,必须设为public
才能编译通过。那么这些类、宏和运行时函数应该标记为@:dox(hide)
,代表不生成文档的内部功能,例如:
class MyMacroClass { private static function internalRuntimeHelper(s:String):Void { // 为myMacro专用的内部函数。 } macro public static function myMacro():haxe.macro.Expr return { macro MyMacroClass.internalRuntimeHelper("some arguments"); } } class Main { public static function main() { MyMacroClass.myMacro(); // 编译失败,无法访问internalRuntimeHelper } }
class MyMacroClass { @:dox(hide) // Good! public static function internalRuntimeHelper(s:String):Void { // 为myMacro专用的内部函数。 } macro public static function myMacro():haxe.macro.Expr return { macro MyMacroClass.internalRuntimeHelper("some arguments"); } } class Main { public static function main() { MyMacroClass.myMacro(); // 编译成功 } }
注意:不得跨模块访问@:dox(hide)
的类成员;不得跨包访问@:dox(hide)
的类:
// myPackage1/MyClass1.hx package myPackage1; class MyClass1 { @:dox(hide) public static function hideFunction():Void { } public static function main():Void { // Good! hideFunction(); } }
// myPackage1/MyClass2.hx package myPackage1; @:dox(hide) class MyClass2 { public static function main():Void { // Bad! MyClass1.hideFunction(); } }
// myPackage2/MyClass3.hx package myPackage2; // Bad! import myPackage1.MyClass2; class MyClass3 { }
C#
除了本文特别提及之处,应遵守Framework Design Guidelines和All-In-One Code Framework Coding Standards。
TODO
All-In-One Code Framework Coding Standards禁止使用TODO注释。这条规则适用于示例代码,但不适用于我们的产品代码。我们的产品代码中可以出现TODO注释。
Updated