瑞星卡卡安全论坛技术交流区系统软件 【转载】软件开发人员必备工具书 【代码大全】

«12345678»   5  /  16  页   跳转

【转载】软件开发人员必备工具书 【代码大全】

觉得是这样,那么还需要进一步细化PDL,直到有这种感觉为止。
    在每一行注释下面填上代码。在每一行PDL注释语句下面填上代码。这有点像给报纸排版。
首先画好轮廓线,然后再把每一篇文章填到空格中,每一个PDL注释行都相当于给代码画的轮
廓线,而代码相当于文章。同文学文章的长度一样,代码的长度也是由它所要表达的内容多少
决定的。程序的质量则取决于它的设计思想的侧重点和巧妙程度。
在本例中,前两行PDL注释产生了两行代码:
        Procedure RecordErrorMessage
(
ErrorCode:ErrorCode_t;
var status:Status_t
) ;
{ This routine outputs an error message based on an error code
Supplied by the calling routine The way it outputs the message
depends on the current processing state,which it retrieves
on its own. It returns a variable indicating success or failure.}
begin
{ Set the default status }
Status:=Failure;
{ look up the message based on the error code}
LookupErrorMessage(ErrorCode,ErrorMessage);
{ if the error code is valid }
{ determine the processing method }
{ if doing interactive processing }
{ Print the error message interactively and declare success }
{ else doing batch processing }
{ if the batch message file opens properly }
{ log the error message to the batch file,
close the file,and declare success }
{ else the message code is not valid }
{ notify the user that an internal error has been detected }
end; { RecordErrorMessage() }
    这是一个编码的开始,使用了变量Errormessage,所以需要说明它。如果是在事后进行注
释,那么,用两个注释行来注释两行代码就不必要了。但是,采用目前这种方法,是注释的字
面内容而不是它注释了多少行代码。现在,注释行已经存在了,所以还是将其保留。
    代码需要变量说明,而且在每一注释行下面都要加人代码,以下是完整的子程序:
      procedure RecordErrorMessage

这里是填充的代码
这里是新变量ErrorMessage
gototop
 

ErrorCode:ErrorCode_t;
var Status:Status_t
);
{This routine outputs an error message based on an error code
Supplied by the calling routine. The way it outputs the message
depends on the current processing state, whict it retrieves
on its own. It returns a variable indicating success or failure. }
var
ProcessingMehod: ProcessingMethod_t;
ErrorMessage: Message_t;
FileStatus: Status_t;
begin
{set the default status}
Status:=Failure;
{look up the message based on the error code }
LookupErrorMessage(ErrorCode,ErrorMessage);
{if the error code is valid}
if (ErrorMessage.ValidCode) then begin
{determine the processing method}
ProcessingMethod := CurrentProcessingMehod;
{if doing interaction processing}
if (ProcessingMethod = Interactive) then begin
{print the error message interactively and declare success }
PrintInteractiveMessage(ErrorMessage.Text);
Status := Success
end
{else doing batch processing}
else if (ProcessingMethod = Batch) then begin
{if the batch message file opens properly}
FileStatus := OpenMessageFile;
If (FileStatus = Success) then begin
{log the error message to the batch file,close the file,
and declare success}
这里是变量声明
gototop
 

LogBatchMessage ( ErrorMessage.Text ) ;
CloseMessageFile;
Status := Success
end { if }
end { else }
end
{ else the message code is not valid }
else begin
{ notify the user that an interanl error has been detected }
PrintlnteractiveMessage ( 'Internal Error; Invalid error code' ,
'in RecordErrorMessage()' }
end
end; { RecordErrorMessage () }

