写模块化的代码
最后更新于:2022-04-01 05:22:17
有些人吵着闹着要让程序“模块化”,结果他们的做法是把代码分部到多个文件和目录里面,然后把这些目录或者文件叫做“module”。他们甚至把这些目录分放在不同的VCS repo里面。结果这样的作法并没有带来合作的流畅,而是带来了许多的麻烦。这是因为他们其实并不理解什么叫做“模块”,肤浅的把代码切割开来,分放在不同的位置,其实非但不能达到模块化的目的,而且制造了不必要的麻烦。
真正的模块化,并不是文本意义上的,而是逻辑意义上的。一个模块应该像一个电路芯片,它有定义良好的输入和输出。实际上一种很好的模块化方法早已经存在,它的名字叫做“函数”。每一个函数都有明确的输入(参数)和输出(返回值),同一个文件里可以包含多个函数,所以你其实根本不需要把代码分开在多个文件或者目录里面,同样可以完成代码的模块化。我可以把代码全都写在同一个文件里,却仍然是非常模块化的代码。
想要达到很好的模块化,你需要做到以下几点:
* 避免写太长的函数。如果发现函数太大了,就应该把它拆分成几个更小的。通常我写的函数长度都不超过40行。对比一下,一般笔记本电脑屏幕所能容纳的代码行数是50行。我可以一目了然的看见一个40行的函数,而不需要滚屏。只有40行而不是50行的原因是,我的眼球不转的话,最大的视角只看得到40行代码。
如果我看代码不转眼球的话,我就能把整片代码完整的映射到我的视觉神经里,这样就算忽然闭上眼睛,我也能看得见这段代码。我发现闭上眼睛的时候,大脑能够更加有效地处理代码,你能想象这段代码可以变成什么其它的形状。40行并不是一个很大的限制,因为函数里面比较复杂的部分,往往早就被我提取出去,做成了更小的函数,然后从原来的函数里面调用。
* 制造小的工具函数。如果你仔细观察代码,就会发现其实里面有很多的重复。这些常用的代码,不管它有多短,提取出去做成函数,都可能是会有好处的。有些帮助函数也许就只有两行,然而它们却能大大简化主要函数里面的逻辑。
有些人不喜欢使用小的函数,因为他们想避免函数调用的开销,结果他们写出几百行之大的函数。这是一种过时的观念。现代的编译器都能自动的把小的函数内联(inline)到调用它的地方,所以根本不产生函数调用,也就不会产生任何多余的开销。
同样的一些人,也爱使用宏(macro)来代替小函数,这也是一种过时的观念。在早期的C语言编译器里,只有宏是静态“内联”的,所以他们使用宏,其实是为了达到内联的目的。然而能否内联,其实并不是宏与函数的根本区别。宏与函数有着巨大的区别(这个我以后再讲),应该尽量避免使用宏。为了内联而使用宏,其实是滥用了宏,这会引起各种各样的麻烦,比如使程序难以理解,难以调试,容易出错等等。
* 每个函数只做一件简单的事情。有些人喜欢制造一些“通用”的函数,既可以做这个又可以做那个,它的内部依据某些变量和条件,来“选择”这个函数所要做的事情。比如,你也许写出这样的函数:
~~~
void foo() {
if (getOS().equals("MacOS")) {
a();
} else {
b();
}
c();
if (getOS().equals("MacOS")) {
d();
} else {
e();
}
}
~~~
写这个函数的人,根据系统是否为“MacOS”来做不同的事情。你可以看出这个函数里,其实只有`c()`是两种系统共有的,而其它的`a()`, `b()`, `d()`, `e()`都属于不同的分支。
这种“复用”其实是有害的。如果一个函数可能做两种事情,它们之间共同点少于它们的不同点,那你最好就写两个不同的函数,否则这个函数的逻辑就不会很清晰,容易出现错误。其实,上面这个函数可以改写成两个函数:
~~~
void fooMacOS() {
a();
c();
d();
}
~~~
和
~~~
void fooOther() {
b();
c();
e();
}
~~~
如果你发现两件事情大部分内容相同,只有少数不同,多半时候你可以把相同的部分提取出去,做成一个辅助函数。比如,如果你有个函数是这样:
~~~
void foo() {
a();
b()
c();
if (getOS().equals("MacOS")) {
d();
} else {
e();
}
}
~~~
其中`a()`,`b()`,`c()`都是一样的,只有`d()`和`e()`根据系统有所不同。那么你可以把`a()`,`b()`,`c()`提取出去:
~~~
void preFoo() {
a();
b()
c();
~~~
然后制造两个函数:
~~~
void fooMacOS() {
preFoo();
d();
}
~~~
和
~~~
void fooOther() {
preFoo();
e();
}
~~~
这样一来,我们既共享了代码,又做到了每个函数只做一件简单的事情。这样的代码,逻辑就更加清晰。