首页 编程修养

编程修养

举报
开通vip

编程修养 制作:raincatss 博客:http://raincatss.cublog.cn/ 第1页/共29页 版权声明:本文转载自 CSDN·BLOG·陈皓专栏,所有权利归原作者 所有。制作本 PDF 之目的仅限于方便学习。 编程修养 概述概述概述 概述 什么是好的程序员?是不是懂得很多技术细节?还是懂底层编程?还是编程速度比较快? 我觉得都不是。对于一些技术细节来说和底层的技术,只要看帮助,查资料就能找到,对于 速度快,只要编得多也就熟能生巧了。 我认为好的程序员应该有以下几方面的素质: 1、有专研...

编程修养
制作:raincatss 博客:http://raincatss.cublog.cn/ 第1页/共29页 版权声明:本文转载自 CSDN·BLOG·陈皓专栏,所有权利归原作者 所有。制作本 PDF 之目的仅限于方便学习。 编程修养 概述概述概述 概述 什么是好的程序员?是不是懂得很多技术细节?还是懂底层编程?还是编程速度比较快? 我觉得都不是。对于一些技术细节来说和底层的技术,只要看帮助,查资料就能找到,对于 速度快,只要编得多也就熟能生巧了。 我认为好的程序员应该有以下几方面的素质: 1、有专研精神,勤学善问、举一反三。 2、积极向上的态度,有创造性思维。 3、与人积极交流沟通的能力,有团队精神。 4、谦虚谨慎,戒骄戒燥。 5、写出的代码质量高。包括:代码的稳定、易读、规范、易维护、专业。 这些都是程序员的修养,这里我想谈谈“编程修养”,也就是上述中的第 5点。我觉得,如 果我要了解一个作者,我会看他所写的小说,如果我要了解一个画家,我会看他所画的图画 , 如果我要了解一个工人,我会看他所做出来的产品,同样,如果我要了解一个程序员,我想 首先我最想看的就是他的程序代码,程序代码可以看出一个程序员的素质和修养,程序就像 一个作品,有素质有修养的程序员的作品必然是一图精美的图画,一首美妙的歌曲,一本赏 心悦目的小说。 我看过许多程序,没有注释,没有缩进,胡乱命名的变量名,等等,等等,我把这种人统称 为没有修养的程序,这种程序员,是在做创造性的工作吗?不,完全就是在搞破坏,他们与 其说是在编程,还不如说是在对源程序进行“加密”,这种程序员,见一个就应该开除一个, 因为他编的程序所创造的价值,远远小于需要在上面进行维护的价值。 程序员应该有程序员的修养,那怕再累,再没时间,也要对自己的程序负责。我宁可要那种 动作慢,技术一般,但有良好的写程序风格的程序员,也不要那种技术强、动作快的“搞破 坏”的程序员。有句话叫“字如其人”,我想从程序上也能看出一个程序员的优劣。因为, 程序是程序员的作品,作品的好坏直截关系到程序员的声誉和素质。而“修养”好的程序员 一定能做出好的程序和软件。 有个成语叫“独具匠心”,意思是做什么都要做得很专业,很用心,如果你要做一个“匠”, 也就是造诣高深的人,那么,从一件很简单的作品上就能看出你有没有“匠”的特性,我觉 得做一个程序员不难,但要做一个“程序匠”就不简单了。编程序很简单,但编出有质量的 程序就难了。 shaofeng 高亮 制作:raincatss 博客:http://raincatss.cublog.cn/ 第2页/共29页 我在这里不讨论过深的技术,我只想在一些容易让人忽略的东西上说一说,虽然这些东西可 能很细微,但如果你不注意这些细微之处的话,那么他将会极大的影响你的整个软件质量, 以及整个软件程的实施,所谓“千里之堤,毁于蚁穴”。 “细微之处见真功”,真正能体现一个程序的功底恰恰在这些细微之处。 这就是程序员的——编程修养。我总结了在用 C/C++语言(主要是 C语言)进行程序写作 上的三十二个“修养”,通过这些,你可以写出质量高的程序,同时也会让看你程序的人渍 渍称道,那些看过你程序的人一定会说:“这个人的编程修养不错”。 ———————————————————————— 01、版权和版本 02、缩进、空格、换行、空行、对齐 03、程序注释 04、函数的[in][out]参数 05、对系统调用的返回进行判断 06、if 语句对出错的处理 07、头文件中的#ifndef 08、在堆上分配内存 09、变量的初始化 10、h和 c文件的使用 11、出错信息的处理 12、常用函数和循环语句中的被计算量 13、函数名和变量名的命名 14、函数的传值和传指针 15、修改别人程序的修养 16、把相同或近乎相同的代码形成函数和宏 17、表达式中的括号 18、函数参数中的 const 19、函数的参数个数 20、函数的返回类型,不要省略 21、goto语句的使用 22、宏的使用 23、static的使用 24、函数中的代码尺寸 25、typedef的使用 26、为常量声明宏 27、不要为宏定义加分号 28、||和&&的语句执行顺序 29、尽量用 for而不是 while做循环 30、请 sizeof类型而不是变量 31、不要忽略Warning shaofeng 高亮 shaofeng 高亮 shaofeng 高亮 制作:raincatss 博客:http://raincatss.cublog.cn/ 第3页/共29页 32、书写 Debug版和 Release版的程序 ———————————————————————— 111 1 、版权和版本、版权和版本、版权和版本 、版权和版本 ——————— 好的程序员会给自己的每个函数,每个文件,都注上版权和版本。 对于 C/C++的文件,文件头应该有类似这样的注释: /************************************************************************ * * 文件名:network.c * * 文件描述:网络通讯函数集 * * 创建人: Hao Chen, 2003年 2月 3日 * * 版本号:1.0 * * 修改记录: * ************************************************************************/ 而对于函数来说,应该也有类似于这样的注释: /*================================================================ * * 函 数 名:XXX * * 参 数: * * type name [IN] : descripts * * 功能描述: * * .............. * * 返 回 值:成功 TRUE,失败 FALSE * * 抛出异常: * * 作 者:ChenHao 2003/4/2 * ================================================================*/ 制作:raincatss 博客:http://raincatss.cublog.cn/ 第4页/共29页 这样的描述可以让人对一个函数,一个文件有一个总体的认识,对代码的易读性和易维护性 有很大的好处。这是好的作品产生的开始。 222 2 、缩进、空格、换行、空行、对齐、缩进、空格、换行、空行、对齐、缩进、空格、换行、空行、对齐 、缩进、空格、换行、空行、对齐 ———————————————— i) 缩进应该是每个程序都会做的,只要学程序过程序就应该知道这个,但是我仍然看过不 缩进的程序,或是乱缩进的程序,如果你的公司还有写程序不缩进的程序员,请毫不犹豫的 开除他吧,并以破坏源码罪起诉他,还要他赔偿读过他程序的人的精神损失费。缩进,这是 不成文规矩,我再重提一下吧,一个缩进一般是一个 TAB键或是 4个空格。(最好用 4个空 格)<--由网友 vector_3d提醒改正 ii) 空格。空格能给程序代来什么损失吗?没有,有效的利用空格可以让你的程序读进来更 加赏心悦目。而不一堆表达式挤在一起。看看下面的代码: ha=(ha*128+*key++)%tabPtr->size; ha = ( ha * 128 + *key++ ) % tabPtr->size; 有空格和没有空格的感觉不一样吧。一般来说,语句中要在各个操作符间加空格,函数 调用时,要以各个参数间加空格。如下面这种加空格的和不加的: if ((hProc=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid))==NULL){ } if ( ( hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) ) == NULL ){ } iii) 换行。不要把语句都写在一行上,这样很不好。如: for(i=0;i'9')&&(a[i]<'a'||a[i]>'z')) break; 我拷,这种即无空格,又无换行的程序在写什么啊?加上空格和换行吧。 for ( i=0; i '9' ) && ( a[i] < 'a' || a[i] > 'z' ) ) { break; } } 好多了吧?有时候,函数参数多的时候,最好也换行,如: CreateProcess( NULL, cmdbuf, NULL, NULL, bInhH, dwCrtFlags, envbuf, 制作:raincatss 博客:http://raincatss.cublog.cn/ 第5页/共29页 NULL, &siStartInfo, &prInfo ); 条件语句也应该在必要时换行: if ( ch >= '0' || ch <= '9' || ch >= 'a' || ch <= 'z' || ch >= 'A' || ch <= 'Z' ) iv) 空行。不要不加空行,空行可以区分不同的程序块,程序块间,最好加上空行。如: HANDLE hProcess; PROCESS_T procInfo; /* open the process handle */ if((hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid)) == NULL) { return LSE_MISC_SYS; } memset(&procInfo, 0, sizeof(procInfo)); procInfo.idProc = pid; procInfo.hdProc = hProcess; procInfo.misc |= MSCAVA_PROC; return(0); v) 对齐。用 TAB键对齐你的一些变量的声明或注释,一样会让你的程序好看一些。如: typedef struct _pt_man_t_ { int numProc; /* Number of processes */ int maxProc; /* Max Number of processes */ int numEvnt; /* Number of events */ int maxEvnt; /* Max Number of events */ HANDLE* pHndEvnt; /* Array of events */ DWORD timeout; /* Time out interval */ HANDLE hPipe; /* Namedpipe */ TCHAR usr[MAXUSR];/* User name of the process */ int numMsg; /* Number of Message */ int Msg[MAXMSG];/* Space for intro process communicate */ } PT_MAN_T; 怎么样?感觉不错吧。 这里主要讲述了如果写出让人赏心悦目的代码,好看的代码会让人的心情愉快,读起代码也 就不累,工整、整洁的程序代码,通常更让人欢迎,也更让人称道。现在的硬盘空间这么大 , 不要让你的代码挤在一起,这样它们会抱怨你虐待它们的。好了,用“缩进、空格、换行、 空行、对齐”装饰你的代码吧,让他们从没有秩序的土匪中变成一排排整齐有秩序的正规部 制作:raincatss 博客:http://raincatss.cublog.cn/ 第6页/共29页 队吧。 333 3 、程序注释、程序注释、程序注释 、程序注释 —————— 养成写程序注释的习惯,这是每个程序员所必须要做的工作。我看过那种几千行,却居然没 有一行注释的程序。这就如同在公路上驾车却没有路标一样。用不了多久,连自己都不知道 自己的意图了,还要花上几倍的时间才看明白,这种浪费别人和自己的时间的人,是最为可 耻的人。 是的,你也许会说,你会写注释,真的吗?注释的书写也能看出一个程序员的功底。一般来 说你需要至少写这些地方的注释:文件的注释、函数的注释、变量的注释、算法的注释、功 能块的程序注释。主要就是记录你这段程序是干什么的?你的意图是什么?你这个变量是用 来做什么的?等等。 不要以为注释好写,有一些算法是很难说或写出来的,只能意会,我承认有这种情况的时候 , 但你也要写出来,正好可以训练一下自己的表达能力。而表达能力正是那种闷头搞技术的技 术人员最缺的,你有再高的技术,如果你表达能力不行,你的技术将不能得到充分的发挥。 因为,这是一个团队的时代。 好了,说几个注释的技术细节: i) 对于行注释(“//”)比块注释(“/* */”)要好的说法,我并不是很同意。因为一些老版本 的 C编译器并不支持行注释,所以为了你的程序的移植性,请你还是尽量使用块注释。 ii) 你也许会为块注释的不能嵌套而不爽,那么你可以用预编译来完成这个功能。使用“#if 0” 和“#endif”括起来的代码,将不被编译,而且还可以嵌套。 444 4 、函数的、函数的、函数的 、函数的 [in][out][in][out][in][out] [in][out] 参数参数参数 参数 ——————————— 我经常看到这样的程序: FuncName(char* str) { int len = strlen(str); ..... } char* GetUserName(struct user* pUser) { return pUser->name; } 不!请不要这样做。 shaofeng 高亮 shaofeng 高亮 shaofeng 高亮 制作:raincatss 博客:http://raincatss.cublog.cn/ 第7页/共29页 你应该先判断一下传进来的那个指针是不是为空。如果传进来的指针为空的话,那么,你的 一个大的系统就会因为这一个小的函数而崩溃。一种更好的技术是使用断言(assert),这里 我就不多说这些技术细节了。当然,如果是在 C++中,引用要比指针好得多,但你也需要对 各个参数进行检查。 写有参数的函数时,首要工作,就是要对传进来的所有参数进行合法性检查。而对于传出的 参数也应该进行检查,这个动作当然应该在函数的外部,也就是说,调用完一个函数后,应 该对其传出的值进行检查。 当然,检查会浪费一点时间,但为了整个系统不至于出现“非法操作”或是“Core Dump” 的系统级的错误,多花这点时间还是很值得的。 555 5 、对系统调用的返回进行判断、对系统调用的返回进行判断、对系统调用的返回进行判断 、对系统调用的返回进行判断 —————————————— 继续上一条,对于一些系统调用,比如打开文件,我经常看到,许多程序员对 fopen返回的 指针不做任何判断,就直接使用了。然后发现文件的内容怎么也读出不,或是怎么也写不进 去。还是判断一下吧: fp = fopen("log.txt", "a"); if ( fp == NULL ){ printf("Error: open file error\n"); return FALSE; } 其它还有许多啦,比如:socket返回的 socket号,malloc返回的内存。请对这些系统调用返 回的东西进行判断。 666 6 、、、 、 ififif if 语句对出错的处理语句对出错的处理语句对出错的处理 语句对出错的处理 ——————————— 我看见你说了,这有什么好说的。还是先看一段程序代码吧。 if ( ch >= '0' && ch <= '9' ){ /* 正常处理代码 */ }else{ /* 输出错误信息 */ printf("error ......\n"); return ( FALSE ); } 这种结构很不好,特别是如果“正常处理代码”很长时,对于这种情况,最好不要用 else。 shaofeng 高亮 shaofeng 高亮 制作:raincatss 博客:http://raincatss.cublog.cn/ 第8页/共29页 先判断错误,如: if ( ch < '0' || ch > '9' ){ /* 输出错误信息 */ printf("error ......\n"); return ( FALSE ); } /* 正常处理代码 */ ...... 这样的结构,不是很清楚吗?突出了错误的条件,让别人在使用你的函数的时候,第一眼就 能看到不合法的条件,于是就会更下意识的避免。 777 7 、头文件中的、头文件中的、头文件中的 、头文件中的 #ifndef#ifndef#ifndef #ifndef —————————— 千万不要忽略了头件的中的#ifndef,这是一个很关键的东西。比如你有两个 C文件,这两个 C文件都 include了同一个头文件。而编译时,这两个 C文件要一同编译成一个可运行文件, 于是问题来了,大量的声明冲突。 还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用, 你都要加上这个。一般格式是这样的: #ifndef <标识> #define <标识> ...... ...... #endif <标识>在理论上来说可以是自由命名的,但每个头文件的这个“标识”都应该是唯一的。 标识的命名规则一般是头文件名全大写,前后加下划线,并把文件名中的“.”也变成下划 线,如:stdio.h #ifndef _STDIO_H_ #define _STDIO_H_ ...... #endif (BTW:预编译有多很有用的功能。你会用预编译吗?) shaofeng 高亮 shaofeng 高亮 制作:raincatss 博客:http://raincatss.cublog.cn/ 第9页/共29页 888 8 、在堆上分配内存、在堆上分配内存、在堆上分配内存 、在堆上分配内存 ————————— 可能许多人对内存分配上的“栈 stack”和“堆 heap”还不是很明白。包括一些科班出身的 人也不明白这两个概念。我不想过多的说这两个东西。简单的来讲,stack上分配的内存系 统自动释放,heap上分配的内存,系统不释放,哪怕程序退出,那一块内存还是在那里。stack 一般是静态分配内存,heap上一般是动态分配内存。 由 malloc系统函数分配的内存就是从堆上分配内存。从堆上分配的内存一定要自己释放。 用 free释放,不然就是术语——“内存泄露”(或是“内存漏洞”)—— Memory Leak。于 是,系统的可分配内存会随malloc越来越少,直到系统崩溃。还是来看看“栈内存”和“堆 内存”的差别吧。 栈内存分配 ————— char* AllocStrFromStack() { char pstr[100]; return pstr; } 堆内存分配 ————— char* AllocStrFromHeap(int len) { char *pstr; if ( len <= 0 ) return NULL; return ( char* ) malloc( len ); } 对于第一个函数,那块 pstr的内存在函数返回时就被系统释放了。于是所返回的 char*什么 也没有。而对于第二个函数,是从堆上分配内存,所以哪怕是程序退出时,也不释放,所以 第二个函数的返回的内存没有问题,可以被使用。但一定要调用 free释放,不然就是Memory Leak! 在堆上分配内存很容易造成内存泄漏,这是 C/C++的最大的“克星”,如果你的程序要稳定, 那么就不要出现 Memory Leak。所以,我还是要在这里千叮咛万嘱付,在使用 malloc系统 函数(包括 calloc,realloc)时千万要小心。 制作:raincatss 博客:http://raincatss.cublog.cn/ 第10页/共29页 记得有一个 UNIX上的服务应用程序,大约有几百的 C文件编译而成,运行测试良好,等 使用时,每隔三个月系统就是 down一次,搞得许多人焦头烂额,查不出问题所在。只好, 每隔两个月人工手动重启系统一次。出现这种问题就是 Memery Leak在做怪了,在 C/C++ 中这种问题总是会发生,所以你一定要小心。一个 Rational的检测工作——Purify,可以帮 你测试你的程序有没有内存泄漏。 我保证,做过许多 C/C++的工程的程序员,都会对malloc或是 new有些感冒。当你什么时 候在使用 malloc和 new时,有一种轻度的紧张和惶恐的感觉时,你就具备了这方面的修养 了。 对于 malloc和 free的操作有以下规则: 1)配对使用,有一个malloc,就应该有一个 free。( C++中对应为 new和 delete) 2)尽量在同一层上使用,不要像上面那种,malloc在函数中,而 free在函数外。最好在同 一调用层上使用这两个函数。 3) malloc分配的内存一定要初始化。free后的指针一定要设置为 NULL。 注:虽然现在的操作系统(如:UNIX和Win2k/NT)都有进程内存跟踪机制,也就是如果 你有没有释放的内存,操作系统会帮你释放。但操作系统依然不会释放你程序中所有产生了 Memory Leak的内存,所以,最好还是你自己来做这个工作。(有的时候不知不觉就出现 Memory Leak了,而且在几百万行的代码中找无异于海底捞针,Rational有一个工具叫 Purify, 可能很好的帮你检查程序中的Memory Leak) 999 9 、变量的初始化、变量的初始化、变量的初始化 、变量的初始化 ———————— 接上一条,变量一定要被初始化再使用。C/C++编译器在这个方面不会像 JAVA一样帮你初 始化,这一切都需要你自己来,如果你使用了没有初始化的变量,结果未知。好的程序员从 来都会在使用变量前初始化变量的。如: 1) 对 malloc分配的内存进行memset清零操作。(可以使用 calloc 分配一块全零的内存) 2) 对一些栈上分配的 struct或数组进行初始化。(最好也是清零) 不过话又说回来了,初始化也会造成系统运行时间有一定的开销,所以,也不要对所有的变 量做初始化,这个也没有意义。好的程序员知道哪些变量需要初始化,哪些则不需要。如: 以下这种情况,则不需要。 char *pstr; /* 一个字符串 */ pstr = ( char* ) malloc( 50 ); if ( pstr == NULL ) exit(0); strcpy( pstr, "Hello Wrold" ); shaofeng 高亮 制作:raincatss 博客:http://raincatss.cublog.cn/ 第11页/共29页 但如果是下面一种情况,最好进行内存初始化。(指针是一个危险的东西,一定要初始化) char **pstr; /* 一个字符串数组 */ pstr = ( char** ) malloc( 50 ); if ( pstr == NULL ) exit(0); /* 让数组中的指针都指向 NULL */ memset( pstr, 0, 50*sizeof(char*) ); 而对于全局变量,和静态变量,一定要声明时就初始化。因为你不知道它第一次会在哪里被 使用。所以使用前初始这些变量是比较不现实的,一定要在声明时就初始化它们。如: Links *plnk = NULL; /* 对于全局变量 plnk初始化为NULL */ 101010 10 、、、 、 hhh h 和和和 和 ccc c 文件的使用文件的使用文件的使用 文件的使用 ————————— H文件和 C文件怎么用呢?一般来说,H文件中是 declare(声明),C文件中是 define(定 义)。因为 C文件要编译成库文件(Windows下是.obj/.lib,UNIX下是.o/.a),如果别人要使 用你的函数,那么就要引用你的 H文件,所以,H文件中一般是变量、宏定义、枚举、结 构和函数接口的声明,就像一个接口说明文件一样。而 C文件则是实现细节。 H文件和 C文件最大的用处就是声明和实现分开。这个特性应该是公认的了,但我仍然看 到有些人喜欢把函数写在 H文件中,这种习惯很不好。(如果是 C++话,对于其模板函数, 在 VC中只有把实现和声明都写在一个文件中,因为 VC不支持 export关键字)。而且,如 果在 H文件中写上函数的实现,你还得在 makefile中把头文件的依赖关系也加上去,这个 就会让你的 makefile很不规范。 最后,有一个最需要注意的地方就是:带初始化的全局变量不要放在 H文件中! 例如有一个处理错误信息的结构: char* errmsg[] = { /* 0 */ "No error", /* 1 */ "Open file error", /* 2 */ "Failed in sending/receiving a message", /* 3 */ "Bad arguments", /* 4 */ "Memeroy is not enough", /* 5 */ "Service is down; try later", /* 6 */ "Unknow information", /* 7 */ "A socket operation has failed", /* 8 */ "Permission denied", /* 9 */ "Bad configuration file format", /* 10 */ "Communication time out", ...... 制作:raincatss 博客:http://raincatss.cublog.cn/ 第12页/共29页 ...... }; 请不要把这个东西放在头文件中,因为如果你的这个头文件被 5个函数库(.lib或是.a)所 用到,于是他就被链接在这 5个.lib或.a中,而如果你的一个程序用到了这 5个函数库中的 函数,并且这些函数都用到了这个出错信息数组。那么这份信息将有 5个副本存在于你的执 行文件中。如果你的这个 errmsg很大的话,而且你用到的函数库更多的话,你的执行文件 也会变得很大。 正确的写法应该把它写到 C文件中,然后在各个需要用到 errmsg的 C文件头上加上 extern char* errmsg[]; 的外部声明,让编译器在链接时才去管他,这样一来,就只会有一个 errmsg 存在于执行文件中,而且,这样做很利于封装。 我曾遇到过的最疯狂的事,就是在我的目标文件中,这个 errmsg一共有 112个副本,执行 文件有 8M左右。当我把 errmsg放到 C文件中,并为一千多个 C文件加上了 extern的声明 后,所有的函数库文件尺寸都下降了 20%左右,而我的执行文件只有 5M了。一下子少了 3M啊。 [ 备注 ] ————— 有朋友对我说,这个只是一个特例,因为,如果 errmsg在执行文件中存在多个副本时,可 以加快程序运行速度,理由是 errmsg的多个复本会让系统的内存换页降低,达到效率提升。 像我们这里所说的 errmsg只有一份,当某函数要用 errmsg时,如果内存隔得比较远,会产 生换页,反而效率不高。 这个说法不无道理,但是一般而言,对于一个比较大的系统,errmsg是比较大的,所以产生 副本导致执行文件尺寸变大,不仅增加了系统装载时间,也会让一个程序在内存中占更多的 页面。而对于 errmsg这样数据,一般来说,在系统运行时不会经常用到,所以还是产生的 内存换页也就不算频繁。权衡之下,还是只有一份 errmsg的效率高。即便是像 logmsg这样 频繁使用的的数据,操作系统的内存调度算法会让这样的频繁使用的页面常驻于内存,所以 也就不会出现内存换页问题了。 111111 11 、出错信息的处理、出错信息的处理、出错信息的处理 、出错信息的处理 ————————— 你会处理出错信息吗?哦,它并不是简单的输出。看下面的示例: if ( p == NULL ){ printf ( "ERR: The pointer is NULL\n" ); } 告别学生时代的编程吧。这种编程很不利于维护和管理,出错信息或是提示信息,应该统一 处理,而不是像上面这样,写成一个“硬编码”。第 10条对这方面的处理做了一部分说明。 如果要管理错误信息,那就要有以下的处理: 制作:raincatss 博客:http://raincatss.cublog.cn/ 第13页/共29页 /* 声明出错代码 */ #define ERR_NO_ERROR 0 /* No error */ #define ERR_OPEN_FILE 1 /* Open file error */ #define ERR_SEND_MESG 2 /* sending a message error */ #define ERR_BAD_ARGS 3 /* Bad arguments */ #define ERR_MEM_NONE 4 /* Memeroy is not enough */ #define ERR_SERV_DOWN 5 /* Service down try later */ #define ERR_UNKNOW_INFO 6 /* Unknow information */ #define ERR_SOCKET_ERR 7 /* Socket operation failed */ #define ERR_PERMISSION 8 /* Permission denied */ #define ERR_BAD_FORMAT 9 /* Bad configuration file */ #define ERR_TIME_OUT 10 /* Communication time out */ /* 声明出错信息 */ char* errmsg[] = { /* 0 */ "No error", /* 1 */ "Open file error", /* 2 */ "Failed in sending/receiving a message", /* 3 */ "Bad arguments", /* 4 */ "Memeroy is not enough", /* 5 */ "Service is down; try later", /* 6 */ "Unknow information", /* 7 */ "A socket operation has failed", /* 8 */ "Permission denied", /* 9 */ "Bad configuration file format", /* 10 */ "Communication time out", }; /* 声明错误代码全局变量 */ long errno = 0; /* 打印出错信息函数 */ void perror( char* info) { if ( info ){ printf("%s: %s\n", info, errmsg[errno] ); return; } printf("Error: %s\n", errmsg[errno] ); } 这个基本上是 ANSI的错误处理实现细节了,于是当你程序中有错误时你就可以这样处理: bool CheckPermission( char* userName ) 制作:raincatss 博客:http://raincatss.cublog.cn/ 第14页/共29页 { if ( strcpy(userName, "root") != 0 ){ errno = ERR_PERMISSION_DENIED; return (FALSE); } ... } main() { ... if (! CheckPermission( username ) ){ perror("main()"); } ... } 一个即有共性,也有个性的错误信息处理,这样做有利同种错误出一样的信息,统一用户界 面,而不会因为文件打开失败,A程序员出一个信息,B程序员又出一个信息。而且这样做 , 非常容易维护。代码也易读。 当然,物极必反,也没有必要把所有的输出都放到 errmsg中,抽取比较重要的出错信息或 是提示信息是其关键,但即使这样,这也包括了大多数的信息。 121212 12 、常用函数和循环语句中的被计算量、常用函数和循环语句中的被计算量、常用函数和循环语句中的被计算量 、常用函数和循环语句中的被计算量 ————————————————— 看一下下面这个例子: for( i=0; i<1000; i++ ){ GetLocalHostName( hostname ); ... } GetLocalHostName 的意思是取得当前计算机名,在循环体中,它会被调用 1000次啊。这是 多么的没有效率的事啊。应该把这个函数拿到循环体外,这样只调用一次,效率得到了很大 的提高。虽然,我们的编译器会进行优化,会把循环体内的不变的东西拿到循环外面,但是 , 你相信所有编译器会知道哪些是不变的吗?我觉得编译器不可靠。最好还是自己动手吧。 同样,对于常用函数中的不变量,如: GetLocalHostName(char* name) { char funcName[] = "GetLocalHostName"; sys_log( "%s begin......", funcName ); 制作:raincatss 博客:http://raincatss.cublog.cn/ 第15页/共29页 ... sys_log( "%s end......", funcName ); } 如果这是一个经常调用的函数,每次调用时都要对 funcName进行分配内存,这个开销很大 啊。把这个变量声明成 static吧,当函数再次被调用时,就会省去了分配内存的开销,执行 效率也很好。 131313 13 、函数名和变量名的命名、函数名和变量名的命名、函数名和变量名的命名 、函数名和变量名的命名 ———————————— 我看到许多程序对变量名和函数名的取名很草率,特别是变量名,什么 a,b,c,aa,bb,cc,还有 什么 flag1,flag2, cnt1, cnt2,这同样是一种没有“修养”的行为。即便加上好的注释。好的变 量名或是函数名,我认为应该有以下的规则: 1) 直观并且可以拼读,可望文知意,不必“解码”。 2) 名字的长度应该即要最短的长度,也要能最大限度的表达其含义。 3) 不要全部大写,也不要全部小写,应该大小写都有,如:GetLocalHostName 或是 UserAccount。 4) 可以简写,但简写得要让人明白,如:ErrorCode -> ErrCode, ServerListener -> ServLisner,UserAccount -> UsrAcct 等。 5) 为了避免全局函数和变量名字冲突,可以加上一些前缀,一般以模块简称做为前缀。 6) 全局变量统一加一个前缀或是后缀,让人一看到这个变量就知道是全局的。 7) 用匈牙利命名法命名函数参数,局部变量。但还是要坚持“望文生意”的 原则 组织架构调整原则组织架构设计原则组织架构设置原则财政预算编制原则问卷调查设计原则 。 8) 与 标准 excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载 库(如:STL)或开发库(如:MFC)的命名风格保持一致。 141414 14 、函数的传值和传指针、函数的传值和传指针、函数的传值和传指针 、函数的传值和传指针 ———————————— 向函数传参数时,一般而言,传入非 const的指针时,就表示,在函数中要修改这个指针把 指内存中的数据。如果是传值,那么无论在函数内部怎么修改这个值,也影响不到传过来的 值,因为传值是只内存拷贝。 什么?你说这个特性你明白了,好吧,让我们看看下面的这个例程: void GetVersion(char* pStr) { pStr = malloc(10); strcpy ( pStr, "2.0" ); } main() { 制作:raincatss 博客:http://raincatss.cublog.cn/ 第16页/共29页 char* ver = NULL; GetVersion ( ver ); ... ... free ( ver ); } 我保证,类似这样的问题是一个新手最容易犯的错误。程序中妄图通过函数GetVersion给指 针 ver分配空间,但这种方法根本没有什么作用,原因就是——这是传值,不是传指针。你 或许会和我争论,我分明传的时指针啊?再仔细看看,其实,你传的是指针其实是在传值。 151515 15 、修改别人程序的修养、修改别人程序的修养、修改别人程序的修养 、修改别人程序的修养 ——————————— 当你维护别人的程序时,请不要非常主观臆断的把已有的程序删除或是修改。我经常看到有 的程序员直接在别人的程序上修改表达式或是语句。修改别人的程序时,请不要删除别人的 程序,如果你觉得别人的程序有所不妥,请注释掉,然后添加自己的处理程序,必竟,你不 可能 100%的知道别人的意图,所以为了可以恢复,请不依赖于 CVS或是 SourceSafe这种 版本控制软件,还是要在源码上给别人看到你修改程序的意图和步骤。这是程序维护时,一 个有修养的程序员所应该做的。 如下所示,这就是一种比较好的修改方法: /* * ----- commented by haoel 2003/04/12 ------ * * char* p = ( char* ) malloc( 10 ); * memset( p, 0, 10 ); */ /* ------ Added by haoel 2003/04/12 ----- */ char* p = ( char* )calloc( 10, sizeof char ); /* ---------------------------------------- */ ... 当然,这种方法是在软件维护时使用的,这样的方法,可以让再维护的人很容易知道以前的 代码更改的动作和意图,而且这也是对原作者的一种尊敬。 以“注释 — 添加”方式修改别人的程序,要好于直接删除别人的程序。 161616 16 、把相同或近乎相同的代码形成函数和宏、把相同或近乎相同的代码形成函数和宏、把相同或近乎相同的代码形成函数和宏 、把相同或近乎相同的代码形成函数和宏 ————————————————————— 有人说,最好的程序员,就是最喜欢“偷懒”的程序,其中不无道理。 制作:raincatss 博客:http://raincatss.cublog.cn/ 第17页/共29页 如果你有一些程序的代码片段很相似,或直接就是一样的,请把他们放在一个函数中。而如 果这段代码不多,而且会被经常使用,你还想避免函数调用的开销,那么就把他写成宏吧。 千万不要让同一份代码或是功能相似的代码在多个地方存在,不然如果功能一变,你就要修 改好几处地方,这种会给维护带来巨大的麻烦,所以,做到“一改百改”,还是要形成函数 或是宏。 171717 17 、表达式中的括号、表达式中的括号、表达式中的括号 、表达式中的括号 ————————— 如果一个比较复杂的表达式中,你并不是很清楚各个操作符的忧先级,即使是你很清楚优先 级,也请加上括号,不然,别人或是自己下一次读程序时,一不小心就看走眼理解错了,为 了避免这种“误解”,还有让自己的程序更为清淅,还是加上括号吧。 比如,对一个结构的成员取地址: GetUserAge( &( UserInfo->age ) ); 虽然,&UserInfo->age中,->操作符的优先级最高,但加上一个括号,会让人一眼就看明白 你的代码是什么意思。 再比如,一个很长的条件判断: if ( ( ch[0] >= '0' || ch[0] <= '9' ) && ( ch[1] >= 'a' || ch[1] <= 'z' ) && ( ch[2] >= 'A' || ch[2] <= 'Z' ) ) 括号,再加上空格和换行,你的代码是不是很容易读懂了? 181818 18 、函数参数中的、函数参数中的、函数参数中的 、函数参数中的 constconstconst const ——————————— 对于一些函数中的指针参数,如果在函数中只读,请将其用 const修饰,这样,别人一读到 你的函数接口时,就会知道你的意图是这个参数是[in],如果没有 const时,参数表示[in/out], 注意函数接口中的 const使用,利于程序的维护和避免犯一些错误。 虽然,const修饰的指针,如:const char* p,在 C中一点用也没有,因为不管你的声明是不 是 const,指针的内容照样能改,因为编译器会强制转换,但是加上这样一个说明,有利于 程序的阅读和编译。因为在 C中,修改一个 const指针所指向的内存时,会报一个Warning。 这会引起程序员的注意。 C++中对 const定义的就很严格了,所以 C++中要多多的使用 const,const的成员函数,const 制作:raincatss 博客:http://raincatss.cublog.cn/ 第18页/共29页 的变量,这样会对让你的代码和你的程序更加完整和易读。(关于 C++的 const我就不多说 了) 191919 19 、函数的参数个数(多了请用结构)、函数的参数个数(多了请用结构)、函数的参数个数(多了请用结构) 、函数的参数个数(多了请用结构) ————————————————— 函数的参数个数最好不要太多,一般来说 6个左右就可以了,众多的函数参数会让读代码的 人一眼看上去就很头昏,而且也不利于维护。如果参数众多,还请使用结构来传递参数。这 样做有利于数据的封装和程序的简洁性。 也利于使用函数的人,因为如果你的函数个数很多,比如 12个,调用者很容易搞错参数的 顺序和个数,而使用结构 struct来传递参数,就可以不管参数的顺序。 而且,函数很容易被修改,如果需要给函数增加参数,不需要更改函数接口,只需更改结构 体和函数内部处理,而对于调用函数的程序来说,这个动作是透明的。 202020 20 、函数的返回类型,不要省略、函数的返回类型,不要省略、函数的返回类型,不要省略 、函数的返回类型,不要省略 —————————————— 我看到很多程序写函数时,在函数的返回类型方面不太注意。如果一个函数没有返回值,也 请在函数前面加上 void的修饰。而有的程序员偷懒,在返回 int的函数则什么不修饰(因为 如果不修饰,则默认返回 int),这种习惯很不好,还是为了原代码的易读性,加上 int吧。 所以函数的返回值类型,请不要省略。 另外,对于 void的函数,我们往往会忘了 return,由于某些 C/C++的编译器比较敏感,会报 一些警告,所以即使是 void的函数,我们在内部最好也要加上 return的语句,这有助于代 码的编译。 212121 21 、、、 、 gotogotogoto goto 语句的使用语句的使用语句的使用 语句的使用 ————————— N年前,软件开发的一代宗师——迪杰斯特拉(Dijkstra)说过:“goto statment is harmful !!”, 并建议取消 goto语句。因为 goto语句不利于程序代码的维护性。 这里我也强烈建议不要使用 goto语句,除非下面的这种情况: #define FREE(p) if(p) { \ free(p); \ p = NULL; \ 制作:raincatss 博客:http://raincatss.cublog.cn/ 第19页/共29页 } main() { char *fname=NULL, *lname=NULL, *mname=NULL; fname = ( char* ) calloc ( 20, sizeof(char) ); if ( fname == NULL ){ goto ErrHandle; } lname = ( char* ) calloc ( 20, sizeof(char) ); if ( lname == NULL ){ goto ErrHandle; } mname = ( char* ) calloc ( 20, sizeof(char) ); if ( mname == NULL ){ goto ErrHandle; } ...... ErrHandle: FREE(fname); FREE(lname); FREE(mname); ReportError(ERR_NO_MEMOEY); } 也只有在这种情况下,goto语句会让你的程序更易读,更容易维护。(在用嵌 C来对数据库 设置游标操作时,或是对数据库建立链接时,也会遇到这种结构) 222222 22 、宏的使用、宏的使用、宏的使用 、宏的使用 —————— 很多程序员不知道 C中的“宏”到底是什么意思?特别是当宏有参数的时候,经常把宏和 函数混淆。我想在这里我还是先讲讲“宏”,宏只是一种定义,他定义了一个语句块,当程 序编译时,编译器首先要执行一个“替换”源程序的动作,把宏引用的地方替换成宏定义的 语句块,就像文本文件替换一样。这个动作术语叫“宏的展开” 使用宏是比较“危险”的,因为你不知道宏展开后会是什么一个样子。例如下面这个宏: #define MAX(a, b) a>b?a:b 当我们这样使用宏时,没有什么问题: MAX( num1, num2 ); 因为宏展开后变成 num1>num2?num1:num2;。 但是,如果是这样调用的,MAX( 17+32, 25+21 ); 呢,编译时 制作:raincatss 博客:http://raincatss.cublog.cn/ 第20页/共29页 出现错误,原因是,宏展开后变成:17+32>25+21?17+32:25+21,哇,这是什么啊? 所以,宏在使用时,参数一定要加上括号,上述的那个例子改成如下所示就能解决问题了。 #define MAX( (a), (b) ) (a)>(b)?(a):(b) 即使是这样,也不这
本文档为【编程修养】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_665450
暂无简介~
格式:pdf
大小:373KB
软件:PDF阅读器
页数:0
分类:互联网
上传时间:2013-11-19
浏览量:2