找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 4815|回复: 0

转帖【编码原则十日谈】

[复制链接]
发表于 2009-7-15 14:54:40 | 显示全部楼层 |阅读模式
1.     前言
1.1.  为什么写这篇文章
编写本文档的最初目的,是为研究院平台研发一部制定一套C++编码规范,以提高编码质量,进而提高软件产品的质量。在下笔之前本人一直在思考,为什么以前也有编码规范,但一直没有很好的效果呢?其实制定编码规范容易,执行编码规范难,难在要先让大家从心里接受编码规范,彻底理解编码规范的重要性。正是因为这个原因,在本文中,并没有真正灌输具体的编码规范,而是从根本的角度分析编码规范的必要性,同时定义什么是真正的高手,以及如何写好代码等。
在本文档中,对编码过程,提出了十条原则,希望读者读完本文之后,分十天,每天理解一条原则。真正的规范,不是写在纸上的,而是应该留在心里面。
本文档虽名为编码原则,其实更重要的是倡导一种编码的艺术,所以本文档的目标读者可以是所有的开发工程师,而不仅仅是C++开发工程师。
1.1.  为什么要有编码原则/规范
认为原则或者规范是用来约束人的想法,是完全错误的。与之相反,原则指导人们如何做事会感到身心愉悦,从而使事情变得简单,使事物趋向完美。古语说:“不以规矩,不成方圆”,并不是说没有规矩,就不让人去做方圆,就做不出方圆,而是说如果有了规矩,可以很轻松、方便地做出方圆。编码原则是也是所有原则或者规矩的一种,没有编码原则,显然也可以编码,但有了编码原则,就会让编码的过程变得轻松、简单,会让代码看上去赏心悦目,会提高编码的效率,更会提高产品的稳定性、可靠性和可维护性。
其实规范或者原则,在对个体的行为做了一些约束的同时,实际上建立了整体的一个秩序和稳定性,而整体的秩序和稳定性又反过来影响个体,使个体的效率更高。非常简单的一个例子就是路面上的红绿灯,有了红灯的限制之后,虽然车会暂时停一下,但整个路面的交通变得有秩序,计算下来每辆车的车速其实都在提高。这就是规范和原则带来的好处。
现代软件开发,都是由团队合作完成的,一个人基本上不太可能完成一个中等规模以上的软件。如果一个软件是由很多人共同参与完成,但是代码看上去就好像是同一个人写的,那几乎可以确定,这个软件绝对是一个优秀的软件,而且注定会载入史册。这个团队也可以面对任何挑战,百战百胜。
1.2.  编码原则的两条指导思想
要编写优秀的代码,其实很简单。众多原则可以概括成两条指导思想:
l  客户第一
l  勿以恶小而为之,勿以善小而不为
如果能充分理解这两句话,后面的内容其实就没有必要看下去了。当你某一天已经忘掉这篇文档,但编写出来的代码仍然美观,程序仍然稳定,别人在看你代码的时候不会抱怨的时候,说明你已经完全理解这两句话了。
本小节会对这两条指导思想做一个详细的说明。
1.2.1.   客户第一
客户第一是最重要的。
客户第一,简单的四个字里,蕴藏着大智慧。做到客户第一,让客户满意;客户满意了,自己当然会得到更多。这其实就是预先取之,必先予之的道理。
从广义上讲,任何自己以外的人,都可以算作是客户。从编码的角度来看,以下人员都可以称之为客户:
l  调用你模块的人
l  可能阅读你代码的人
l  可能拷贝你代码的人
l  可能修改你代码的人
l  测试人员
l  ……
从这个角度上看,在一个团队里,每个人都是你的客户,而你同时也是每个人的客户,任何人都不可能独善其身。所以在编写代码的时候,心里一定不能只想着仅完成自己的工作,更重要的是要考虑客户使用你的产品(代码)的成本。很简单的道理,如果你不考虑客户第一,那么别人在用你的代码时就会迷惘,就必须要花费更多的时间,同时也会经常过来问你,这样你也浪费了很多时间,计算下来大家的时间都浪费了。如果每个人都能坚持客户第一,这就是人人为我,我为人人的和谐团队。
从编码上讲,客户第一,就是要做到让上述的客户在使用你的代码时心情愉悦,让他们可以无障碍地使用你的代码。如何做到这一点?只要每个人都遵守编码的规范和原则。
1.2.2.   勿以恶小而为之,勿以善小而不为
很多事情,之所以向不好的方向发展,可能都是起源于某个时刻一个很小的恶念,这种例子举不胜举。交通事故的发生,往往是因为开车前喝了点小酒、遇到马上要变红灯的时候急于冲过去、甚至可能仅仅是因为在不该停车的地方停了一下,导致后面的车撞过来等等。
在一个项目里,写代码的时候,因为偶尔的偷懒而导致严重后果的惨痛经历,相信很多人都有过,例如在分配内存的时候,认为肯定会分配成功,就懒得去检查,结果某一天内存分配失败了,程序就崩溃了。再小的恶,也可能导致致命的问题。在编码的时候,如果脑子里冒出了想偷懒的想法,那一定不要偷懒,因为现在偷懒了一下,感觉少写了几行代码,好像省了点时间,以后可能要花费百倍的时间来补救,得不偿失。有时候有些不好的代码运行效率不高,这时候也不能认为这么点性能损失无所谓,因为很可能这个代码会经常被别人调用到,一点点的性能损失乘上一个调用次数,可能就是一个很大的性能问题了。
经常有人在有些时候,脑子里会闪过这样的观点:“何必这么迂腐?”,然后就做出了违背原则的事情。岂不知,这正是脑子中一闪而过的恶念。晚上过马路的时候,遇见红灯而路上并没有车,很多人就会想:“为啥这么迂腐?既然没车,我就过去好了”,很有可能你在过去的时候,正好一辆车过来,就出事故了。遵守原则的人是值得尊敬的,绝不是迂腐。
一闪而过的善念,如果做了,可能会给别人带来极大的便利。这个案例在《聊斋》里面最多了(聊斋里有很多由于一念之善,放生了一只小动物,后来受用无穷的故事,^_^)。哪怕是写完程序顺手按一下ALT+F8(Visual Studio中的格式化代码的快捷键),把代码弄得整齐一些,以后有人看你的代码的时候也会比较舒服一些。代码写的是否优美,对于编译器和编译之后运行来说,或许没多大影响,但这个虽然是小善,如果做了,就会对整个软件产生正面的影响。
举手之劳可以做得更好的事情,一定要抢着做,善莫大焉。
积小恶为大恶,积小善为大善,勿以恶小而为之,勿以善小而不为,与各位读者共勉。
1.3.  高手的定义和高手追求的目标
在各行各业里,高手总是让人尊敬,人人都希望成为高手,很多人都会很崇拜高手。但很多人对高手并没有一个正确的定义,正是因为没有理解什么是真正的高手,导致成为高手的路上走了很多冤枉路,崇拜错了对象。
这里的高手,不仅指的是软件上的高手,而是指任何一个行业中的高手,例如武林高手,烹饪高手,甚至是杀猪的,扫地的高手等等。三百六十行,行行出状元。
人们一般会认为,高手就是在某个行业里出类拔萃的人,他们拥有一般人所不具备的特殊技能,可以很轻松地完成某项任务。这个观点有一定道理,但只是一个并不算太重要的条件。仅仅满足这个条件的人,是属于武侠小说中常见的那种寻常好手,例如山西彭家五虎断门刀,巨鲸帮,海沙帮之类的,虽然比整个武林中大多数人要厉害,但完全称不上高手。某个行业最厉害的人,不是他的技巧最厉害,肯定是别的方面。
寻常好手,追求的目标是术,是各种技巧、方法;高手追求的是道,是各种内在的规律和原则,或者反过来说因为追求的是道,所以成了高手。明道优术,道为术之灵,术为道之体;以道统术,以术得道。如果理解了某个行业的道,自然精通这个行业的术。所以某个行业中的高手,就是明白了这个行业的道的高人。他们行事看似随意,其实里面自有分寸,举手投足无不恰到好处。如果还是不清楚什么是高手,可以回想一下以前上学时候,周围是不是有些人,平常看上去也并不是很用功学习,但是成绩确是非常的好,学什么都特别快,而且对知识的理解超过一般人,绝不是靠死记硬背。仔细回想一下他们平常是如何学习的,或许对理解什么是高手有一定帮助。再举一个例子,数学、物理等的公式,如果硬记是非常困难的,但是如果自己去推导一遍,完全理解这些公式是怎么来的,那么这些公式就会留在脑子里了,根本不需要专门去记。
金庸的《射雕英雄传》第十二章<亢龙有悔>里面有一段话,很能说明问题:洪七公品味之精,世间稀有,深知真正的烹调高手,愈是在最平常的菜肴之中,愈能显出奇妙功夫,这道理与武学一般,能在平淡之中现神奇,才说得上是大宗匠的手段。对高手来说,任何花哨的东西都是多余的,都是应该剔除掉的。
回到软件上来,软件开发从技术上讲,其实并没有太多有技术含量的东西,更多的只是设计和编码。当各种编程语言,各种技巧掌握到一定程度之后,就应该有更高的要求。我们可以看到很多真正的高手,无论学什么东西,总是很快就可以理解,掌握,精通。一看就会,一会就精,这已经是达到了某种境界。他们不会刻意地去学习什么,但很多道理自然而然的就明白了,这就是因为他们已经接近了大道了,例如周伯通口中的王重阳就是这样的人。而且其实各行各业其实有很多相通之处,真正的高手无论做什么都会比别人成功。
某种技术,学习怎么使用并不重要,重要的是你要理解他为什么要这么设计。只要你理解了为什么要这样做,怎么使用这还需要专门花功夫去学吗?
高手从不崇拜任何人,但高手和高手之间惺惺相惜,互相尊敬,互相景仰,会互相探讨,一起悟道,共同达到更高的层次。这里的高手,就是古文中经常出现的一类人——圣人。
这一小节的目的,是希望读者明白什么是高手,从而知道自己应该怎么做,应该有什么样的追求目标,最终也成为高手。
最后,不要盲目崇拜所谓的高手,与其崇拜一个子虚乌有的高手,不如看一看周围,说不定在你边上就有很多真正的高手,你完全可以经常和他们切磋、悟道,一起提高。
1.4.  程序员的美学修养
美,指的是内在的美,并不是表面是否华丽的美。
对道的追求,从某种意义上说也是对美的一种追求。美好的东西,总是使人有一种亲近的感觉,让人不由自主地喜欢它。编码的美,简单的说体现在代码是令人赏心悦目,是否有很好的可维护性、可移植性和可重用性,编译的速度是否比较快,运行是否稳定,运行速度是否比较快,等等。
我们在编写代码,或者阅读代码的时候,看到乱七八糟的代码,总是会觉得很烦躁,如果编译代码要很长时间,更是让人觉得软件开发是一件非常枯燥的事情。反过来,如果代码整整齐齐,有良好的命名规范,逻辑清晰,并且编译速度还很快,则会认为软件开发是一件轻松愉快的事情。这就是人们先天的,追求美好事物,摒弃丑恶事物的习性。
有了正确的审美观,有助于平常的工作和生活中发现和创造美好的东西,而发现或创造美好东西的过程,反过来又有助于提高我们的审美观,这是一个正向反馈的过程。不懂美的人,无论做什么事情,都不可能做到优秀。所以我们平常应该多注意观察周围的美好事物,提高我们的审美水平,如果能这样,不仅编码水平会提高很多,同时更会发现周围其实充满了欢乐,世界非常的美好。
要提高审美水平,其实也很简单。有时候不要太执着于技术,而是应该亲近大自然,去享受大自然里的美。如果能和大自然融为一体,那你会突然发现,可能原来感觉很难的技术问题,已经迎刃而解了。总之,只要有一个open的心态,你会发现很多就在身边的美好的东西。
1.5.  编码的十条原则
Stephen Hawkin说:“一个数学公式可以吓跑一半读者”。原则太多,也会吓跑原则的读者。现在的法律,让人眼花缭乱,大多数人都不知道里面的内容,汉高祖约法三章,简单明了,人人都知道。阿里巴巴的价值观是“六脉神剑”,大家能记住并且贯彻,如果是“降龙十八掌”甚至是“少林七十二绝技”,那一定没多少人能记住。因此本文只列出最常用的十条编码原则,只要在编码的时候坚持这些原则,一定能写好代码。
这是条编码原则分别为:
l  好的软件是开发出来的,不是测出来的
l  使用别的库之前,要先理解再用
l  写代码时,一定要想着别人
l  代码是用脑子写的,而不是用手
l  任何警告可能都是致命的
l  使用和平台相同的编码规范
l  有怪异的BUG?相信科学
l  缩写要慎用,宁可写全称,也不要有歧义
l  代码不可太长,函数不可有太多的语句
l  在合适的地方加上空行和注释
这十条原则,每天理解一条,十天之后,相信大家的代码质量会上升很多。
在接下来的章节里,将对这十条原则进行一些说明。
2.     第一天:好的软件是开发出来的,不是测出来的
第一天的目标,是要让读者理解到,好软件是开发出来的,不是测出来的。之所以把这一条原则放在最重要的地方,是要让编写代码的人有一个正确的理念和心态。
有些开发人员在编码的时候会这么想:“反正到时候软件要测试过,现在就随便写写算了”,这个观点是极端错误的,也是完全违背前面说过的两条指导思想的,是在作恶。一款软件是否成熟稳定可靠,很大程度上取决与开发阶段工作质量的高低,测试阶段是软件发布之前的最后一道关口,只能做一些不久工作,并不是所有问题通过测试都能解决掉的。
在项目中,我们往往会看到,有些人BUG非常的多,而且很多BUG还不容易重现,结果只好经常加班加点的来解决问题。但是很不幸,有时候解决了一个BUG之后,有创造了更多的BUG。但有些人BUG就比较少,而且即使有BUG,也能很快的知道是什么地方引起的,很快就可以定位并且一劳永逸地修复掉,他们每天很轻松,也不需要加班。是什么原因导致两种截然不同的现象出现呢?
如果打开这两类人编写的代码来看的话,原因就一目了然了。通过阅读他们的代码,你会发现,第一类人的代码一定是比较混乱的,结构不合理,逻辑不清晰,而第二类人的代码,一定是整齐划一,井然有序的,里面逻辑也很清晰。这样的代码,即使不小心出现了BUG,也一定可以很快找出问题所在的。
一个软件从需求到设计到编码到测试到发布,实际上是一个完整的流程,里面的每个步骤都是很重要的。一条优秀的流水线,每个环节都能从上一个环节拿到几乎完美的半产品,同时也提供一个几乎完美的半成品给下一个环节。这样当整个流程完成的时候,出来的就是一个完美的产品。所以说作为程序员,做出一个软件交到测试手中的时候,应该基本上没什么BUG,所有想到的问题应该在编码阶段全部解决掉,测试的作用只是帮助我们找到一些没有考虑到的情况,模拟不同用户可能的各种操作,发现一些大家事先不知道的问题。
3.     第二天:使用别的库之前,要先理解再用
第二天的目标,是希望读者学习到如何使用别的库的方法。别的库,从广义上讲,只要不是自己编写的代码,都可以称之为别的库,包括第三方的库,从网上下载的代码片段,别的同事编写的代码等。在本章里面,为了方便起见,上面所列举的内容,通称为第三方库。
3.1.  为什么要先理解再用
《孙子·谋攻篇》中说:“知己知彼,百战不殆;不知彼而知己,一胜一负;不知彼,不知己,每战必殆。”
在编码阶段,程序员是己,使用到的第三方库是彼。如果既了解自己的能力,又理解第三方库的实现原理,开发的软件一定会是非常顺利的,可以保质保量按时完成;如果只了解自己的能力,但不理解第三方库的时候就去使用,那软件能否保质保量按时完成,成败就要看运气了,几率各是一半;如果既不了解自己,又不理解第三方库就去使用,项目一定失败,这样的人万万不可在团队中出现。
在使用一个第三方库之前,一定要先理解它。任何一个东西,你只要理解了,就能驾驭它,骑马、开车、做饭、杀猪,各行各业都是如此。在项目开发的过程中,花点时间去理解一个你要用到的第三方库,并不会浪费整体的项目时间,因为你花了时间去了解它,后面用到它的时候会比较顺利,就能把时间省回来,非常的划算。你了解了这个库,你就可以驾驭它,就可以少了很多查资料,沟通的成本。这就是磨刀不误砍柴工的道理。
经常会有这样的情况,某人的代码运行出错了,或者达不到预期,花了很多的时间查找之后发现,原来是调用某个库的时候没有正确调用,这就是做到知己知彼导致的严重问题,这种情况是一定要在项目中避免发生的。
先理解再用,最直接的方法就是遇到问题之后自己通过跟踪代码去找到答案,通过跟踪代码,比看任何文档,请教任何人效果都好。跟踪代码,可以按照代码作者的思路,一步一步理解里面的思想,可以提高自己的能力,顺便还可以帮助原作者发现问题。
对于一些优秀的第三方库,阅读其中的代码,理解其中的思想,其实是一件快乐的事情。好代码是可以仔细品味的,有时候看到里面的一些精妙之处,会让人有忍不住想击节赞叹的冲动。这样的库,是克遇而不可求的,更是一定要好好理解它,吃透它。
3.2.  关于Boost
值得一提的是,在某些第三方的库(特别是像Boost这种功能强大,但是实现复杂的库)中,当使用到某些辅助类之前应该想一想,是否真的需要用到这个东西?以Boost为例,如果能花一些时间了解一下Boost库的实现代码,可以看到,Boost库里有些功能其实并不是太实用,或者说用起来代价太大,性价比太低。不可否认Boost的确很强大,但Boost里面的有些特性,感觉纯粹只是因为别的语言,例如java里有某个功能,而硬生生用C++实现出来,实际上这些特性用起来并没有带来多大便利,而且代价还高,属于非常不划算的用法。典型的就是Boost里面的foreach功能。Boost的foreach和直接写一个for循环相比较,能节省的只是区区一行代码,但由于Boost中foreach的实现原理非常复杂,导致编译速度降低,而且编译出来的二进制代码有将近2K;同样的功能用for循环来写,速度快,编译出来的二进制代码大小几乎可以忽略不计。如果使用之前先看一下foreach实现的原理,或许在实际编程中就不会再去使用这个功能。
还有Boost的bind功能,如果看一下bind的实现原理,就可以断定,如果要编译使用了bind的代码,一定会是非常慢的,因为编译器只能是按照bind的各种模板,一个一个的匹配过去,根本就无法优化。仔细想想,bind究竟给我们带来了什么好处?带来了多大的便利?其实并没有太多的好处。所以说bind基本上也是一个性价比非常低的功能,要谨慎地使用。
3.3.  关于字符串类
字符串类基本上可以称得上是C++程序员用的最多的一个第三方的库了。
不像别的语言,原生对字符串支持的就很好。在C++里,字符串类完全是通过代码对字符串的数据和常用操作进行了封装,以方便开发人员使用。由于不同需求,字符串类五花八门,在Windows上,以下几种字符串类用的比较多:
l  CString(CStringA,CStringW)
l  _bstr_t
l  std::string(STL里面的)
l  CComBSTR
每个字符串实现的原理都不一样,如果大家随便按照自己的爱好来使用字符串类,这样就不可避免地出现各种字符串类之间的转换,这个转换伴随着频繁的内存分配/释放和频繁的内存拷贝,造成很多额外的性能开销,完全是一种浪费。
如果能看一下这些字符串类的实现原理,对他们做一番了解的话,在项目开始的时候就应该先选定一种类型,大家尽量使用一个相同的字符串类,这样就可以避免很多转换的开销,会大大提高程序的性能,并减少内存的浪费。
3.4.  本章总结
总之,在使用第三方库之前,先去了解一下,是绝对有必要的。
再次强调,知己知彼,百战不殆。
4.     第三天:写代码时,一定要想着别人
第三天,是希望读者写代码的时候有正确的价值观,真正做到客户第一。
客户第一,说起来容易,做起来难。在前面说过,你之外的任何人,都可能成为你的客户。从一个团队来看,团队里的其他成员都是你的客户,你也是他们的客户,你不是一个人在战斗,不是一个人,你必须在任何时候为你的客户考虑。
写代码时,如果心里没有想着别人,只管自己工作是否完成,这种做法对整个项目是极其有害的,因为你写出来的代码,总是会有人来调用,来阅读,如果只按照自己的想法来写的话,显然会增加别人的劳动成本和时间,这不是一个负责人的程序员应该做的。
反之,如果写代码的时候,心里面会先想一下这几个问题:
l  谁会使用我的代码?
l  别人调用我的模块是否方便?接口是否一目了然?
l  客户会希望怎么使用我的代码?
l  客户会希望我的模块里有什么功能?
l  我这样写的代码有没有隐患?性能是否够好?
l  客户调用我的代码之前,有没有性能上的开销?
l  我这样写编译速度会不会慢?
l  ……
如果能这样想,那编写出来的代码肯定是人人喜欢,别人调用起来也会非常方便。千万不要认为心里想着别人,自己就吃亏了。恰恰相反,心里想着别人,其实一点都不吃亏,因为心里能想着别人的人,编写出来的代码一定很稳定,别人在用你的代码的时候也没有必要再来打扰你,这会为你节省下很多修改BUG和很多回答别人问题的时间,你可以很轻松地完成这个项目,可以不用或者很少加班,算一算实在是赚大了。
写代码时,心里想着别人,会不会一心二用,会不会太累?答案是不会。这就引出下一个原则:代码是用脑子写来的,不是用手。
5.     第四天:代码是用脑子写的,而不是用手
第四天,希望读者掌握编码的正确方法。
真正的编码,绝对不是仅仅把设计好的思想,通过一双手在键盘上敲击的方式变成代码,然后编译。真正的编码,绝对是一项很有乐趣的脑力活动,而不是体力活动。
不仅在计算机行业,其实任何一个行业,高手都是通过脑子做事(有时候也叫用心做事),不是用手做事。手只是大脑在有了完善的思路和步骤之后自然而然的被大脑驱动起来,做东西的一个工具而已。庖丁解牛的故事大家都知道,到了庖丁这个境界,绝对不是仅仅用手,而是用心灵,也就是用脑。
在编码中,很多经常可以看到用手而不是用脑的例子。只是为了完成任务,完全不考虑会有什么后果。最常见到的就是拷贝别人代码的时候,经常可以看到是直接拷过来完事,也不考虑一下两个地方的代码是否完全可以通用?有没有什么地方可能需要修改一下?等等。甚至有时候看到有人在拷贝代码的时候,连别人的名字都一起拷贝过来了。如果在一个代码文件中,发现某个头文件在同一个文件中被显式地包含了两次以上,或者在文件中显示包含了另外一个头文件,但实际上这个文件是根本就用不到的等,可以肯定这个人的这部分代码是从别的地方拷贝过来的,而且拷贝的过程基本上是没有经过大脑,而是纯粹用手拷贝过来,例如:
#include “stdafx.h”
#include “abc.h”                                            < -- 注意,这个头文件被包含了不只一次
#include “something.h”
#include “abc.h”
#include “test.h”
还有一种情况,类似与这样的场景:如果点击了菜单1,做某个操作,然后把某个变量置为1,如果点击了菜单2,做和1相同的操作,然后把1里面的变量置为2,点击菜单3,做和1相同的操作,变量置为3,诸如此类。这样的场景,经常可以看到这样的代码:
void OnMenu1()
{
       // do something
       ......
       var = 1;
}

