从类比现实生活的角度看待编码

来源:互联网 发布:软件项目立项管理办法 编辑:程序博客网 时间:2024/05/04 01:01

有了解过“分形”这个概念的人对 Julia 集应该不会陌生,因为由该 Julia 集形成的图是那样的奇特:当我们用一个放大镜瞄准这样一张图,并不断增加放大镜的倍数时,会发现无论放大多少倍,在我们眼前的图景永远是一个模样,毫厘不爽。

自然界中充满了无数这样类似的现象,即局部与整体是相似的,不只是结构相似,组织方法也类似。在软件开发方法中,面向对象的编码模式也许是广博的,难以看清的,但不识庐山真面目,只缘生在此山中,当我们缩短焦距,视野囊括整个面向对象,用自己身边熟悉的场景来类比地看待它的时候,也许有些东西就会好理解些。

一、面向对象相比于面向过程的优势是分工

在自然经济条件下,互通有无不会像今天这样自然和必须,人们的日常生活所需,往往要靠自己大包大揽,终日劳累才能满足。这些古人在现在看来也许是全才,但最多也只是博而不专,其制作绝对难以称得上高明,也不可能形成规模。然而在分工占据主导的现代社会,一个人就不用如此劳累苦困,他要的一切,只要掏出钱包,物品不仅种类繁多,而且质量良好。

与自然经济类似,面向过程也在干大包大揽的事,处理的数据倾向于处在同一个作用域内,各种处理过程因为围绕数据打转,所以也聚集在同样的范围内,这将会增加系统交织的程度,即每个处理过程都要考虑大量的输入可能性,这就注定了面向过程最终会遇到不可逾越的瓶颈,从而发生软件危机。而面向对象则不同,它不会在自己内部干所有的事情,而是交由不同的对象去做,它为了达到目的所要做的只是向特定对象发送请求,就好像用钱买东西一样,这样做的好处是,当需求变得复杂的时候,因为有对象的分割作用,逻辑不会纠缠在一起,因此扩展是无限制的。

现实生活中,分工的起初肯定会导致单一的任务,就像《摩登时代》中展现的那样,但之后的发展会使得工作更加细致化,每一个细节化的任务中包含出越来越不可忽略的环节,于是更细致的分工开始,如此循环。与此类似,面向对象也会递归地将对象划分为不同的对象组合。那么划分对象层级到什么时候?如果要认真下来,甚至可以划分到一条语句就是一个对象的地步,但没有必要,也不合理,抽象为对象的目的是将复杂度控制在我们能用脑袋就可以控制且方便的地步。

可以看到,正如城市聚集效应可以让任何一个行业都因可以忙个不停而经济发达一样,在面向对象引入后,我们可以很方便地共享它具有的功能。

二、面向过程藏形于面向对象之中

现在,我们能够很容易地感知面向对象的存在,但却忽略了面向过程的继续存在。实际上,在面向过程经过任务的对象间划分而让位于面向对象的时候,它仍旧在对象中保留了它的形迹,每一个对象的内部实现依然采用面向过程的组织方式。在这里,如果将对象比作一个家庭,那么私有成员就是从全局作用域中划分得来的家底,是家庭内部的共享资源,是对象提供服务的成本,私有方法就是内部如何与外界打交道的协商过程,而协商的内容就是如何处置共享资源,公共方法就是与外界打交道的过程,与外界互通有无,提供服务的过程,属性乃是向外界传递消息的某种状态。

所谓封装者,大概就是面向过程在其时代没落而隐形于对象内时的说法,就好像末代皇帝溥仪成了新中国的公民,他不再管天下了,而代之管理起一个家庭。

