浏览 7662 次
锁定老帖子 主题:思考软件开发(1)——面向对象的前前后后
精华帖 (3) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2004-03-29
思考软件开发(1)——面向对象的前前后后 面向对象,这个概念真是了得,每一个程序员,都肯定听过它,学过它,思考过它,为它着迷,为它困惑,为它... 但是,为什么是OO呢?为什么要OO呢?面向对象已经像真理一样的放在我们面前,为什么很多时候,我们还是觉得不对劲呢? 我一直对历史很感兴趣,因此也很想分析一下关于面向对象的历史。在这个思想出现之前,编程是怎么样的?在这之后又是怎么样的? 当然,这篇文章不是一篇严格意义上的“OO传”,而是一种概念演进上的探讨,也许并不是很符合时间的序列。 第一章:对于面向过程思想的分析 第一部分:执行过程 第一节:消除冗余 最早的程序是什么样的呢?让我们回忆一下吧。在那个遥远的年代里,每一个程序员都是天才,因为只有天才,才有可能理解那似乎毫无意义的0和1的千变万化的组合。是的,所有的程序,只有两种符号,0和1。 理解这样的程序很困难吗?其实不困难,其实非常的简单。不要认为我在说大话,真的很简单。因为,只要你的思考,能够和电脑一样,你就会认为“它”是非常简单的。 那时候的电脑,体积虽然庞大,其实功能并不强大,指令的字节数,也不过就是8位,甚至只有4位。记住那少得可怜的命令,真是易如反掌。 困难的不是0和1,而是你如果要想一个程序正确运行,你就必须了解一切。了解电脑的每一个零件,每一个存储空间,以及每一个指令执行之后的后果。 如果你只是想要在某一个寄存器里放一个数字,那非常简单,放就是了。但是如果你想输出一个Hello World。那么你的脑子里,必须能够将整个过程,像放电影一样,放一遍。每一个过程状态,你都必须了然于胸。如果你想做更加复杂的程序,那么这个在脑子里预演的过程,一定会超出很多人的智力极限,只有天才,才能始终清晰的,准确地像电脑一样在脑子里预演。 然后出现了汇编语言,这种方式解决了一个大问题,哪怕是最伟大的天才,看多了0和1,也会眼花。如果天才能够不眼花,那么他的工作效率,就会更高。 然后出现了高级语言,比如说C。如果说机器语言和汇编语言是给天才用的话,那么C语言只不过是给高手用的。你不需要是天才,就可以理解C。C并不简单,但是相对于汇编,那是简单太多了。 为什么会简单那么多呢?因为你不需要了解一切,就可以令电脑为你工作。使用print函数的人,完全不需要了解为什么那些字就能够出现在屏幕上。打个比方,孩子还小的时候,我必须把他从卧室抱到书房,如果他长大了,能走路了,我只需要对他说:“到书房来”。他就会过来,而不需要我去关心他先出左脚,还是先出右脚,会不会被门撞伤。 且慢,刚才这个比喻其实是错误的,电脑远没有一个会走路的小孩那么聪明,如果门的情况没有输入到行走函数里去,它就一定会撞伤自己。所以我在调用这个函数之前,一定得很清楚他的功能,需要的参数,可能犯的错误等等。 高级语言的使用者,可以完成同样的功能,但是完成的水平天差地远,区别就在于这个使用者,是不是清楚,他所调用的函数的细节。 我读《人月神化》这本书,作者在20年后,还写下了一些他的思想的变化,其中一个引人注目的变化就是,原来作者认为,对于系统了解得越多越好。在20年OO思想大行其道之后,他终于承认,封装是有必要的。 面向过程的思维,最高的境界,就是能够了解全部,只有能够了解全部的人,才能开发出最好的面向过程的程序。我们举个例子。 最初的程序是这样的: void main();{ if(用户输入==1);{ 做某事1; 做某事1; 做某事1; 做某事1; 做某事1; 做某事2; 做某事3; 做某事4; 做某事5; 做某事2; 做某事3; 做某事4; 做某事5; 做某事6; 做某事7; 做某事9; 做某事10; } if(用户输入==2);{ 做某事7; 做某事9; 做某事10; 做某事7; 做某事9; 做某事10; 做某事7; 做某事9; 做某事10; 做某事1; 做某事1; 做某事1; 做某事1; } } 然后我们就可以优化这个程序 void main();{ if(用户输入==1);{ 做某事1五次();; 做某事2~6各一次();; } if(用户输入==2);{ 做某事7.9.10各一次();; 做某事7.9.10各一次();; 做某事7.9.10各一次();; 做某事1四次();; } } void 做某事1五次();{ 做某事1; 做某事1; 做某事1; 做某事1; 做某事1; } void 做某事1四次();{ 做某事1; 做某事1; 做某事1; 做某事1; } void 做某事2~6各一次();{ 做某事2; 做某事3; 做某事4; 做某事5; 做某事6; } void 做某事7.9.10各一次();{ 做某事7; 做某事9; 做某事10; } 然后我们还可以再优化 void main();{ if(用户输入==1);{ 做某事1五次();; 做某事2~6各一次();; } if(用户输入==2);{ for(int i=0;i<3;i++);{ 做某事7.9.10各一次();; } 做某事1四次();; } } void 做某事1五次();{ for(int i=0;i<5;i++); 做某事1; } } void 做某事1四次();{ for(int i=0;i<4;i++); 做某事1; } } void 做某事2~6各一次();{ 做某事2; 做某事3; 做某事4; 做某事5; 做某事6; } void 做某事7.9.10各一次();{ 做某事7; 做某事9; 做某事10; } 再优化 void main();{ if(用户输入==1);{ 做某事1N次(5);; 做某事2~6各一次();; } if(用户输入==2);{ for(int i=0;i<3;i++);{ 做某事7.9.10各一次();; } 做某事1N次(4);; } } void 做某事1N次(int N);{ for(int i=0;i<N;i++); 做某事1; } } void 做某事2~6各一次();{ 做某事2; 做某事3; 做某事4; 做某事5; 做某事6; } void 做某事7.9.10各一次();{ 做某事7; 做某事9; 做某事10; } 这样的优化,有一个非常明确的目的,同样的代码,不要重复出现,类似的代码,最好也能够归并。但是要做到这一点,其实非常困难,因为最好的优化,是需要对整个系统有一个全面的了解,才可能的。 第二节:有含义的命名 接下来如何优化呢? 不对,我们应该先不想优化的事情。 如果我们可以把软件开发的目标分为两类的话,那么一个目标是为了别人,另一个目标就是为了自己。从机器码转变到汇编指令,就是为了自己。而不断的尽一切可能的消除代码冗余,就是为了别人(当时使用软件的用户,机器都不如现在的好,硬盘不大,内存极小)。当然同样也对自己有好处,因为做同样事情的代码,只在一个地方存在。 如果软件开发人员没什么追求,那么前面的两种努力也就够了,但是如果想要更方便自己呢?给函数命名是一个关键的办法。 每一个机器指令,都可以有一个“稍微有点意义的命名”,那么一个完成了更多任务的函数,应该有一个更加有意义的命名。这不只是为了满足程序员的虚荣心(啊,我开发了一个ScreenPrint函数),也是其他程序员(包括他自己)能够更加清晰、快速、方便的理解这个函数。 比如说: void main();{ if(用户输入==1);{ 拉警报(5);; 通知各单位();; } if(用户输入==2);{ for(int i=0;i<3;i++);{ 对下级单位进行检查();; } 拉警报(4);; } } void 拉警报(int N);{ for(int i=0;i<N;i++); 做某事1; } } void 通知各单位();{ 做某事2; 做某事3; 做某事4; 做某事5; 做某事6; } void 对下级单位进行检查();{ 做某事7; 做某事9; 做某事10; } 这样的代码,就开始有点含义了,一个初次阅读这个代码的人,可以仅仅因为自己的基本的语言能力,初步的猜测出这个程序的含义来。甚至进而猜出这整个程序的目的——“大概是关于一个单位的安全管理方面的事情吧”。 第三节:有意义的分层与分类 但是这样的命名只能在最后才能做,也就是说,我们只能用从底向上的思维方法。不断的提炼出更高级的函数。在提炼出函数之后,还要仔细的考虑这个函数的工作内容,然后给它一个恰当的命名。因为一段从“逻辑含义上没有冗余的程序”,可能非常难以命名。 这样的思维逻辑,也无法想像:先定出函数名,然后再写函数这样的工作方式。 一个稍微大一点的面向过程的程序,就一定会用到函数,我们可以把不调用任何函数的函数,称为底层函数,然后把不被任何函数调用的函数,成为顶层函数,那么其他的函数就可以称之为中层函数。如果我们完全采用自底向上的优化,会得到什么样的层次结构呢?——一片混乱。 因为这样的优化,只对计算机有意义,只有执行这个程序的计算机才能理解。而人基本上就无法理解了。 因此我们必须将函数分层,把那些基本上没有相互调用的函数,放在一起,然后给它一个有意义的命名。比如说:I/O层。 同样的事情还发生在函数的分类上,我们把做类似事情的函数,放在一起,也给它一个有意义的命名。比如说:打印模块。 有意义的分类,与有意义的分层,很有可能会丧失执行效率,造成代码冗余,但是这样的牺牲是值得的。我从《重构》这本书上看到过一句话,觉得非常经典:“写出机器能懂的代码很容易,写出人能懂的代码很难。”(大意如此)。 自底向上的思维模式,和自顶向下的思维模式,从本质上是矛盾。自底向上时,意义是不存在的,外面的世界是不存在的,各种函数和变量的命名,也是“权宜之计”,完全可以变成另外的名字,这对于电脑来说,没有任何区别。而自顶向下时,意义是必须的,整个系统的设计,就是按照真实世界的意义,再层层分解,逐步细化的。一般来说,自顶向下开发的程序,都会比自底向上开发出来的程序要“烂”一点,当然,这是在电脑的眼光看来。 但是,当系统复杂到一定程度之后,自底向上就是不可能的任务,没有人能够完成,我们只能选择折中的方案,首先大致的进行自顶向下的划分,然后分工到具体的个人后,再进行自底向上的优化开发。 特别提请注意:这里存在一个很大的矛盾,存在着两种不同的思维模式之间的碰撞,这个矛盾,即使到今天,也没有得到妥善的解决。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |