必读网 - 人生必读的书

TXT下载此书 | 书籍信息


(双击鼠标开启屏幕滚动,鼠标上下控制速度) 返回首页
选择背景色:
浏览字体:[ ]  
字体颜色: 双击鼠标滚屏: (1最慢,10最快)

程序员修炼之道

_5 Andrew(美)
N 1 # then north 1
E 2 # then east 2
S 1 # then back south
U # pen up
  请实现解析这种语言的代码。它应该被设计成能简单地增加新命令。(解答在281页)
6. 设计一种解析时间规范的BNF文法。应能接受下面的所有例子:(解答在282页)
4pm, 7:38pm, 23:42, 3:16, 3:16am
7. 用yacc、bison或类似的解析器生成器为练习6中的BNF文法实现解析器。(解答在282页)
8. 用Perl实现时间解析器(提示:正则表达式可带来好的解析器)。(解答在283页)
13 估算
  快!通过56k modem线发送《战争与和平》需要多少时间?存储一百万个姓名与地址需要多少磁盘空间?1 000字节的数据块通过路由器需要多少时间?交付你的项目需要多少个月?
  在某种程度上,这些都是没有意义的问题——它们都缺少信息。然而它们仍然可以得到回答,只要你习惯于进行估算。同时,在进行估算的过程中,你将会加深对你的程序所处的世界的理解。
  通过学习估算,并将此技能发展到你对事物的数量级有直觉的程度,你就能展现出一种魔法般的能力,确定它们的可行性。当有人说“我们将通过ISDN线路把备份发给中央站点”时,你将能够直觉地知道那是否实际。当你编码时,你将能够知道哪些子系统需要优化,哪些可以放在一边。
提示18
Estimate to Avoid Surprises
估算,以避免发生意外
  作为奖励,在这一节的末尾我们将透露一个总是正确的答案——无论什么时候有人要你进行估算,你都可以给出答案。
多准确才足够准确
  在某种程度上,所有的解答都是估算。只不过有一些要比其他的更准确。所以当有人要你进行估算时,你要问自己的第一个问题就是,你解答问题的语境是什么?他们是需要高度的准确性,还是在考虑棒球场的大小?
l 如果你的奶奶问你何时抵达,她也许只是想知道该给你准备午餐还是晚餐。而一个困在水下、空气就快用光的潜水员很可能对精确到秒的答案更感兴趣。
l p的值是多少?如果你想知道的是要买多少饰边,才能把一个圆形花坛围起来,那么“3”很可能就足够好了。如果你在学校里,那么“22/7”也许就是一个好的近似值。如果你在NASA(美国国家航空航天管理局),那么也许要12个小数位。
  关于估算,一件有趣的事情是,你使用的单位会对结果的解读造成影响。如果你说,某事需要130个工作日,那么大家会期望它在相当接近的时间里完成。但是,如果你说“哦,大概要六个月”,那么大家知道它会在从现在开始的五到七个月内完成。这两个数字表示相同的时长,但“130天”却可能暗含了比你的感觉更高的精确程度。我们建议你这样度量时间估算:
时长
报出估算的单位
1-15天

3-8周

8-30周

30+周
在给出估算前努力思考一下
  于是,在完成了所有必要的工作之后,你确定项目将需要125个工作日(25周),你可以给出“大约六个月”的估算。
  同样的概念适用于对任何数量的估算:要选择能反映你想要传达的精确度的单位。
13 估算(2)
估算来自哪里
  所有的估算都以问题的模型为基础。但在我们过深地卷入建模技术之前,我们必须先提及一个基本的估算诀窍,它总能给出好的答案:去问已经做过这件事情的人。在你一头钻进建模之前,仔细在周围找找也曾处在类似情况下的人。
  看看他们的问题是怎么解决的。你不大可能找到完全相符的案例,但你会惊奇有多少次,你能够成功地借鉴他人的经验。
