编码规范——程序员的自我修养(代码样例为lua代码或c++代码)

------------------------------------
--命名部分(适用于lua代码及c++代码)
------------------------------------
--
命名规范(统一的命名规范有助于提高编码效率):
--
***************
代码命名规范:
1.全局变量所有单词所有字母均大写,字母间加下划线。如UI_CONST。
2.函数名及局部变量名首单词首字母小写,其后单词首字母大写,非首字母均小写,字母间无下划线。如importModules和tempPosition。
3.类名所有单词首字母大写,非首字母均小写,字母间无下划线。如ObjBoss。
4.类中成员变量的命名在局部变量命名规范基础上额外要求加单下划线如_uniqueObj和_cardId,对于那些希望仅内部访问的变量(私有变量)则加双下划线如__index。
5.代码缩进(每行起始缩进)统一使用一个tab,一个tab的长度设置为4个空格单位。


附加:为保持函数调用方式一致,建议所有类的成员函数定义时无论需不需要访问self均加上self(即定义时加上:语法糖).


***************
文件、文件夹命名规范(适用于代码文件、美术资源文件、配置资源文件):
1.文件命名为首单词首字母小写,其后单词首字母大写,非首字母均小写,字母间无下划线。如cdEffect.png和guanYu.png
2.文件夹命名为所有字母小写,字母间加下划线(这样可与文件命名区分开)。如res,sprite,src,naming_standard


***************
表格列名及Cocostudio中控件命名规范:
1.表格列名及Cocostudio中控件名导入到程序中后均作为变量,故命名规范参照代码中局部变量命名规范,即:
首单词首字母小写,其后单词首字母大写,非首字母均小写,字母间无下划线。如attcFreq和pauseBtn。


------------------------------------
--编码风格部分
------------------------------------
--
变量的申明或定义:
--
lua作为一门语法宽松的动态语言,其允许你在任何时候任何地点给table或者userdata添加变量。
但如果真这样作则将导致对象管理上的极大困难(尤其在初始化和清空的时刻),而且查找bug时
将会吃下这样做的苦果。因此对此部分做如下要求:


***************
类的任何非静态成员属性都应该在ctor()函数中被恰当的申明,不允许在ctor()外给类或者对象添加变量。

*因此如下是符合要求的:

		----------------------------------------
		--obj的动画管理器,提供对某个obj的诸动画资源进行管理,包括暂停、播放等其他的操作
		ObjAnimMgr = class("ObjAnimMgr")

		--------------------------------------------------public
		function ObjAnimMgr:ctor()
			--动画所属的父节点(obj)
			self.__parentObj	= nil
			--cocos2d-x中动画的armature对象(如需要获得请通过getArmature())
			self.__armature 	= nil
			--cocos2d-x中动画的animation对象(get from armature)(如需要获得请通过getAnimation())
			self.__animation 	= nil
			--帧事件回调函数列表
			self.__frameEventCallback = {}
			--动作播放完回调函数列表
			self.__animOverCallback   = {}
		end
*而如下的代码是不符合要求的:

		----------------------------------------
		--obj的动画管理器,提供对某个obj的诸动画资源进行管理,包括暂停、播放等其他的操作
		ObjAnimMgr = class("ObjAnimMgr")

		--------------------------------------------------public
		function ObjAnimMgr:ctor()
		end

		function ObjAnimMgr:someFunc()
			if nil == self.frameEventCallback then
				self.frameEventCallback = {}
			end

			--do others
		end
--
注释(必要的注释是你对项目组所应当负的责任):
--
必要的注释不可或缺,过度的注释则反过来降低了代码的清晰度。
自己看懂自己的代码一般没有多大问题,但当别人接手你的代码后,必要的注释能够帮助他迅速的理解并掌握你的代码,从而降低你离职给项目组带来的影响。
必要的注释能帮助同组同事更好的理解你负责的模块或功能,从而当他人的开发跨越了你负责的部分时减少因理解而带来的bug及其他隐患。
全组成员养成加注释的好习惯,能大大降低因员工离职而可能带来的他人接手其代码的障碍及隐患,同时提高组内横向需求开发时的效率及质量。


以下情况下建议注释:
***************
1.逻辑代码中的复杂深奥难懂,或者有特殊意义的部分,加上注释有助于他人对此部分的理解。


