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

«910111213141516   12  /  16  页   跳转

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

实上,设计过程很少有像最终结果那样井井有条。
设计是一个复杂的过程。因为你很难把正确答案与错误答案区分开来。如果你让三个人分
别设计同一个程序,他们带回来的往往是三个大相径庭的方案,而且其中每一个看起来都非常
适用。它是一个复杂的过程还因为你在设计过程中曾钻过许多死胡同、犯过许多错误。说它是
一个复杂的过程也是因为你不知道什么时候设计方案已经足够完善了。什么时候算完成呢?对
这个问题的通常答案是“当你没有时间时”。
7.5.3  设计是一个“险恶”的过程
Horst Rittel和 Melvin Webber 把“烦人”的问题,定义成只有通过解决它或者部分解决它,
才能给出明确定义的问题。这个似是而非的定义事实上暗示着你不得不首先“解决”这个问题,
对其有一个清楚的定义和理解,然后再重新解决一遍,以获得正确的解决办法。这一过程对软
件开发就像母爱和面包对你我一样必不可少。
在现实中,关于险恶问题的一个富于戏剧性的例子便是托卡马大桥的设计。在修建大桥时,
主要考虑的便是它应该能承受设计载荷并能抗12级大风。然而,没人想到风在吹过桥时会产生
“卡门旋涡”——一种特殊的空气动力学现象,从而使桥产生横向简谐振动。结果,在1940 年
的一天,只在7 级风的作用下,桥便因振动而坍塌了。
说它是险恶问题的典型例子是因为直到桥坍塌时,也没有一个设计师想到应该在这个设计
中考虑空气动力学问题。只有在建成大桥(解决问题)之后,才使得他们意识到了要考虑的这
一“额外”问题,经过重新设计,新桥至今依然屹立在河上。
你在学校中设计的程序和在实际工作中设计的程序最重要的不同是:在学校中遇到的程序
设计问题,几乎没有哪个是险恶的,教师留给你的程序作业都是预先想好让你一次即可完成的。
如果哪位教师留给你们一个程序作业,当你们完成后他又突然改变了作业题目,接着,当你即
将完成那个程序时,他又改变了主意,会怎么样呢?我想,如果有谁胆敢这样的话,你们肯定
会把它绞死。但在实际工作中,几乎总是这样。
7.5.4  设计是一个启发的过程
进行有效设计的关键是要认识到它是个启发的过程。设计中,总是吃一堑,长一智的。往
返设计的概念事实上解释了设计是个启发过程这一事实,因为你要把任何设计方法都只当成一
种工具。一种工具只对一种工作或者一种工作的某一部分才有效,其余的工具适合其它的工作,
没有一种工具是万能的。因此,你往往要同时使用几种工具。
一种很有效的启发工具就是硬算。不要低估它。一个有效的硬算解决方案总比优雅却不能
解决问题的方案要好。以搜索算法的开发为例,虽然在1946年就产生了对分算法的概念,但直
到16 年后,才有人找到了可以正确搜索各种规模的表的算法。
图示法是另一种有力的启发工具。一幅图抵得上一千个单词。你往往不愿用那一千个单词
而宁愿用一幅图,因为图形提供了比文字更高的抽象水平,有时或许你想在细节上处理某一问
题,但是,更常见的是在总体上处理问题。
往返设计的一个附加的启发能力是你在设计的头几次循环中,可以暂时对没有解决的细节
问题弃之不管,你不必一次就对一切都做出决定,应记住还有一个问题有待做出决定,但同时
要意识到,你目前还没有充分的信息来解决这个问题。为什么在设计工作的最后10%的部分苦
gototop
 

苦挣扎呢?往往在下一循环中它们会自然获得解决。为什么非要在经验和信息都不足的情况下
草率决定呢?你完全可以在以后等经验和信息丰富时做出正确决定。有些人对一次设计没能彻
底解决问题会感到很不舒服,但与其很不成熟地勉强解决问题,不如把问题暂放一个,待到信
息足够丰富时,再解决它。
最重要的设计原则之一是不要死抱着一种方法不放。如果编写PDL无效的话,那么就作图,
或用自然语言写出来,要么就写一小段验证程序,或者使用一种完全不同的方法,比如硬算解
决法,坚持用铅笔不停地写和画,大脑或许会跟上。如果这一切都无效,暂时放开这个问题。
出去自由自在地散散步,或者想一下别的,然后再回到这个问题上。如果你已经尽了全力但还
是一无所获,那么暂时不考虑这个问题往往会比坚持冥思苦想更快得到答案。最后,可以借鉴
其它领域中的方法来解决软件设计中的问题。关于问题解决中的启发方法的最初的一本专著是
G. Polya 的《How To solve in》一书(1957),Polya的书推广了数学中解决问题的方法,表 7-1
就是对其所用方法的总结,本表摘自Polya 的书中的类似的总结表:
表7-1  怎样解决问题

l.理解问题,你必须理解要解决的问题
问题是什么?条件是什么?数据是什么?有可能满足条件吗?已知条件足以确定未知
吗?已知条件是否不够充分?是否矛盾7 是否冗余?
画一个图,引入恰当的符号,把条件的不同部分分解开。
2.设计一个方案。找到已知数据和未知之间的联系。如果不能找出直接联系的话,你可能不得
不考虑一些辅助问题,但最后,你应该找到一个解决方案。
以前你是否遇到过这个问题?或者是见过与它稍有不同的问题?是否知道与其相关的问
题?是否知道在这个问题中有用的定理?
看着未知!努力回忆起一个有着相同或类似未知的问题。这里有一个与此相关的你以前
解决过的问题,你能利用它吗?是能利用它的结论还是能用它的方法?是否该引入辅助
要素以使这个问题可以再用?
能否重新表述一下问题?能用另外一种方式表述它吗?返回到定义。
如果你无法解决这个问题,可以先试着解决一些别的问题,是否能想象出一个容易解决的
相关问题;一个广义些的问题或是一个更特殊的问题?一个相似的问题呢?能否解决问
题的一部分呢?仅保留一部分条件,忽略其余条件;未知可以被决定到什么程度?会发生
什么变化?能否从数据中推导出一些有用的东西?能否找出适于确定未知的其余数据?
能否改变数据或未知?同时改变两者呢?这样做能否使新的未知和新的数据更接近些?
是否使用了全部的数据?使用全部条件了吗?是否考虑了这个问题的全部必要条件?
3.执行你的计划。
执行你解决问题的计划,同时检查每一步工作。你是否可以认定每一步都是正确的?你
能证明这点吗?
4.回顾,检查一下答案。
你能检查一下答案吗?能检查一个论证吗?能否用另外一种方法推导出答案?能否一眼
就看出答案?
能否在其它问题中再利用本题的答案或者结论?
7.5.5  受迎的设计特点
高质量的设计往往有一些共同的特点。如果你能达到这些目标,那么可以认为你的设计也
是非常成功的。有些目标是互相矛盾的。但是这是设计的挑战所在,在相互矛盾的目标之间做
gototop
 