void OnMenu2()
{
       // do something
       ......
       var = 2;
}

void OnMenu3()
{
       // do something
       ......
       var = 3;
}
很显然,OnMenu2和OnMenu3是从OnMenu1拷贝过来的,这个也是一个典型的用手写代码,而不是用脑子写代码的案例。合理的写法应该是:
// 公共函数
void DoMenu(int v)
{
       // do something
       ......
       var = v;
}

void OnMenu1()
{
       DoMenu(1);
}


void OnMenu2()
{
       DoMenu(2);
}

void OnMenu3()
{
       DoMenu(3);
}
代码这样写,简洁明了,两种写法的优劣,一目了然。并且如果do something地方有修改的话,只要改一个地方就可以,没有必要每个函数都去改了。看似用大脑很辛苦,其实像这种情况,会比第一种写法更加省时间,也更加省力气。
其实只要写代码的时候不要急于求成,用心,用脑去写,很多问题、很多BUG根本就不会发生。这一条原则,请各位读者牢记。
6.     第五天:任何警告可能都是致命的
第五天,希望读者能认识到,好的代码,是应该没有警告的。
我曾经见过有些代码,编译的时候有无数的警告,但是没有任何人想过要把这些警告给修复了。仅从这一点来看,这个产品一定不会成功,因为开发人员的心态首先就有问题。
有些警告,可能看似不会造成问题,但实际上不一定,例如:
void Sample(void)
{
       int var1 = 0;
       UINT var2 = 10;

       if(var1 < var2)          < -- 注意,有警告warning C4018: “>”: 有符号/无符号不匹配
       {
// do something
       }
}
这种警告,貌似对程序运行结果没有影响,但事实上真的是这样的吗?我们把代码改一下:
void Sample(void)
{
       int var1 = 0;
       UINT var2 = 10;

       while(var1 <= var2)   < -- 注意,有警告warning C4018: “>”: 有符号/无符号不匹配
       {
              var2 --;
       }
}
还是相同的警告,但是程序就要出大问题了,为什么?因为当var2减到0之后,再执行var2 --;得到的结果不是-1,而是4294967295了,结果是,这个函数成了死循环。
到了这里,你还会认为有些警告是无关紧要的吗?
再者,其实有些警告,是本来就可能导致严重问题的,例如变量没有初始化的问题等。但如果警告太多,那么重要的警告可能就会淹没在一堆警告里面,难以找到,从而导致软件不稳定。看到这里,你还会认为有些警告是无关紧要的吗?
其实,任何警告理论上都可能会引起严重后果,千真万确。与其去找反例来反驳我这个观点,不如写代码的时候,顺手把遇到的所有警告都给改了,这显然是举手之劳。这就是一个“勿以恶小而为之,勿以善小而不为”的道理。
7.     第六天:使用和平台相同的编码规范
第六天,我们讲一点编码规范方面的问题。
编码规范,说复杂可以写一本书,说简单其实也极其简单。只要明白了规范的作用,一切都不是问题。
规范的作用,在本文的开头已经加以详细说明,不必重复。从统计上来说,遵守规范的人,一定比不遵守的人多,所以只要你的代码风格和大多数人相同或者相似性很高,就基本上可以认为你在遵守规范了。
如果要问在编码的时候,使用什么样的编码规范是最好的?答案很简单,编码规范无优劣,你在什么平台上开发,就用和这个平台相同的编码规范,不同平台的编码规范,千万不要混用。在Windows上开发,就按照微软的编码规范来编码,给变量或者函数命名的时候,就用匈牙利命名法;在Linux下开发,则按照Linux下的编码规范来编码,给变量或函数命名的时候,就按照Linux下的方式来命名。不同平台的编码规范不要混杂在一起用,否则有一种不伦不类的感觉,人为造成困扰。
为什么要使用和平台相同的编码规范呢?原因很简单,既然在某个平台上开发,那么这些开发人员平常看得最多的必定是这个平台上一些软件的代码等,肯定会比较习惯这个平台的各种风格,也愿意按照这个风格和规范来编码。从客户第一的角度出发,每个人编码的时候,都应该考虑到大多数人的习惯,那么只有和平台相同的编码规范才是符合大多数人的习惯的。
如果要换一个平台平台来开发的时候,要改变编码规范是否困难?其实转换编码规范并不困难,真正困难的是自己观念的转变,看你是否抵触这个转变。如果愿意接受变化,其实这个改变也就是几天的习惯过程而已,远没那么痛苦的。
编码规范本身并没有好坏之分,任何规范只要能认真执行,都是好规范。即使规定大家必须使用中文来给变量命名(VS2005是支持中文变量的),只要大家都这么做,照样可以开发出优秀的软件。所以说没有必要特别制定编码规范,需要制定的是确保编码规范能得到执行的制度保证。也没有必要特别的更改,优化某个规范,需要更改的是自己的观念。还有,规范在执行的时候,还是“迂腐”一点比较好,不要随便搞通权达变的事情,那种“制度是死的,认识活的”这种想法,其实是错误的,有了这种想法,就相当于给制度和规范开了一个后门,那么制度和规范就失去作用了。
如果真的遇到“制度是死的,人是活的”这种情况,说明这个制度不完善。其实制度不是死的,不是不能更改的。你可以对这个制度和规范提出质疑,但是在制度没有更改之前,还是请无条件服从,没有商量余地。
8.     第七天:有怪异的BUG?相信科学
第七天,说一下遇到一些奇怪的BUG的时候,正确的处理方式和解决方法。
在软件测试过程,或者软件发布之后,有时候会有一些BUG,这些BUG还都比较怪异,很难重现,但每天总能碰上几个,属于“灵异”BUG。这种BUG,经常会困扰开发人员,往往要花费大量的时间和资源才能解决这种问题,在项目后期,很多时间都花在这种BUG上面了。一般开发人员在遇见这种BUG的时候,总会有手足无措的无助感,因为看自己的代码貌似无论如何也不会出现问题,因此就会怀疑是否是因为调用了别人的代码或者库导致的?就会怀疑甚至指责别的同事或者第三方库等。
遇到这种BUG,其实不用着急,肯定是可以解决的。记住一点,这个世界是物质的,是不可能存在真正的灵异现象的,一定要相信科学,赵某人曾经说过,要以科学的发展观来解决BUG。任何“灵异”的BUG,一定是因为代码写的有问题,有漏洞造成的,而且是一定可以找到原因的。
收到一个BUG报告的时候,首先应该做的是重新检查自己的代码,看哪里有漏洞?是否有什么地方考虑不周?有无可能某些代码是按照自己没有考虑到的分支来执行的?是否按照规范来写代码等等。不要轻易怀疑甚至指责别人的模块,第三方库等,更不要轻易怀疑是编译器的BUG之类的。虽然最终或许真的是别人模块,第三方库,甚至编译器,CPU引起的BUG。做开发工作,一定要有严于律己,宽于律人的心态,只有确认自己的模块确实没有问题了之后,或者实在是找不出原因的时候,再去找别的原因。如果人人能做到这样,其实这些BUG根本不会产生。
解决BUG的比较有效的方法是,把某一个事物分割成几个不同的小模块,这些小模块可大可小,可以大到一个DLL,小到一行代码。每次假设其中一个有问题,而剩下的都是可以信任的,这么一个一个的调过去,直到找到真正有问题的地方。一开始可能会比较痛苦,随着经验的丰富,定位错误会越来越快,错误被成功定位,可以说BUG就已经解决了。
关于如何解决BUG,方法很多,可以专门写一篇文章进行介绍,这里不再多说。
最后记住这一点,对待BUG,相信科学,没有灵异BUG。
9.     第八天:缩写要慎用,宁可写全称,也不要有歧义
第八天和之后的几天,我们讨论一下代码的美观问题。
美的东西,总是会让人喜欢;丑的东西,总会让人感到不适,这一点是毋庸置疑的。任何一个程序员,在看别人写的代码的时候,必然希望看到美观的代码,任何一个程序员,也希望能写出美观的代码给别人看。关于美观与否,每个人有不同的看法,但还是有很多地方是相同的,因此接下来几天,我们会一起了解一下这些相同开发中比较重要的几点。
有时候为了节省空间或者时间,我们会把一些比较常见的单词用缩写来描述。缩写大多数时候会带来好处,例如字符串string,有时候写成str,的确可以做到既简洁明了,又含义明确。但有些不好的缩写,从字面上难以理解这个缩写原始的含义是什么,这就会给阅读代码的人造成困惑,甚至某一天自己回过头来看代码的时候也会搞不清楚。
记得在刚进入公司的时候,看老贸易通的代码,里面很多类,很多函数前面都有Ft这个缩写作为前缀。一看到这个缩写,第一反应就是:难道是Function Type的缩写?但看实际的含义好像又不是这样。后来问了别的同事才知道,Ft竟然是富通(FuTong)两个字拼音声母的缩写(富通是最早外包开发贸易通的公司)。像这种缩写,纯属画蛇添足,多此一举,完全不应该存在。
一个缩写,如果第一眼看上去不能理解是什么意思,那这个缩写就不应该使用,取而代之的是宁可使用全称。例如SL这个缩写,一眼看上去基本上不知道是什么意思,直到看了代码才知道是Serialization(序列化)的意思。像这个缩写,就是属于不太好的缩写,不如索性写成全称,或者写成SRLZ。因为写成全称,最多只是会导致代码长一点,可能不太好看,但意思明确。写成缩写,却可能导致代码的含义难以理解,两者相比较,缩写有歧义危害更大。
缩写,应该使用业界通用的,知名的缩写,例如mgr,str,db等,自己尽量不要创造缩写,否则得不偿失。
10.             第九天:代码不可太长,函数不可有太多的语句
第九天,讲得仍然是关于代码美观方面的内容。
从人体工程学来说,如果看一个东西,需要头左右转动,或者需要用手不停地拖动滚动条才能看到全部,这个东西是会很快令人感到疲劳的。如果一行代码写的太长,阅读代码的时候,要么必须用鼠标拖动水平滚动条左右拖动;或者如果屏幕够宽,可以通过头左右转动来看到全部,这都是一种不太讨人喜欢的写法。同样如果函数里有太多的语句,例如有几百行,相当于好几个屏幕,就会发生看了这个屏幕却忘了另一个屏幕内容的情况,这些都是非常不利于代码的编写和阅读的。
合理的写法是:
l  从代码长度来看,要做到在正常的窗口布局中,永远不会出现水平滚动条,如果一行的内容实在是太多,使用回车的方式,把一行语句分成多行来写。
l  从函数行数来看,要做到在正常的窗口布局中,一个函数最多两屏幕,如果函数实在内容太多,应该把函数里面的内容提取一部分出来,放在另一个函数中被原函数调用。
l  有一些全部由宏组成的类似于表的写法是例外,例如MFC的消息映射表等。
正常的窗口布局,指的是大多数人所使用的代码编辑器的窗口布局。
11.             第十天:在合适的地方加上空行和注释
第十天的内容,和前两天的类似,也是关于代码美观和可读性方面的内容。
文章读起来是否朗朗上口,不在于文字是否优美,而在于文字中的的停顿;音乐是否动听,不在于旋律的优美,而在于音乐中的节奏和停顿。代码也是一样,一份代码看上去是否美观,很大程度上决定与代码的段落是否合理。文章中的停顿,靠句号和逗号来实现;音乐中的停顿,靠的是休止符来实现;代码中的段落,靠的是适当的空行来实现。
阅读文章时候,遇到深奥难懂的地方,如果有人从旁指导,就会豁然开朗;欣赏音乐时候,遇到不能理解的地方,如果有人指点一二,必能更深地感受到音乐之美;查看代码的时候,遇到晦涩不明的地方,如果有适当的注释,问题必能迎刃而解。
11.1.     关于空行
正如我们平常写文章,会用到逗号和句号,把一句很长的话分开,免得阅读的人读的喘不过气来。在代码中一样需要把一些比较长的语句分开,免得阅读代码的人读起来不愉悦,喘不过气来。在代码中的标点符号,就是使用空行。
适当的地方加上空行,代码就会展现出段落,有了段落,就能比较容易地分出侧重点,可以有针对性地忽略一些段落,或者侧重在另一些段落。
关于空行的位置,一般认为:
l  变量声明部分和代码实现部分中间,应该有空行
int nValue = 1;
CString strTest;
                              < -- 注意,这里是空行