理解提问内容
  任何估算练习的第一步都是建立对提问内容的理解。除了上面讨论的精确度问题以外,你还需要把握问题域的范围。这常常隐含在问题中,但你需要养成在开始猜想之前先思考范围的习惯。常常,你选择的范围将形成你给出的解答的一部分:“假定没有交通意外,而且车里还有汽油,我会在20分钟内赶到那里。”
建立系统的模型
  这是估算有趣的部分。根据你对所提问题的理解,建立粗略、就绪的思维模型骨架。如果你是在估算响应时间,你的模型也许要涉及服务器和某种到达流量(arriving traffic)。对于一个项目,模型可以是你的组织在开发过程中所用的步骤、以及系统的实现方式的非常粗略的图景。
  建模既可以是创造性的,又可以是长期有用的。在建模的过程中,你常常会发现一些在表面上不明显的底层模式与过程。你甚至可能会想要重新检查原来的问题:“你要求对做X所需的时间进行估算。但好像X的变种Y只需一半时间就能完成,而你只会损失一个特性。”
  建模把不精确性引入了估算过程中。这是不可避免的,而且也是有益的。你是在用模型的简单性与精确性做交易。使花在模型上的努力加倍也许只能带来精确性的轻微提高。你的经验将告诉你何时停止提炼。
把模型分解为组件
  一旦拥有了模型,你可以把它分解为组件。你须要找出描述这些组件怎样交互的数学规则。有时某个组件会提供一个值,加入到结果中。有些组件有着成倍的影响,而另一些可能会更为复杂(比如那些模拟某个节点上的到达流量的组件)。
  你将会发现,在典型情况下,每个组件都有一些参数,会对它给整个模型带来什么造成影响。在这一阶段,只要确定每个参数就行了。
给每个参数指定值
  一旦你分解出各个参数,你就可以逐一给每个参数赋值。在这个步骤中你可能会引入一些错误。诀窍是找出哪些参数对结果的影响最大,并致力于让它们大致正确。在典型情况下,其值被直接加入结果的参数,没有被乘或除的那些参数重要。让线路速度加倍可以让1小时内接收的数据量加倍,而增加5毫秒的传输延迟不会有显著的效果。
  你应该采用一种合理的方式计算这些关键参数。对于排队的例子,你可以测量现有系统的实际事务到达率,或是找一个类似的系统进行测量。与此类似,你可以测量现在服务1个请求所花的时间,或是使用这一节描述的技术进行估算。事实上,你常常会发现自己以其他子估算为基础进行估算。这是最大的错误伺机溜进来的地方。
计算答案
  只有在最简单的情况下估算才有单一的答案。你也许会高兴地说:“我能在15分钟内走完五个街区。”但是,当系统变得更为复杂时,你就会避免做出正面回答。进行多次计算,改变关键参数的值,直到你找出真正主导模型的那些参数。电子表格可以有很大帮助。然后根据这些参数表述你的答案。“如果系统拥有SCSI总线和64MB内存,响应时间约为四分之三秒;如果内存是48MB,则响应时间约为一秒。”(注意“四分之三秒”怎样给人以一种与750毫秒不同的精确感。)
  在计算阶段,你可能会得到看起来很奇怪的答案。不要太快放弃它们。如果你的运算是正确的,那你对问题或模型的理解就很可能是错的。这是非常宝贵的信息。
追踪你的估算能力
  我们认为,记录你的估算,从而让你看到自己接近正确答案的程度,这是一个非常好的主意。如果总体估算涉及子估算的计算,那么也要追踪这些子估算。你常常会发现自己估算得非常好——事实上,一段时间之后,你就会开始期待这样的事情。
  如果结果证明估算错了,不要只是耸耸肩走开。找出事情为何与你的猜想不同的原因。也许你选择了与问题的实际情况不符的一些参数。也许你的模型是错的。不管原因是什么,花一点时间揭开所发生的事情。如果你这样做了,你的下一次估算就会更好。
估算项目进度
  在面对相当大的应用开发的各种复杂问题与反复无常的情况时,普通的估算规则可能会失效。我们发现,为项目确定进度表的惟一途径常常是在相同的项目上获取经验。如果你实行增量开发、重复下面的步骤,这不一定就是一个悖论:
