赫墨拉游戏-服务端-编程规范
为了方便大家交流和代码共享,现在此做出一个共同的代码风格约定。本规范适用于项目C++语言以及其他语言与C++的交集部分。
PS:本规范只对预计可能导致代码风格有较大不同的部分作出约定。
一、命名规范
对于所有类型的命名都有效的规范:
命名是非常重要的,
l 名称表述清楚,使用统一的英文词汇。
l 不使用英文简写,除非是一些固有的缩略语。
l 单词命名优先表达清楚意思,其次才考虑缩短长度,不过也不要太长。
l 对于无法用英文表述的命名,使用拼音。每个词看做一个单词,中间不用大写,
例如:
修仙副本 xiuxianCopy
炫阳套装 xuanyangSuit
因为汉字太多的话,大写太多会很乱。
l 使用驼峰命名法,严禁全字母大写并且没有”_”的命名。
l 所有函数、变量、类等,都必须要加注释,即使一看就懂的也翻译一下中文。
l 命名要一致,不同的功能,对于相同的概念,要用相同的名称。
1. 变量命名规范
l 变量名前不使用任何表示类型的前缀。
l 类内部数据成员,以前缀“_”开头;对于公开的数据成员,可以不使用前缀。
l 变量首字母使用小写。
2. 函数命名规范
l 函数名必须直观,并且能正确表达其内在功能。
l 对于函数参数中的引用和指针类型视情况以const修饰。
l 对于不修改数据成员的类成员函数,以const修饰。
l 函数名以小写开头。
3. 类和结构名命名规范
l 类名以C开头,结构以S开头,都以大写开头。
l 接口类的命名沿用上面规则,并以前缀“I”开头,例如:IInterfaceMgr,IModule。
4. 常量与宏命名规范
l 尽量不使用宏定义;
l 对于常数定义,尽量采用const修饰或enum定义;
l 对于简单的操作,采用inline函数方式定义;
l 常量名第一个字母大写。
5. 文件命名规范
l 目录名每个单词首字母大写,不使用任何分隔符;
6. typedef类型命名规范
l typedef std::vector<int> SeqInt; //vector的全部都在前面加Seq。
l typedef ::std::map<int, int> DictIntInt;//map在cdl中加前缀Dict,在代码中用Map。
l 必须以大写开头。
二、布局规范
应当以IDE的原来的风格为优先。
l 程序体建议以TAB缩进,少使用空格缩进。
l 每一个函数(或类方法)之间至少保留一个空行。
l 在程序体内,具有语义转换时,必须保留一个空行。
l 不编写过长的函数代码,如果函数过长应该考虑分拆为多个函数。
l 多次使用的代码,尽量抽出来作为一个函数。
l 除“.”、“→”、“::”外,双目操作符(如:“+”、“*=”等),两侧各留一个空格。
l 单目操作符(如:“++”、“!”)与操作数之间不留空格。
l 逗号分隔符的左侧不留空格,右侧留一个空格。
l 当判断一个指针是否为空时,使用“!ptr”的形式判断。
l 不要在同一行了声明多个变量。
l 当一个函数的返回值是指针变量或引用变量时,类型与操作符(“*”或“&”)之间不留空格,操作符之后留一个空格;
l 对于复合语句,如:while, for, if等,无论复合语句中包含多少个语句,一律另起一行,并使用{}括起来;
l 尽量保持头文件中不包含头文件;
l 对于数据成员都公开的类,使用struct关键字来定义,否则使用class关键字。
l 对于计数器或迭代器,可使用前++(--)或后++ (--)时,一律使用前++(--)。
一些非常细的规则可以看这里:
http://blog.csdn.net/u012175089/article/details/51078360
三、编码的建议
这里只是一些建议和可能遇到的问题。
1.尽量把运算提前,能够在启动程序时进行的运算,就不要等到使用的时候才进行。
例如对于某些用[xxx,yyy]这样包着的字符串,应该在初始化的时候就进行分解,用DictIntInt来保存。而不是每次用到的时候拆一下。
又如每一级的属性,在升级之后,一个个再加起来。这是设计上的错误,在设计配置文件的时候,要充分考虑计算量。同时也要考虑策划的配置量,通常只要配一次,以后改动很小的数据就不需要考虑策划的工作量了。
2.内存中的配置文件,尽量使用map来代替vector。
3.代码里面的code很多,物品、技能、buff、updateCode等等,几乎每个配置都有一个,最好在使用的时候加上各自的单词,例如:itemCode,skillCode,buffCode等等,不能只写code,难以阅读,而且代码增多之后很容易造成混乱。
4.尽量使用公共的工具类函数,一般都放在Common/Public目录下,Util类是最常用的。
5.字符串拼接,一般使用“,”,如果要加多一层,建议使用“;”。使用公用的函数来构建和分解字符串。
6.DataTime转成int,可以去到2030年以后都不会出错,使用json保存的时候,可以放心使用int,方便比较,减少转换,而且减少json的长度。
7.智能指针不要互相指引,否则无法自动析构。
8.json原则上是用来保存一些比较散乱的字段,如果是有多个字段的结构的数组,可以考虑构建一个新的表。Json字段通常很大,如果是数组,很容易会超过最大长度。而且,很难调试和查看内存。Json最大的用途是用来存储和传输,不要过分依赖。无法调试的。
9.保存到数据库的数据,尽量使用延时保存。
10.不要写依赖于顺序而毫无逻辑的代码。
sCodeAttribute.attribute.push_back(attr.attack); sCodeAttribute.attribute.push_back(attr.life); sCodeAttribute.attribute.push_back(attr.physicalDefense); sCodeAttribute.attribute.push_back(attr.magicalDefense); sCodeAttribute.attribute.push_back(attr.wreck); sCodeAttribute.attribute.push_back(attr.block); sCodeAttribute.attribute.push_back(attr.miss); sCodeAttribute.attribute.push_back(attr.demiss); sCodeAttribute.attribute.push_back(attr.crit); sCodeAttribute.attribute.push_back(attr.decrit);
这样的代码,中间加个字段,就会完全乱了。
11.推送数据要尽量缩减,通常一个功能的全部数据在登录的时候推一次,之后更新某个字段才逐个字段推送更新。
12.声明变量的时候顺手初始化一下。
13.循环用的迭代器,如果是后面没有使用到,就应当放到循环内部。不同循环不要共用迭代器。
14.配置文件还是服务端来设计,策划只是给建议和参考。我们要充分考虑策划的命名,绝大部分是不能直接使用的,通常口头说的是一个名称,写到文档又是一个,等上线后又是一个命名。如果不理想,还会改很多次。而且,一个名称可能会改掉,用在其他系统上面。这个时候就非常混乱了。
15.要考虑卡顿的问题。出现卡一般可以分为几种情况:
(1)玩家实在是太多,现在的行业情况,几乎不可能出现了,只有在游戏后期,很多个服合成一个服才会出现。但是这样的玩家,在线的还是很少的。所以平时一般不考虑。
(2)功能业务上设计有问题,让玩家同一个非常短的时间段内必须做某件事情。例如设个5分钟的运镖3倍时间,这个时刻一到,所有玩家都在运镖。碰到这种问题,要主动跟策划商量,让其认识到可能发生的情况。这种错误很很明显,可能每天到这个时间段,就会卡,也会有很多玩家反映。但是修改的目标也很明确。
(3)代码逻辑问题,最容易出现的就是for循环。例如300级的坐骑,如果每级的属性都是独立计算,每次访问,升级,甚至走一步都检测一下。还得循环几百次,累计起来的计算量会很大的。这种错误非常隐蔽,多少次循环才算是有问题?这个无法下定论,但是计算量肯定是累计在那里,这个功能累积一下,那个功能累积一下,整个系统就会很卡。这种卡是无时无刻,偶尔爆发一下完全动不了,难以寻找,也不知服务端还是客户端的问题。通常都是伴随着游戏的整个生命周期。几乎无法根治,只能在设计和编写代码的时候有意识地避免。
16.独立的功能尽量独立管理自己的数据,如果使用了一些写死的数据,也要改成常量。
17.除数必须检测是否为0,不管是否从业务上看起来不可能为0.
18.概率的问题,C++随机数最大值是30000多。另外,涉及到战斗和属性相关的,统一使用1000,用常量FIGHT_RATING。其他的没什么特殊要求用100就行了。
19. 在一段程序里面,对于一些检测判断,比较容易return或者抛异常的,应该放在前面。变量尽可能放到要用的时候才声明。
20. 尽量不要把int当bool来使用。代码有变动之后很容易出错,而且就算用int来存放结果,很多时候0不一定代表错误。
21.同一个功能的配置,建议放在同一个表里面。当然,东西很少的小功能,可以放到t_const里面。但是也有很多功能,可能内容很散,有多种配置,而且数据量不是很大,可以放到同一张表里面。
通常用这种格式来写,但是要注意几点:
1.必须有desc字段,写清楚每个字段的意义,上面的还不是很详细。应该这样:
type类型value1:阶数,value2:星数,value3:exp,value4:属性id
2.在代码里面,要写自己功能的结构,不能直接使用value1,value2,value3.在loadConfig函数里面就应该转换完成。这种通用表格的内容不应该出现在业务层的代码里面。
22.一般情况下,只有接口访问才能够直接抛异常,其他地方抛异常会出问题的,例如登录。
23. 对于某些属性,过了某个时间失效的情况。设个定时器,到了这个时间点,就把所有的都改掉,这是策划的思想。我们通常在使用到的时候检测一下是否失效了就行了。