strText.Format(“%d”, nValue);

l  大括号前面或后面如果同一级别有代码,那么大括号和代码之间应该有空行,如果大括号前后也是大括号,则没有必要加空行
注:大括号前面,一般指的是for或者if的前面,并不是真的指大括号直接的前面
for(int i = 0;i < 100;i ++)
{
       // do something
}
                                                   < -- 注意,这里是空行
if( .... )
{

}

像这种情况,没有必要在大括号后面加空行
for(int i = 0;i < 10;i ++)
{
                     for(int j = 0;j < 10;j ++)
                     {

                     }                         < -- 注意,这个括号后面没必要加空行

}

l  对于一些及其简单的函数,例如总共只有几行的函数,空行可加可不加

11.2.     关于注释
记住这一点,好代码不需要注释,差的代码无论怎么注释也没有用。
与其费力气写注释,不如把代码写的好一些。优秀的代码,是一件艺术品,里面自有它的规律和韵味。阅读好的代码,感觉像是在阅读一篇诗歌或者散文,按顺序看下去,代码里面的设计思想等自然而然就能明白。像某个类做什么用?每个函数是干嘛的?里面的参数是什么含义等,都可以直接从代码上看出来。
虽然好代码不需要注释,但必要的注释仍然不可或缺。在某些关键的地方,加一些注释,说明一下这个地方为什么要这么写,或者说某个地方用了什么算法等,这会给以后看你的代码的人带来极大的便利,属于一个善举,这种利人利己的事情,一定要做。
注释,一定要准确,千万不能乱写。不正确的注释,不但不能给人带来便利,反而会误导阅读代码的人,是帮倒忙。
当代码发生变化的时候,注释也要随着发生变化,以配合新的代码,否则老的注释就会变成不正确的注释。如果某段代码被删除了,最好把这个注释也删除掉,或者至少在注释上要说明,这段代码已经删除了。
总之,要把写注释当作写代码来看待,不能因为注释被编译器忽略就不重视。代码是红花,注释是绿叶,二者合理搭配,程序整体才和谐。
12.             总结
经过十天的学习,相信大家会改变大家在开发过程中的一些习惯和观念,如果这个改变,能对我们的开发带来一些正面的影响的话,本人将会非常的开心,因为这说明了上面这么多文字是有价值的。
如果有一天看项目中的代码,到了从代码上(注释不算)看不出某个模块是谁开发的地步,说明我们已经真正理解了这些原则,到这时候,我们开发的软件一定会是最完美的软件。

[ 本帖最后由 wishel 于 2009-7-15 14:57 编辑 ]
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-4-26 11:35 , Processed in 0.018654 second(s), 6 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表