第三章 地势坤,君子以厚德载物 ——《箴言》第一章读书笔记之三



梁先生在说完“入门最基本的方法就是从C语言入手”时,意犹未尽地又再次给我们描绘了神奇的“高手的境界”:

“事实上,到达高手的境界以后,不管什么语言不语言的,其实都根本不用去学,只要拿过来看两天,就全部精通。”“所有的语言都是很花哨的表面东西。高手马上就能透过它的表象而看到它的本质。这才是真正的高手。他不要再去学什么Java,或者其他什么语言。当他真正要写个Java程序的时候,只要把Java程序拿过来看一看,瞄一瞄书,就全部清楚了。如果这时他学VB就更容易了,我想不用一天的时间,就能学会。到达高手的境界以后,所有的事物都是触类旁通的。”

却又说:“如果你没有入门,即使去书店找n本书,天天背它,你也不会成为高手。”

梁先生从未入门到入门,不知是看的那一本无字天书,他没有“和盘托出”给我们,只是打发我们“可以去任何一个书店去找一本书回来自己看就可以了”。而现在未入门的我们“即使去书店找n本书,天天背它,你也不会成为高手”了,通往出神入化境界的大道成了一道我们不可逾越的鸿沟,我们朝圣的路也变得遥遥无期了。没有天书的我们,就像贾宝玉梦游太虚幻境而误闯迷津一般,可宝玉是“幸运”的,而我们却欲误堕落其中而不得。望着那些美妙的幻境,欲“触类旁通”也不可能了,“可卿——不——大师,让我们堕落其中去朝圣吧!”


朝圣不得,大概还是让我们从CPU入手的罢,接着梁先生嘱咐我们“另外一点也很重要,即好的程序员必须具备开放性思维,也就是思考问题的方法。程序员,尤其是现在很多的程序员,都被误导从MFC入手,这就很容易形成一种封闭式的思维方式。这也是微软希望很多人只能学点表面的东西,不致成为高手,所以他大力推荐MFC之类的工具,但也真有很多人愿意去上他的当,最后真正迷失方向。……如果你真正有一种开放性的思维,在你能够成为高级程序员的时候,对MFC这些是不屑一顾的,MFC,VB根本不会在考虑的范围之内。”

无论何种原因(是语言特性、语言大环境、硬件支撑环境、市场策略等导致微软迫不得已提供源代码,或还是其他原因等等导致的),微软在提供MFC库的同时还是提供了实际的源代码,至少没有在通往Win32 API(以此为例)层次的路上设置可望不可及的迷津来款待我们。如果微软只希望我们“只能学点表面的东西,不致成为高手”,那么提供MFC库的源代码或许就是一向“方向把握得很准确”的比尔•盖茨先生所犯下的一个方向性错误了。“没有什么东西能逃出你的手掌心”的编程高手撒播的光辉竟然不能普照到MFC的头上。对之“不屑一顾”,当然不会“去上他的当”,也不会“最后真正迷失方向”,自然更加不会“出现问题时,问题出在哪里就搞不清楚了”。“什么问题一看就能明白,一看就能抓住最核心的问题,最根本的根本,而不会被其他的枝叶或表象所迷惑”的高手,竟然不能深入到MFC中,更不用说浅出了,更不用说深深浅浅、浅浅深深地出入自如了,——高手只有“做到这一步后才算比较成功”的。后面谈到线程与GDI的“冲突”问题时我们将会更加见识到高手的化境之谜。

具有开放性思维的高手竟不能容忍MFC有一席之地,不知开放性思维的葫芦里卖的究竟是什么灵丹妙药,还有待下文了。看来确实形成了“一种封闭式的思维方式”。