出合理的折衷。某些高质量设计的特点同时也是高质量程序的特点——可靠性。其余的则是设
计所独有的。
以下是设计所独有的一些特点:
智力上的可管理性。对于任何系统来说,智力上的可管理性都是其重要目标之一。它对于
整个系统的完整性是非常重要的,并且会影响程序员们开发和维护系统的难易程度。
低复杂性。低复杂性实际上是智力上的可管理性一部分,由于上述同样的原因,这点也很
重要。
维护的方便性。维护的方便性意味着设计时要为负责维护的程序员着想。在设计中,要不
停地想象维护程序中将会对你的设计提出的问题。应该把维护程序员当作你的听众,同时把系
统设计成明白易懂的。
最小的联系性。最小的联系性指的是按照保持子程序之间的联系最少的原则来设计,应该
利用强内聚,松散耦合和信息隐蔽等作为指导原则来设计系统,使其内部的联系性尽可能少。
最小的联系性可以极大地减小综合、测试和维护阶段的工作量。
可扩充性。可扩充性指的是不必影响系统的内部结构,就可以对系统的功能进行强化,你
可以改变系统的某一部分而不影响其余部分,使得最大可能性变动对系统带来的影响最小。
可重复使用性。 可重复使用性指的是把系统设计成其中许多部分是可以被其它系统借用
的。
高扇入。高扇入指的是对于一个给定的子程序来说,应该有尽可能多的子程序调用它。高
扇入表明一个系统在低层次上充分利用了功能子程序。
低或中等程度扇出。低或中等扇出指的是对一个确定的子程序来说,它所调用的子程序应
该尽可能地少。高扇出(大约7 个以上)说明一个子程序控制了许多其它子程序,因此可能是
很难理解的。而中等扇出则表明一个子程序只把任务交给了数量较少的其它子程序,因此是比
较容易理解的。低扇出(少于4 个)看起来像是一个子程序没有把足够的任务交给其余的子程
序去做,但经验表明并不是这样。一项研究表明有42%只调用一个子程序的子程序是没有错误
的,有32%的调用 2~7 个子程序是没有错误的,而在调用7 个以上子程序的情况中,只有12%
是没有错误的(Card, Church 和Agresi,1986)。由此,Card认为0~2 个扇出是最优的。
可移植性。可移植性指的是把系统设计成很容易转到另外的环境下运行。
简练性。简练性指的是把系统设计得没有任何多余部分。Voltaire曾说过,当一本书不能删
掉,而不是不能添补任何内容时,才可以认为它已完成了。在软件中,这也是非常正确的,因
为当你对系统进行改进时,你不得不对冗余的代码进行开发、评审、测试和维护等等工作,而
且在开发软件的新版本时,新版本也不得不与这些冗余的代码兼容。最有害的观点是“多加入
些又不会有害,怕什么呢?”
成层设计。成层设计指的是尽量分解的层次是成层的,这样你可以在每一个单独的层次上
观察系统,同时也可以使观察的层次是连续的。也就是说当你在某一层次上观察系统时,不会
看到在其它层次上看到的东西。你会经常遇到某些子程序和软件在几个层次上起作用。这样会
使系统很混乱,应尽力避免。
如果在编写一个先进系统时,不得不借用许多旧的、设计得不好的代码,那么你可以在新
系统中建立一个层(layer),与那些旧代码相联接。精心设计这个层使它把旧代码的缺点隐含起
来,从而使新层表现了一整套连续的功能。然后,让程序的其余部分调用些子程序而不是直接
gototop
 

调用旧代码。成层设计的好处是:(l)它可以使你避免与拙劣的旧代码直接打交道;(2)一旦
你想废弃那些旧代码中的子程序的话,只要修改一下接口层就可以了。
标准化技求。标准化技术是深受欢迎的。一个系统使用的奇特的、非标准的技术越多,当
别人第一次读它时就会越感到可怕,也越难理解。应该通过采用常用的、标准化的技术使得人
们在阅读它时是一种熟悉的感觉。
7.5.6    检查表
高层次设计
本表给出了在评估设计质量时,通常要考虑一些问题。本表是3.4 节中结构设计检查表的
补充,这个表所考虑的主要是设计质量。3.4 节中的检查表则侧重于结构设计和设计内容。这
个表中的某些内容是相互重合的。
•  是否使用了往返设计方法,应从几个方案中选择最好的,而不是首次尝试就确定方案。
•  每个子程序的设计是否都和与其相关的子程序设计一致?
•  设计中是否对在结构设计层次上发现但并未解决的问题作了充分说明?
•  是否对把程序分解成目标或模块的方法感到满意?
•  是否对把模块分解成子程序的方法感到满意?
•  是否明确定义了子程序的边界?
•  是否是按照相互作用最小的原则定义子程序的?
•  设计是否充分利用了自顶向下和自底向上法?
•  设计是否对问题域要素、用户接口要素、任务管理要素和数据管理要素进行了区分?
•  设计是智力上可管理的吗?
•  设计是低复杂性吗? 
•  程序是很容易维护的吗?
•  设计是否将子程序之间的联系保持在最低限度?
•  设计是否为将来程序可能扩展作了准备?
•  子程序是否是设计成可以在其它系统中再使用的?
•  低层次子程序是高扇入的吗?
•  是否绝大多数子程序都是低或中等程度扇出的?
•  设计易于移植到其它环境吗?
•  设计是简练的吗?是不是所有部分都是必要的?
•  设计是成层的吗?
•  设计中是否尽量采用了标准化技术以避免奇特的、难以理解的要素?
7.6  小结
•  设计是一个启发的过程。固执地坚持某一种方法只会抑制创造力,从而产生低质量的程。
    坚持设计方法上有一些不屈不挠的精神是有益的,因为这可以迫使你对这种方法进行充
    分理解。但是,一定要确信你是在不屈不挠而不是顽固不化。
gototop
 

•  好的设计是通过迭代逼近得到的:你尝试过的设计方案越多,你最终所确定的设计方案
  也越好。
•  结构化设计比较适合于小规模的子程序组合,同时,它对于功能变化可能性比数据大的
      问题也是较适用的。
•  面向对象设计更适于子程序与数据的组合,通常在比结构化设计抽象程度更高些的层次
  上适用。它尤其适合于数据变动可能性大于功能变动可能性的问题。
•  设计方法仅是一种工具,你对工具运用得好坏决定了你所设计的程序的质量。利用不好
  的设计方法,也可能设计出高质量的程序。而即使是好的方法,如果运用不当的话, 也
  只能设计出拙劣的程序。但不管怎样,选择正确的工具更容易设计出高质量的软件。
•  许多关于设计的丰富而有用的信息都是在本书之外的。在这里所论述的,不过是冰山的
  一角而已。
gototop
 

第八章  生成数据
目录
8.l  数据识别
8.2  自建数据类型的原因
8.3  自建数据类型的准则
8.4  使变量说明更容易
8.5  初始化数据的准则
8.6  小结

相关章节
变量命名:见第9 章
使用变量时的一些考虑:见第10 章
使用基本数据类型:见第11 章
使用复杂数据类型:见第12 章
在结构设计阶段定义数据:见3.4 节
说明数据:见19.5 节
数据结构布置:见18.5 节

本章的内容既包括高层次的子程序、模块和程序设计中要考虑的问题,也包括对数据实现
问题基本要素的讨论。
数据结构在创建阶段能带来的收益大小,在某种程度上是由它对创建前的高层次工作影     
响大小决定的。好的数据结构所带来的收益往往是在需求分析和结构设计阶段体现出来的。为
了尽可能地利用好的数据结构带来的收益,应在需求分析和结构设计阶段就定义主要数据结构。
数据结构的影响同时也是由创建活动所决定的。由创建活动来填平需求分析与结构设计     
之间的壕沟是很正常也是很受欢迎的。在这种微观问题上,仅靠画几张蓝图来消除一致性的缺
陷是远远不够的。本章的其余部分将论述填平这一壕沟的第一步——生成进行此项工作的数据。
如果你是一位专家级的程序员,本章的某些内容对你来说可能已是司空见惯了。你可以浏
览一下标题和例子,寻找你不熟悉的内容看就可以了。

8.1  数据识别
有效生成数据的第一步是应该知道该生成什么样的数据结构。一张良好的数据结构清单是
程序员工具箱中的一件重要工具。对数据结构基本知识的介绍不在本书范围之内。但你可以利
用下面的“数据结构识别测试题”来看一下自己对其知道多少。
gototop
 

8.1.1  数据结构识别测验题
每遇到一个你所熟悉的词可以计1 分。如果你认为你知道某个词但并不知道其确切内容,
计0.5分。当作完题后,把你的得分加到一起,然后再根据测验题后面的说明来解释你的得分。

        ____抽象数据类型          ____文字型
        ____数组                  ____局部变量
        ____B 树            ____查找表
        ____位图            ____指针
        ____逻辑变量(布尔变量)____队
        ____字符变量          ____记录
        ____命名常量          ____回溯
        ____双精度              ____集合
        ____枚举流            ____堆栈
        ____浮点            ____字符串
        ____散列表            ____结构化变量
        ____堆            ____树
        ____索引          ____联合
        ____整型            ____数值链
        ____链表              ____变体记录
                                ____最后得分

    以下是得分解释办法(可随便些):
0~14 分  你是个初级程序员,可能是计算机专业一年级的学生,或者是一个正在自学
第一种语言的自学者。如果读一下后面列出的书的话,你将会学到很多。本
章的许多论述都是针对高级程序员的,如果你读完那些书再阅读本书将更
有益。
15~19 分  你是个中级程序员或是个健忘的富有经验的程序员虽然你对表中许多概念
都已经很熟了,但最好还是先阅读一下后面列出的书。
20~24 分  你是个专家级的程序员,你的书架上很可能已经插上了后面所列出的书。
25~29 分  关于数据结构,你知道的比我还多!可以考虑写一本你自己的专著(请别忘
了送我一本)。
30~32 分  你是个自大的骗子。“枚举流”、“回溯”和“数值链”并不是指数据结构
的,是我故意把它们加进去以增加难度的,在阅读本章其余部分之前,请先
阅读引言中关于智力诚实性的内容。
    以下是关于数据结构的一些不错的书:
