zoukankan      html  css  js  c++  java
  • 大道至简第2章

    <?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

    ·第二章· 是懒人造就了方法

    僰道有故蜀王兵兰,亦有神作大滩江中。其崖崭峻不可破,(冰)乃积薪烧之。

    ——《华阳国志》

    第一节 是懒人造就了方法

    战国时期的李冰凿了一座山。

    《史记》中的蜀守冰,凿离堆,说的是李冰在成都做太守的时候凿出了离堆。一说是李冰将都江堰附近的玉垒山凿了一个大口子,叫宝瓶口,而凿的石头就堆成了离堆。另一说,则是李冰的确凿了一座(溷)崖,但是是在沫水,亦即今天的大渡河。

    在哪里凿的山,是史学家都说不清楚的事。但的确凿了一座山,而且方法是(因)其崖崭峻不可破,(冰)乃积薪烧之

    我们已经看到事物的进化了。《列子·汤问篇》里的愚公要碎石击壤,而李冰就已经懂得积薪烧之了。

    会有人说愚公是碎石,但史书中并没有说他碎石的方法究竟是斧钺以凿之,还是积薪以烧之。但想想在那个时代,如果有人懂得了烧石头这个方法,哪有不立即载文志之,永世传承的。

    再说了,愚公嘛。愚者怎么会呢?这还需要分析吗?需要吗?

    所以愚公会凿,而李冰会烧。那李冰又是为什么会用这种方法来碎石的呢?如果李冰也像愚公那样日复一日地督促着他的团队凿石开山,那他一定没有时间来学习、寻找或者观察;当然也不会发现这种方法可以加快工程进度,使得一大座山在短时间内就被哗啦哗啦地给掉了。

    要知道李冰的团队可是成百上千人,要修堰筑坝,还要凿离堆,当然还要吃喝拉撒睡。所以李冰如果忙起来的话,他必然是受命以来,夙夜忧叹,必然食难下咽,睡无安枕。反之,李冰一定是个闲人,可以闲到没事去看火能不能把石头烧爆。

    在这么大的工程里,如果有一个人会闲到看火烧石头,那他一定很懒。那么多事堆着不去做,去看烧石头,你说他不是懒是什么。

    正是一个懒人造就了烧石头这个碎石的方法。愚公太勤快了,勤快得今天可以比昨天多凿一倍的石头。或许在愚公的项目计划案的首页里就写着朱批大字:吾今胜昨倍许,明胜今倍许,而山不加增,何苦而不快。但是越发勤快,愚公将越发没有机会找到更快的方法。

     

        人的精力终归是有极限的。提出新的方法,解决的将是影响做事成效的根本问题。而愚公可以多吃点饭,多加点班,但突破不了人精力的极限。

    记住,在两千年前的某一天,闲极无聊的李冰下厨给夫人炒了一个小菜,他突然发现垒灶的鹅卵石被烧得爆裂开来,遇水尤甚。从此《史记》上就记下了蜀守冰,凿离堆,而《华阳国志》上则记下了他做这件事的方法积薪烧之

    第二节 一百万行代码是可以写在一个文件里的

    早期写的程序,都是将代码打在穿孔纸带上,让计算机去读的。要让计算机读的纸带当然是连续的,这无须多讲。其实我也没有那样写过程序,其中的苦楚我也不知道。

    后来有了汇编语言,可以写一些代码了。这时的代码是先写在文本文件里,然后交给一个编译器去编译,再由一个链接器去链接,这样就出来了程序。

    第一个写汇编的人,写的可能是有名的“Hello World”程序,那个程序写在一个文件里就行了。所以后来就成了习惯,大家都把代码写到一个文件里。在早期的汇编语言里,GOTO语句用得是非常非常频繁的,将一条语句GOTO到另一个文本文件里去,既不现实也不方便。所以大家习以为常,便统统地把代码写到一个文件里。

    再后来出现了高级语言,什么C呀,Pascal呀之类的。既然大家已经形成习惯了,所以很自然地会把一个程序写到一个文件里。无论这个程序有多大,多少行代码,写到一个文件里多方便呀。

    直到如今语言发展得更高级了。可是程序员的习惯还是难改,一旦得了机会,他们总还是喜欢把代码写到一个文件里的。

    好了,有人说我是想当然尔。嗯,这当然是有实据的。记得Delphi 1.0版发布的时候,全世界一片叫好声。连不支持双字节这样的大问题,都不影响它在华语地区的推广。然而不久,爆出了一个大BUG!什么大BUG呢?Delphi 1.0的编译器居然不支持超过64KB的源代码文件!

    这被Fans们一通好骂。直到我用Delphi 2.0时,一个从VB阵营转过来的程序员还跑过来问我这件事。好在Delphi 2.0改掉了这个BUG,这让当时我的面子上好一阵风光。

    64KB的文件是什么概念呢?

    1行代码大概(平均)是30字节,64KB的源代码是2 184行,如果代码风格好一点,再多一些空行的话,差不多也就是3 000行上下。

    也就是说,在Delphi 1.0的时代(以及其后的很多很多时代),程序员把3 000行代码写到一个文件里,是司空见惯的事了。如果你不让他这样写,还是会被痛骂的呢。

    所以呢,按照这一部分人的逻辑,一百万行代码其实是可以写在一个文件里的。不但可以,而且编译器、编辑器等也都必须予以支持。这才是正统的软件开发。

    勤快的愚公创造不了方法。这我已经说过了。对于要把一百万行代码写到一个文件里,并且查找一个函数要在编辑器里按5 000PageUp/PageDown键的勤快人来说,是不能指望他们创造出单元文件(Unit这样的开发方法来的。

    然而单元文件毕竟还是出现了。这个世界上,有勤快人就必然有懒人,有懒人也就必然有懒人的懒方法。

    有了单元文件,也就很快出现了一个新的概念:模块。把一个大模块分成小模块,再把小模块分成更细的小小模块,一个模块对应于一个单元。于是我们可以开始分工作了,一部分人写这几个单元的代码,另一部分则写那几个。

    很好,源代码终于可以被分割开来了。结构化编程的时代终于开始了,新的方法从此取代了旧的方法。而这一切的功劳,应当归功于那个在按第5 001PageDown键时,突然崩溃的程序师。他发自良心地说:不能让这一切继续下去了,我一定要把下一行代码写到第二个文件里去。我发誓,我要在编译器里加入一个Unit关键字。

    Turbo Pascal 3.0中才开始有了UsesUnit关键字。在ANSI Pascal标准里并没有它。

    汇编语言是面向特定硬件系统的一种助记符,所以只应该存在指令(而不包括语句)。因此有没有GOTO,取决于硬件指令系统的设计,而与这种汇编语言没什么关系。

    早期的计算机被用来完成复杂的科学运算,因此不可能真的有人无聊到去写Hello World

    第三节 你桌上的书是乱的吗

    几周之前,在一所电脑培训学校与学生座谈时,一个学员问我:为什么我学了一年的编程,却还是不知道怎么写程序呢

    我想了想,问了这个学员一个问题:你桌上的书是乱的吗?

    他迟疑了一下,不过还是回答我道:比较整齐。

    我当时便反问他:你既然知道如何把书分类、归整得整整齐齐地放在书桌上,那怎么没想过如何把所学的知识分类一下,归纳一下,整整齐齐地放在脑子里呢?

    如果一个人学了一年的编程,他的脑袋里还是晕乎乎的,不知道从哪里开始,也不知道如何做程序。那想来只有一个原因:他学了,也把知识学进去了,就是不知道这些知识是干什么的。或者说,他不知道各种知识都可以用来做什么。

    其实结构化编程的基本单位是过程(Procedure,而不是上一小节说到的单元(Unit。然而在我看来,过程及其调用是CPU指令集所提供的执行逻辑,而不是普通的开发人员在编程实践中所总结和创生的方法

    这里要提及CPU指令集的产生。产生最初的指令集的方式我已经无可考证,我所知道的是,CISC指令集与RISC指令集之争在1979年终于爆发。前者被称为复杂指令集,然而经过Patterson等科学家的研究,发现80%CISC指令只有在20%的时间内才会用到;更进一步的研究发现,在最常用的10条指令中,包含的流程控制只有条件分支(IF...THEN...跳转(JUMP” 调用返回(CALL/RET”……

    于是CISCRISC(精简指令集计算机)替代了。动摇CISC指令集地位的方法,就是分类统计。

    X86系统中,循环是用条件分支(或循环指令)来实现的,而且条件分支指令并不是IF...THEN...,这里用这两个关键字,仅用于说明问题。

    正如CISC指令集搅乱了一代程序设计师的思路一样,大量的知识和资讯搅乱了上面向我提问的那位学员的思想。他应该尝试一下分类,把既有的知识像桌子上的书一样整理一下,最常用的放在手边,而最不常用的放在书柜里。如果这样的话,我想他已经在九个月前就开始写第一个软件产品了。

    你桌上的书还是乱的吗?

    第四节 我的第一次思考:程序 = 算法 + 结构 + 方法

    我对程序的本质的第一次思考其实发生在不久前。那是我在OICQ上与Soul(王昊)的一次谈话。

    SoulDelphiBBS现任的总版主,是我很敬重的一位程序员。那时我们正在做DelphiBBS的一个“B计划II”,也就是出第二本书。他当时在写一篇有关面向对象(OOP的文章,而我正在写《Delphi源代码分析》。在这本书的初期版本里,有面向对象这一部分的内容。

    这段对话的确很长。如果你不是非常有经验的程序员,那么不能完整地阅读和理解这段文字是很正常的。部分读者甚至可以跳过这段引文,直接阅读后面的结论。

    我们的对话摘要如下。

    Soul:我在写书讨论面向对象的局限性

    我:嗯。这个倒与我的意见一致。哈哈哈。

    绝对可以用面向过程的方法来实现任意复杂的系统。要知道,航天飞机也是在面向过程的时代上的天。但是,为了使一切变得不是那么复杂,还是出现了面向对象程序设计的方法。

      ——我那本书里,在面向对象一部分之前的引文中,就是这样写的。

    Soul:现在的程序是按照冯·诺伊曼的第一种方案做的,本来就是顺序的,而不是同步的。CPU怎么说都是一条指令一条指令执行的。

    我: 面向过程是对流程结构编程方法的高度概括。而面向对象本身只解决了结构编程方法的问题,而并没有对流程加以改造。

    Soul:确实如此。确实如此。

    我:对流程进一步概括的,是事件驱动程序模型。但这个模型不是OO(面向对象)提出的,而是Windows的消息系统内置的。所以,现在很多人迷惑于对象事件,试图通过OO来解决一切的想法原本就是很可笑的。

    Soul:我先停下来,和你讨论这个问题,顺便补充到书里去。

    我: 如果要了解事件驱动的本质,就应该追溯到Windows内核。这样就涉及到线程、进程和窗体消息系统这些与OO无关的内容。所以,整个的RAD(快速应用程序开发)编程模型是OOOS(操作系统)一起构建的。现在很多的开发人员只知其OO的外表,而看不到OS的内核,所以也就总是难以提高。

    SoulOO里面,我觉得事件的概念是很牵强的,因为真正的对象之间是相互作用,就好像作用力和反作用力,不会有个顺序的延时。

    我:应该留意到,整个的事件模型都是以记录消息的方式来传递的。也就是说,事件模型停留在面向过程编程时代使用的数据结构的层面上。因此,也就不难明白,使用或不使用OO都能写Windows程序。

      因为流程还是在面向过程时代。

    Soul:所以所谓的面向对象的事件还是顺序的。所以我们经常要考虑一个事件发生后对其他过程的影响,所以面向对象在现在而言还是牵强的。

    我:      如果你深入OS来看SHE(结构化异常处理),来看Messages(窗体消息),就知道这些东西原本就不是为了OO而准备的。面向对象封装了它们,却无法改造它们的流程和内核。因为OO的抽象层面并不是这个。

    事件的连续性并不是某种编程方法或者程序逻辑结构所决定的。正如你前面所说的,那是CPU决定的事。

    Soul:比如条件选择,其实也可以用一种对象来实现,而事实却没有。这个是因为CPU的特性和面向对象太麻烦。

    我: 可能,将CPU做成面向对象的可能还是比较难以想象和理解。所以MS才启动.NET Framework。我不认为.NET在面向对象方法上有什么超越,也不认为它的FCL库会有什么奇特的地方——除非它们足够庞大。但是我认为,如果有一天OS也是用.NET Framework来编写的,OS一级的消息系统、异常机制、线程机制等都是.NET的,都是面向对象的。那么,在这个基础上,将事件驱动并入OO层面的模型,才有可能。

    Soul:所以我发觉面向对象的思维第一不可能彻底,第二只能用在总体分析层上。在很多时候,实质上我们只是把一个顺序的流程折叠成对象。

    我:倒也不是不可能彻底。有绝对OO的模型,这样的模型我见过。哈哈,但说实在的,我觉得小应用用绝对OO”的方式来编写,有失应用的本意。我们做东西只是要,而不是研究它用的是什么模型。所以,“Hello World”也用OO方式实现,原本就只是出现在教科书中的Sample罢了。哈哈。

    Soul:还有不可能用彻底的面向对象方法来表达世界。 因为这个世界不是面向对象的。是关系网络图,面向对象只是树,只能片面地表达世界。所以很多时候面向对象去解决问题会非常痛苦。所以编程退到数据结构更合理,哈哈。

    我:如果内存是层状存取的,那么我们的数据结构就可以基于多层来形成多层数据结构体系。如果内存是树状存取的,那么我们当然可以用的方式来存取——可惜我们只有顺序存取的内存。

            程序=数据+算法
    ——
    这个是面向过程时代的事。
    程序=数据+算法+方法
    ——
    OO时代,我们看到了事件驱动和模型驱动,所以出现了方法问题。

    Soul:我的经验是:总体结构面向对象,关系数据结构,实现算法。
    看来我们对面向对象的认识还是比较一致的。

    我第一次提到我对程序的理解是程序=数据+算法+方法,就是在这一次与Soul的交谈之中。在这次的交谈中的思考仍有些不成熟的地方,例如我完全忽略了在面向过程时代的方法问题。实际上面向过程开发也是有相关的方法的。

    所谓面向过程开发,其实是对结构化程序设计在代码阶段的一个习惯性的说法。而我忽略了这个阶段的方法的根本原因,是即使没有任何方法的存在,只需要单元(Unit模块(Module的概念,在面向过程时代,一样可以做出任意大型的程序。在那个时代,方法问题并不会像鼻子一样凸显在每一个程序员的面前。

    在面向过程开发中,过程(Procedure是由CPU提供的,单元(Unit则是编译器提供的(机制)。程序员无须(至少不是必须)再造就什么方法,就可以进行愚公式的开发工作了。

    如果不出现面向对象的话,这样伟大的工程可能还要再干一百年……

    而与面向对象是否出现完全无关的一个东西,却因为过程单元的出现而出现了。这就是工程(Engineering

     

    【愚公移山记:智叟商晋】

    京城初将所学的技法投入到工程中去,提高了工效,公输一家很开心。端木启则把粗铁变成了好马,进而居奇得利,也很开心。

     

  • 相关阅读:
    java客户端集成RocketMq
    java8常见流式操作
    Spring源码架构以及编译
    Rocket消息存储原理
    由二叉树中序和先序遍历求二叉树的结构
    10.14重写ENqUEUE和DEQUEUE,使之能处理队列的下溢和上溢。
    10.12 说明如何用一个数组A[1..n]来实现两个栈,使得两个栈中的元素总数不到n时,两者都不会发生上溢,注意PUSH和POP操作的时间应为O(1)。
    用类模板实现对任何类型的数据进行堆栈进行存取操作。
    java struts2+urlrewrite 配置404错误
    c++ sizeof 及别名定义2种示例
  • 原文地址:https://www.cnblogs.com/CharmingDang/p/9663794.html
Copyright © 2011-2022 走看看