如果对象的一个处理过程的条件输入源自对象内部,那这属于面向过程的范围,如果来自对象外部,则属于对象交互的范畴了。那么,怎么处理这一外部传入的输入?接纳这一输入的方式有两种,其一是通过方法的参数,处理后输入的数据如果不再被使用,那么就不应该将其作为私有数据存放起来,如果还有后续处理,而这处理又是对象内部的,则需要用私有数据存放,其二是通过属性,无论是否用完总还会被使用,所以它本身就是存放数据的,而且内部和外部都可以访问。当使用属性接纳外部输入作为交互的中介的时候,在整体(它的上一层级)上看,对象更像是一个数据的容器。这又变得很像面向过程了,在这个全局作用域内,任务仍是聚集在一起的,不同在于数据划成了一个个的“部落”,而且用完就可以释放掉,不再关心。另外,这个全局作用域本身不再是整个应用程序,而只是一个对象的层级,任务群则是这个层级内的对象间的交互。

如此看来,面向过程不只是存在了最底层对象中,也存在于任何一个层次的对象中,只是在这里,面向过程都进行了或多或少的限定,任务不再复杂,都控制在思维能够达到的范围内。现在以面向对象的眼光,可以认为面向过程是将整个系统作为了一个对象,一个不含任何对象的底层对象,原始对象,而面向对象的明确提出则将这个对象小化,最后留在了对象世界的金字塔底端。为了应付这样的变化,还引入了对象交互的管理技巧。面向对象就像一个动物,个头上无论多么惊人,不是靠细胞体积的增加,而是靠细胞数量的增加以及细胞间的组合协调。

三、对象封装是对面向过程的囚禁

向对象提出任务要求,其实是向它索要一种功能,而不需要知道其如何实现,实际上对象也不会向外公布其实现的,这就是所谓的封装。对于成功的封装,应该满足三个条件,功能充足、松耦合和高内聚。所谓功能充足指提供的代码要符合其名称所暗示的那样,不大也不小。面向对象的威力在于面向过程没有的松耦合性,但这必须以面向过程的高内聚性进行良好封装作为基础。高内聚性可以通过两点达到,其一是对任务的执行是顺序化的,也就是尽可能地消除“交织”,其二是如果不能“行云流水”,那么必须对每一个函数的输入可能性、输入合法性、处理的准备工作、输出的完整性和善后处理进行严谨完整的控制,也就是能够理清“交织”。

在封建的管理体系中,一个里的民众不用了解它所属乡的情况,因为乡的情况总会传递下来到里,里的民众只需跟里和里里的其他人打交道。这个的启发作用在于,编码的时候我们不用把顶层的状态揉合到一个底层的任务中,任务只听命于“直接上司”,只消观察“同行”的情况。

我们之所以这么放心地使用着.NET Framework 代码库,摆弄着这么多控件,是因为这些类都经过良好的思考,在很大程度上(它们也是人做的啦)将封装做实了。一个类如果封装不好,它就是一个有泡沫的实体,当用它来做大厦的基础时,你会犹疑,因为它承载力不够,随时都有坍塌的可能,因而这座楼必定危险,更不用说会成多大规模。

四、封装需要全盘考虑汇成的文档来保证

可以看到可能性是如此多,不管是面向过程还是面向对象。如果不是事先设计,在编码的时候就很容易顾此失彼,因而每增加一种可能性的时候,原先的布局可能因容纳不进新的条件可能性而告无用,即使可以解决,也必然要增加若干新的成员或变量,这无疑是在增加系统的复杂性。当系统或部分变得复杂时,需要面临很多可能性的考虑,这是个细致活,没有全盘看逻辑随时都有出错的可能,因而每增加一个需求或需求变动的时候,都需要花大量的时间和精力重新做全盘整理。

也许,我们会痛下决心考虑重新设计,但没有改变行事风格,这还是治标不治本,由于没有完整思路,在遇到另外一个新增条件时又要舍弃,这样看舍弃是没有尽头的。那么,我们试图编写扩展性强的代码来应付这个不测如何?不行,编码时的实现即可的心理习惯异常强大,正如如果没有规范,人们宁愿用硬编码而不用常量定义,这样来得快速而方便,其实更重要的一点是,扩展是需要预期的,如果没有事先的通盘设计,我们很难预估扩展的需求会是在哪个地方。没有文档指导的编码,会走入歧途,因为人有太多的局限:不良的编码习惯、达成即可的心理暗示。在编码如火如荼的时候,我们较少会花时间考虑整理精神状态,总想摆脱对编码规范的遵守,特别是在没有一个完整的通盘把握而自信的状态下,一个人对坚持规范(而且这个规范的存在也许也是模糊的)有种不信任感,不知道仅仅这点能否带着他走向编码成功,甚至完美。因而哪怕知道应该,但有的编码风格还是会被熟练地丢弃掉。

现实生活之所以能有条不紊,那绝对是有成熟的宏观把控,让人相信这样的生活不会走差。相反,如果对生活的各种可能的事情未加以假设,那么当发生了一件意外的事情,我们也许能够把握而处理好,但是这是靠临时的智慧,更多的时候我们可能手足无措。面向对象是一种重设计的思想,对象交互性,其本质是对协议的倚重,如果没有文档,我们的思维很容易限制在顶层,而不能尽可能往底层渗透,细致化,因而底层最终会被面向过程的思维全面霸占,而且它会向高层渗透,因而这种高的封装的范围要求扩大,这意味着内聚的保证需要考虑太多的方面,这容易出错。

面向过程对协议的要求更细致,但如果面向过程的空间被大大压缩,那么需要考虑的就不会太多,因而面向过程的部分的文档要求就少了,甚至我们单用头脑就可轻易控制。面向对象的文档内容描述不是基于实现逻辑,而是基于功能,而面向过程正与此相反,基于功能的文档描述比基于逻辑(实现)的文档的描述更容易。前者是层级式的管控,比较简单,而后者就是大杂烩,管理难度大。如果将面向对象和面向过程比作弹簧的两端,那么我们的编程过程可能就像其产生的波的密部,当偏向于面向对象,我们就可以将较多精力花在相对容易控制的对象交互上,反之则需要花大精力在面向过程的交织上。如果有通盘的考虑,我们可以将编码的天平向面向对象倾斜。

编写文档的目的是,产生比坏习惯更大的诱惑力,使人朝好的方向写代码时是舒服的,而这诱惑力正是向编码者证明,如文档所述的那样编码,绝对可以到达顺利、快速、完美地完成任务的目的。由此看来,除了从维护或存档的角度看,更应该从开发的角度来看待文档。

五、编码的两翼是对业务精熟和模拟方案的择优

面向过程和面向对象都在模拟现实世界,只是后者模拟得更好,同样地,在面向对象的体系里,还是会有模拟的好坏区别。体现为同样的任务,可以这样划分对象,可以那样划分对象,同一任务都有不同的方案选择,好的选择,做起来就顺畅。关于怎么模拟,就像现实世界的组织方式,都会创新的,但我们应该模拟那些已有的好的组织方式,在这里,经验和总结是重要的,我想,设计模式就是一种好的模拟,是一种极好地模仿世界的方法思想。然而无论是什么样的模拟,脱离了对业务的把握,对需求的理解,都是不会奏效的,正如一个厨师如果不知道他所掌握的原材料,不理解其特性,不关心食客偏好,是难以做出美食来的。如何把握业务?看原来的产品,剥离其包装,回放制作过程,看其核心,这是比较耗时的方法,请教同行,这个比较零散,周期比较长,这两种方法都是必要的,但比较系统的方法是了解业务文档,甚至是一本系统介绍业务逻辑的书。

注释

1.此篇的描述可能与读者的经验有不契合处,这是维护桌面应用程序的经验。

2.面向对象的核心在于对象间的分工与协作。

3.好的对象划分和扎实的封装是用好面向对象的基础。

4.在面向对象的世界里也存在对现实世界模拟的差别,设计模式是一种极好的模拟。

0 0
原创粉丝点击