l 检查需求
l 分析风险
l 设计、实现、集成
l 向用户确认
  一开始,你对需要多少次迭代、或是需要多少时间,也许只有模糊的概念。有些方法要求你把这个作为初始计划的一部分定下来,但除了最微不足道的项目,这是一个错误。除非你在开发与前一个应用类似的应用,拥有同样的团队和同样的技术,否则,你就只不过是在猜想。
  于是你完成了初始功能的编码与测试,并将此标记为第一轮增量开发的结束。基于这样的经验,你可以提炼你原来对迭代次数、以及在每次迭代中可以包含的内容的猜想。提炼会变得一次比一次好,对进度表的信心也将随之增长。
提示19
Iterate the Schedule with the Code
通过代码对进度表进行迭代
  这也许并不会受到管理部门的欢迎,在典型情况下,他们想要的是单一的、必须遵守的数字——甚至是在项目开始之前。你必须帮助他们了解团队、团队的生产率、还有环境将决定进度。通过使其形式化,并把改进进度表作为每次迭代的一部分,你将给予他们你所能给予的最精确的进度估算。
在被要求进行估算时说什么
  你说:“我等会儿回答你。”
  如果你放慢估算的速度,并花一点时间仔细检查我们在这一节描述的步骤,你几乎总能得到更好的结果。在咖啡机旁给出的估算将(像咖啡一样)回来纠缠你。
相关内容
l 算法速度,177页
挑战
l 开始写估算日志。追踪每一次估算的精确程度。如果你的错误率大于50%,设法找出你的估算误入歧途的地方。
练习
9. 有人问你:“1Mbps的通信线路和在口袋里装了4GB磁带、在两台计算机间步行的人,哪一个的带宽更高?”你要对你的答案附加什么约束,以确保你的答复的范围是正确的?(例如,你可以说,访问磁带所花时间忽略不计。) (解答在283页)
10. 那么,哪一个带宽更高? (解答在284页)
14 纯文本的威力
每个工匠在开始其职业生涯时,都会准备一套品质良好的基本工具。木匠可能需要尺、计量器、几把锯子、几把好刨子、精良的凿子、钻孔器和夹子、锤子还有钳子。这些工具将经过认真挑选、打造得坚固耐用、并用于完成很少与其他工具重合的特定工作,而且,也许最重要的是,刚刚出道的木匠把它们拿在手里会觉得很顺手。
  随后学习与适应的过程就开始了。每样工具都有自身的特性和古怪之处,并且需要得到相应的特殊对待。每样工具都需要以独特的方式进行打磨,或者以独特的方式把持。随着时间的过去,每样工具都会因使用而磨损,直到手柄看上去就像是木匠双手的模子,而切割面与握持工具的角度完全吻合。到这时,工具变成了工匠的头脑与所完成的产品之间的通道——它们变成了工匠双手的延伸。木匠将不时增添新的工具,比如饼式切坯机、激光制导斜切锯、楔形模具——全都是奇妙的技术,但你可以肯定的是,当他把原来的某样工具拿在手里,当他听到刨子滑过木料发出的歌声时,那是他最高兴的时候。
  工具放大你的才干。你的工具越好,你越是能更好地掌握它们的用法,你的生产力就越高。从一套基本的通用工具开始,随着经验的获得,随着你遇到一些特殊需求,你将会在其中增添新的工具。要与工匠一样,想着定期增添工具。要总是寻找更好的做事方式。如果你遇到某种情况,你觉得现有的工具不能解决问题,记得去寻找可能会有帮助的其他工具或更强大的工具。