Aho, Alfred V., John E. Hopcroft 《Data Structure and Algorithms, Reading,Mass》
Addison-Wesley,1983。
Reingold, Edward M 和Wilfred J.Hansen《Data Structures》, Boston: Little, Brown, 1983。
Wirth, Niklaus,《Algorithms and Data Structures》, Englewood Cliffs,N.J.;Prentice Hall, 1986。
gototop
 

8.2  自建数据类型的原因
程序语言所赋予你的阐明自己对程序理解的最强有力的工具之一便是程序员定义的变量
类型。它们可以使程序更容易阅读。如果使用C、Pascal 或其它允许用户定义类型的语言,那
么一定要利用这一点。如果使用的是Fortran,Generic Basic 等不允许用户定义变量类型的语言,
那接着看下去,或许读完本节后你就想换一种语言了。
为了说明自定义类型的效力,让我们来看一个例子。假设你正编写一个把x、y、z坐标转
换为高度、经度、纬度坐标的程序,你认为可能会用到双精度浮点数,但是在十分确定之前,
你只想用单精度浮点数。这时,可用C 中的typedef 语句或Pascal 中的type 说明来为坐标专门
定义一种变量(当然也可用其它相当的语言)。以下是在 C 中是如何建立这种变量的:
typedef float Coordinate_t; /* for coordinate variables */
这个类型定义说明了一种新的类型,Coordinate_t,在功能上它与float 是一样的,为了使
用这种新类型,应用它来说明变量,就像用float来预先定义一种类型一样。以下是一个用C写
成的例子:
Routine1(...)
{
Coordinate_t latitude; /* latitude in degrees */
Coordinate_t longitude; /* longitude in degrees */
Coordinate_t elevation; /* elevation in meters from earth center */
...
}
...
Routine2(...)
{
Coordinate_t x; /* x coordinate in meters */
Coordinate_t y; /* y coordinate in meters */
Coordinate_t z; /* z coordinate in meters */
...
}
在这段代码中,变量x,y,z和变量latitude,longitude,elevation都是Coordinate_t类型的。
现在,假设程序发生了变化,发现最终还是得用双精度变量。由于你为坐标专门定义了一
种类型,因此所要做的就是改变一下类型定义而已。而且只需在一个地方进行改动;在typedef
语句中。下面是改变后的类型定义:
typedef double Coordinate_t; /* for coordinate variables */  ——原浮点已改为双精度类型

下面来看第二个例子,它是用Pascal写成的。假设你正在生成一个工资发放系统,其中雇
员的名字至多有30 个字母。你的用户告诉你从来没有任何人的名字长度超过30 个字母。你是
否该在程序中把数字30作为文字型变量来使用呢?如果你这样作的话,那你就过于轻信你的用
gototop
 

户了。更好的办法是为雇员名字定义一种类型:
Type
EmployeeName_t = arrayy[1..3] of char;
当引入数组或字符串时,最好定义一个命名常量来表示数组或字符串的长度,然后
在类型定义中使用这个命名常量,在程序中,你会许多次用到了这个常量——以下
是你将使用它的第一个地方,它看起来是这样的:
Const
Namelenght_c = 30;——这里是命名常量的说明

Type
EmployeeName_t = array[1..NameLength_c] of Char;——这里是命名常量使用的地方

一个更有说服力的例子是将自定义类型与信息隐蔽的思想结合在一起。在某些情况
下,你想隐蔽的信息是关于数据类型的。
在坐标例子中用C 写成的程序已经在走向信息隐蔽的途中了。如果你总是用Coordinate_t
而不是用float或double,事实上已经隐蔽了数据的类型。在C 或Pascal中,这些便是语言本身
能为信息隐蔽所做的一切,其余部分便是你或后来的使用者必须遵守这个规则,不查看
Coordinate_t的定义。C 和Pascal所赋予的都是广义的而不是狭义的信息隐蔽能力。
其它像Ada和C++等语言则更进一步支持狭义的信息隐蔽。下面是在Ada 语言中,用包来
定义Coordinate_t的代码:
package Transformation is
type Coordinate_t is private; ——这个语句说明coordinate_t作为包的专用说明