关于规范的格式是入门的基础。“以前所有的C语言的书中,太不重视格式的问题,写的程序像一堆堆的垃圾一样。这也导致了现在的很多程序员中有很多是废码、垃圾代码,这和那些入门的书非常有关系。因为这些书从不强调代码规范,而真正的商业程序绝对是规范的。……正确的程序设计思路是成对编码,先写上面的大括号,然后马上写下面的大括号。这样一个函数体就已经形成了。它没有任何问题。”然后又说明了for语句的循环体之大括号也照此打,“这就是一种成对编码。”尽管不耐烦但还是继续抄罢,“成对编码就涉及到代码规范的问题。为什么我说上面一个大括号,下面一个大括号,而不说成是前面一个大括号,后面一个大括号呢?如果是一般C语言的书,则它绝对说是后面加个大括号,回过头前面加个大括号。事实上,这就是垃圾程序的写法。正确的思路是写完行给它回车,给它大括号独立的一行,下面大括号也是独立的一行,而且这两个大括号跟那个for单词中间错开一个TAB。代码格式一定不能乱,一定要格式非常清楚……而且结合成对编码思维。”


没想到Brian Kernighan和Dennis Ritchie等人的K&R大括号风格,竟成了“垃圾代码”之源,而且他们写的那些K&R风格的代码竟成了“垃圾代码”,自然还有那些UNIX代码。他们和Bjarne Stroustrup,Andrew Koenig,Stanly Lippman等等大概成了写“垃圾代码”的大师罢,尽管可以列出一堆堆的写“垃圾代码”的大师来,但耐心没有练到家也就作罢了。他们冒犯了梁先生规定的大括号规范,所以都不应当侧身大师之列而应该归入写“垃圾代码”的不屑一顾的行列之中。

他们当中最渺小的人,对于我们的“编程高手”来说,恐怕都是个巨人。我们的大师要是放下架子去写写那样的“垃圾代码”来又将如何?当然只要是真正的大师总会供在庙堂之上让其大吃冷猪肉的。


“正确的程序设计思路是成对编码,先写上面的大括号,然后马上写下面的大括号。这样一个函数体就已经形成了。它没有任何问题。”

这也只有在C语言之类的具有那样“开放性思维”的语言中才在语法上可以不被追究,因而看起来也“没有任何问题”。高手是看不到那些强类型检查的语言(如C++、Java等)的,也可能对自己经常谈起的MFC之类不屑一顾的东西太健忘了,以至于竟不知到MFC用的就是C++语言这种强类型检查语言。即使在C语言中,从语义上说也不是一律“没有任何问题”的,如果具有返回值的函数要求根据不同情况返回不同的结果,那么我们的高手也只有大手一挥,哪管什么要求不要求的统统随它去吧——让系统自动返回一个系统缺省值—而了事了,真是达到“手到擒来”的化境境界了。看来,“没有任何问题”,这也只能是哪些自己“钻”入到程序中的高手们自己骗骗自己的把戏而已。

至于Linux那些“烂”代码、“垃圾代码”,自然就更是不屑一顾的。VC&MFC的大括号也许就更不符合“规范的格式”了,“如果是MFC之类的东西,那你就不用找了,因为即使找,也找不出有价值的东西,全部是VC自动给你生成的一堆堆的垃圾框架,相对于网上Linux程序来说,它可能更‘臭’一些。”真是笑傲江湖的好气魄!

不过有了“规范的格式”这个大救星情形就完全不同了。“你从Linux中或网上下载了一个‘烂’程序后,该怎么去阅读它?最好的方法是先把程序所有的格式都这里好,先别去读它。把所有的格式安装这种规范花的方法,把它的括号全部这里好。这时候你再读那个程序,只要半分钟就读懂了,但是你可能整理一个小时。但如果不这样做,你可能读两个小时都读不清楚该程序。”“烂”程序一经格式规范化后就“只要半分钟就读懂了”,这简直就是化腐朽为神奇之功效,可谓法力无边。恐怕Linux和Apache经过这样一番点“金”之后就不再只有作者自己能读懂了罢,至少还有高手半分钟就可以了。

看来有梁先生一人足矣!我们都来帮忙为其整理格式和括号、帮忙打打下手就可以了,什么难题不难题的,只要格式规范化后交给梁先生一人就可以半分钟懂了,不佩服也不行,到那时,天下还有什么难懂的程序、不理解的代码?半分钟一个半分钟一个,兵来将挡。到那时,恐怕我们程序员真的只有两种职业选择了:要么去打括号去做格式规范化,要么卷铺盖走人。因为程序也不是“编”、“写”出来的,那也轮不到我们去编写;具有非凡魔力的“我就是程序,程序就是我”的高手既然已经是程序了,并且又“没有我自己的任何思维,我的所有思维都是这个程序”,那么自然测试和调试也是多余的,虽然梁先生还是把调试特地摆了出来。


在谈到规范的格式之第二点即“代码的注释”时,梁先生云“代码本身体现不出价值来,有价值的代码一定是不仅格式非常规范,而且还有很详细的设计思路和注释,这个很重要的。首先要养成这种习惯,教科书里面很少讲为什么要做注释,注释该怎么注。有些人爱在哪儿下注释就在哪儿下注释,甚至在语句中间也加,中间也可弄两个斜杠放两个花括号写点注释。注释格式是非常重要的,但很少人去注意它。现在的程序没有注释,则基本上是没法用的,也就跟你拿一个可执行程序没什么两样。你拿过来还不能随便改,你改了后编出来的程序绝对不能用。所以,程序如果没有详细的注释,别人就算拿到了代码也没有用,体现不出它的价值来。……如果代码没有注释和规范,是没有价值的”。

原来我们的出神入化的高手,如果没有注释和规范,他就再也神气不起来了,首先是代码就没有价值了,其次是程序、代码基本上是没法用的,第三,代码改了后编出来的程序绝对不能用。其实,代码要不要注释以及如何注释,早就是老生常谈了。一见到乱打大括号的“垃圾代码”就不屑一顾的人,那里会把以前的代码注释放在眼里呢。代码注释的历史,至少几乎跟高级语言的历史一样悠久。历史上最早产生的高级语言Fortran几乎就在诞生之初就将注释作为其语言定义的一分子了,梁先生现在像发现新大陆似的一本正经地告诉我们要做注释,即使如此,我们想学学如何注释时,除了空泛地反对不能爱在哪儿下注释就在哪儿下注释和反对在语句中间下注释的观点外,却什么也没有学到,虽然说了一大堆。


接下了谈到“正确的入门方法”的第二点:调试的重要性。上面已经说过,在“我就是程序,程序就是我”的编程高手那里,测试和调试都是多余的。没有把测试摆出倒有点自知之明的,“所有的程序写出来后一定是有问题的,既然有问题,就一定会有一个解决问题的思路。解决问题的方法就是调试的方法。”这里由于梁先生把测试挡回去了,所以直接根据“所有的程序写出来后一定是有问题的”这个原则来做调试了,连什么具体问题都不用去通过测试发现,也不用去诊断了,而直接在还在“空中楼阁的理论”的指导下就去调试了,而且还大抵是在汇编级(CPU指令系统)上一步一步地调试呢,在探究CPU指令呢,真是令人佩服之至。我们的高手又发展到为调试而调试了。

但既然摆出调试来了,那我们就只好跟着走罢。“你可能觉得我把调试的作用说得言过其实了,举例子说明一下吧。请把以下的C程序改写成汇编代码:

  int i;
  extern int In[], Out[];
  for(i=0;i<100;i++)
     {
     Out[i] *= In[i];
     }

我们发现90%的人写出来的汇编代码可能是不正常的或有错误的。要么是不了解32位汇编,要么是不循环,要么只有循环没有处理等。”

就这么小的一段代码,引用外部数组变量却又不给出其数组大小的注释说明,使得它们成了“活络”,是否越界最终还得梁先生来裁定;结果是否溢出及其如何处理到最后也得看梁先生的脸色了;没想到梁先生此次又如此吝啬,竟然不“和盘托出”他的这段汇编代码来让大家见识见识了。后面看着梁先生把网上下载的如BASIC解析器的“烂”代码给我们抄了一遍又一遍,足足用了近二十页,倒是大方得很,也不嫌累了。梁先生说“我们发现90%的人写出来的汇编代码可能是不正常的或有错误的”,这里的“可能”二字泄漏了梁先生的秘密,他大抵又在“挖空心思”地推测、猜想了。如果真是去经过发现,那么就不会是模棱两可的“可能是”了。