让需要驱动你的采购。
  许多新程序员都会犯下错误,采用单一的强力工具,比如特定的集成开发环境(IDE),而且再也不离开其舒适的界面。这实在是个错误。我们要乐于超越IDE所施加的各种限制。要做到这一点,惟一的途径是保持基本工具集的“锋利”与就绪。
  在本章我们将讨论怎样为你自己的基本工具箱投资。与关于工具的任何好的讨论一样,我们将从考察你的原材料——你将要制作的东西——开始(在“纯文本的威力”中)。然后我们将从那里转向工作台(workbench),在我们的工作范围也就是计算机。要怎样使用计算机,你才能最大限度地利用你所用的工具?我们将在shell游戏中讨论这一问题。现在我们有了工作所需的材料及工作台,我们将转向一样你可能用得最频繁的工具:你的编辑器。在强力编辑中,我们将提出多种让你更有效率的途径。
  为了确保不会丢失先前的任何工作成果,我们应该总是使用源码控制系统——即使是像我们的个人地址簿这样的东西!同时,因为Murphy先生实在是一个乐观主义者,如果你没有高超的调试技能,你就不可能成为了不起的程序员。
  你需要一些“胶合剂”,把大量魔术“粘”在一起。我们将在文本操纵中讨论一些可能的方案,比如awk、Perl以及Python。
  就如同木匠有时会制作模具,用以控制复杂工件的打造一样,程序员也可以编写自身能编写代码的代码。我们将在“代码生成器”中讨论这一问题。
  花时间学习使用这些工具,有一天你将会惊奇地发现,你的手指在键盘上移动,操纵文本,却不用进行有意识的思考。工具将变成你的双手的延伸。
纯文本的威力
  作为注重实效的程序员,我们的基本材料不是木头,不是铁,而是知识。我们搜集需求,将其变为知识,随后又在我们的设计、实现、测试、以及文档中表达这些知识。而且我们相信,持久地存储知识的最佳格式是纯文本。通过纯文本,我们给予了自己既能以手工方式、也能以程序方式操纵知识的能力——实际上可以随意使用每一样工具。
什么是纯文本
  纯文本由可打印字符组成,人可以直接阅读和理解其形式。例如,尽管下面的片段由可打印字符组成,它却是无意义的:
Fieldl9=467abe
  阅读者不知道467abe的含义是什么。更好的选择是让其变得能让人理解:
DrawingType=UMLActivityDrawing
  纯文本并非意味着文本是无结构的;XML、SGML和HTML都是有良好定义的结构的纯文本的好例子。通过纯文本,你可以做你通过某种二进制格式所能做的每件事情,其中包括版本管理。
  与直接的二进制编码相比,纯文本所处的层面往往更高;前者通常直接源自实现。假定你想要存储叫做uses_menus的属性,其值既可为TRUE,也可为FALSE。使用纯文本,你可以将其写为:
es_menus=FALSE
  把它与0010010101110101对比一下。
  大多数二进制格式的问题在于,理解数据所必需的语境与数据本身是分离的。你人为地使数据与其含义脱离开来。数据也可能加了密;没有应用逻辑对其进行解析,这些数据绝对没有意义。但是,通过纯文本,你可以获得自描述(self-describing)的、不依赖于创建它的应用的数据流。
提示20
Keep Knowledge in Plain Text
用纯文本保存知识
缺点
  使用纯文本有两个主要缺点:(1)与压缩的二进制格式相比,存储纯文本所需空间更多,(2)要解释及处理纯文本文件,计算上的代价可能更昂贵。
  取决于你的应用,这两种情况或其中之一可能让人无法接受——例如,在存储卫星遥测数据时,或是用做关系数据库的内部格式时。
  但即使是在这些情况下,用纯文本存储关于原始数据的元数据也可能是可以接受的(参见“元程序设计”,144页)。
  有些开发者可能会担心,用纯文本存储元数据,是在把这些数据暴露给系统的用户。这种担心放错了地方。与纯文本相比,二进制数据也许更晦涩难懂,但却并非更安全。如果你担心用户看到密码,就进行加密。如果你不想让他们改变配置参数,就在文件中包含所有参数值的安全哈希值作作为校验和。
文本的威力
  既然更大和更慢不是用户最想要的特性,为什么还要使用纯文本?好处是什么?
