10高质量的子程序概述
1. 子程序
子程序是为实现一个特定的目的而编写的一个可被调用的方法或过程。例如 C++ 中的函数(funtion), Java 中的方法(method)。对于某些使用方式, C 和 C++ 中的宏(macro)也可以认为是子程序。你可以把创建高质量子程序的很多技术应用到所有这些情况中。
2. 高质量的子程序
抛开计算机本身,子程序也算得上是计算机科学中一项最为重大的发明了。子程序的使用使得程序变得更加易读,更易于理解,比任何变成语言的任何功能特性都更容易。
子程序也是迄今为止发明出来的用以节约空间和提高性能的最为重要的手段。设想,如果对每个子程序调用都重复写代码,而不是转到相应的子程序,那么代码会变得多么臃肿。再设想一下,如果要对一段代码的性能进行改善,但这些代码反复出现在不同的地方,而不是被纳入一个子程序,那该有多困难。正是子程序使得现代的编程成为可能。
3. 创建子程序的正当理由
3.1. 降低复杂度
创建子程序一个最主要的原因,就是为了降低程序的复杂度。可以通过创建子程序来隐藏一些信息,这样就不必再去考虑这些信息了。当然,在你要编写这个子程序的时候肯定是要考虑他们的。不过一旦程序写好了,你就应该能忘记这些细节,可以直接调用该子程序而无须了解其内部工作细节。创建子程序还有其他一些原因 —— 如缩小代码规模、改善可维护性、提高正确性等。但是如果没有子程序的抽象能力,我们的智力将根本无法管理复杂的程序。
当内部循环或条件判断的嵌套层次很深时,就意味着需要从子程序中提取出新的子程序了。把嵌套的部分提取出来形成一个独立的子程序,可以降低外围子程序的复杂度。
3.2. 引入中间、易懂的抽象
把一段代码放入一个命名恰当的子程序内,是说明这段代码用意最好的方法之一。与读下面这一串语句相比,
if(node <> NULL) then
while ( node.next <> NULL) do
node = node.next
leafName = node.next
end while
else
leafName = ""
end if
读懂下面这条语句就更容易:
leafName = GetLeafName(node);
这段新程序如此之短,只要给它取个好的名字就足够说明它的用意了。与上面的 8 行代码相比,这个名字提供了更高层次的抽象,从而使代码更具可读性,也更容易理解,同时也降低了原来包含着上面那段代码的程序的复杂度。
3.3. 避免代码重复
毋庸置疑,创建子程序最普遍的原因是为了避免代码重复。事实上,如果在两段子程序内编写相似的代码,就意味着代码分解出现了差错。这时应该把两段子程序中的重复代码提取出来,将其中的相同部分放入一个基类,然后再把两段程序中的差异代码放入派生类中。还有另一个办法,你也可以把相同的代码放入新的子程序中,再让其余的代码来调用这个子程序。
与代码的重复出现相比,让相同的代码只出现一次可以节约空间。代码改动起来也更方便,因为你只需要在一处修改即可。这时的代码也会更加可靠,因为为了验证代码的正确性,你只需要检查一处代码。同时,这样做也会使改动更加可靠,因为你可以避免需要相同的更改时,却做了一些略有不同的修改。
3.4. 支持子类化
覆盖(override)简短而规整的子程序所新代码的数量,要比覆盖冗长而邋遢的子程序更少。如果你能让覆盖的子程序保持简单,那你在实现派生类的时候也会减少犯错的几率。
3.5. 隐藏顺序
把处理事件的顺序隐藏起来是一个好主意。比如,如果一个程序通常都是先从用户那里读取数据,然后再从一个文件夹中读取辅助数据,那么,无论是从用户那里读取数据的子程序还是从文件中读取数据的子程序,都不应该依赖另一个子程序是否已执行。
再举一个有关顺序的例子,假设你写了两行代码,先读取栈顶的数据,然后减少 stackTop 变量的值。你应该把这两行代码放到一个叫 PopStack() 的子程序中。从而把这两行代码所必须执行党的顺序隐藏起来。把这种信息隐藏起来,比让它们在系统内到处散布要好得多。
3.6. 隐藏指针操作
指针操作的可读性通常都很差,而且也容易出错。通过把这些操作隔离在子程序内部,你就可以把精力集中于操作的意图本身,而不是指针操作机制的细节。同时,如果此类操作都能在一个位置完成,那么你对代码的正确性就会更有把握。如果你发现了比指针更合适的数据类型,也可以对程序做出修改,而不用担心会破坏了那些原本要使用指针的代码。
3.7. 提高可移植性
可以用子程序来隔离程序中不可移植的部分。从而明确识别和隔离未来的移植工作。不可移植的部分包括编程语言所提供的非标准功能、对硬件的依赖,以及对操作系统的依赖等。
3.8. 简化复杂的波尔判断
为了理解程序的流程,通常并没有必要去研究那些复杂的布尔判断的细节。应该把这些判断放入函数中,以提高代码的可读性,因为:(1)这样就把判断的细节放到一边了;(2)一个具有描述性的函数名字可以概括出该判断的目的。
把布尔判断的逻辑放入单独的函数中,也强调了它的重要性。这样做也会激励人们在函数内部做出更多的努力,提高判断代码的可读性。最终,代码的主流程和判断代码都变得更加清晰。
3.9. 改善性能
通过使用子程序,你可以只在一个地方优化代码。把代码集中在一处可以更方便地查出哪些代码的运行效率低下。同时,在一处进行的优化,就能使用到(无论是直接调用还是间接使用)该子程序的所有代码都从中受益。把代码集中到一处之后,想用更高效的算法或更快速高效的语言来重写代码也更容易做了。
3.10 . 确保所有的子程序都很小
编写有效的子程序时,一个最大的心理障碍是不情愿为一个简单的目的而编写一个简单的子程序。一个一个只有三行代码的子程序可能看起来有些大才小用,单经验可以表明,一个很好而又小巧的子程序会有很多用。