每一个注释都产生了一行或一行以上的代码,每一块都在注释的基础上形成了一个完整的
思想。保留了注释以便提供一个关于代码的高层次解释,在子程序的开始,对使用的所有变量
都作了说明。
现在,让我们再回过头来看一下前面的关于这个例子的要求定义和最初的PDL,从最初的
5 行要求定义到12 行的初始PDL,接着这段PDL 又扩大成为一个较大的子程序。即使要求定
义是很详尽的。子程序的创建还是需要在PDL和编码阶段进行潜在的设计工作。这种低层次的
设计工作正是为什么编码不是一件琐碎事的原因,同时,也说明本书的内容是很重要的。
非正式地检查代码。在注释下面填上代码之后,可以对每一块代码作一简略检查。尽力想
一下什么因素可能破坏目前的块,然后证明这种情况不会发生。
一旦完成了对某一子程序的实现,停下来检查一下是否有误。在PDL 阶段,就已经对其作
了检查。但是,在某些情况下,某些重大问题在子程序实现之前是不会出现的。
使得问题直到编码阶段才出现的原因是多方面的。在PDL阶段引入错误可能到了详尽的实
现阶段才会变得明显。一个在PDL阶段看起来完美无缺的设计,在用程序语言实现时可能会变
得一塌糊涂。在详尽的实现阶段,可能会发现在结构设计或功能分析阶段引入的错误,最后,
代码可能存在一种司空见惯的错误——混用语言,毕竟大家都不是尽善尽美的嘛。由于上述原
因,在继续工作之前,要检查一下代码。
进行收尾工作。检查完代码是否存在问题后,再检查一下它是否满足本书所提到的通用质
量标准。可以采取几个步骤来确认子程序的质量是否满足要求。
·  检查子程序的接口。确认所有的输入和输出数据都已作出了解释,并且使用了所有
参数。关于细节问题,见5.7 节“怎样使用子程序参数”。
·  检查通用设计质量。确认子程序只完成一项任务而且完成得很好,与其它于程序交
叉是控制不严的表现。并且,应该采用了预防错误的设计。关于细节问题,见第五
章“高质量程序的特点”。
·  检查子程序的数据。查找出不精确的变量名、没有使用的数据、没有说明的数据等
gototop
 

等。要了解详情,见关于数据使用的第八到第十二章。
·  检查子程序的控制结构。查找无限循环、不适当的嵌套等错误。详见关于使用控制结
构的第13 到17 章。
·  检查子程序设计。确认已说明了子程序的表达式、参数表和逻辑结构。详见第18章 “设
计与风格”。
·  检查子程序的文档。确认被翻译成注释的PDL仍然是精确的。检查算法描述,查找接
口假设和非显式依赖的文件资料,查找不清楚的编码等等。详见第19 章“自我证明的
代码”。
按需要重复步骤。如果程序的质量很差,请返回PDL阶段。高质量程序是一个逐步的过程,
所以在重新进行设计和实现活动时,不要犹豫不决。
4.5  检查子程序
在设计并实现了子程序之后,创建活动的第三个主要步骤是进行检查,以确认所实现的软
件是正确的。你或许会问,对代码进行的非正式检查和收尾工作难道不足以保证其正确性吗?
的确,这两项工作会从某种程度上保证代码正确性,但不能完全保证,在这一阶段工作中漏掉
的错误,只有在后面的测试工作中才会被发现。而到那时纠正它们的成本将变得很高,因此,
最好现在就进行查错工作。
在心里对子程序进行查错处理。在前面提到过的非正式检查和清扫工作就是两种内心检查
方法。另一方法是在心中执行每一个路径。在心中执行一个子程序是比较困难的,这个困难也
是造成很困难保持子程序小规模的原因之一。要保证检查到每一个规定路径和中止点,同时还
要检查所有的例外情况。可以自己进行这项工作,此时叫作“桌面检查”。也可以与同事们一道
检查,这叫作“同事评审”、“过一遍”或者“视察”,这要取决于如何进行这项工作。
业余爱好者与职业程序员之间的最大区别就是迷信还是理解。在这里,“迷信”这个词指并
不是指在月圆之夜产生各种错误或使你毛骨悚然的一段程序。它指的是你对代码的感觉代替对
代码的理解。如果你总是认为编译程序或者硬件系统有故障,那说明你还处在迷信阶段。只有 5%
的错误是由编译程序、硬件或者是操作系统引起的(Brown and sampson, 1973, Ostrand and
Weyuher, 1984)。进入理解境界的程序员总是怀疑自己的工作,因为他们知道 95% 的错误出自
这里。要理解每一行编码的意义,并且要明白为什么需要它。没有仅仅因为有效便是正确的东
西。如果你不知道为什么它是有效的,那么往往它是无效的,只不过你没有发现罢了。
最后,要指出的是有了一个有效的子程序并非就完事大吉了。如果你不知道为什么它是有
效的,那就研究并讨论它,或者用替代方案重新实现一次,直到你弄明白为止。
编译子程序。如果检查完了子程序,那就开始编译它。直到现在才开始编译工作,似乎是
我们的工作效率太低了,因为早在几页之前程序就完成了。的确,如果早一些开始编译,让计
算机去检查没有声明的变量,命名冲突等是可能会节约一些时间。
但是,如果晚一些开始编译,将会获得许多收益。其中的一个主要原因是,一旦开始编译,
那么你脑袋里的秒表便开始嘀嗒作响了,在第一次编译之后,你就开始不停地想:下次编译一
定让它全对。结果,在这种“就只再编译一次”的压力下,作了许多匆忙的、更易产生错误的
修改,反而浪费了更多的时间。所以,在确信子程序是正确的之前,不要急于开始编译。
gototop
 

本书的重点之一,就是想告诉读者如何避免陷入把各种代码拼凑到一起,通过试运行检验
它是否有效的怪圈。而在确信程序是正确的之前,就匆忙开始编译,恰恰是陷入了这种怪圈。
如果你还没有进入这个怪圈,那最好还是当确信程序正确之后再开始编译。
以下是在编译时,尽可能地检查出全部错误的指导方针:
·  尽可能把编译程序的警告级别调到最高。只要允许,编译程序应尽量测试,将发现许
多难以察觉的错误。
·  消除所有编译程序指出的错误和提出警告的原因。注意编译程序关于你的代码说了些
什么。大量的警告往往意味着代码质量不高,所以应该尽量理解所得到的每个警 告。
在实际中,反复出现的警告可能产生以下影响:你忽略掉它们,而事实上它们掩盖了
更严重的错误。或者它们会变得使人痛苦,就像日本式的灌水酷刑。因此,比较安全
或痛苦较小的办法,是消灭这些隐蔽的问题以消除警告。
使用计算机来检查子程序错误。子程序编译之后,将其放入调试程序,逐步运行每一行代
码,要保证每一行都是按预期的运行。用这种简单的办法,你可以发现许多错误。
在调试程序中逐步运行程序之后,用在开发子程序时设计的测试用例对其进行测试。或许 
你将不得不搭些支架来支撑你的子程序——即那些仅在测试阶段用于支持子程序而最终不包括
在产品中的代码。这些代码可能是调用子程序的程序,也可能是被子程序所调用的子程序。
消除子程序中的错误。一旦发现有错误,就要消除它。如果开发的子程序此时问题较多, 那
它将在这一阶段耗费较长的时间。如果发现程序中的错误异乎寻常的多,那就重新开发一个,
不要试图修补它。修补往往意味着不充分的理解,而且肯定会在现在和将来产生更多的错误,
而进行一个全新的设计将防止这一点。恐怕没有比重新写一个完美无缺的子程序来代替一个漏
洞百出的子程序更能让人满意的事了。
4.5.l 检查表
创建子程序
·  是否检查过先决条件已经满足了?
·  定义子程序将要解决的问题了吗?
·  结构设计是否足够清楚,使得你可以给子程序起个好名字?
·  考虑过如何测试子程序了吗?
·  是否从模块化水平或者满足时间和内存要求角度考虑过效率问题?
·  是否查阅过参考书;以寻找有帮助的算法?
·  是否用详尽的PDL设计子程序?
·  在必要时,是否在逻辑设计步骤前考虑了数据?
·  是否检查过PDL,它很容易理解吗?
·  是否注意到了足以使你返回到结构设计阶段的警告(使用了全局数据,更适合其它子
程序的操作,等等)。
·  是否使用了PDL到代码流程,是否把PDL 作为编码基础并把原有的PDL 转为注释?
·  是否精确地把PDL翻译成了代码?
·  在作出假设时,验证它们了吗?
·  是从几个设计方案中选择了最好的,还是随意选择了一个方案?
gototop
 

·  是否彻底理解你的代码?它容易理解吗?
4.6  小    结
·  要想写好PDL,首先要用易懂的自然语言,避免拘泥于某种程序语言,其次要在意向
层次上写PDL,描述设计作什么而不是如何作。
·  PDL到代码流程方法是详细设计的有力工具,而且使得编码非常容易。可以把PDL直
接翻译成注释,但要注意保证注释是精确而有用的。
·  应该在工作的每一步中都检查子程序,并鼓励同事们检查。这样,可以在投入的资金
和工作努力最少时便发现错误,从而极大降低改错成本。
gototop
 

第五章    高质量子程序特点
目录
5.1  生成子程序的原因
5.2  子程序名称恰当
5.3  强内聚性
5.4  松散耦合性
5.5  子程序长度
5.6  防错性编程
5.7  子程序参数
5.8  使用函数
5.9  宏子程序
5.10  小结
相关章节
生成子程序的步骤:见第4 章
高质量模块的特点:见第6 章
通用设计技术:见7.5 节
软件结构设计:见3.4 节
第四章讲述了生成子程序时应该采取的步骤,其重点是创建过程。本章的重点则是子程序
本身,即区分好的子程序和低劣子程序的特征。
如果在进入现实而又困难的子程序细节问题之前,想阅读关于高层次设计的问题,那么请
首先阅读第七章,然后再阅读本章。由于模块也要比子程序抽象,因此,也可在读完第六章后
再阅读本章。
在讨论高质量子程序的细节问题之前,我们首先来考虑两个基本名词。什么叫“子程序”?
子程序是具有单一功能的可调用的函数或过程。比如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);
gototop
 