l 保证不过时
l 杠杆作用
l 更易于测试
保证不过时
  人能够阅读的数据形式,以及自描述的数据,将比所有其他的数据形式和创建它们的应用都活得更长久。句号。
  只要数据还存在,你就有机会使用它——也许是在原来创建它的应用已经不存在很久之后。
  只需部分地了解其格式,你就可以解析这样的文件;而对于大多数二进制文件,要成功地进行解析,你必须了解整个格式的所有细节。
  考虑一个来自某遗留系统的数据文件。关于原来的应用你的了解很少;对你来说最要紧的是它保存了客户的社会保障号列表,你需要找出这些保障号,并将其提取出来。在数据文件中,你看到:
123-45-6789
...
567-89-0123
...
901-23-4567
  识别出了社会保障号的格式,你可以很快写一个小程序提取该数据——即使你没有关于文件中其他任何东西的信息。
  但设想一下,如果该文件的格式是这样的:
AC27123456789B11P
...
XY43567890123QTYL
...
6T2190123456788AM
  你可能就不会那么轻松地识别出这些数字的含义了。这是人能够阅读(human readable)与人能够理解(human understandable)之间的区别。
  在我们进行解析时,FIELD10的帮助也不大。改成
123-45-6789
就会让这个练习变得一点也不费脑子——而且这些数据保证会比创建它的任何项目都活得更长久。
杠杆作用
  实际上,计算世界中的每一样工具,从源码管理系统到编译器环境,再到编辑器及独立的过滤器,都能够在纯文本上进行操作。
Unix哲学
  提供“锋利”的小工具、其中每一样都意在把一件事情做好——Unix因围绕这样的哲学进行设计而著称。这一哲学通过使用公共的底层格式得以实行:面向行的纯文本文件。用于系统管理(用户及密码、网络配置,等等)的数据库全都作为纯文本文件保存(有些系统,比如Solaris,为了优化性能,还维护有特定数据的二进制形式。纯文本版本保留用作通往二进制版本的接口)。
  当系统崩溃时,你可能需要通过最小限度的环境进行恢复(例如,你可能无法访问图形驱动程序)。像这样的情形,实在可以让你欣赏到纯文本的简单性。
  例如,假定你要对一个大型应用进行产品部署,该应用具有复杂的针对具体现场的配置文件(我们想到sendmail)。如果该文件是纯文本格式的,你可以把它置于源码控制系统的管理之下(参见源码控制,86页),这样你就可以自动保存所有改动的历史。像diff和fc这样的文件比较工具允许你查看做了哪些改动,而sum允许你生成校验和,用以监视文件是否受到了偶然的(或恶意的)修改。
更易于测试
  如果你用纯文本创建用于驱动系统测试的合成数据,那么增加、更新、或是修改测试数据就是一件简单的事情,而且无须为此创建任何特殊工具。与此类似,你可以非常轻松地分析回归测试(regression test)输出的纯文本,或通过Perl、Python及其他脚本工具进行更为全面彻底的检查。
最小公分母
  即使在未来,基于XML的智能代理已能自治地穿越混乱、危险的Internet、自行协商数据交换,无处不在的纯文本也仍然会存在。事实上,在异种环境中,纯文本的优点比其所有的缺点都重要。你需要确保所有各方能够使用公共标准进行通信。纯文本就是那个标准。
相关内容:
l 源码控制,86页
l 代码生成器,102页
l 元程序设计,144页
l 黑板,165页
l 无处不在的自动化,230页
l 全都是写,248页
挑战
l 使用你喜欢的语言,用直接的二进制表示设计一个小地址簿数据库(姓名、电话号码,等等)。完成以后再继续往下读。
1. 把该格式转换成使用XML的纯文本格式。
2. 在这两个版本中,增加一个新的、叫做方向的变长字段,在其中你可以输入每个人的住宅所在的方向。
  在版本管理与可扩展性方面会遇到什么问题?哪种形式更易于修改?转换已有的数据呢?
