笔者认为所谓优雅的代码包含三部分涵义:风格好、结构好和性能好的三好代码。所有的编程语言都适用,只不过每种语言的特性和语法不同,实践三好代码的方式和途径也不尽相同。需要注意的是,在有些时候这三种内涵之间是矛盾的。例如,结构好的代码,有时候却会导致性能的下降。遇到这种情况,也不必纠结,在各种利弊之间做好权衡,保证全局利益的最大化才是王道。本文以C#语言为例,总结C#编程实践中,如何做到代码的优雅。
一、简洁的代码风格
代码风格顾名思义,即程序员编写代码的书写风格。笔者认为简洁的代码风格,将大大的提高代码的可读性以及降低后续的维护代价。早前,程序员编写代码有一个误区,认为代码是写给计算机读的,只要程序正确,风格不重要。其实并不然,程序员的代码是写给其他人看的,代码风格的简洁统一对于其他人的阅读非常重要。近些年来,代码风格的重要性在业界已达成共识,然而在紧张的开发进度压力下,程序员也往往会忽略代码风格。从系统的整个生命周期来看,这种只顾眼前,不顾长远的做法是不可取的。代码风格主要包含以下部分:
1. 命名规范:C#语言中,推荐使用驼峰式命名法。对于公共变量、函数和类等使用大驼峰;对于私有变量、局部变量等使用小驼峰。如:用户名(user name)的大驼峰命名为:UserName, 小驼峰命名为:userName。除命名形式外,对于命名的语义也非常重要,尽量用简短、清晰和常见的单词来命名。当你发现命名仍然很长时(通常不超过30个字符),往往是因为该命名承载的逻辑太多所导致,此时应该拆分逻辑。
2. 代码注释:关于注释的讨论有很多,笔者不赞成通篇的注释,也不赞成完全不写注释。注释的目的是为了帮助程序员阅读代码,所以一切都以代码的可读性为目的。不能为了注释而注释,本末倒置。笔者认为,在一个命名恰到好处的系统中,不需要存在太多的注释,只需对一些公共接口进行注释。因为通过命名空间、类名和函数名等上下文环境已清晰的表达了大部分程序逻辑的含义。
3. 代码布局:利用好换行会使代码的布局比较美观。例如:函数之间应该换行,函数内部实现可用换行作为逻辑片段的结束。
对于代码风格的讨论有很多,在此只对一些经常被忽略或者有争议话题做一个简单的介绍。更多代码规范的细节,应该系统的阅读相关书籍。同时,在C#编程中,笔者推荐Resharper代码重构工具。Resharper会在编程过程中实时地给出一些代码风格的建议,并且有很好的可视化提示。如果你对Resharper默认的风格不满意,还可以定制你自己或者团队的代码风格。
二、高复用的代码结构
如果说代码风格是外功,那么代码结构属于内功,需要一段时间的积累,才能写出代码结构好的程序。前人总结了很多代码规范,开发人员只需遵守,就可以写出易读的代码。而代码结构则不同,很多书籍介绍了如何实现好的代码结构,但对解决方案的解释都比较抽象。例如,四人帮的设计模式将实践中经常遇到的很多业务问题,抽象成几类固定的模式,并总结出了如何组织代码结构,实现高复用性。开发人员需要具备一定的编程经验后,再去理解这些抽象的模式,才能够做到在实践中举一反三,学以致用。
在面向对象编程中,编写可复用程序有三种方式:继承、多态和泛型。设计模式的实现也是通过这三点特性来实现的。通过继承,子类可以复用父类的数据和行为;通过多态,可以简化编译期的代码量,子类的实际行为可以在运行时决定。例如:子类可以在复用父类的行为的基础上,还可以定义自己的特殊行为,并可以以父类的类型进行传参,运行时会调用子类的具体行为;通过泛型,可以实现类型的参数化,相比多态,泛型是编译期特性,不仅可以保证类型安全,而且在编译期就决定行为,效率比多态的运行时寻址更高。
除上面提到的代码逻辑结构以外,代码的物理结构也有讲究。一般而言:单个文件只放一个类;单个代码文件的长度不应该超过255行;一个函数的长度应该不超过屏幕的可视范围;即不用滚动条就能看到完整的函数体;括号的嵌套层级应控制在三层以内,可以通过转置判断逻辑的方式来降低嵌套层级;上面这些建议都是统计的结果,大量的实践证明这些方式是最佳的。仔细思考这些看似没有技术含量的建议,实则背后透露出来软件设计的理念:分离关注点、单一职责的逻辑单元等。从软件质量的角度说,大量的代码逻辑集中在一处,代码的质量往往比较差。还有一个奇怪的现象,代码缺陷总是扎堆出现,在某个代码缺陷的附近往往还存在其他的缺陷。
代码结构是一个很大的话题,建议有兴趣的同学看看两本书史蒂夫·迈克康奈尔的《代码大全》和四人帮的《设计模式》。读完之后,相信对于代码结构的理解会更加深入。
三、高效的代码性能
前面提到代码风格是外功,代码结构是内功,那么代码的性能就是内功心法。看过武侠小说的人都明白内功心法的重要性,心法不对容易“走火入魔”。在当前计算机硬件速度飞速发展的年代,代码的性能仿佛是一个被遗忘的角落,尤其是在中小型系统中,低效的程序逻辑可以用昂贵的计算机硬件来弥补。但是,作为一个优秀的软件工程师必然是一个程序性能工程师。在前面的博客中写过C#性能优化方法和实践,读者可以参考。
代码的性能有时候与代码的结构会产生冲突,为了保证代码的复用性,代码结构往往设计的比较灵活,导致代码结构臃肿。而高性能的代码结构一定是简单直接的。将一个函数分成10个子函数,代码结构清晰了,但是性能也会降低。因为函数的调用也会消耗程序性能。这两者之间应该做好权衡,尽量做到性能优良的同时,保证代码结构的复用性。现在的高级语言编译器已非常强大,如C#编译器会在编译期从执行效率的角度出发,将代码的结构进行优化产生MSIL中间代码。
最后,在开发的过程,往往会产生一些无效代码。如:无效的Using namespace, 无效的变量声明和某些代码路径的无效代码。对于前两项可以用代码重构工具(Eg.ReSharper)批量删除或者依赖编译器优化。对于最后一项,需要在理解代码逻辑的情况下,重新组合代码的执行顺序。如下面一段代码,当isOk=true,list的初始化就是不必要的,应该放到判断逻辑的后面性能会好些。
void Calculate(bool isOk) { var list = new List<string>{"a", "b", "c"}; if(isOk){ return; } //calc logic return; }
本文,蜻蜓点水般地介绍了笔者认为的优雅代码的三个方面:风格、结构和性能。都是概要性的,管中规豹而已。要深入的理解,还需要针对每个话题,单独详细的介绍。