http://tech.163.com/school · 2005-04-04 22:28:23 · 来源: vckbase
如果你是初学者----------------请不要阅读;
但有志成为中高级程序员--------请务必阅读;
如果你是中级程序员------------请务必阅读;
如果你高级程序员--------------请批评指正。
本文是我在“软件工程师班”开学第一节课的讲义,和“计算机软件设计发展”讲座
上的内容整理而成。写作本文的目的是引导学生从更高的层次来看待程序设计方法,为将来
成为高级程序员而做好理论准备。
一、计算机硬件环境对软件设计方法的限制
计算机的发明到现在已经60年了,计算机程序设计方法也伴随着计算机硬件技术的提
高而不断发展。硬件环境对软件设计既有严重的制约作用,也有积极的推动作用。
在我的大学母校(此处删除6个字),数学系的一些老师,有幸成为了我国第一代的计算机
DIY一族。呵呵,不要以为是组装PC机呦,他们组装的可是小型机。一人多高铁皮柜大小的
主机,加上纸带机(后期改进为读卡机),组装好后,除了供学校自己的科研使用外,还在
全国各地销售了十
几台。当时(七十年代)一台的售价是10几万元人民币,如果换算到今天,相当于价值大约
为100多万元,非常高档的小型计算机了。下面大家猜猜,这么高档的计算机,它的内存是
多少那?(都把嘴闭好了,我要公布答案了)—— 4K。
一块50公分见方的内存板,
插入到主机箱中,好了------ 1K;
再插一块内存板,好了------ 2K;
再插一块内存板,好了------ 3K;
再插一块内存板,好了------ 4K;
再......不行了,插不起了,太贵了!这就是当时的环境。这样的环境下,用什么写
程序那?当然只有机器码了。先用汇编写,然后翻阅手册手工改写为机器码,然后打卡或穿
纸带,输入运行。可以想象,在当时的条件下,什么叫好的程序那?什么叫优秀的程序那?
—— 技巧!
程序设计的最初始阶段,是讲究技巧的年代。如何能节省一个字节,如何能提高程序
运行的效率,这些都是要严肃考虑的问题。而所谓的程序的易读性,程序的可维护性根本不
在考虑范围之内。
今天,35岁以上的学习过计算机的朋友可能都使用过一种个人计算机——APPLE-II(
中国也生产过这种计算机的类似产品“中华学习机”)。主频1M,内存48K(扩展后,最多
可达到64K)。我就是使用这样的计算机长大的
:)。当年,类似的个人计算机产品,还有PC1500,Layser310等。这种计算机上已经固化了
BASIC 语言,当然只是为学习使用。要想开发出真正的商业程序,则必须使用汇编,否则的
话,程序就比蜗牛还要慢了。于是,程序设计中对于技巧的运用,是至关重要的了。
题外话1:
比尔盖茨是 BASIC 的忠实拥护和推动者。当年,他在没有调式环境的状况下,用汇编
语言写出了一款仅有 4K 大小的 BASIC 解释器,且一次通过。确实另人佩服。(不象现在
微软出品的程序,动辄几十兆。)这也许就是比尔对 BASIC
情有独忠的原因,每当微软推出(临摹)一个新技术,则他会立刻在 BASIC 中提供支持。
题外话2:
在 APPLE-II 上有一款游戏软件“警察抓小偷”,当年熬夜玩游戏,乐趣无穷。后来
这款游戏被移植到了PC上,咳~~~根本没有办法玩,因为小偷还没跑就被警察抓到了。硬件
的速度提升,另我无法再回味以前的时光了。
二、结构化程序设计
随着计算机的价格不断下降,硬件环境不断改善,运行速度不断提升。程序越写越大
,功能越来越强,讲究技巧的程序设计方法已经不能适应需求了。记得是哪本书上讲过,一
个软件的开发成本是由:程序设计 30% 和程序维护 70%
构成。这是书上给出的一个理论值,但实际上,从我十几年的工作经验中,我得到的体会是
:程序设计占 10%,而维护要占 90%。也许我说的还是太保守了,维护的成本还应该再提高
。下面这个程序,提供了两种设计方案,大家看看哪个更好一些那?
题目:对一个数组中的100个元素,从小到大排序并显示输出。(BASIC)
方法1:冒泡法排序,同时输出。
FOR I=1 TO 100
FOR J=I+1 TO 100
IF A[I] > A[J] THEN T=A[J]: A[J]=A[I]: A[I]=T
NEXT J
? A[I]
NEXT I
方法2:冒泡法排序,然后再输出。
FOR I=1 TO 100
FOR J=I+1 TO 100
IF A[I] > A[J] THEN T=A[J]: A[J]=A[I]: A[I]=T
NEXT
NEXT
FOR I=1 TO 100
? A[I]
NEXT
显然,“方法1”比“方法2”的效率要高,运行的更快。但是,从现在的程序设计角度来看
,“方法2”更高级。原因很简单:(1)功能模块分割清晰——易读;(2)也是最重要的
——易维护。程序在设计阶段的时候,就要考虑以后的维护问题。比如现在是实现了在屏幕
上的输出,也许
将来某一天,你要修改程序,输出到打印机上、输出到绘图仪上;也许将来某一天,你学习
了一个新的高级的排序方法,由“冒泡法”改进为“快速排序”、“堆排序”。那么在“方
法2”的基础上进行修改,是不是就更简单了,更容易了?!这种把功能模块分离的程序设
计方法,就叫“
结构化程序设计”。
三、对程序设计中技巧使用的思考
我可以肯定,大家在开始学习程序设计的时候,一定都做过这样一个题目:求100以内
的素数。老师在黑板上,眉飞色舞地写出了第一个程序:(C程序)
方法1:
for(i=1; i<100; i++)
{
for(j=2; j< i; j++)
if(i%j == 0) break;
if(j >= i) printf("%d,", i);
}
然后,老师开始批判这个程序“这个叫什么呀?太慢了!因为我们都知道大偶数不可
能是素数了,因此,要排除掉!” 于是,意尤未尽地写出了第二个程序:
方法2:
printf("2,");
for(i=3; i<100; i+=2)
{
for(j=2; j< i; j++)
if(i%j == 0) break;
if(j >= i) printf("%d,", i);
}
老师说:“看!我们只改动了一点点,程序运行的速度就提高了一倍多”。然后运用
诱导式教学法继续提问“程序的效率,还能再提高吗?能!”,得意地写出第三个程序:
方法3:
printf("2,");
for(i=3; i<100; i+=2) ''不考虑大偶数
{
for(j=3; j< i/2; j+=2) ''不考虑用偶数去测试,而且只验算到一半就足够了
if(i%j == 0) break;
if(j >= i) printf("%d,", i);
}
“大家看,我们又只改动了一点点,运行速度又提高了一倍多。可以了吗?不可以!
我们还能再提高”。于是又高傲地写出了第四个程序:
方法4:
printf("2,");
for(i=3; i<100; i+=2)
{
int k = sqrt(i);
for(j=3; j<= k; j+=2)
if(i%j == 0) break;
if(j >= k ) printf("%d", i);
}
然后,开始证明为什么我们判断素数的时候,只需要验算到平方根就足够了:
假设p是合数,那么令:p=a*b。反正法:由于我们已经判断了p的平方根以内的整数都
不能被p整除,于是 a>SQRT(p)。基于同样的理由 b>SQRT(p)。于是 p = a * b > SQRT(p)
* SQRT(p) = p 得出矛盾, 命题得正。
的确,“方法4”的确比“方法1”的运行速度要提高了好几倍,甚至好几十倍。但我
们仔细分析测试看看。
(1)“程序4”到底比“程序1”快了多少那?我在某台计算机上进行测试(P4,
1.5G)得到的速度对比表:
计算范围 100 1000 10000 100000
速度差 0.00秒 0.01秒 0.18秒 15秒
(2) 在10万以上,才会看出一些差别。而这种差别根本就不够底偿程序设计阶段的
付出。如果计算的范围再大,那么不管是“方法1”,还是“方法4”都不是好的算法。(计
算素数的另外一个比较优秀的算法叫“漏筛法”)
(3)写出“方法1”,只要具有小学四年级的数学水平就够了,而“方法4”则需要初
中三年级的水平并且还要具备一些“数论”的知识。
(4)从维护性看,如果你写的程序需要另外一个程序员来维护,或者若干时间以后,
你重新来阅读这段程序,那么就会对这个程序产生很多疑问:这个求平方根是干什么用的?
其实,就这个题目来说,使用到“方法3”就已经足够了。
总结发言:
I. 计算机的价格每年下降一半,而运算速度每年提高一倍”,因此我们应该把速度提
高的任务交给硬件实现。
II. 从易读性、维护性出发,程序员只负责按定义给出软件实现。算法的问题是数学
家解决的。