上一节我们讨论了美丽的子程序对整体系统的影响,但是大家可能还是比较模糊,让我们来看一个实际的例子:
在讨论高质量子程序的细节问题之前,我们首先来考虑两个基本名词。什么叫“子程序”?子程序是具有单一功能的可调用的函数或过程。比如C 中的函数,Pascal 或Ada 中的函数或过程,Basic中的子程序或Fortran 中的子程序。有时,C 中的宏指令或者Basic中用GOSUB调用的代码块也可认为是子程序。在生成上述函数或过程中,都可以使用创建高质量子程序所使用的技术。什么是“高质量的子程序”?这是一个比较难以回答的问题。反过来最简单回答方式是指出高质量子程序不是什么。下面是一个典型的劣质子程序(用Pascal写成):
Procedure HandleStuff ( Var InputRec:CORP_DATA,CrntQtr:integer,
EmpRec:Emp_DATA, Var EstimRevenue:Real, YTDRevenue:Real,
ScreenX:integer,ScreenY:integer,Var NewColor:Color_TYPE,
Var PrevColor:COLOR_TYPE,Var Status:STATUS_TYPE,
ExpenseType:integer);
begin
for i := 1 to 100 do begin
InputRec.revenue[1]:= 0;
InputRec.expense[i]:=CorpExpensse[CrntQtr,i]
end;
UpdateCorpDatabase(EmpRec);
EstimRevenue:=YTDRevenue * 4.0 /real(CrntQtr)
NewColor:=PrevColor;
status:=Success;
if ExpenseType=1 then begin
for i:= 1 to 12 do
Profit[i]:= Revenue[i]-Expense.Type[i]
end
else If ExpneseType= 2 then begin
Peofit[i]:=Revenue[i] - Expense.Type2[i]
end
else if ExpenseType= 3 then
begin
Profit[i]:=Revenue[i] - Expense.Type3[i]
end
end
这个子程序有什么问题?给你一个提示:你应该至少从中发现10个问题。当你列出所发现的问题后,再看一下下面所列出的问题;
· 程序的名字让人困惑。Handlestuff()能告诉我们程序是干什么的吗?
· 程序没有被说明。
· 子程序的布局不好。代码的物理组织形式几乎没有给出其逻辑组织形式的任何信息。布局的使用过于随心所欲,程序每一部分的布局都是不一样的。关于这一点。只要比较一下ExpenseType=2 和ExpenseType=3 两个地方的风格就清楚了。
· 子程序的输入变量值InPutRec 被改变过。假如它作为输入变量,那它的值就不该变化。假如要变化它的值,就不该称之为输入变量InputRec。
· 子程序进行了全局变量的读写操作。它从CorpExpense 中读入变量并写给Profit。它应该与存取子程序通信,而不应直接读写全局变量。
· 这个子程序的功用不是单一的。它初始化了某些变量。对一个数据库进行写操作,又进行了某些计算工作,而它们又看不出任何联系。一个子程序的功用应该是单一,明了的。
· 子程序中没有采取预防非法数据的措施。假如CrntQtr 的值为“0”,那么,表达式YTDRevenue*4.0/real(CrntQtr)就会出现被零除的错误。
· 程序中使用了几个常数:100, 4.0, 12, 2和3。
· 在程序中仅使用了域的CORP_DATA 型参数的两个域。假如仅仅使用两个域,那就该仅仅传入特定的域而不是整个结构化变量。
· 子程序中的一些参数没有使用过。ScreenX 和ScreenY 在程序中没有涉及。
· 程序中的一个参数被错误标定了。PrevColor被标定为变量型参数,然而在程序中又没有对其赋值。
· 程序中的参数太多。程序中参数个数的合理上限应该是七个左右。而这个程序中则多达11个。程序中的参数多得怕人,恐怕没谁会仔细检查它们,而且连数一下都不愿意。
这么一小段代码竟然有这么多的错误,这些错误你是不是能够都避免呢?假如是,那么恭喜你,你的程序员的功底相当深。虽然说这些错误并不是很大不了的东西,丝毫不影响程序的正确性和速度。在系统越来越复杂的现代,假如系统里的每一个子程序的复杂度、可读性、耦合度都影响到整体系统的成功与否。但是假如是在写个人的程序,那你是对自己的虐待,假如是做为开发团体的一分子,那么,你的做法是对同伴的虐待。
除了计算机本身之外,子程序可以说是计算机科学最重大的发明。子程序使得非常好读而且也非常轻易理解,编程语言中的任何特性都不能和这一点相比。像上例中那样使用子程序,简直就是对子程序的践踏,甚至可以说是一种犯罪。
子程序也是节省空间和提高性能的最好手段。想象一下,假如用代码段去代替程序中对子程序的每一次调用,那么程序将会有多么庞大。假如不是把多次重复使用的代码段存放在子程序中,而是直接把它放在程序中,那么对其进行性能改进将是一件很困难的事。是子程序使现代编程成为可能。
现在,你可能有些不耐烦。“是好,子程序的确很了不起,我一直都在使用它”。你说,“你的讨论似乎像是在纠正什么,你到底想让我做什么呢?”
我想说的是:有许多合理的原因使得我们去生成子程序。但是生成方法有好有坏。作为一个计算机专业的本科生,可以认为生成子程序的主要原因是避免代码段的重复。我所用的入门课本告诉我说,之所以使用于程序,是因为它可以避免代码段的重复,从而使得一个程序的开发、调试、注释和维护工作都变得非常轻易。除了一些关于如何使用参数和局部变量的语法细节之外,这就是那本课本关于子程序理论与实践内容的全部。这实在不是一个完全而合理的解释。
子程序的好处我们在上一节有具体的阐述,既然子程序有着这么多的好处,那么编写一个美丽甚至完美的子程序对我们有什么困难的呢,其实这是一个思想和习惯的问题。程序员就一定是那种穿裤衩儿背心,听着摇滚写程序的人吗?我觉得真正的程序员应该是一丝不苟的,分析需求、确定架构、做概要设计和具体设计、单元测试、整理所有文档。麻烦吧,是很麻烦,但是这会为你带来意想不到的惊喜。你会发现你的程序是那么的清楚:再多的版本也不会把你的脑袋搞晕,一个小错误在数万行的代码中轻易的被定位del,用户提出新的需求是你的脸色不再难看…。所有的这些只意味着一件事:你成功的控制住了出自你手的程序。
编写子程序的最大心理障碍是不情愿为了一个简单的目的而去编写一个简单的子程序。写一个只有两或三行代码的子程序看起来是完全没有必要的。但经验表明,小的子程序也同样是很有帮助的
。
小型子程序有许多优点,其中之一是改进了可读性。我曾在程序中采用过如下这样一个仅有一行的代码段,它在程序中出现了十几次:
Points = DeviceUnits * ( POINTS_PER_INCH / DeviceUnitsPerInch ( ) )
这决不是你所读过的最复杂的一行代码。很多人都明白它是用来转换的。他们也会明白程序中的每行这个代码都在作同一件事,但是,它还可以变得更清楚些,所以,我创建了一个恰当命名的子程序来作这些工作。
DeviceUnitsToPoints(DeviceUnits Integer): Integer;
begin
DeviceUnitsToPoints = DeviceUnits *
( POINTS_PER_INCH / DeviceUnitsPerInch ( ) )
end
在用这段子程序来代替那些十几次重复出现的代码行后,这些代码行基本上都成了如下的样子:
Points = DeviceUnitsToPoints ( DeviceUnits )
这显然更具可读性,甚至已经达到了自我说明的地步。
这个例子还暗示了把简单操作放入函数的另外一个原因:简单操作往往倾向于变成复杂操作。在写这个子程序时我还不知道这一点,但在某种情况下,当某个设备打开时,DeviceUnitPerInch()会返回零,这意味着我不得不考虑到被“0”除的情况,这样,又需要另外的三行代码;
DeviceUnitsToPoints( DeviceUnit:integer):integer;
begin
if( DeviceUnitsPerInch ( ) <> 0 ) then
DeviceUnitsPoints = DeviceUnits *
( POINTS_PER_INCH / DeviceUnitsPerInch ( ) )
else
DeviceUnitsToPoints= 0
end
假如原来的代码行仍然在程序中出现十几次,那么这一测试也要重复十几次,需要新增加36 行代码。而一个简单子程序轻而易举地便把36 变成了3。
在这一节中,我们看到了子程序的质量问题,高质量的子程序和低质量的子程序对系统的影响的差别是极为明显的。你可以喝着咖啡,好整以暇的看着你井然有序的程序,也可以抓耳挠腮的对着你混乱不堪的程序几乎发狂。这都取决于你良好的编程习惯,选择一下吧。
视频教程列表
文章教程搜索
C语言程序设计推荐教程
C语言程序设计热门教程
|