ISO/IMC 9126对于软件的质量进行了定义,其中包括如下几项:
可理解性(Understandabiliy)、可维护性(Maintainability)、可扩展性(Extensibility)、容错性(Fault Tolerance)和可测试性(Testability)。
这些都和阅读质量相关。在诸多组织里,管理人员和开发人员一样都把目光集中在软件的运行质量上,而很少关心代码的阅读质量。其中很重要的原因是,这些人从来没有听过这些概念。即使有些组织提出过编码规范的要求,但是这也只是一种形式上的要求,至于实际上做的如何,很少有人关心。那么代码的阅读质量为什么这么重要,它又用哪些指标进行衡量呢?
以我参加过的A项目为例,那些代码阅读起来难懂,逻辑关系复杂的地方发生了Bug的话,平均修改时间为3天左右,而那些在阅读方面下了功夫的代码,发生Bug后的平均修改时间为1小时左右。另外一个B项目,没有经过阅读质量提升的代码无法执行自动测试。而进过阅读质量提升的代码可以执行测试,项目的整体工时缩短为原来的1/20。代码的阅读质量不是单独存在的,它和其他一些软件工程上的指标是有关联的。提升阅读质量会在其他指标上产生倍增的效果。
明确可以测量的代码的阅读质量的指标有如下几个:
1.嵌套层数(Nested Depth)
如前文所述,嵌套层数多的代码难以阅读。并且嵌套层数多的地方也经常是Bug易发生,难修改的地方。
2.圈复杂度(Cyclomatic Complexity)
圈复杂度是用来表示函数从入口到出口的路径条数的指标,研究表明,圈复杂度越高,则bug越多。通常我们建议项目中的圈复杂度不要超过10,最好控制在5以下。
但是,阅读质量还有一些不可以简单量化的考察点:
1. 命名的友好性
命名友好的代码容易阅读,读起来不产生迟钝,也很少需要跳来跳去的查阅。
2. 注释的友好性
友好的注释只在需要的地方才出现,它对代码的理解起辅助作用,而不友好的注释则在不该出现的地方出现;地方对了却又要么文不对题,要么词不达意。
3. 风格的友好性
风格友好的代码,读起来不用让读者自己对括号进行配对。也无需自性调整对齐。
4. 结构的友好性
结构友好的代码,嵌套层数少,在循环中很少会用跳转语句或者下标符号,在分支判定中很少用复杂的逻辑运算。
总而言之,代码的阅读质量可以以其被理解的速度来评价。
下面是一些书写高质量的代码的基本原则:
1. Don't Repeat Yourself
不要重复你自己,重复代码,类似代码都是问题的隐患。程序员为了能够节省时间,喜欢复制现成的代码,稍加修改制作自己的调用的函数(方法),这种做法将会把原有的问题复制一份,修改的时候也要修改两份。重复代码的问题在诸多问题之中往往被列为首位。
2. Single Responsibility Principle
单一职责原则。一个类只干与自己相关的事情,一个方法只做一件事情,一个变量只表达一个意思。如果违背这个原则,那么代码理解的速度就会降低。并且,调用的时候会由于名称的误导而导致重复调用、意外行为等不可预期的错误。而修改代码的时候也因为其职责过多而致使影响性变大,导致无从下手,或者不敢下手。
3. Open Closed Principle
开放封闭原则,指的是代码对变更封闭,对扩展开放。当系统需要扩展的时候,可以通过派生子类或者重载函数的方式进行扩展,而无需修改原来的代码。这样做的好处是,当系统进行扩展的时候由于无需修改原来的代码,所以对原来系统的影响性也可以降到最小。
4. Liskov Substitute Principle
里氏代换原则。子类可以替代父类。这句话看似废话。但是实际上含义很深。比如经典的对面向对象技术的质疑话题:园非椭圆问题。即根据面向对象原则,园应该是椭圆的一个子类。但是当椭圆进行单轴拉伸的时候,圆无法做到这一点。实际上这违反了里氏代换原则。即圆是受限制的椭圆,圆无法继承椭圆所有行为和属性。所以,在面向对象课上老师爱用的例子:动物->狗这个例子也会有其谬误之处。动物在这里更应该是多个接口,而不是类,因为它不能保证所有的属性都会被所有的子类正确的继承。例如:
5. Interface Segregation Principle
接口分离原则。一个接口只做一类事情,将无关的行为分离成为其他的接口。从而使得接口单元变小。则调用方只需要了解自己相关的几个接口就可以了,而无需了解那些与自己无关的接口。同时也避免了去访问那些它不应该访问的接口。
6. Dependency Inversion Principle
依赖倒置原则。有两层意思:访问依赖于抽象,不依赖于具体实现;实现依赖于抽象,抽象不依赖于实现。当调用方和实现都依赖于接口的时候,二者的耦合度就大为降低了。
7. Law of Demeter
迪米特法则。也叫最少知识原则。即对象只需要了解那些它需要的属性,只访问那些和它关系最密切的对象。这样可以减少对象的外部依赖。但是,需要了解的是,迪米特法则会创建大量的中介类。
下一讲,将会讲述造成劣质代码的第二个原因——编程语言知识不足