以下是在另一个包中使用Coordinate_t的代码:
with Transformation:

procedure Routine1(…)…
latitude: Coordinate_t;
longitude: Coordinate_t;
begin
-- statements using latitude and longitude

end Routine1;
注意Coordinate_t在包定义中是说明为专用的,这意味着只有Transformation包中的专用部
分才知道Coordinate_t 的定义,而程序其余部分则都不知道。在有一群程序员的开发环境中,
只有包的定义部分才是开放的。对于从事另一个包的程序员来说,他是不可能查寻Coordinate_t
的类型的。在这里,信息是狭义隐蔽的。
这些例子已经阐明了建立自己的类型的几条理由:
· 使得改动更加容易。建立一种新类型工作量极小,但这却可以带来极大的使用灵活性。
gototop
 

· 避免过度分散的信息分布。硬性类型会使程序中充斥着数据类型细节,而不是使其集
中在一个地方。这正是 6.2 节中所讨论的集中化原则。
· 为了增加可靠性。在Ada和Pascal中,可以定义类似Age_t = 1~99的类型。然后,编
译程序会产生运行检查信息,以保证Age_t类型总是在1~99 的范围内。
· 为了补偿语言的弱点。如果语言中不具备某种定义好的类型,可以自己定义它。例如,
C 中不含逻辑类型,通过建立你自己的类型,很容易弥补这个缺点:
typedef int Boolean_t;
8.3  自建数据类型的准则
以下是几条生成自己的类型时应牢记的准则:
建立具有面向功能名称的类型。应避免用暗指计算机本身数据类型的名称。要使用代表实
际问题某一部分的名称。在上例中,为坐标建立的新类型命名就很恰当,因为它代表了客观世
界中的实体。同样,你也可以为现金、工资发放代号、年龄等客观世界中的实体建立新变量。
要避免使用含有已定义变量类型的名称。比如像 BigInteger和 LongString等指的是计算机
数据而不是客观世界中实体的名称就应避免使用。建立自己的类型其最大优点是,可以在程序
及其实现语言之间建立一个绝缘层,而指向程序语言类型的名称则会破坏这个绝缘层,使用已
定义的类型不会带来任何优点。面向问题的名称可以增加易改进性,并且使数据说明成为自注
释的。
避免使用已定义类型。如果类型存在变动的可能性,那么除了在typedef和type 定义之外,
不要再使用已定义的类型。建立面向功能的类型是非常容易的,而改变程序中该类型的数据是
非常困难的。而且,当使用自建的面向功能类型说明变量时,也同时对变量进行了说明。
Coordinate_x所告诉你的关于x 的信息要比float x 多得多。因此应尽可能使用自建类型。
不要对已定义类型重新定义。改变标准类型的定义往往令人困惑。例如,语言中已经有了
Integer 类型,而你又自建了叫作Integer 的类型。这样,程序的阅读者往往会记住你所定义的
Integer的含义,而仍把它当作语言中的标准 Integer类型。
定义替换类型以增强移植性。与避免重新定义标准类型的建议相反,你可能想为标准类型
定义一种替换类型,从而使得在不同的硬件环境下变量所代表的都是同一实体。例如,你可以
定义INT 类型来代替标准的int 类型,它们之间的唯一区别便是字母的大小写。但当你把程序
移到新的硬件环境下时,你只要重新定义一个INT类型,就可以在新的硬件环境下使得数据类
型与旧环境下相容。
如果你所用的语言是不区分大小写的,你将不得不使用其它办法来对标准类型和替换类型
加以区别。
使用其它类型来建立新类型。你可以在已经建立的简单类型的基础上建立复杂类型。这种
变量类型可以进一步推广你用原来类型所达到的灵活性。
8.4  使变量说明更容易
本节论述的是怎样才能更顺利地进行变量说明。显然,这是件很容易的事情,你甚至会认
gototop
 
«910111213141516   12  /  16  页   跳转
页面顶部
Powered by Discuz!NT