不管三七二十一,经过在汇编级(即指令系统级别)上一步一步地地毯式的轰炸过了,如果还是不能避免“程序出现问题的话,要能考虑到各种各样可能的情况,绝对没有任何臆测。”不用测试、不用诊断、并且也绝无任何臆测,就能考虑到各种各样可能的情况,恐怕也只有“以机器的思路来考虑问题”的高手才有这样的通天本领了。但这丝毫不影响高手随后估计各种的可能:可能完全是编译器的错误;可能是程序错误如增加功能导致的错误;可能是野指针的错误;最后还可能是一种常见的错误:只管创建、不管释放销毁对象的MFC错误。


“MFC里面很常见的一种设计思维,就是任何一个东西,只管创建,不管释放、销毁。这种思路是现在很多程序员做的程序没用几下就会死机的原因。这绝对是错误的设计思路。而MFC让你这么做,就是让你永远做不了高手,你写的程序永远不可能稳定。”“MFC里面的所有的结构也好,变量也好,只需要你去分配一个,几乎就不要你去释放它。这绝对是错误的,程序一定要成对编写。”

一向对MFC不屑一顾的高手,当然现在随便放放厥词又有什么值得大惊小怪的呢。只要稍稍懂点MFC类知识的人、甚至是只要懂点C++类知识的人都不会不知道构造函数与析构函数的这种“成对编码”的存在与意义,这些都是MFC中也是C++类的ABC常识,当然MFC还有它自己的“成对编码”规则如CWinAPP::InitIntance()与CWinApp::ExitInstance()等。出神入化的高手似还在C的内存分配与释放的方式里自享其乐。我们的高手在这里“触类旁通”的结果原来就是发现了MFC“只管创建,不管释放、销毁”的错误!但无知不是证据。

至此“正确的入门方法”就算结束了,我们发觉除了从C语言入手还算可取的思想外其他什么也没有真正学到,尽管高手武断地一口否定了从任何其他语言入手的可能。只认可一种大括号的风格而把其他所有的可取的风格都贬为垃圾的做法,只会增长我们的夜郎自大、目中无人的坏脾气。关于注释,又吝啬地不“和盘托出“地告诉我们具体如何做法而不具备可操作性,只是告诉我们要不要做注释的老生常谈。最后摆出的调试也竟只剩了瞎猫撞死耗子似的为调试而调试了。

 

原来打大括号、做注释、加以步步汇编级调试就是解决国际化问题的关键。我们现在总算明白了。——空空如也。

笔记至此,耐心实在有点吃紧。“我在做什么?”《大道至简》教我们这样反问自己,当然高手们大抵只用“你在做什么”来发问的,如果是高手自问那结果也是截然不同的:“是懒人造就了方法”,所谓懒人,亦即闲人,与勤快而不思的愚公相对,下文有细说,他们“提出新的方法,解决的将是影响做事成效的根本问题”。由于高手对开放性思维的情有独钟,即将结束之际又谈起了它。好吧。


“在开放的思维下,我要做这个程序的时候,就会考虑怎么把它拆成几个独立的、分开的模块,最简单的,怎么把这个模块尽量能单独调用,而不是我要做个很大的EXE程序。一个极普通的程序员,如果他能考虑到将程序分成好几个动态库,那么他的思维就已经有点开放性了,就已经不是MFC那些思维方式了。思考问题的时候能把它拆开,就是说,任何问题,如果你能把它拆开来思考,这就是简单的开放性思维。…… 如果连函数都不会分的话,那就是典型的封闭式思维。……所有的问题都由一个函数来解决,他就不会把它拆成几个模块。我问他,把一件工作拆成几件模块不是更清晰吗?他说,拆出来后的模块执行会更慢些。这就是明显的封闭式思维和非封闭式思维的区别。你看MFC的思路,那就是一层套一层的,要把所有的类都实现了,然后继承。它从CWnd以后,把所有的东西都包括进去了,组成一个巨型的类。这个巨型的类连界面到实现统统包括在里面。这时你怎么拆?根本就没有拆的方法,这就是封闭式思维。如果一个系统,一个程序不能拆的话,则它基本上是做不好的。因为任何一个程序,如果它本身的复杂度越大,它可能出错的几率就越大。比如最简单的,哪个函数越大,则该函数的出错几率越大。但如果把该函数分成很多小的函数,每个小的函数的出错几率就会很小,那么组合起来的整个程序的出错几率就很小。这就是为什么要把它拆出来的原因。”