begin
for i := 1 to 100 do begin
InputRec.revenue[1]:= 0;
InputRec.expense:=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:= Revenue-Expense.Type
end
else If ExpneseType= 2 then begin
Peofit:=Revenue - Expense.Type2
end
else if ExpenseType= 3 then
begin
Profit:=Revenue - Expense.Type3
end
end
这个子程序有什么问题?给你一个提示:你应该至少从中发现10个问题。当你列出所发现
的问题后,再看一下下面所列出的问题;
· 程序的名字让人困惑。Handlestuff()能告诉我们程序是干什么的吗?
· 程序没有被说明(关于说明的问题已经超出了个别子程序的范围,详见第19章“自我
说明的子程序”)。
· 子程序的布局不好。代码的物理组织形式几乎没有给出其逻辑组织形式的任何信息。
布局的使用过于随心所欲,程序每一部分的布局都是不一样的。关于这一点。只要比
较一下ExpenseType=2 和ExpenseType=3 两个地方的风格就清楚了(关于布局问题,
详见第十八章“布局与风格”)。
· 子程序的输入变量值InPutRec 被改变过。如果它作为输入变量,那它的值就不该变
化。如果要变化它的值,就不该称之为输入变量InputRec。
· 子程序进行了全局变量的读写操作。它从CorpExpense 中读入变量并写给Profit。它应
该与存取子程序通信,而不应直接读写全局变量。
· 这个子程序的功用不是单一的。它初始化了某些变量。对一个数据库进行写操作,又
进行了某些计算工作,而它们又看不出任何联系。一个子程序的功用应该是单一,明
了的。
· 子程序中没有采取预防非法数据的措施。如果CrntQtr 的值为“0”,那么,表达式
YTDRevenue*4.0/real(CrntQtr)就会出现被零除的错误。
· 程序中使用了几个常数:100, 4.0, 12, 2和3。关于“神秘”(magic)数的问题见11.1
节“常数”
gototop
 

· 在程序中仅使用了域的CORP_DATA 型参数的两个域。如果仅仅使用两个域,那就该
仅仅传入特定的域而不是整个结构化变量。
· 子程序中的一些参数没有使用过。ScreenX 和ScreenY 在程序中没有涉及。
· 程序中的一个参数被错误标定了。PrevColor被标定为变量型参数,然而在程序中又没
有对其赋值。
· 程序中的参数太多。程序中参数个数的合理上限应该是七个左右。而这个程序中则多
达11个。程序中的参数多得怕人,恐怕没谁会仔细检查它们,而且连数一下都不愿意。
除了计算机本身之外,子程序可以说是计算机科学最重大的发明。子程序使得非常好读而
且也非常容易理解,编程语言中的任何特性都不能和这一点相比。像上例中那样使用子程序,
简直就是对子程序的践踏,甚至可以说是一种犯罪。
子程序也是节省空间和提高性能的最好手段。想象一下,如果用代码段去代替程序中对子
程序的每一次调用,那么程序将会有多么庞大。如果不是把多次重复使用的代码段存放在子程
序中,而是直接把它放在程序中,那么对其进行性能改进将是一件很困难的事。是子程序使现
代编程成为可能。
现在,你可能有些不耐烦。“是好,子程序的确很了不起,我一直都在使用它”。你说,“你
的讨论似乎像是在纠正什么,你到底想让我做什么呢?”
我想说的是:有许多合理的原因使得我们去生成子程序。但是生成方法有好有坏。作为一
个计算机专业的本科生,可以认为生成子程序的主要原因是避免代码段的重复。我所用的入门
课本告诉我说,之所以使用于程序,是因为它可以避免代码段的重复,从而使得一个程序的开
发、调试、注释和维护工作都变得非常容易。除了一些关于如何使用参数和局部变量的语法细
节之外,这就是那本课本关于子程序理论与实践内容的全部。这实在不是一个完全而合理的解
释。下面这节将详细描述为什么和怎样生成子程序。
5.1  生成子程序的原因
以下是关于为什么要生成于程序的一些合理原因,其中有些原因之间可能有互相重叠的地
方。
降低复杂性。使用子程序的最首要原因是为了降低程序的复杂性,可以使用子程序来隐含
信息,从而使你不必再考虑这些信息。当然,在编写子程序时,你还需要考虑这些信息。但是,
一旦写好子程序,就可能不必再考虑它的内部工作细节,只要调用它就可以了。创建子程序的
另外一个原因是尽量减小代码段的篇幅,改进可维护性和正确性。这也是一个不错的解释,但
若没有子程序的抽象功能,将不可能对复杂程序进行明智的管理。
一个子程序需要从另一个子程序中脱离出来的原因之一是,过多重数的内部循环和条件判
断。这时,可以把这部分循环和判断从子程序中脱离出来,使其成为一个独立的子程序,以降
低原有子程序的复杂性。
避免代码段重复。无可置疑,生成子程序最普遍的原因是为了避免代码段重复。事实上,
如果在两个不同子程序中的代码很相似,这往往意味着分解工作有误。这时,应该把两个子程
序中重复的代码都取出来,把公共代码放入一个新的通用子程序中,然后再让这两个子程序调
gototop
 

