可读的代码

过去,我有过在不同的场合吐槽别人的代码写得烂。而我写的仅仅是比别人好一点而已——而不是好很多。

然而这是一件很难的事,人们对于同一件事物未来的考虑都是不一样的。同样的代码在相同的情景下,不同的人会有不同的设计模式。同样的代码在不同的情景下,同样的人会有不同的设计模式。在这里,我们没有办法讨论设计模式,也不需要讨论。

我们所需要做的是,确保我们的代码易读、易测试,看上去这样就够了,然而这也是挺复杂的一件事:

  • 确保我们的变量名、函数名是易读的
  • 没有复杂的逻辑判断
  • 没有多层嵌套 (事不过三)
  • 减少复杂函数的出现(如,不超过三十行)
  • 然后,你要去测试它。这样你就知道需要什么,实际上要做到这些也不是一些难事。

只是首先,我们要知道我们要自己需要这些。对于没有太多编程经验的人,建议先从两个基本点做起:

  • 命名
  • 函数长度

首先要说的就是程序员认为最难的一个话题了——命名。

命名

命名是一个特别长的,也是特别忧伤的故事。我想作为一个程序员的你,也相当恐惧这件事。一个好的函数名、变量名应该包含着这个函数的信息,如这个函数是干什么的,或者这个函数是怎么来的,这个变量名存储的是什么。

正因为取名字是一件很重要的事,所以它也是一件很难的事。一个好的函数名、变量名应该能正确地表达出它的涵义。如你可以猜到下面的代码中的i是什么意思吗?

  1. fruits = ['banana', 'apple', 'mango']
  2. for i in fruits: # Second Example
  3. print 'Current fruit :', i

那如果换成下面的代码会不会更容易阅读呢?

  1. fruits = ['banana', 'apple', 'mango']
  2. for fruit in fruits: # Second Example
  3. print 'Current fruit :', fruit

而命令还存在于对函数的命名上,如我们可能会用 getNumber 来表示去获取一个数值,但是要知道这样的命名并不是在所有的语言中都可以这样用。如在 Java 中存在 getter 和 setter 这种模式,如下的代码所示:

  1. public String getNumber() {
  2. return number;
  3. }
  4. public void setNumber(String number) {
  5. this.number = number;
  6. }

如果我们是去取某个东西的数值,那么我们应该使用 retrieveNumber 这样的更具代表性的名字。

在《编写可读代码的艺术》也提到了这几点:

  1. 选择专业的词。最好是可以和业务相关的,它应该极具表现力。
  2. 避免像 tmp 和 retval 这样泛泛的名字。不得不提到的一点是,tmp 实在是一个有够烂的名字,将其变为 timeTemp 或者类似的会更直观。它只应该是名字中的一部分。
  3. 用具体的名字代替抽象的名字。
  4. 为名字赋予更多的信息。
  5. 名字应该有多长。
  6. 利用名字的格式来传递含义。

函数长度

函数是指一段在一起的、可以做某一件事儿的程序。

这就意味着从定义上来说,这段函数应该只做一件事——但是什么才是真正的一件事呢?实际上还是 TASKING,将一个复杂的过程一步步地分解成一个个的函数,每个函数只做他的名称对应的事。对于一个任务来说,他有一个稳定的过程,在这个过程中的每一步都可以变成一个函数。

因此,长的代码意味着一件事——这个函数可能违反了单一职责原则,即这个类做了太多的事。通常来说,一个类,只有一个引起它变化的原因。当一个类有多个职责的时候,这些代码就容易耦合到一起了。

对于函数长度的控制是为了有效控制分支深度。如果我们用一个函数来实现一个复杂的功能,那么不仅仅在我们下次阅读的时间会花费大量的时间。而且如果我们的代码没有测试的话,这些代码就会变得越来越难以理解。而在我们写这些函数的时候就没有测试,那么这个函数就会变得越来越难以测试,它们就会变成遗留代码。

其他

虽然只想介绍上面的简单的两点,但是顺便在这里也提一下重复代码~~。

重复代码

在《重构》一书中首先提到的 Code Smell 就是重复代码(Duplicate Code)。重复代码看上去并不会影响我们的阅读体验,但是实际上会发生这样的事——重复的代码阅读体验越不好。

DRY(Don’t Repeat Yourself)原则是特别值得玩味的。当我们不断地偏执的去减少重复代码的时候,会导致复杂度越来越高。在适当的时候,由于业务发生变更,我们还需要去拆解这些不重复的代码。