高手谈起了模块化原理如函数拆分,程序拆分为模块等等,反来复去地就是一个“拆”字,函数要不要拆,程序要不要拆成模块、要不要拆成动态库的方式,等等。其实,模块化的历史也至少和高级语言的历史一样“悠久”,甚至在第一个高级语言Fortran产生之前就已经产生模块化的思想了。【现在重复一下这段历史,疏漏之处请多指正。】大概早在1948年左右,剑桥大学计算机实验室的David Wheeler就发明了函数调用,在1951年他写了一篇杰出的论文来介绍如何设计库。而1956年Fortran出现之初几乎也就有子程序的概念了,它的子程序可以独立编译,模块化的思想已经在高级语言中变成现实了;1958年Fortran II时就有了较完善的能够处理返回值的函数机制了。到二十世纪六十年代末发生软件危机,1968年北大西洋公约组织的计算机科学家们就在联邦德国召开国际会议讨论软件危机问题了,“软件工程”这门新兴的工程学科也就此诞生了。结构程序设计的概念虽然早几年就提出来了,但到这时人们经过了激烈争论、讨论(如goto语句的讨论)后才认识到需要确立一种新的程序设计思想、方法和风格。这样逐渐地形成了结构程序设计的思想、方法、原则。只用“顺序、分支、循环”三种控制结构来实现任何单入口单出口的程序,也早在1966年被Bohm和Jacopini证明了。【1972年进一步完善结构程序设计思想,Mills:程序应只有一个入口和一个出口。】虽然人们对什么是结构程序设计有争议,但一般认为包含了自顶向下逐步求精的设计方法和单入口单出口的控制结构(实际运用中有所放松)。虽然面向对象设计思想直到二十世纪八十年代后期和九十年代才流行起来,但是面向对象的思想产生的年代至少可以追溯到二十世纪六十年代中期(Simula67)。耐心不够,介绍就到此为止罢。


关于模块化思想,历史早已经走过要不要“拆”的婴幼儿时期了,时值2003年,我们的高手还停留在这个“好蛮荒,好远古”的时代里做着“开放性思维”的编程高手的“手到擒来”的伟大梦想。


无独有偶,时值2007年还有更“好远古”的事发生着。为了不至于遗漏要点,只好大段抄了,就当作磨炼耐心的一个好机会吧。《大道至简》(2007年版)第二章《是懒人造就了方法》中云,
―――――――――――――――――――――――――
早期写的程序,都是将代码打在穿孔纸带上,要让计算机读的纸带当然是连续的,这无须多讲。后来有了汇编语言,可以写一些代码了。这时的代码是先写在文本文件里,代码习惯地写到一个文件里。再后来出现了高级语言,什么C呀,Pascal呀之类的。既然大家已经形成习惯了,所以很自然会把一个程序写到一个文件里。无论这个程序有多大,多少行代码,写到一个文件里多方便呀。直到如今语言发展得更高级了。可是程序员的习惯还是难改,一旦得了机会,他们总还是喜欢把代码写到一个文件里的。好了,有人说我是想当然尔。嗯,这当然是有实据的。Delphi 1.0的编译器居然不支持超过64KB的源代码文件!这被导致Fans们一通好骂。好在Delphi 2.0改掉了这个大BUG。64KB的文件是什么概念呢?1行代码大概(平均)是30字节,64KB的源代码是2184行,如果再多些空行的话,差不多也就是3000行上下。也就是说,在Delphi 1.0的时代(以及其后的很多很多时代),程序员把3000行代码写到一个文件里,是司空见惯的事了。所以呢,按照这一部分人的逻辑,一百万行代码其实是可以写在一个文件里的。不但可以,而且编译器、编辑器等也都必须予以支持。这才是正统的软件开发。勤快的愚公创造不了方法。这我已经说过了。对于要把“一百万行代码写到一个文件里”,并且查找一个函数要在编辑器里按5000次PageUp/PageDown键的勤快人来说,是不能指望他们创造出“单位文件(Unit)”这样的开发方法来的。然而单元文件毕竟还是出现了。这个世界上,有勤快人就必然有懒人,有懒人也就必然有懒人的懒方法。有了单元文件,也就很快出现了一个新的概念:模块。把一个大模块分成小模块,再把小模块分成更细的小小模块,一个模块对应于一个单元。于是我们可以开始分工作了,一部分人写这几个单元的代码,另一部分则那几个。很好,源代码终于可以被分割开来了。结构化编程的时代终于开始了,新的方法从此取代了旧的方法。而这一切的功劳,应当归功于那个在按第5001次PageDown键,突然崩溃的程序师。他发自良心地说:“不能让这一切继续下去了,我一定要把下一行代码写到第二个文件里去。我发誓,我要在编译器里加入一个Unit关键字。
―――――――――――――――――――――――――

看着这些妙不可言的文字,这里几乎是逐句引述的。且不说一些枝节上的“想当然耳”,单就大的方面来说罢。没想到,居然说模块、模块化的思想是到Delphi产生后才形成的,而历史上模块化思想出世时Delphi的“爷爷”(Pascal(1970年)——Turbo Pascal——Delphi)都还远没有出生呢!居然说结构化编程的时代也继Delphi产生之后才终于开始了,历史上,结构化编程时代开始时(二十世纪六十年代末到七十年代初)Delphi的爷爷也仅算是打了个擦边球,那时连Delphi的“爸爸”也还远没有出生呢!真是够“激荡新思”的。

看着这些谬误百出、极尽穿凿附会、极不负责任的文字,绝不是一种享受!这样的人还说是“鲁班带了个坏头”呢。我们看到的不是那样的一些书籍,“在这些著作中,各种各样的人力图以最诚恳的态度弄清种种问题,对于解决这些问题,他们也许或多或少是缺乏资料的;在这些著作中,无论有着怎样的科学上和文字上的缺陷”,那种“善良愿望总是值得赞许的。”

创造这种“历史壮观”的所谓的“懒人”真是功不可没,的确,他们“一定是个闲人,可以闲到没事去看火能不能把石头烧爆。在这么大的工程里,如果有一个人会闲到看火烧石头,那它一定很懒。那么多事堆着不去做,去看烧石头,你说他不是懒是什么。”“正是一个懒人造就了‘烧石头’这个‘碎石’的方法。愚公们太勤快了,勤快得今天可以比昨天多凿一倍的石头。……但是越发勤快,愚公将越发没有机会找到更快的方法。”的确,不论从时间上看、还是从精力上看、还是从卖弄聪明的程度上看,也只有我们的懒人才有这样的机会了,全靠他们了,因为他们“解决的将是影响做事成效的根本问题”,“而愚公可以多吃点饭,多加点班,但突破不了人精力的极限。” 自然啦,愚公们那里有时间、有精力、有闲心去作这些超越历史极限的凭空想象呢!好一个解决“影响做事成效的根本问题”的闲人!

随后我们愚公的闲人又谈起所谓的“面向对象程序设计”的缺乏流程等的局限性、“不是OO提出的、而是Windows的消息系统内置的‘事件驱动’程序模型”、“事件驱动的本质”、“追溯到Windows内核”,等等,话题扯的多了,只有以后到适当时后我们再来欣赏闲人的超越极限了。(如果闲人知道C++的早期就是C With class,大概不免更要狂喜罢,不过也许不会,因为闲人可以做超越历史的更令人狂喜不已的想象(这是我们愚公们大抵所做不到的),哪里会在乎我们愚公们所知道的那点点历史呢。)