***************
2.逻辑代码中的承上启下,阶段分割处,加上注释有助于他人对整体过程的认识。
*因此如下代码是被推荐的:
		--计时调度器更新函数
		function ClientTimer:__updateScheduler()
			--检查移除池并移除其中的scheduler
			for k, v in pairs(self.__delayRemovePool) do
				self.__schedulerTbl[v] = nil
			end
			self.__delayRemovePool = {}

			--检查增加池并增加其中的scheduler
			for k, v in pairs(self.__delayAddPool) do
				local schedulerInfo = { isAlive 	= true, 
										elapsed 	= 0.0, 
										interval 	= v.interval, 
										func 		= v.func, 
										timerType 	= v.timerType, 
										paused 		= v.paused, 
										calledTime 	= 0,
										removeFunc 	= v.removeFunc,}

				self.__schedulerTbl[v.id] = schedulerInfo	
			end
			self.__delayAddPool = {}

			--scheduler tick
			for schedulerId, schedulerInfo in pairs(self.__schedulerTbl) do
				if (not schedulerInfo.paused) and schedulerInfo.isAlive then
					--未被暂停,需要tick
					local dt = self:getDeltaSec(schedulerInfo.timerType)
					schedulerInfo.elapsed = schedulerInfo.elapsed + dt 
					if schedulerInfo.elapsed > schedulerInfo.interval then
						--到了触发时间,触发
						schedulerInfo.elapsed = 0.0
						--调用次数累加
						schedulerInfo.calledTime = schedulerInfo.calledTime + 1
						schedulerInfo.func(dt)
					end
				end
			end
		end
***************
3.变量在申明时,申明变量的时候添加注释表明其用途和一些注意事项,之后变量使用过程中不必做注释。
*因此如下代码是被推荐的:
		function ObjAnimMgr:ctor()
			--动画所属的父节点(obj)
			self.__parentObj	= nil
			--cocos2d-x中动画的armature对象(如需要获得请通过getArmature())
			self.__armature 	= nil
			--cocos2d-x中动画的animation对象(get from armature)(如需要获得请通过getAnimation())
			self.__animation 	= nil
			--帧事件回调函数列表
			self.__frameEventCallback = {}
			--动作播放完回调函数列表
			self.__animOverCallback = {}
		end


***************
4.接口提供处,在提供接口时加入充分必要的注释,能使他人一眼就明白该接口的作用和使用方法从而降低错误使用接口的可能。
接口处的注释不够丰富或明确将大大提高模块的使用难度,使得调用者需要深入了解模块的实现才能掌握模块的使用方法。
接口处的注释方式视接口的功能和参数复杂度而定。
*如简单的接口可使用一句话作为注释,如:
<span style="white-space:pre">		</span>--全局暂停,将暂停整个时间管理器(而不是单个时间线),用于暂停按钮
		function ClientTimer:globalPause()
			self.__lastPauseBeginTime 	= socket.gettime()
			self.__paused				= true
			cc.Director:getInstance():pause()
		end
*而复杂的接口(比如功能复杂或者参数较多或行为有一定的不确定性)则需要更详细的说明,如:
		---------------------------------------------
		--公有方法		
		--功能:			创建计时调度器
		--参数func:			计时回调函数
		--参数intervalTime:	计时时长
		--参数tag:			调度器所属的时间线
		--参数removeFunc:	该计时器被移除时的回调函数(有则调用),调用时参数为被调度器总共被调用的次数
		--返回:			创建的调度器id
		---------------------------------------------
		function ClientTimer:createScheduler(func, intervalTime, tag, removeFunc)
			assert(tag, "tag cannot be nil")
			self.__schedulerCnt = self.__schedulerCnt + 1
			table.insert(self.__delayAddPool, {	id 			= self.__schedulerCnt, 
												func 		= func, 
												interval 	= intervalTime, 
												timerType 	= tag,
												paused 		= false,
												removeFunc 	= removeFunc,})

			return self.__schedulerCnt
		end
5.模块文件开头处,用以标示该本文件的内容(如模块的功能)、修改信息等。此部分注释能帮助他人了解到该模块的功能及维护者和版本更迭过程,从而减少一些沟通成本。
该条仅针对那些功能相对较独立(不会被频繁修改)的模块文件。
*因此如下代码是被推荐的:

		--Obj暂停管理器,考虑到本游戏需要大量暂停功能,故以管理器整合此部分代码

		ObjPauseManager = class("ObjPauseManager")
		--others
--
‘封装(提高封装能力,是个人能力进阶的必经之路):
--
"最小暴露原则":只要是对外界无用的变量(属性或方法),或是不期望被外界直接获取的变量,都应该像宝贝一样隐藏起来——即能不暴露的,就不暴露。
隐藏方法:
对于类的属性或方法,c++中通过访问控制属性private来实现隐藏,lua中可以使用明确的双下划线__来表示该变量是私有变量,外界不应该直接引用。
*因此如下代码是被推荐的:
		--该属性为私有属性,外界切勿直接调用
		self.__parentObj	= nil
		--该方法为私有方法,外界切勿直接调用
		function ObjAnimMgr:__onAnimationEvent(armature, movementType, movementId)
			--todo
		end