15 shell游戏
  每个木匠都需要好用、坚固、可靠的工作台,用以在加工工件时把工件放置在方便的高度上。工作台成为木工房的中心,随着工件的成形,木匠会一次次回到工作台的近旁。
  对于操纵文本文件的程序员,工作台就是命令shell。在shell提示下,你可以调用你的全套工具,并使用管道、以这些工具原来的开发者从未想过的方式把它们组合在一起。在shell下,你可以启动应用、调试器、浏览器、编辑器以及各种实用程序。你可以搜索文件、查询系统状态、过滤输出。通过对shell进行编程,你可以构建复杂的宏命令,用来完成你经常进行的各种活动。
  对于在GUI界面和集成开发环境(IDE)上成长起来的程序员,这似乎显得很极端。毕竟,用鼠标指指点点,你不是也同样能把这些事情做好吗?
  简单的回答:“不能”。GUI界面很奇妙,对于某些简单操作,它们也可能更快、更方便。移动文件、阅读MIME编码的电子邮件以及写信,这都是你可能想要在图形环境中完成的事情。但如果你使用GUI完成所有的工作,你就会错过你的环境的某些能力。你将无法使常见任务自动化,或是利用各种可用工具的全部力量。同时,你也将无法组合你的各种工具,创建定制的宏工具。GUI的好处是WYSIWYG——所见即所得(what you see is what you get)。缺点是WYSIAYG——所见即全部所得(what you see is all you get)。
  GUI环境通常受限于它们的设计者想要提供的能力。如果你需要超越设计者提供的模型,你大概不会那么走运——而且很多时候,你确实需要超越这些模型。注重实效的程序员并非只是剪切代码、或是开发对象模型、或是撰写文档、或是使构建过程自动化——所有这些事情我们全都要做。通常,任何一样工具的适用范围都局限于该工具预期要完成的任务。例如,假定你需要把代码预处理器集成进你的IDE中(为了实现按合约设计、多处理编译指示,等等)。除非IDE的设计者明确地为这种能力提供了挂钩,否则,你无法做到这一点。
  你也许已经习惯于在命令提示下工作,在这种情况下,你可以放心地跳过这一节。否则,你也许还需要我们向你证明,shell是你的朋友。
  作为注重实效的程序员,你不断地想要执行特别的操作——GUI可能不支持的操作。当你想要快速地组合一些命令,以完成一次查询或某种其他的任务时,命令行要更为适宜。这里有一些例子:
找出修改日期比你的Makefile的修改日期更近的全部.c文件。
Shell
find . -name ' *.c' –newer Makefile –print
GUI
打开资源管理器,转到正确的目录,点击Makefile,记下修改时间。然后调出 “工具/查找”,在指定文件处输入*.c。选择“日期”选项卡,在第一个日期字段中输入你记下的Makefile的日期。然后点击“确定”。
构造我的源码的zip/tar存档文件。
Shell
zip p *.h *.c 或
tar cvf r *.h *.c
GUI
调出ZIP实用程序(比如共享软件WinZip[URL 41]),选择[创建新存档文件],输入它的名称,在“增加”对话框中选择源目录,把过滤器设置为“*.c”,点击“增加”,把过滤器设置为“*.h”,点击“增加”,然后关闭存档文件。
在上周哪些Java文件没有改动过?
Shell
find . -name '*.java' -mtime +7 –print
GUI
点击并转到“查找文件”,点击“文件名”字段,敲入“*.java”,选择“修改日期”选项卡。然后选择“介于”。点击“开始日期”,敲入项目开始的日期。点击“结束日期”,敲入1周以前的日期(确保手边有日历)。点击“开始查找”。
上面的文件中,哪些使用了awt库?
Shell
find . -name '*.java' -mtime +7 -print |
xargs grep 't'
GUI
把前面的例子列出的各个文件装入编辑器,搜索字符串“Jt”。把含有该字符串的文件的名字写下来。
  显然,这样的例子还可以一直举下去。shell命令可能很晦涩,或是太简略,但却很强大,也很简练。同时,因为shell命令可被组合进脚本文件(或是Windows下的命令文件)中,你可以构建命令序列,使你常做的事情自动化。
提示21
Use the Power of Command Shells
利用命令shell的力量
返回书籍页