我们再接着看看编程高手对C++继承、MFC继承等面向对象思想的举重若轻的本领,其实我们早就见识过了。高手硬要把联系紧密的具有继承关系的类“拆”开(高手的意思大抵指的是要解除、剥离那种关系并“拆成几个独立的”),不然就成封闭式思维了。看来我们的高手也非要把具有高内聚的函数、模块活生生地“拆”开来不可了,不管付出什么代价,而且看样子是要一直“拆”到底,最后恐怕只有剩下实在不能拆的语句之类的了。我们的高手大概看着C++类的继承之类的东西就不屑一顾了,以致对继承与模块化的关系的理解也一片混乱。如果高手只知道模块化即拆分而不知什么叫模块独立(如内聚等)等的话,我们的高手应还有一个没有“和盘托出”的秘密:我们的编程高手还是一个至精至纯的“拆”家。没有综合就没有分析。

“但如果把该函数分成很多小的函数,每个小的函数的出错几率就会很小,那么组合起来的整个程序的出错几率就很小。这就是为什么要把它拆出来的原因。”
按照高手的思路,如果再进一步细拆下去的话,以致进一步细拆出来的更小的“每个小的函数的出错几率就会很小”很小,“那么组合起来的整个程序的出错几率就很小”很小;如此细拆下去,恐怕“手到擒来”就会变成“拆到擒来”了,“组合起来的整个系统的出错几率”大概可以忽略不计了罢,总算“努力做到物我合一的境界”了。大概这才是“保证程序可预测性”的“直接的捷径”吧,相比之下,高手一再强调的“一步一步调试过”“所有的代码”这种方法倒有点像老牛拉破车了。因拆分而带来的模块之间的交互代价原来不在高手的考虑范围之内。

至于说到动态库,梁先生说“有了动态库,当你要改进某一项功能的时候,你可以不动任何其他的地方,只要该其中你拆处理的这一块。这一块是一个动态库,然后把它改进,只要把这个动态库调试好后,这个系统就可以升级。”因为“动态库,包括它的代码和数据都是独立的,绝对不会跟其他的动态库串在一起。但是,如果你把所有功能放到一个EXE的工程里面,它的数据和代码就都是放到一起的,最后产生可执行程序的时候,就会互相干扰。而动态库就不会,这是有操作系统来保证的。”

恰恰相反,动态库因为要共享进程的地址空间,要想避免因它们的互相干扰而可能导致应用的崩溃,这是操作系统也保证不了的,因为这主要是程序员的责任,高手在这里“手到擒来”地意淫了不会说话的操作系统。

“从理论上看,动态库也是一个文件,我做这个工程的时候也是一个独立的文件,但它就会出现这样的问题。”“按道理,我只改这个文件,其他系统也不需要进行调试。理论上看起来是一样的,而实际的结果往往就是因为你改动了这个文件,使得原来跑得很好的这个系统,现在不能跑了或者出现了很奇怪的现象。如何解析这个问题?”

高手自然不会首先从自己找原因的,首先怪到了编译器的头上,“事实上,这就涉及到编译器产生代码的方法,如果不了解这点的话,永远找不出问题来。不存在没有BUG的编译器,包括VC,它也会产生编译上的问题。”

最后当排除编译器问题后问题依然时,由于一时忘记了还有操作系统这个替罪羊才不得不想到了自己,这对于出神入化的编程高手来说,不能不有点尴尬,“就算把这些问题都排除,你的软件也可能因为你加了某些功能,而影响了其他的文件,这个几率甚至非常大。”

刚吹起的不干扰的理论泡沫在被事实戳破了后,以前那种“只要把这个动态库调试好后,这个系统就可以升级”的思想也得修正了,“这又得把你以前的测试工作重头再来一遍了。”

