作为一名资深老程序员,也来谈一谈什么样的代码算是好的代码,以及怎样写出好代码。
其实一个人写的代码跟这个人的性格是大致吻合的,房间很乱的人写的代码大概率也杂乱无章,现实世界还有物理规则的限制,代码世界里随心所欲,程序员在无拘束的代码世界里必须要提升自我修养。
最近看到几篇讨论代码好坏的帖子,不少人的观点中,好的代码应该具备良好的可读性,清晰的注释,要符合编程规范,变量命名要具备可读性等等。
这些都对,但是如果一段代码只符合这些,那是只注重了表面功夫,一段代码质量的核心要求还是这段代码要好用,毕竟,代码是要来用的而不是拿来看的。
功能完备
完整的功能是第一要素,这里强调完整性,要求代码实现十条功能,一条也不能遗漏。
有人说这还不简单?其实真的不是太容易,因为大多数时候,只是程序员认为自己功能全部实现了而已,程序员自己理解的功能跟实际需要的功能往往差距很大。
比如客户想要一个美女,这个客户说想要美女的时候心里想的是林志玲,因为这个客户喜欢温婉的古典美。
程序员表示收到了,但是别看这个这个程序员是个宅男,但那只是表象,当他听到美女的时候,心理想到的却是火辣的性感女神。
当客户拿到程序员实现的美女时,内心肯定是崩溃的,因为客户也不知道该喜欢好还是不喜欢好,你说性感美女不美吗?也美,但就是不能打动客户的心啊。
这只是个通俗的比喻,造成这种现象的原因是因为对一个需求的量化准则没有进行明确的规定,靠人的感觉去做往往南辕北辙,因为人和人的差异性非常大。
做过测试的同学应该非常清楚,很多问题单都是因为规格的问题跟开发人员吵来吵去,测试人员认为手机温度达到75度就应该过温保护降低CPU功耗,但是程序员认为80度才应该过温保护,为了这5度的事争论不休。
所以,真正实现一段程序的完备性并不是那么容易的,那我们应该怎么做?
需求澄清:好的程序员会在这个环节花费大量的时间,那些说声好嘞就去写代码的程序员一般我都会把他们叫回来跟他们谈谈人生。要想做出客户想要的林志玲,你得跟客户反复的沟通,鼻子多大嘴什么形状都得问清楚,程序员需要设计好问题去深入发掘客户这个需求的原动力是什么,明白客户的需要究竟是什么。
快速原型:如果有可能,最好先实现一个简单的原型让客户确认,原型最大的作用是有一个可以进行讨论的实体,而不是凭感觉泛泛而论。客户如果说对对对我就是要这个,那就继续实现这个原型的细节,如果客户说这里不对那里得修改,那请跟客户把需要修改的点讨论清楚。比如客户觉得林志玲有点瘦,胖一点就完美了,那请跟客户讨论好是胖到100斤还是102斤。
测试驱动开发:最近几年比较流行的方法,开发之前先把验收用例写好,并跟你的客户达成一致。手机过温保护就是80度,验收用例写得清清楚楚,高一度低一度都不行,其实这样程序员实现起来反而省心,因为不用去猜80度合适还是75度合适。
要写出功能完备的代码就不能偷懒,请多花点时间细致澄清你的需求,别怕浪费时间,如果你实现偏了最后返工,只会浪费更多的精力,还会导致你的客户对你的不满。
可靠性强
这个要求一定要放在第二位,这比后面的要求更重要,如果说完备性决定了代码能不能用,可靠性就决定了代码好不好用。
可靠性通俗点来说就是不管你怎么折腾,程序就是不会挂,你的代码放在那里好几年都布满灰尘了,但是运行起来还是杠杠的,从来不需要人来操心。
可靠性比完备性更难达成,你能保证一次运行正确但是难保证一直运行正确,能保证一天运行顺畅但是难保证几年运行都不出问题,能保证过得了夏天但是不一定能搞定冬天(我没有影射苹果手机)。
可靠性难做的原因主要是要应对大量的未知的、不可控的应用场景,做功能完备性是做确定性的事情,保证可靠性是做不确定的事情。这就好比把桥修通车是做功能完备性,但是货车一上就坍塌,一点洪水就翻倒,过了几年就变脆,这都是可靠性没做好。
一段代码的可靠性更像是良心工程,因为代码还没有桥那样的实体摸得着看得见,可靠性到底好不好也要等代码运行一段时间后才能检验,所以有些程序员就会忽略这方面的考虑。
那么怎么做好代码的可靠性呢?
变量边界值保护:变量的边界值是触发可靠性问题的重灾区,边界值不仅仅是0和256这样的整数边界,还有大量功能上的边界。一般0和256这样的整数边界和溢出边界都保护不好的程序员我觉得应该先下岗反思,很多定位困难的踩内存程序跑飞问题都是由于边界值溢出导致的,随手一段代码让别人熬夜定位故障好多天。功能性的边界也需要仔细考虑,我见过有些电机控制程序在器件移动到头的时候还一个劲猛转,什么样坚强的结构都挡不住金刚钻成年累月这样折腾。
处理所有程序分支:很多程序员很懒,写代码只写正常功能那一个分支,他们脑子里事物的发展只有一条路径,不存在任何的异常和干扰因素。如果现实世界都是那么单纯就好了,可惜天不随人愿。请在所有的程序分支都写上异常处理,否则一旦跑入没有处理的分支,程序就有可能崩溃。如果不方便所有分支都详细写异常处理,那请在函数结尾处做兜底。
完善单元测试:这么多年编程经验告诉我,哪里没有测试到,哪里就肯定会出问题。写作单元测试用例是有完善的工程方法的,别因为测试用例多就偷懒,请把单元测试用例写完备,从代码的最底层就把测试做好,这些测试用例自动化以后,可以长久守护你的代码。
加强系统可靠性测试:很多人做测试大多数集中在测试功能完备性上,按我的经验,你要想保证程序的可靠性,需要的测试用例至少是功能测试的两倍以上。各种使用场景、各种严苛环境、跨月的长期运行测试,条件允许的话建议都要考虑。有些人可能会说这样做是不是成本太高了?我的建议是测试用例的考虑要尽量完备,如果觉得有些没有必要可以评估后裁剪掉,那种考虑都没考虑全面的测试是不能满足要求的。
再次重申,程序可靠性最考验一个程序员的良心和修养,这是个需要大量精力才能做好的活。
性能优秀
性能在目前的高性能硬件环境下并没有那么重要了,或者说大多数情况下并不是主要矛盾。
但是在特殊的应用环境下,性能要求还是非常非常高,比如春节抢红包、通讯设备大批量用户同时接入、双11抢购、国家骨干网服务器等。
高性能和低性能的代码效率可能相差千百倍,提升代码性能是个技术活,必须对代码运行机制和操作系统的原理有深刻的理解,否则你不知道代码性能从哪些方面入手。
一个好的程序员不仅要会写代码,还要把数据结构、操作系统、编译原理等专业课知识学精学透,那些觉得只需要学好一门编程语言就可以做程序员的人,很容易变成只能编码的底层码农,这不是开玩笑。
好的系统架构:架构一定简洁,不要有过多的分支和循环往复的复杂依赖。太复杂的架构不仅会导致很低的运行效率,还会导致程序难以维护。如果你觉得你的架构就是需要这么复杂,那请找更高水平的人讨论,大道至简,你的复杂性只是因为你对事物的总结不够深刻。
高效的数据结构算法:算法一定要高效,如果没什么好的想法,建议复习下大学课程《数据结构》,简单的排序算法也可以提升一百倍效率。如果你简单的使用一下哈希算法或者折半查找,你的算法就可以升华,所以一定要有设计高效算法的潜意识和能力。实在设计不出来,上网找个编程社区搜索一下。
减少进程切换:进程的切换往往伴随着保存现场,数据压栈出栈,内存反复寻址读取写入等一系列复杂的操作,如果你的一项需要高性能的代码在计算过程中产生了进程切换,那性能的影响是灾难性的,所以这方面在实现的时候一定要格外注意。
少读写内存:尽量在高效运算时不要读写内存,CPU的运算速度极快而读写内存极慢,CPU停下来等内存的感觉就好像等了一个世纪那么久。所以请将一切数据都准备好再开始计算,实在不行就一次性读取大段数据,不要读一点算一点,那样子效率最低。
宏的效率比函数调用高:函数调用是个非常复杂的过程,而宏在编译的时候是直接在调用宏的地方就地展开的,实际运行的时候不存在调用函数的那一堆动作,所以,如果你需要高效率,可以把简单功能适当封装成宏。
性能的提升确实需要一定的技术含量,个人建议在需要性能的时候再刻意的去考虑性能提升,优先把功能完备性和可靠性做好。
可扩展性强
可扩展性时常被程序员忽略,因为这是个后效性的质量品质,一般都不会当场就被检验出来,都是在用了一年半载以后,需要增加功能进行软件升级的时候,才会集中爆发。
本人就曾经遇到过一个案例,只需要将一个模块的用户数从50人修改到100人,整个软件产品竟然涉及几百个修改点,客户觉得两分钟就改完的工作,连修改待验证折腾了两个星期。
当然这只是一个例子,可扩展性的含义不仅仅是修改软件的规格,总的来说,可扩展性是为了应对未来的变化。
从架构做起:在设计你整个软件架构的时候,要充分考虑你软件所实现功能的未来发展可能性,以便未来增加新的功能。比如你做了个软件可以发送文字,你自然要考虑到未来可能会发送音频或者图片甚至视频,扩展性做得好的,会将这些的扩展接口都留下来,未来新增这些模块甚至不需要修改老的功能任何一行代码。
处理好善变的那些规格:使用人数、允许接入的连接数、温度的上下限、告警门限,可以讲这些规格就没有完全稳定的时候,一直处在变化中。这些数据都需要集中进行管理,严禁个人私自定义甚至直接使用具体的数值来做控制,要按照可配置的思路设计规格变量。
可扩展性很难用一种量化的标准去衡量,多数时候依赖软件设计人员的经验。
基础要求
代码逻辑清晰可读性强,有完备的代码注释,统一的变量命名规则,符合较低的圈复杂度,符合编程规范,所有这些个人认为都是程序员的基本功,本文就不展开详细论述了。
如果有人不太理解编程规范是个什么东东,可以网上搜索华为的编程规范来学习一下,规定的都是一些非常基础,但是一不小心就会犯错误的编码技术问题。
总之,想要写好一段代码也不是那么容易的,资深程序员需要在软件工程能力和计算机基础科学理论上多投入精力,干一行爱一行,努力提升作为一个程序员的自我修养。
转载自网络 不用于商业宣传 版权归原作者所有,侵权删。
领取专属 10元无门槛券
私享最新 技术干货