count[campus, ethnicgroup, j]++[size=0.833em]我对代码长度减少了一个数量级太满意了,结果忽视了另一个就在眼皮底下的问题。
3.2 格式信函编程
[size=0.833em]在常去的网店键入你的名字和密码并成功登录以后,弹出的下一页网页类似这样:
Welcome back,Jane!We hope that you and all the members of the Public family are constantlyreminding your neighbors thereon Maple Street to shop with us.AS usual,we will ship your order to Ms. Jane Q. public 600 Maple Street Your Town, Iowa 12345...[size=0.833em]作为程序员,你会意识到隐藏在这一幕之后所发生的事情——计算机在数据库中查找你的用户名并返回如下所示的字段:
Public|Jane|Q|Ms.|600|Maple Street|Your Town|Iowa|12345[size=0.833em]但是,程序如何依据你的个人数据库记录来构建这个定制的网页呢?急躁的程序员可能会试图按照下面所示的方式开始编写程序:
read lastname, firstname, init, title, streetnum, streetname, tomn, state, zipprint "Welcome back,",firstname, "!"print "We hope that you and all the members"print "of the", lastname, "family are constantly"print "reminding your neighbous there"print "on", streetname, "to shop with us. "print "As usual, we will ship your order to"print " ",title, firstname, init ".", lastnameprint " ", streetnum, streetnameprint " ", town ",", state, zip...[size=0.833em]这样的程序很有诱惑性,但是也很乏味。
[size=0.833em]一个更巧妙的方法是编写一个格式信函发生器(form letter generator)。该发生器基于下面所示的格式信函模板(form letter schema):
Welcome back, $1!We hope that you and all the membersof the $0 family are constantlyreminding your neighbors thereon $5 to shop with us.As usual, we will ship your order to $3 $1 $2. $0 $4 $5 $6, $7 $8...[size=0.833em]符号$i代表记录中的第i个字段。于是,$0代表姓,等等。模板使用下面的伪代码来解释。在伪代码中,文字符号$在输入模板中记为$$。
read fields from database loop from start to end of schema c = next character in schema if c ! ='$' printchar c else c = next character in schema case c of '$': printchar '$' '0' - '9': printstring field[c] default: error("bad schema")[size=0.833em]在程序中,该模板使用一个长字符串数组表示。数组中的文本行以换行符结束。(Perl和其他脚本语言使其更容易实现。可以使用形如$lastname的变量。)
[size=0.833em]编写该发生器和模板程序比编写显而易见的程序要简单些。将数据从控制中分离会获得许多好处:如果重新设计信函,那么模板可以使用文本编辑器来修改,从而第二个特定页的准备会很简单。
[size=0.833em]报表模板的概念曾极大地简化了我维护过的一个5 300行代码的Cobol程序。程序的输入是家庭财务状况的描述,其输出是一个小册子,总结了财务现状并推荐未来理财策略。这里是一些相关数值:120个输入字段、18页上的400行输出语句、300行用来清除输入数据的代码、800行用于计算的代码以及4 200行用于输出的代码。据我估算:4 200行的输出代码可以使用一个最多几十行代码的解释程序和一个400行的模板来代替,而代码的计算部分保持不变。按这种形式编写原始程序所得到的Cobol代码的长度至多为原来的三分之一,并且维护起来也容易得多。
3.3 一组示例
[size=0.833em]菜单。我希望我的Visual Basic程序的用户可以通过点击菜单项来实现在几个选项之间的选择。我浏览了一系列的优秀示例程序,发现了一个允许用户在选项中进行八选一操作的程序。查看该菜单对应的代码,得到如下所示的选项0的代码:
sub menuitem0_click() menuitem0.checked = 1 menuitem1.checked = 0 menuitem2.checked = 0 menuitem3.checked = 0 menuitem4.checked = 0 menuitem5.checked = 0 menuitem6.checked = 0 menuitem7.checked = 0[size=0.833em]选项1的代码几乎是一样的,相异的部分如下:
sub menuitem1_click() menuitem0.checked = 0 menuitem1.checked = 1 ...[size=0.833em]依次类推,选项2至选项7亦是如此。总而言之,菜单项的选择总计需要大约100行代码。
[size=0.833em]我自己编写的程序也与之相似。我从有两个选项的菜单着手编程,此时的代码是合理的。当我添加第三个、第四个和后续的选项时,我为代码所具有的功能而倍感兴奋,以至于没能停下来去整理混乱的代码。
[size=0.833em]稍作观察以后,可以将大部分代码转化为一个函数uncheckall,该函数将每个checked字段置0。于是第一个函数变成:
sub menuitem0_click() uncheckall menuitem0.checked = 1[size=0.833em]但是,此时的代码中还是有7个相似的函数。
[size=0.833em]幸运的是,Visual Basic支持菜单选项数组。因此可以将8个相似的函数使用一个函数表示:
sub menuitem_click(int choice) for i = [0, numchoices) menuitem.checked = 0 menuitem[choice].checked = 1[size=0.833em]将重复的代码使用通用的函数表示,使程序由100行减少至25行,而数组的恰当使用又使代码减至4行。添加下一个选择也更容易,并且可能存在错误的程序现在犹如水晶一般晶莹剔透。该方法仅仅使用了几行代码就解决了我的问题。
[size=0.833em]出错信息。混乱系统的数百个出错信息散布在所有代码中。同时,这些出错信息又与其他输出语句混杂在一起。而清晰系统则通过一个专用函数来访问这些出错信息。考虑一下分别使用“混乱”和“清晰”两种组织形式来实现下面这种需求的难度:产生所有可能的出错信息列表,使每个“严重”出错信息产生一声报警并将出错信息翻译成法语或德语。
[size=0.833em]日期函数。给定年份和该年中的某一天,返回该天所处的月份和月中的日子。例如,2004年的第61天是3月1日。在其Elements of Programming Style中,Kernighan和Plauger给出了一个直接从他人的程序中摘录出来的实现该任务的55行程序。随后,他们用一个5行的程序解决了该问题,该程序用到了一个有26个整数的数组。习题4介绍了关于日期函数表示的问题。
[size=0.833em]单词分析。许多计算问题都是由英文单词的分析引起的。在13.8节将会看到拼写检查器如何使用“后缀去除”来精简字典:例如单词“laugh”就不存储其所有的不同结尾(“-ing”“-s”“-ed”等)。语言学家们已经得出了对应这些任务的一系列法则。1973年,Doug McIlroy在编写他的第一个实时文本语音合成器的时候,就知道代码并不适合表示这些法则。他更愿意使用1 000行代码和一个400行的表来实现。有人尝试在不增加表的情况下修改程序,其结果是增加20%的内容就需要增加2 500行额外的代码。McIlroy声称他现在可以通过增加更多的表,使用少于1 000行的代码来完成该扩充任务。需要自己尝试一下类似的法则集的话,见习题5。
3.4 结构化数据
[size=0.833em]什么才是结构清晰的数据?随着时间的推移,其标准也在逐步提高。早些年,结构化数据就意味着选择恰当的变量名。后来,在程序员使用平行数组(parallel array)[2]或寄存器偏移量的地方,编程语言加入了记录或结构以及指向它们的指针。我们学会了使用名为insert或search的函数来代替处理数据的代码,这有助于在改变数据的表达方式时不损坏程序的其他部分。David Parnas[3]对这种方法进行了扩展,他发现对系统待处理数据进行研究可以深入认识到优秀的模块化结构。
[size=0.833em]下一步是“面向对象编程”。程序员们学会识别设计中的基本对象,向外公开一个抽象的对象及其基本操作,并隐藏具体的实现细节。使用诸如Smalltalk和C++的编程语言,可以将这些对象封装在类中。在第13章中,我们在研究集合的抽象和实现时会仔细研究这种方法。
3.5 用于特殊数据的强大工具
[size=0.833em]曾几何时,程序员需要从头开始编写每个应用程序。现代工具允许程序员(以及其他人员)花费最少的精力来编写应用程序。本节所列出的一些工具仅为示范性的,并不完备。每种工具都使用数据的某一视图来解决特定但又通用的问题。诸如Visual Basic、Tcl等语言和各种shell都提供了连接这些对象的“胶水”。
[size=0.833em]超文本。在20世纪90年代早期,网站的数量还只有数千个的时候,我所阅读的入门参考书都是存储在CD-ROM上面的。那些资料令人眼花缭乱,包括百科全书、字典、年鉴、电话号码簿、古典文学、教科书、系统参考手册等,所有这些资料都可以放在我的手掌心里。不幸的是,不同资料集的用户界面也是一样地令人头晕目眩:每个程序都有其特别之处。现在我可以轻松地访问所有CD上的或网上的数据(甚至更多),而我所用的界面通常就是网页浏览器。这使用户和开发人员都轻松多了。
[size=0.833em]名字—值对。书目数据库中的项可能如下所示:
%title The C++ Programming Language, Third Edition%author Bjarne Stroustrup%publisher Addison-Wesley%city Reading, Massachusetts%yesr 1997[size=0.833em]Visual Basic使用这种方法描述界面的控件。窗体左上角的文本框可以使用如下的属性(名字)和设置(值)来描述:
[size=0.833em](完整的文本框包含36个名字—值对。)例如要展宽文本框时,可以使用鼠标拖动右边框,或者输入一个更大的整数来替代215,或者使用运行时赋值语句
txtSample.Width = 400[size=0.833em]程序员可以选择最方便的方式来操作这个简单但功能很强大的结构。
[size=0.833em]电子表格。搞明白本部门的预算对我来说似乎有点困难。习惯上,我会为这项工作编写一个庞大的程序,用户界面也是沉闷生硬的。而另一位程序员从一个更广的视角入手,采用电子表格实现该程序,同时也使用了少量的Visual Basic函数。用户界面对财务人员等主要用户来说很熟悉。(如果今天我还需要编写大学调查程序,数据为数值数组的这个事实会促使我尝试将数据放到电子表格中。)
[size=0.833em]数据库。多年以前,一位程序员在纸质日志上记录了他最初的十几次跳伞的详细信息以后,决定将自己跳伞数据的记录自动化。再早几年,记录这样的数据需要使用复杂的记录格式,并且需要使用手工程序(或使用“报表程序发生器”)来完成数据的录入、更新和提取。当时,该程序员和我都被他完成该工作时所使用的新发明的商业数据库震惊了。他可以在几分钟之内完成数据库操作的新界面,而不再需要几天的时间。
[size=0.833em]特定领域的编程语言。图形用户界面(GUI)已经替代了许多古老沉闷的文本语言。但是特殊用途的编程语言在某些应用程序中依然很有效。当需要计算数据时,我并不喜欢使用鼠标在屏幕上点击一个虚拟的计算器,而是倾向于采用如下所示的方式直接输入数学公式:
n = 100000047 * n * log(n)/log(2)[size=0.833em]相比于用炫丽的文本框和操作按钮组合来定义一个查询,我更倾向于用下面这样的语言来写:
(design or architecture) and not building[size=0.833em]以前使用数百行可执行代码来定义的窗口,现在可以使用数十行HTML代码来定义。这些语言对一般的用户输入来说可能不够时尚了,但是在某些应用场合它们依然是有效的工具。
3.6 原理
[size=0.833em]虽然本章中的故事横跨数十年并涉及多种编程语言,但是每个故事的精髓都是一致的:“能用小程序实现的,就不要编写大程序”。许多结构都见证了Polya在How to Solve It [4]一书中提到的发明家悖论:“更一般性的问题也许更容易解决”。对于程序设计来说,这意味着直接编写解决23种情况的问题很困难;而编写一个处理n种情况的通用程序,再令n = 23来得到最终结果,却相对要容易一些。
[size=0.833em]本章集中讨论了数据结构对软件的一个贡献:将大程序缩减为小程序。数据结构设计还有许多其他正面影响,包括节省时间和空间、提高可移植性和可维护性。Fred Brooks[5]在《人月神话》第9章中的评论就是针对节省空间的。而对于想要获得其他属性的程序员来说,下面的建议可谓金玉良言:
[size=0.833em]程序员在节省空间方面无计可施时,将自己从代码中解脱出来,退回起点并集中心力研究数据,常常能有奇效。(数据的)表示形式是程序设计的根本。
[size=0.833em]下面是退回起点进行思考时的几条原则。
- [size=0.833em]使用数组重新编写重复代码。冗长的相似代码常常可以使用最简单的数据结构——数组来更好地表述。
- [size=0.833em]封装复杂结构。当需要非常复杂的数据结构时,使用抽象术语进行定义,并将操作表示为类。
- [size=0.833em]尽可能使用高级工具。超文本、名字—值对、电子表格、数据库、编程语言等都是特定问题领域中的强大的工具。
- [size=0.833em]从数据得出程序的结构。本章的主题就是:通过使用恰当的数据结构来替代复杂的代码,从数据可以得出程序的结构。万变不离其宗:在动手编写代码之前,优秀的程序员会彻底理解输入、输出和中间数据结构,并围绕这些结构创建程序。
3.7 习题
[size=0.833em]1.本书行将出版之时,美国的个人收入所得税分为5种不同的税率,其中最大的税率大约为40%。以前的情况则更为复杂,税率也更高。下面所示的程序文本采用25个if语句的合理方法来计算1978年的美国联邦所得税。税率序列为0.14,0.15,0.16,0.17,0.18,…。序列中此后的增幅大于0.01。有何建议呢?
if income
作者: PF3686 时间: 2020-11-24 02:53
转发了
作者: LH4206 时间: 2020-11-24 03:09
转发了
作者: vito 时间: 2020-11-24 03:22
转发了
作者: gyzgdq 时间: 2020-11-24 03:35
转发了
作者: gyzgdq 时间: 2020-11-24 03:49
转发了
欢迎光临 创意电子 (https://wxcydz.cc/) |
Powered by Discuz! X3.4 |