编程经验谈.doc
《编程经验谈.doc》由会员分享,可在线阅读,更多相关《编程经验谈.doc(30页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、编程经验谈以下主要由softit提出,StoneLee整理第一章 程序结构性设计第一节 层次分明,层次即不能太多,也不能太少一、 层次太少的范例1、 正例比如我们的棋牌游戏客户端,适合分两个主要类来处理。一个是逻辑和数据类,一个是界面类。这类似Microsoft的Cview/Cdocument结构。这样的好处是:l 层次清洗,易于阅读和理解很清楚,如果我想了解数据结构和算法的话,主要看“逻辑和数据类”。l 支持未来界面的可变性支持未来界面的可变性。未来界面发生变化,则主要改动界面类即可。而且这两个类的关系主要应该是单向的,即Cview知道Cdocument,并根据Cdocument的情况来进行
2、做图。也可能存在双向性,即Cdocument的数据发生改变后,要通知Cview发生改变。在MFC中,Cdocument-UpdateAllViews()这个方法实现这个功能。但这样,Cdocument里必须放Cview的指针,而且必须在Cdocument里有初始化的函数,例如Cdocument-AddView()。这些都加重了程序的复杂性,所以,尽量采用单向联系,能简化程序尽量简化。注意:和界面有关的数据和逻辑应该放在界面类,比如:牌的间距等数据。2、 反例我个人在编写很多游戏的客户端时,将所有逻辑和数据都放在GameView类里,使GameView非常庞大,有几千行代码。这样,阅读和维护都有
3、困难。特别是第三人维护。二、 层次太多层次太多的错误比比皆是。很多程序员以为划分很多类就很牛比,就显示自己对对象学和C+的精通和高水平。先不要说其他人,说说自己吧。我在写第一个游戏“升级”时,犯下了教条主义错误。当时为了表达一个双张拖拉机,就做了如下的类:首先是单张牌类:CCard,表示单张牌,方法有,获得此牌的内部值、花色、权值,比较单张大小然后做了双张对牌类CdblCard类,这个类继承了CCard类,方法主要是继承的函数然后做了双张拖拉机类CdblTractorCard类,这个类继承了CCard类,同时内部数据有CdblCard类的内部变量。(比如可以用一个开始CdblCard类对象+一
4、个长度数据nLen表达双张拖拉机)。后来再加上三张的类,加上逻辑类,加上界面类,整个程序非常复杂。经常在引用一个数据时,要调用其继承的数据或方法,经常做类型转换工作,这个工程像个大蜘蛛。后来,在新的升级中,我只用了一个类就就将所有这些类解决了。对于单张牌,我就用Byte数据表达,对于CdblCard,也是一个Byte数据就可以了,只要函数调用时,知道其代表双张对即可。我觉得相对于层次太少,层次太多是我们主要犯的错误,特别是那些读了很多教科书,但写程序很少或者是写程序很多但没有吃过亏或则是吃了亏还朦胧不懂的人。第二节 类的定义要简捷,不要做过多的工作这和下面一节有共性。这样的类一般有如下特点:n
5、 对其他类的疯狂引用n 代码超过千行n 什么都能做第三节 类的互相关系:关联要尽量简单最好是单向的。我个人不喜欢双向关联。这样意味着你阅读一个类时,必须理解另外一个类。单身汉是快乐的,夫妻就要麻烦很多。未来的工程文档里,主要应该说明两点:n 类的关联关系n 对象的生成和销毁大家有空可以阅读一下Rose,这将是未来我们文档的一个重要规范。第二章 程序健壮性第一节 不要相信传入的参数,即使调用函数是自己编的模块接受外部参数时,做更多的检查。原则上,不论外部如何操控,一个模块自身必须严格安全的,如对外部对模块调用的顺序和参数都不要有默认的假设。最多的问题来自于空指针的使用。例如下面的例子相当普遍:v
6、oid Reverse(char* str)int nLen=0;char *pTemp = str;while (*pTemp+ != 0)nLen+;/ 下面省略.还记忆得这个例子吗,是考试的一道题目,这道题功能很多人实现了,但有很多毛病,为什么这样说?原因很简单:你怎么知道str不是NULL?所以正确答案如下:void Reverse(char* str)if (str = NULL) return;int nLen=0;char *pTemp = str;while (*pTemp+ != 0)nLen+;/ 下面省略.如果有的程序员追求效率,认为str不可能上null,那么,代码应该
7、如下书写:void Reverse(char* str)assert(str);/ MFC下,可以是ASSERT(str);int nLen=0;char *pTemp = str;while (*pTemp+ != 0)nLen+;/ 下面省略.这样在debug下编译,如果str是NULL,就可以使程序立刻停止下来,检测到错误;如果是release,则没有任何多余代码。这对于我们检测到错误,是非常有帮助的。记住一点:不要相信任何传入参数,包括自己编写的代码。第二节 变量初始化下面也是我们常犯的一个错误:void Sample()char *szTemp;./ 很多代码lstrcpyn(szT
8、emp, “abc”, 3);/ 此句出错上面代码,当出错时,我们很难调试,因为szTemp里面的值并不是0x000000,而是乱七八糟的值,有经验的人容易判断出是szTemp未初始化,而如果中间的代码过多,我想谁也不曾想到是szTemp未初始化导致。所以,正确的写法如下:void Sample()char *szTemp = NULL;./ 很多代码lstrcpyn(szTemp, “abc”, 3);/ 此句出错这样,我们就很容易找到错误了。有些人认为,自己很牛,完全能控制主这些变量,也许几个变量、几十行代码还控制得住,但多了,肯定不行。还有人认为这是浪费效率,可这个效率值得浪费,因为它带
9、来的程序健壮性会远远超过多几个汇编指令的开销(其实我们代码里浪费的指令的地方多啦,难道还在这么一个小地方讲学究!)唯一可以例外的是: for(;)循环中经常使用的i,j,k等控制变量,因为它们肯定在for中做初始化。同时,对于一些自定义结构,我们最好也养成初始化的好习惯,常见写法如下:typedef struct tagSample int iMax;BOOL bFound;char *szName;Sample() memset(this, 0, sizeof(Sample);int GetMax(); Sample;这里构造函数里的memset()函数,非常有用。它将所有值都初始化,避免了
10、上面可能出现的问题,是个很好的代码范例。也可使用:ZeroMemory()来替换memset,只不过一个是Win32,一个C Runtime Library。第三节 关于缓冲越界一、 用:lstrcpyn()替换strcpystrcpy不是很安全(因为没有边界检测),应该尽可能用lstrcpyn代替strcpy。可以写成:lstrcpyn(),也可简单写成lstrcpyn()。二、 Debug下无问题,Release就有问题Debug下程序好好的,到Release下,就出现问题,一般情况下,这是数据越界导致。为什么会这样?因为Debug下分配内存和Release下分配内存是不一样的。比如:ne
11、w char(10),Release下是十个字节,但Debug下是更多的字节,这是因为MFC为了调试程序,跟踪内存泄露,所采用的特殊做法。三、 开设较大内存的变量我们在设置变量时,最好不要刚刚好,可以稍微多分配一些,特别是针对那些稳定性要求很高的应用,比如服务器系统。比如,我们一个变量是char m_szMsg10。为了提高程序稳定性,我们可以提高其大小,如char m_szMsg32。有的古板的C程序员会加了,说:这样浪费内存。我们用多少字节就要多少字节。请大家记住:现代计算机系统,内存已经很大了,已不是DOS下的640K,而是以G为单位。请忘掉Bill Gates说的那就可笑的千古名言:6
12、40K is enough for PC。另外,用了char m_szMsg32和char m_szMsg10,可能更快,至少效率是一样的。为什么?因为计算机是二进制的,他识别10不如识别32来得快,所以在内部地址分配时,实际上操作系统分配的内存都是16或32的倍数。四、 一些错我范例在数据库引擎中,有如下代码if (strlen(szDataSource)+strlen(szDatabaseName)+strlen(szLogin)+strlen(szPassword)=MAX_DATABASE_CONNECTION_STRING_LEN) assert(0);/ 下面设置连接串strcat
13、(m_szDatabaseConnectString,Provider=SQLOLEDB;Data Source=);strcat(m_szDatabaseConnectString,szDataSource);strcat(m_szDatabaseConnectString,;Initial Catalog=);strcat(m_szDatabaseConnectString,szDatabaseName);strcat(m_szDatabaseConnectString,;User ID=);strcat(m_szDatabaseConnectString,szLogin);strcat(
14、m_szDatabaseConnectString,;Password=);strcat(m_szDatabaseConnectString,szPassword);其中szDatabaseConnectString的定义是char m_szDatabaseConnectStringMAX_DATABASE_CONNECTION_STRING_LEN;这段代码是有风险的。应该改为:char m_szDatabaseConnectStringMAX_DATABASE_CONNECTION_STRING_LEN+64;大家可以想想为什么?第四节 关于调试调试有多种方法,重要的是捕捉错误和将错误环境
15、输出。关于捕捉错误,有多种方法,一种是用诸如:ASSERT、VERIFY、assert将程序停住,一种是使用try() catch()或简单的函数返回值来定位错误。这个只能分场合。比如,我们经常使用的CLimitedArray,它的定义范围就是一个静态定长数组,只要其超过长度,在DEBUG下,就应该停下来。便于诊断。当然,也可以返回值,由上层判断进行处理。但这样就复杂化了,将处理工作抛给了调用者。所以简单处理之,还是在CLimitedArray内部,使用ASSERT或VERIFY宏。但另外一些程序,特别是一些普遍使用的接口函数,如通信层底层,则应该考虑尽量不要使用ASSERT等工具将程序停下来
16、,一是这样,程序依赖MFC(虽然可以用assert(),但不如ASSERT方便),二是没有必要停下来,应该交给上层决定如何处理错误。对于不需要停下来的错误,是用try、catch,还是使用函数返回值来定义错误,则完全取决于程序的编写。相对而言,函数返回值较为简单,上层也易控制,程序较简单,出错概率小,应该多采用;而多余多错误返回,程序流程长且复杂的,应该考虑用try、catch。总而言之,使用何种错误调试方式,完全取决于编写工程的需求。但有以下几个原则:n 能使用ASSERT的,尽量使用,因为上层调用不需要处理相关工作,简化上层工作;n 能使用函数返回的,尽量使用,因为上层调用处理时简单易用举
17、个例子,在服务器框架里,有这么一行话:HRESULT STDMETHODCALLTYPE IGameLogicalImpl:SetServerSite(IServerSite * pGameSite)if (pGameSite=NULL) return E_INVALIDARG;。因为,服务器逻辑对象IGameLogicalImpl(实际应该是CGameLogic),如果没有IserverSite对象,就根本无法运行,所以,可以直接改写为ASSERT(pGameSite);这样代码简捷得多,DEBUG调试时,完全可以捕捉到这个错误,而且Release下,我们已假设肯定不会出现空指针的问题。(换
18、句话而言,出了空指针,我们后面什么事情都做不了,就应该停下来,没有其他更好的容错办法了)第三章 程序可读性第一节 可读性第一,效率第二第二节 保持注释与代码完全一致第三节 每个源程序文件,都有文件头说明,说明规格见规范第四节 有二义性的函数、重要函数特别是逻辑复杂的函数需要有函数说明第五节 处理过程的每个阶段都有相关注释说明第六节 在典型算法前都有注释第七节 主要变量(结构、联合、类或对象)定义或引用时,注释能反映其含义第八节 常量定义(DEFINE)有相应说明第九节 利用缩进来显示程序的逻辑结构,缩进量一致并以Tab键为单位,定义Tab为4个字节第十节 循环、分支层次不要超过五层第十一节 注
19、释可以与语句在同一行,也可以在上行第十二节 空行和空白字符也是一种特殊注释,可以增加可读性第十三节 一目了然的语句不加注释第十四节 注释的作用范围可以为:定义、引用、条件分支以及一段代码第十五节 注释行数(不包括程序头和函数头说明部份)应占总行数的 1/5 到 1/3第十六节 while和for比如,在我们的考试的第二题中有个是这样回答的:LIST *pL1;BOOL bResult = FALSE;if (NULL = pList)return FALSE;for (pL1=pList-next; pL1; )这种答法解决了问题,但这里更应该用whileLIST *pL1;BOOL bRes
20、ult = FALSE;if (NULL = pList)return FALSE;pL1 = pList-nextwhile (pL1).pL1 = pList-next;第十七节 常数考虑用宏我在一些程序员编程中发现,他们喜欢用常数。比如:小沈在编写服务器管理器时,各个命令码和定义值全部写成实际数字,而且通篇都是,后来小余接手时,累得吐血。为什么不用宏?用宏的好处:1) 将数字变成了英文,字面就能体现例如:25还是COMMAND_LOGIN,哪个更好懂2) 方便未来维护如果未来修改,只需要修改一处,全局有效第十八节 宏在什么地方,.h or .cpp宏是C+里经常用的一个东西。大家可以读一
21、下侯捷的深入浅出MFC,对宏的理解可以加深。但宏放在哪里好,一般我们将宏放在文件的开头。如果此宏只在.cpp里使用,则请将此宏放在.cpp里,因为这样的好处是:宏定义不冲突,特别是你引用其他人的宏时。第十九节 在VC工程里使用Folder我们经常在VC工程里放了几十个类和结构。(大家读一下老框架就有体会了)这样,我们阅读起来就非常费劲。所以,应该将这些类组织一下。方法,就是使用VC工程的Folder。方法:在ClassView里的TreeView上,可以点鼠标右键,然后选择New Folder菜单。这样,我们可以使整个工程特别清晰。至少,我们要将引用其他人的类和我们自己的类分离。小提示:甚至一
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 编程 经验谈
限制150内