走出软件作坊(二十八)(转)
二十八、代码那些事儿
这个是讲软件研发过程管理的系列,目标人群是那些身处研发管理位置或想成为研发管理的人。所以我不希望这个系列中出现代码,也不希望出现和某种技术密切相关的代码技巧。
但是没有办法,软件开发过程管理,有个很重要的一环,就是软件代码编写。不编写代码,说的天花乱坠,管理做的再扭,也成为不了软件。
最近blog好友到了上限,所以无法加入好友了。于是就加入了N多QQ网友。大多数刚出道一两年,还有不少在校学生,希望认识并聊聊,要和我聊设计模式、OO、SOA,还有人建议我去看看OO和UML的书籍。
我确实没有阅读过OO的书籍,我不是一个死钻牛角尖的人。我只是有什么问题,就去找解决问题的方法,能解决我的问题就OK,而不在乎我用的正不正宗,也不在乎我用的是不是OO。可能它是OO的外壳,但是它实质上可能是伪OO,我也不想去深究和区分什么是正宗OO,什么是伪OO。反正能解决我的问题就行。
我首先遇上的问题是代码大流水的问题。一个代码3000多行,数不清的if..else。我实在无法分析出这个代码到底实现了什么功能,它复杂的就像一把瑞士军刀,如果军刀里面能出来一支小手电也觉得不奇怪。这段代码我想它就是这样。我想写这段代码的人一定是在学校做惯了《图书馆管理系统》,一个程序文件中实现了所有的功能。
所以,经历了痛苦,我就一直要求手下的开发人员,一个函数代码行数不能超过一个屏幕。如果超过一个屏幕,就需要来回滚动。一滚动,就容易把思路滚动的走神,乱了,还得回到上面去重新理逻辑。
这种规定,直接导致了一个后果,函数太多了。但是函数间是有关系的。有时候突然需要增加一个参数,但是此函数已经被其他代码调用了,于是为了省事不改动其他代码(当然,他可能已经忘了再哪些地方都调用到了),他就定义了一个全局变量,在函数之间传。
新的问题有出来了,全局变量的问题。代码在他一个人手里还好说。被维护了好几手,新手对日益复杂的代码已经不太容易理出头绪,但修改任务是有时间限制的,只能在现有理解水平上工作。这位新手看到这个全局变量的命名,感觉是自己能用的到,就用了,还给赋了值。意向不到的事情发生了,数据库中进了错误的数据,怎么来的,不知道,看代码,处理的都正确,代码没有问题,也不知道怎么出现的。
于是禁止大家使用全局变量,能藏到多低的可见级别就藏的多深。但是仍然问题依旧。
于是,我们就把这个全局变量封装成属性,给它赋予读和写的函数。不管有多少个地方调用了它,有谁给它乱赋值,这里掐源头,都做好日志和异常保护校验。
把所有函数,能变成私有的就变成私有的,需要公共供其他人调用的谨慎的放出来。放出来的函数,一定要做好参数的校验,什么空指针,什么没有初始值,什么非法参数值的,尤其是临界值上下边界,都在门口就挡住,根本不往下执行进行业务处理,否则走的越远引起的问题越大。
而且要求每个函数都要做好自己的内存保护工作,自己函数内创建的就要在函数内释放。每个函数要做好异常处理和日志记录。
这样,一个样子像OO的类产生了。它可能只遵守了OO所提了封装,它却没有实现OO所提的继承和多态。所以它可能是个伪OO,但它确实解决了我们的问题。
封装了类之后,又一个问题产生了。几个类之间不知怎么的,总是你中有我,我中有你,互相调用了。就好像左右手互搏,弄不好就会把自己给捆起来。
为了堵住这个问题,规定只能单向依赖,如果发现你必须调用另一个类,另一个类也需要调用这个类,就以一个类为主,另一个类开放事件。这也许就是控制反转,AOP的需求吧(我并没有深入研究AOP,我只记得控制反转这个词,好像是为了解决相互依赖关系问题)。“Don't call us, we'll call you”。
但是代码稳定性的问题仍然没有很好解决,测试组也找出了许多BUG,但是一到客户那里运行,还是出了不少BUG。怎么自己运行的时候找不到呢?
于是,我们在版本测试的时候、第一个版本小规模放给典型客户的时候,都加了断言。一旦软件出现问题,就立即记录日志,并进行软件中断,而不让错误继续错乱的不按我们预想的代码流程走下去。很多年前,我就惊讶于某公司的软件的质量,怎么折腾操作都没有问题,我常常给我的手下拿这个例子来反衬大家的代码质量。直到我有时随便乱点,居然软件中断退出了,报了一个错误号,我一下子想通了,它用了断言。断言阻止了错误的继续扩散,不让恶果之鞭长袖善舞。于是,我要求开发人员经常性使用断言,很多过去悄然发生的错误,测试员只运行可执行程序无法捕捉到,现在都能明确的捕捉到,在测试阶段就尽可能的消灭了那些过去无法明示的BUG。
我过去领导过架构组。架构组的人在2002年的时候,疯狂迷上了UML和设计模式,人手一本《COM本质论》和《设计模式》。我手下有一个新手,就处处是类,处处是抽象,处处是封装,处处是分离,尽量使代码高内聚低耦合。但是这样的的代码太麻烦,他花费了大量的时间,他看自己的代码赏心悦目,别人看他的代码云里雾里,不阅读懂《设计模式》就按照常规理解业务的思路去阅读他的代码根本阅读不懂,不知道他为什么这样写代码,怪异的很。本来,这位想达到可维护性,可阅读性,却真正的失去了可维护性、可阅读性。这和我前几天看我的朋友周爱民写的《大道至简》中写到:有人希望拿UML去统一用户和软件设计者。殊不知UML有多难理解,而UML设计者却认为UML可以描述一切。就这个道理,要理解你的代码还要去读懂《设计模式》,这要求太高了吧。
所幸这位新手自己都每次写的累,慢慢的也就懒了,觉得确实需要分离的时候就分离,觉得没什么必要的就懒得做了。用他自嘲的话说就是:被磨平了。其实,依我看,他现在这个代码状态才是刚刚好,即照顾了设计扩展,又照顾了实用。真正的纯OO,纯设计模式,可能只存在于教学和科学,而不在于我们的商业软件开发。我们作为商业开发,强调的是叫座的基础上叫好,所以折中方案是必须的,客户和我们自己两相宜就OK,是否符合正宗,就不在我们的商业开发管理范畴了。
这位新手还写了大量的注释。在每个源代码文件头都写上几月几号,XX创建的,这个原代码文件主要是干什么的,还画蛇添足的写上版权所有,公司名称。好像这个代码要开源,或者可能会被其他公司窃取了好表明公司版权。甚至每个函数都写了注释,每个参数是什么意思,每个参数可能出现的值代表什么意思,都写的一清二楚。久而久之,也懒的维护了。代码改动了,参数扩展了,参数状态值有了变化,注释说明却没有跟着改动,让后来看代码的人老误解,还不如不写这些注释。
我告诉他:做事不能走极端。要么全写注释,要么不写注释,都是不对的。我只在我认为要小心的地方,或者我自己都觉得很难理解懂的地方我才写注释。否则,我自己都可能会过段时间理解错了。如果某段代码我看看就能看懂,我就不写注释了。咱们做企业管理软件,深入技术又没有,只要代码能把复杂的业务处理描述的逻辑思路清晰就OK。虽然说理解能力不同,我能快速理解了的未必有新手能够理解,但是你看看我的代码你就明白了。
这位新手去看我的代码去了。我的代码几乎没有什么难度技术,但代码也是看上去很舒服。他发现了以下几个关键点:
1.代码风格统一,从命名含义,到大小写,到缩进,都一致。每个源代码文件都一致,确实出于一人之手。许多程序员,光自己的代码就有好几种风格,有时心情好,有时心情不好,有时头脑清醒,有时没有休息好,有时敷衍,有时画蛇添足,有时急躁,从代码就能看出来。而我的代码就像稳定运行每天如一日的机器,好似每个源代码都是在同一天敲的。这就叫发挥稳定。这几天要开奥运会了,运动员天天重复练同一个动作,把每个环节都练的精益求精,其目的就是为了在大赛紧张的压力下也能发挥稳定。人在压力下,非常容易发挥失常。如果人老处于这种压力下训练,那么大赛就像平常一样了。
2.我的代码居然能看出业务流程。函数数量均衡,不像他的代码函数太多,跟踪跳转的很累,也乱了头绪。函数长度也正好在可理解阅读范围内。而且有一个流程控制函数,把流程处理环节串了起来。细深入跟踪某一环节,又发现了更细的流程。每个函数都看起来简单,但整体来看,却实现了复杂的功能。他问我是怎么做到的?我说,我的心中只有业务,业务和代码,我认为只是英语和汉语的区别,表达的是同一个思路。而在你心中,业务是DOC上的文字,代码是你的技术表现,你老需要把业务和代码映射拧在一起,我则不需要。业务流程如何,我的代码流程就是如何。
3.由于我的程序都是小函数组成的,都有明确报错,所以错误很容易找到,即使出错,也扩散不大,都是小bug,对系统整体没有大影响。
他还对我的开发方法不理解,问我为什么要让大家从后台往前台开发,他很不习惯这种方法。他过去开发都是先用开发工具拖拽控件画出界面,然后一个按钮一个按钮的处理业务代码,需要什么字段就在数据库里设计什么表加什么字段。
我问他:你为什么要这样做?
他说:我不理解需求,无法凭空想象,只能先画出来界面,然后有了直接感觉后就在开发中理解业务,边开发边理解。
我问他:那你以前有详细设计说明书没有?
他说没有。
我又问他:那你以前有人单独设计数据库和开发业务处理中间件组件么?
他说没有。
我说,问题就在于此。你没有详细设计说明书,所以你看不到一个形象的东西,而咱们现在至少有PPT画的业务界面,也有输入要求说明,也有数据增删改查说明,也有业务描述说明。而且数据库,一个中大型应用,性能、稳定性、可扩展性,都在于数据库的设计和中间件的设计,如果每一个程序员都要从数据库设计到中间件组件开发到前端客户端开发,那么要想保证这个软件的统一整体质量,那有多难。每个程序员需要懂得多少的技术知识才能达到统一的质量要求。所以,让不同技术高度的人做不同难度的事情,把重要的事情掌握在高素质的人的手中,这样质量就不会跑偏到哪里去。企业管理软件,不外乎是数据的增删改查。数据库的视图和存储过程,已经屏蔽了复杂的表之间的关系,提供了统一的业务实体视图和业务实体的增删改操作。这样中间件组件就容易处理业务实体间的流程,到了客户端,就只剩下数据的输入和输出了,真正成了终端。
我还经常进行代码复查工作。发现有人的代码出现坏迹象,我就让他整改重构自己的代码。否则,定了规范,光喊口号让大家遵守规范又不检查又不惩罚,谁爱遵守规范?
在代码复查的时候,我经常能发现思路局限、想法绕弯、实现缺乏扩展性可变性、缺乏优化、判断有遗漏有风险的代码。我都一一给他们指出,并且告诉更好的方法应该是如何如何。
很多网友都问到如何培养开发人员、培训开发人员、提高开发人员技术。我过去所在的公司是每个周五都不定期召开技术培训会。但是慢慢的也流于形式了。我个人感觉,技术培训会是个好事,但最终达到的效果可能是加强了团队的凝聚力和和谐力,有利于团队建设,但是技术提高,并没有给开发人员在实际工作中有应用。因为大家听了也就听了,会一散实际工作中仍然照旧自己的思维习惯,很难落实下来。
通过代码复查,这就是真真实实的代码,这就是大家最实在的工作成果和天天接触的东西,很有真实性,不脱离,有针对性。通过这种方法,达到了大家对自己日常写代码的技术技能的提高。这就是我对程序员的培训方法。
管理是什么?有网友老跟我讨论企业管理、企业文化、盈利模式、执行力、忠诚度。我不是很喜欢这些空洞抽象的主题,讨论完了也无法解决实际细节问题。而企业的运作,恰恰在于每一步的成功,最终汇合成最终目标的实现,没有脚下的每一步前进,再好的设想也是空想。
管理,就在细节当中。
代码高手,也在于细节当中。
质量、进度、成本、目标、折中,这就是核心。写代码,做管理,道理一样。