要写出好代码,首先需要提升品位。
很多软件工程师写不好代码,在评审他人的代码时也看不出问题,就是因为缺乏对好代码标准的认识。
现在还有太多的软件工程师认为,代码只要可以正确执行就可以了。这是一种非常低的评价标准,很多重要的方面都被忽视了。
1、好代码的特性
鲁棒(Solid and Robust)
代码不仅要被正确执行,我们还要考虑对各种错误情况的处理,比如各种系统调用和函数调用的异常情况,系统相关组件的异常和错误。
对很多产品级的程序来说,异常和错误处理的逻辑占了很大比例。
高效(Fast)
程序的运行应使用尽量少的资源。资源不仅仅包括 CPU,还可能包括存储、I/O等。
设计高效的程序,会运用到数据结构和算法方面的知识,同时要考虑到程序运行时的各种约束条件。
简洁(Maintainable and Simple)
代码的逻辑要尽量简明易懂,代码要具有很好的可维护性。对于同样的目标,能够使用简单清楚的方法达成,就不要使用复杂晦涩的方法。
“大道至简”,能否吧复杂的问题用简单的方式实现出来,这是一种编程水平的体现。
简短(Small)
在某种意义上,代码的复杂度和维护成本是和代码的规模直接相关的。在实现同样功能的时候,要尽量将代码写得简短一些。
简洁高于简短。这里要注意,某些人为了能把代码写得简短,使用了一些晦涩难懂的描述方式,降低了代码的可读性。这种方式是不可取的。
可测试(Testable)
代码的正确性要通过测试来保证,尤其是在敏捷的场景下,更需要依赖可自动回归执行的测试用例。
在代码的设计中,要考虑如何使代码可测、易测。一个比较好的实践是使用 TDD 的方法,这样在编写测试用例的时候会很快发现代码在可测试性方面的问题。
共享(Re-Usable)
大量的程序实际上使用了类似的框架或逻辑。由于目前开源代码的大量普及,很多功能并不需要重复开发,只进行引用和使用即可。
在一个组织内部,应鼓励共享和重用代码,这样可以有效降低代码研发的成本,并提升代码的质量。
实现代码的共享,不仅需要在意识方面提升,还需要具有相关的能力(如编写独立、高质量的代码库)及相关基础设施的支持(如代码搜索、代码引用机制)。
可移植(Portable)
某些程序需要在多种操作系统下运行,在这种情况下,代码的可移植性成为一种必需的能力。
要让代码具有可移植性,需要对所运行的各种操作系统底层有充分的理解和统一抽象。一般会使用一个适配层来屏蔽操作系统底层的差异。
一些编程语言也提供了多操作系统的可移植性,如很多基于 Python 语言、Java 语言、Go 语言编写的程序,都可以跨平台运行。
可观测(Observable)/可监控(Monitorable)
面对目前大量存在的在线服务(Online Service)程序,需要具备对程序的运行状态进行细致而持续监控的能力。
这要求在程序设计时就提供相关的机制,包括程序状态收集、保存和对外输出。
可运维(Operational)
可运维已经成为了软件研发活动的重要组成部分,可运维重点关注成本、效率和稳定性三个方面。
程序的可运维性和程序的设计、编写紧密相关。如果在程序设计阶段就没有考虑可运维性,那么程序运行的运维目标则难以达成。
可扩展(Scalable and Extensible)
可扩展包含 “容量可扩展”(Scalable)和 “功能可扩展”(Extensible)两方面。
在互联网公司的系统设计中,“容量可扩展” 是重要的设计目标之一。系统要尽量支持通过增加资源来实现容量的线性提高。
快速响应需求的变化,是互联网公司的另外一个重要挑战。可考虑使用插件式的程序设计方式,以容纳未来可能新增的功能。
以上十条标准,如要要记住,可能会有些困难。我们可以把它们归纳为四个方面,如下:
方 面 | 对应的特征 |
---|---|
正确和性能 | 鲁棒、高效 |
可读和可维护 | 简洁、简短、可测试 |
共享和重用 | 共享、可移植 |
运维和运营 | 可观测/可监控、可运维、可扩展 |
2、坏代码的例子
关于好代码,上面介绍了一些特征,本节也给出坏代码的几个例子。关于坏代码,本书没有做系统性总结,只是希望通过以下这些例子的展示让读者对坏代码有直观的感觉。
不好的函数名称(Bad Function Name)
如 do(),这样的函数名称没有多少信息量;又如 myFunc(),这样的函数名称,个人色彩过于强烈,也没有足够信息量。
不好的变量名称(Bad Variable Name)
如 a、b、c、i、j、k、temp,这样的变量名称在很多教科书中经常出现,很多人在上学期间写代码时也会经常这样用。如果作为局部变量,这样的名称有时是可以接受的;但如果作为作用域稍微大的变量,这样的名称就非常不可取了。
没有注释(No Comments)
有写注释习惯的软件工程师很少,很多软件工程师认为写注释是浪费事件,是 “额外” 的工作。但是没有注释的代码,阅读的成本会比较高。
函数不是单一目的(The Function has No Single Purpose)
如 LoadFromFileAndCalculate()。这个例子是我编造的,但现实中这样的函数其实不少。很多函数在首次写出来的时候,就很难表述清除其用途;还有一些函数随着功能的扩展,变得越来越庞杂,也就慢慢地说不清它的目的了。
这方面的问题可能很多人都没有充分地认识到—非单一目的的函数难以维护,也难以复用。
不好的排版(Bad Layout)
不少人认为,程序可以正常执行就行了,所以一些软件工程师不重视对代码的排版,认为这仅仅是一种 “形式”。
没有排好版的程序,在阅读效率方面会带来严重问题。这里举一个极端的例子;对于 C 语言来说,“;” 可作为语句的分隔符,而 “缩进” 和 “换行” 对于编译器来说是无用的,所以完全可以把一段 C 语言程序都 “压缩” 在一行内。这样的程序是可以运行的,但是对人来说,可读性非常差。这样的程序肯定是我们非常不希望看到的。
无法测试(None Testable)
程序的正确性要依赖测试来保证(虽然测试并不能保证程序完全无错)。无法或不好为之编写测试用例的程序,是很难有质量保证的。