最后,我们似乎梦游一般又回到了原来的地方:“光会拆还是不够的”。但这丝毫不妨碍高手接着大谈“光”拆的威力,“程序设计流程其实很简单。第一步就是要拆出模块,如果你有开放性思维,则如何软件都非常容易设计。”当我们想听听为什么“光会还是不够的”时,“和盘托出”的高手又闭口不言了,恐怕是要留给我们作为练习题了罢。


最后是关于程序可预测性的。“通用软件绝对是一行一行地编码产生出来的,而且每一行编码的结果要达到一种可预测性。什么叫可预测性?就是你写程序的时候,如果发现一种症状,马上就能想到该症状是由于哪个地方出了错,而不是别的地方,也就是能从症状就能判断出是哪些代码产生了问题,这就是可预测性。……如果某用户说我出现什么状况了,你马上就可以断定错误,而不用去搜索源代码,就能想到程序可能是什么地方有问题,则这就是可预测性。设计程序的时候,如何保证可预测性呢?答案就是我们上面所说的,所有的代码必须经过测试的,必须是一步一步调试过的。只有经过你调试过的代码,你才能知道这个代码做某种运算的时候,它是怎样的执行方法。如果你不知道它的执行方法,你没有进行过测试,则你就没有任何预测性。要达到可预测性,代码在汇编级是怎么执行的,你都得非常清楚。代码对哪些部分进行了什么操作,你都得知道。如果达不到这点,你的可预测性就很差。”

【刚说过“所有的程序都是调试出来的,不是写出来的”、“一个程序绝对是调试出来的,不是编出来的”,我们的高手真是健忘。】

高手关于可预测性所说的要点主要有二:一,能直接从症状就能判断出是哪些代码产生了问题(不用搜索源代码),二,所有的代码必须是在汇编级一步一步调试过(保证可预测性的条件与手段)。的确,高手是惯于做细活的,不说别的,他竟然能够穷尽所有的已知未知的输入情形等穷尽测试;即使如此,设计时在汇编级上一步一步地调试过并不意谓着你知道未来执行时所发生的一切,除非真的“我就是程序,程序就是我”,但另一方面,如果真是如此,哪里还用得着去做什么测试、调试呢。


接着下面梁先生以做“大眼睛”时出现的图片拖动会翻转的问题来举例说明。梁先生大概因为没有来得及把注释去掉,所以就没有“和盘托出”一点那些由于没有注释而变得“没有价值”的代码来,现在也就只做理论上的讨论罢。惯于做视频技术的,以至于不屑一顾PSDK2001或MSDN2001中的有关位图高度正负的说明、bottom-up DIB和 top-down DIB的处理方式,还有那时的DirectX SDK中有关DirectShow部分的有关说明,大概因为我们的总能够深入到汇编级上一步一步调试的高手也总能发现最后是“操作系统的原因,绝对不是软件的问题”罢,自然也大抵不是那个“钻”入到程序中去的“我就是程序,程序就是我”的人的问题了。


至此总算结束了第一章。总之,正确的入门方法也总算有点开悟了,虽然可以从C语言入手,不过我们到觉得开放一点,并非没有其他语言可以入手的;至于开发性的思维似乎还是没有什么开悟,那种穿越历史极限的想象我们觉得不是一下子就能领会、并能一下子“手到擒来”的,未入门的我们还是觉得谦虚一点哪怕一点点都要好,另外还是封闭一点、了解点历史比打扮想象历史要好一点;虽然没有学到什么编程思想、方法和技术,但是我们毕竟知道要尽管“拆”下去问题就好办的这个绝招;当然还没有练就“我就是程序,程序就是我”的我们还是要去测试和调试的,尽管未必总能(总有必要)做到在汇编级上一步一步地去调试;我们也知道打好括号了,至于那些“垃圾代码”,还是留着做高手的陪衬罢。


经过高手的圣殿训示,我们深深地着迷于高手的无极境界,以后还是从入门最基本的方法即C语言实际亦即编程语言入手罢,或许还有机缘巧合遁入迷津呢。



















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值