非类的属性或方法,可用 local 关键字来表明其为本地属性,从而限制外界的访问。
*因此如下代码是被推荐的:
		--该方法为本地方法,外界无法直接调用
		local function importModules()
			--todo
		end
封装指的是模块实现逻辑透明,只对外提供清晰确定的接口,外界不需要也不应该接触到模块内部的具体实现界只需要借助接口来完成功能调用。
良好的封装性将带来如下好处:
***************
1.模块内部发生变化时由于接口保持不变,因而外部代码不需为此做出任何改变。


***************
2.模块的实现代码全局封装在模块内部,因此模块若存在bug也不会延伸到模块外去。


***************
3.更换模块非常容易,新旧模块只需保持接口一致即可(管它内部如何实现)。同样,模块的可移植性也很强。


--
清晰度&耦合度&内聚度(清晰度位居对代码所有考核指标之首,也是其他指标的前提):
--
清晰度不是外观,其重要性应据对位于所有的考评指标之首(包括性能、安全性、防错容错性、可扩展性、可移植性、可重用性、可维护性)。
清晰度是其他指标的前提,没有清晰度其他的指标无从谈起。
1.没有清晰度,意味着程序员无法对程序的执行细节一眼望穿,具体的执行逻辑将如一张巨大的渔网一样纠缠不清。bug越修越多(修一个引发三个,修完这三个又各引发三个,这绝对不是危言耸听)。
2.没有清晰度,常意味着代码很难理解且牵扯很强,正常的流程中存在着复杂的跳转和对外部的依赖,因此耦合度必然很高内聚性必然很低。
3.没有清晰度,扩展一个功能时会发现此部分对一大堆外界存在依赖且流程错综复杂,扩展根本无从下手,因为你动弹不得。
4.清晰度不高,则容错防错无从谈起。代码的正常行为本身就不够明确,如何防范正常行为以外的行为?
代码的主要执行逻辑应该是一条确定的大道,不应该有过多的岔路(条件跳转),绝对拒绝山路十八弯的逻辑。岔路一多,复杂性曾指数增长,其变化的可能远远超出人的思维负载从而突破程序员的掌控,你会被层出不穷的bug所困扰。


耦合:简单来说就是模块内部的行为对外部有依赖,此为耦合。
根据经验,如下几种方式能降低耦合:
***************
1.单入口单出口,任何模块或子系统维护一个统一的入口及出口,再多的需求都应该经由这个入口驱动模块。
单入口不是说某功能就仅只能有一个接口,接口可能有多个,但最终的真实入口应只有一个,如clientTime.lua中,创建scheduler的接口有多个,但最终都导向了唯一的入口createScheduler()
*因此如下代码是被推荐的:
		--真实的入口
		function ClientTimer:createScheduler(func, intervalTime, tag, removeFunc)
			--some code
		end
		--本函数调用createScheduler实现
		function ClientTimer:createSchedulerOnce(func, intervalTime, tag, removeFunc)
			--some code
			schedulerId = self:createScheduler(cbFunc, intervalTime, tag, removeFunc)
			--some code
		end
		--本函数调用createScheduler实现
		function ClientTimer:createObjScheduler(obj, func, intervalTime, removeFunc)
			--some code
			local schedulerId = self:createScheduler(func, intervalTime, obj:getTimerType(), removeFunc)
			--some code
		end
		--...
		--更多
		--...

***************
2.函数的行为应该简单而确定,拒绝功能强大能做这个又能做那个的函数。如果一个函数能根据参数或者外部变量的值而做出不同的行为,你应该考虑把此函数拆分或者换一种新的思路。


***************
3.入口参数一旦确定,函数或模块的内部行为应该是确定的。反之你可能需要思考一下新的实现思路。
*如下的代码是不符合要求的:
	function moduler:sampleFunc(val)
		--upval1为moduler外部变量
		if upval1 then
			--do something
		else
			--do some other
		end
	end


***************
4.当发现有散布在多处的逻辑代码经过一定的调用顺序最后完成了一个较常用到的功能,你可能需要考虑是否应该将这些代码抽象出并形成一个模块(管理器)。 