用新的通用子程序。通过使公共代码只出现一次,可以节约许多空间。这时改动也很方便,因
为只要在一个地方改动代码就可以了。这时代码也更可靠了,因为只需在一个地方检查代码。
而且,这也使得改动更加可靠,因为,不必进行不断地、非常类似地改动,而这种改动往往又
是认为自己编写了相同的代码这一错误假设下进行的。
限制了改动带来的影响。由于在独立区域进行改动,因此,由此带来的影响也只限于一个
或最多几个区域中。要把最可能改动的区域设计成最容易改动的区域。最可能被改动的区域包
括:硬件依赖部分、输入输出部分、复杂的数据结构和商务规则。
隐含顺序。把处理事件的非特定顺序隐含起来是一个很好的想法。比如,如果程序通常先
从用户那里读取数据,然后再从一个文件中读取辅助数据,那么,无论是读取用户数据的子程
序,还是读取文件中数据的子程序,都不应该对另一个子程序是否读取数据有所依赖。如果利
用两行代码来读取堆栈顶的数据,并减少一个Stacktop变量,应把它们放入一个PopStack()子程
序中,在设计系统时,使哪一个都可以首先执行,然后编写一个子程序,隐含哪一个首先执行
的信息。
改进性能。通过使用子程序,可以只在一个地方,而不是同时几个地方优化代码段。把相
同代码段放在子程序中,可以通过优化这一个子程序而使得其余调用这个子程序的子程序全部
受益。把代码段放入子程序也使得用更快的算法或执行更快的语言(如汇编)来改进这段代码
的工作变得容易些。
进行集中控制。在一个地方对所有任务进行控制是一个很好的想法。控制可能有许多形式。
知道一个表格中的入口数目便是其中一种形式,对硬件系统的控制,如对磁盘、磁带、打印机、
绘图机的控制则是其中另外一种形式。使用子程序从一个文件中进行读操作,而使用另一个子
程序对文件进行写操作便是一种形式的集中控制。当需要把这个文件转化成一个驻留内存的数
据结构时,这一点是非常有用的,因为这一变动仅改变了存取子程序。专门化的子程序去读取
和改变内部数据内容,也是一种集中的控制形式。集中控制的思想与信息隐含是类似的,但是
它有独特的启发能力,因此,值得把它放进你的工具箱中。
隐含数据结构。可以把数据结构的实现细节隐含起来,这样,绝大部分程序都不必担心这
种杂乱的计算机科学结构,而可以从问题域中数据是如何使用的角度来处理数据。隐含实现细
节的子程序可以提供相当高的抽象价值,从而降低程序的复杂程度。这些子程序把数据结构、
操作集中在一个地方,降低了在处理数据结构时出错的可能性。同时,它们也使得在不改变绝
大多数程序的条件下,改变数据结构成为可能。
隐含全局变量。如果需要使用全局变量,也可以像前述那样把它隐含起来、通过存取子程
序来使用全局变量有如下优点:不必改变程序就改变数据结构;监视对数据的访问;使用存取
子程序的约束还可以鼓励你考虑一下这个数据是不是全局的;很可能会把它处理成针对在一个
模块中某几个子程序的局部数据,或处理成某一个抽象数据的一部分。
隐含指针控作。指针操作可读性很差,而且很容易引发错误。通过把它们独立在子程序中,
可以把注意力集中到操作意图而不是机械的指针操作本身。而且,如果操作只在一处进行,也
更容易确保代码是正确的。如果找到了比指针更好的数据结构,可以不影响本应使用指针的子
程序就对程序作改动。
重新使用代码段。放进模块化子程序中的代码段重新使用,要比在一个大型号程序中的代
码段重新使用起来容易得多。
gototop
 
«12345678»   5  /  16  页   跳转
页面顶部
Powered by Discuz!NT