--
防错容错(容错最重要的一点是:确定哪些该容哪些不改容):
--
一定的防错容错提升程序的健壮性,过多的防错容错拖累程序的性能并降低可读性。
***************
1.防错应该从源头上防(即在逻辑最起点检测),不要把错误检测留到过程中去反复检测(没必要且拖累性能)。
*因此如下代码是被推荐的:
		--c++代码,摘自天龙服务端
		BOOL	Login::NewLogin()
		{
			__ENTER_FUNCTION

			g_pDataBaseManager	=	 new	LoginDBManager();
			AssertEx(g_pDataBaseManager,"分配数据库管理器失败!");
			Log::SaveLog(LOGIN_LOGFILE,"new LoginDBManager ...OK");

			g_pProcessManager	=	 new  ProcessManager();
			AssertEx( g_pProcessManager,"分配g_pProcessManager 失败!");
			Log::SaveLog( LOGIN_LOGFILE, "new ProcessManager...OK" ) ;
	
			g_pPlayerPool		=	new	  PlayerPool ;
			AssertEx( g_pPlayerPool,"分配g_pPlayerPool 失败!");
			Log::SaveLog( LOGIN_LOGFILE, "new PlayerPool...OK" ) ;

			g_pPacketFactoryManager = new PacketFactoryManager ;
			AssertEx( g_pPacketFactoryManager,"分配g_pFacketFactoryManager 失败!") ;
			Log::SaveLog( LOGIN_LOGFILE, "new PacketFactoryManager...OK" ) ;
	
			g_pProcessPlayerQueue	= new TurnPlayerQueue;
			AssertEx(g_pProcessPlayerQueue,"分配排队队列失败!");
			Log::SaveLog(LOGIN_LOGFILE,"new g_pProcessPlayerQueue...OK");

			//
			//...
			//
		}
***************
2.仅容下那些由不确定因素而导致的错误(且容下这部分错误后程序还能基本正常运作),确定因素导致的错误应该果断报错并终止执行(从而快速的改正错误)(如果这种错误也容,那么你的程序将被容错代码所淹没)。 
因此对于全局管理器或者程序核心对象创建时的错误,绝对不应该容下,而应该直接报错并终止运行。(容下这些错误,程序还能运行吗,既然不能运行,容下它们有何用?)


***************
3.仅在过程中容错,启动时的错误(哪怕它再小)应该毫不留情的暴出。晚一点出门永远胜过带病出门。
因此举例而言,对于配置错误(如填表错误或者缺少资源等)哪怕再小,都不应该容。带病上阵除了会带来彻底的失败外,什么也不会带给你。


***************
4.容下错误,绝对不是隐瞒错误!因此容错时一定要配合给出足够明显的警告信息,如断言 assert(该错误十分严重)或显眼的log(该错误较轻微,尽管它发生了可程序也能继续正常运行)。
*因此如下代码是被推荐的:
		--c++代码,摘自天龙服务端
		LoginPlayer*	pLoginPlayer = (LoginPlayer*)pPlayer ;
		if( pLoginPlayer==NULL )
		{
			--报错
			Assert(FALSE) ;
			return PACKET_EXE_CONTINUE ;
		}

		*因此如下代码是被推荐的:
		--时间线是否处于被暂停状态
		function ClientTimer:isPauseTime(timerType)
			if not self.__timerTbl[timerType] then
				logger:warn("check pause no exist time, timerType = %s !!!", tostring(timerType))
				return
			end
			--...
		end
*如下的代码是不符合要求的:
		--获得delta,其实对于所有计时器其每次delta都是一样的,这里传入计时器名只是判断其有未被暂停
		function ClientTimer:getDeltaSec(timerType)
			if not self.__timerTbl[timerType] then
				--容下错误时未给出任何提示,将导致行为异常且找不到任何原因(即埋下了一个很深且难以被查找到的bug)
				return
			end
			--...
		end
***************
5.(该点存在争议)如果是一些若存在则调试中一定会发现且发现后可以很容易被修改且修改后基本就不会再发生的错误————如参数错误(明显是代码编写导致的错误),可以考虑仅防错而不容错(使用断言 assert ),从而尽量不拖累性能。该点存在一定争议,争议之处在于你很难完全确定这些前提条件是否成立。
*因此如下代码是被推荐的:
		function ObjAnimMgr:create(parent, armature)
			--本函数一定会在调试中被调用,且参数出错明显是代码编写错误
			assert(parent and armature, "parent or armature in ObjAnimMgr:create() is nil!")

			local mgr = self:new()
			return mgr
		end


------------------------------------
--全局
------------------------------------
***************
代码应该赏心悦目,每句话都恰到好处,多之显胖,少之显瘦。

***************
一个模块(工程),应该是一个被精雕细琢的艺术品,出自程序员这个艺术家之手。

***************
不要步入认识的误区:“代码越复杂人越牛逼”。越是新手越容易写出晦涩难懂的代码,高手的代码往往简洁清晰,且这样的代码常常更高效。

***************
永远不要试图用逻辑的方式解决架构的问题,否则你的程序将很快走到全面崩塌的边缘。

***************
动手开发每一个功能单之前应该问自己一个问题:“我是应该是写几句代码,还是几个函数,又或者是一个类,还是一个模块,还是一个工程?”


------------------------------------
--结束
------------------------------------
水平尚浅难免有疏漏或错误之处,若发现上述内容有任何问题,极其欢迎批评指正。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值