《面向对象设计实践指南:Ruby语言描述》—第1章 1.3节设计行为

1.3 设计行为
面向对象设计实践指南:Ruby语言描述

随着常见设计原则和模式的出现与传播,所有的OOD问题可能都已被解决。既然基础的规则都已知道,那么设计面向对象的软件还会有多难呢?

事实证明,它非常难。如果将软件理解为可定制的家具,那么原则和模式便像是木工的工具。了解软件在完成后会是什么样子,并不能让它自我构建成那个样子。应用程序之所以存在,是因为有程序员使用了这些工具。最终的结果可能是,它要么成为一个漂亮的橱柜,要么成为一张摇摇晃晃的椅子。具体是哪一种结果,则取决于程序员使用设计工具的经验。

1.3.1 设计失败
第一种设计失败的情形是缺乏设计知识。程序员最初对设计一无所知。不过,这还不构成威胁,因为在不知道设计首先该干什么的时候,也可以生产出能工作的应用程序。

除了部分语言以外,所有的OO语言比其他类型语言都更容易受到影响,这一情形千真万确。尤其是像Ruby这种“平易近人”的语言很容易“受伤”。Ruby非常友好,它几乎可以让任何人,通过创建脚本完成一些自动的重复性任务。而且,它还有一个像Ruby on Rails那样强悍的框架,将Web应用程序放置到每一位程序员能触及的地方。Ruby语言的语法非常文雅,任何具备将想法串成逻辑顺序能力的人,都可以编写出能工作的应用程序。对面向对象设计一无所知的程序员,使用Ruby也能轻松地获得成功。

不过,虽然成功但却未进行设计的应用程序,总是存在自我毁灭的风险。它们在编写时很轻松,但更改起来则会变得越来越难。程序员过去的经验并不能预测到未来。无痛开发的早期承诺都会逐渐地变得无法兑现。如果程序员在面对每个变更请求时开始说:“是的,我可以添加这个功能,但这会把所有事情都给破坏掉”,那么这时原本乐观的情形也会变得绝望。

稍有经验的程序员也可能遭遇过另一种设计失败的情形。这些程序员对OO设计技术都有所了解,但对于如何应用它们仍然不太清楚。尽管是出于好心使用这些设计技术,但他们很快便会掉进过度设计的陷阱。只有“半桶水”是很危险的。伴随着知识的增加和希望的回归,他们开始无情地进行设计。在热情膨胀之后,他们会不合时宜地乱用这些原则。在不该有的地方,你也能看到他们在使用模式。他们一开始想要建造出一座复杂而又漂亮的代码城堡,最后却痛苦地发现自己早已被一面面石墙团团围住。你能把这种程序员立即辨别出来,因为他们在面对变更请求时总是会报怨:“不行,我不能添加这项功能。它不是设计来干这事儿的。”

最后,当设计行为与编程行为分开时,所开发的那个面向对象软件也注定会失败。设计是一个逐步发现的过程,它依赖于往复不断的反馈。这种往复反馈应该是适时的和递增的。敏捷软件运动(Agile software movement)的这种迭代技术,也因此非常适合用于创建优秀设计的面向对象应用程序。敏捷开发(Agile development)所提倡的这种迭代特性,让设计可以有规律地调整和很自然地演变。当设计是由很遥远的事情所决定时,就没必要进行调整,早期的理解错误能让代码更巩固。当程序员们被强迫编写由孤陋寡闻的专家所设计的应用程序时,他们便可以说:“没错,我当然可以写这个软件,但是这并不是你真正想要的结果,你最终是会后悔的。”

1.3.2 设计时机
敏捷开发方法相信:在客户看到具体的软件之前,他们对所想要的软件是没什么概念的,所以向他们展示软件的时机是宜早不宜迟。如果这个假设前提成立的话,那么在逻辑上你就应该以微量递增的方式来构建软件,逐步将你的方法迭代成满足客户真正需要的应用程序。敏捷开发方法认为:想要生产出客户真正需要的应用程序,最划算的方法是与他们一起合作,逐步地构建软件。这样,每次交付都有了对下一步想法进行更改的机会。敏捷开发的经验表明:这种合作产生出来的软件与最初想象的结果总是存在差异。因此,最终的软件是无法通过其他方式进行预测的。

如果敏捷开发方法正确,那么另外两件事情也会是真的:第一件事情,即大规模预先设计(Big Up Front Design,BUFD)完全没有意义(因为它不可能正确);第二件事情,即没人能预测应用程序什么时候会完成(因为你事先无法知道它最终会干什么)。

有人不太喜欢敏捷开发方法,这也是意料之中的事。“我们不知道在做什么”以及“我们不知道什么时候能完成”这两个问题很难解决。对BUFD的要求始终存在,因为在某种程度上,它提供了一种控制的感觉;否则,便会让人感觉无法控制。尽管这种感觉可能让人很安心,但认为这种编写应用程序的行为会无法继续下去,那只是一时的错觉。

BUFD不可避免会导致客户和程序员之间出现敌对关系。因为在软件真正形成之前的任何大规模设计都不可能是正确的,按照特定保证编写出的应用程序根本无法满足客户的需求。在客户尝试着使用这个应用程序时,便会发现这一点。接着,他们要求进行更改。程序员们都会抗拒这些更改,因为他们是在按计划行事,而实际情况是他们很可能已经落后于计划。项目的参与者开始从努力想要让它变得成功,转变为努力想要避免因其失败而被指责。随着这一情况的出现,这个项目便会逐渐走向消亡。

对于这种“潜规则”大家都心知肚明。当项目错过了它的交货期限时,哪怕出现这种情况的原因是因为修改了规定,但还是错在程序员。不过,如果项目是按时交付的,尽管不满足实际的需要,那么就可以肯定是规定出了问题,所以这时便可以怪罪到客户头上。BUFD的设计文档在开始时常被用作应用程序开发的路线图,但它们会逐渐成为争论的焦点。这些文档不会产生出高质量的软件,相反它们提供的都是一些被严重消化过的话语。在最后,这些话语会被大家竞相引用,没人想成为那个手持烫手山芋挨批的人。

一遍又一遍地做同样的事情,并且期望能得到不同的结果。如果说这很疯狂,那么敏捷宣言则让我们大家都开始有所觉悟。敏捷开发方法之所以有效,是因为它承认:在应用程序最终形成之前,确定性是遥不可及的。敏捷开发方法认可这一事实,因此它提供了许多策略,并将它们用来克服软件开发的各种障碍,同时也无须对具体的目标和时间表了解太多。

不过,敏捷开发方法所说的“不进行大规模的预先设计”,并不是认为完全不做任何设计。在BUFD里使用的“设计”一词与OOD里使用的“设计”有着不同的含义。BUFD几乎全部都是在指定和记录所涉及那个应用程序被期望将来应具备的全部功能及其内部工作原理。如果有某位软件架构师参与进来,则可能预先将决定扩展至如何编排所有的代码上来。OOD所关心的则是更为狭窄的领域,它主要是关于编排已有代码以便让它们更容易更改。

敏捷开发过程为更改提供了保证,而所能做出更改的能力则取决于应用程序的设计。如果无法编写出设计良好的代码,那么在每次迭代的时候,你就必须要重写这个应用程序。

因此,敏捷开发方法并不排斥设计,相反它还需要设计。不仅需要设计,它还需要非常优秀的设计。它需要你付出努力。它的成功离不开简单、灵活和可塑性强的代码。

1.3.3 设计评判
在以前有一段时间,大家常根据程序员所产生的代码行数(也被叫做源代码行数或SLOC)来评判他们。很明显,这种度量会变成什么样子:有的老板认为编程就是一条流水线,在那里同样训练有素的工人都能构建出相同的部件。这样的老板很容易形成这样一种观念,即单个生产力可以通过简单的权重输出进行评判。对于那些迫切需要一种可靠方法来对程序员进行比较和对软件进行评估的管理者来说,尽管SLOC存在有很多明显的问题,但总比什么都没有好。它至少是一种可再生的测量方式。

这种度量方法显然不是程序员想出来的。尽管SLOC可能提供了一种可用来衡量个人努力和应用程序复杂性的衡量标准,但它对整体质量却“只字未提”。它处罚的是有效率的程序员,奖励的却是那些编写冗长代码的人。它经常被专家用来与底层应用程序的危害相对照。当你了解到坐在你身旁的新手程序员常被认为更具效率,因为他或她为实现某项功能编写了很多的代码,而你却能用更少行数的代码来实现它,这个时候你会对此做何反应呢?这种度量方法以损害质量的方式改变了奖励制度。

在当代世界里,SLOC是一个历史产物,它在很大程度上已被新的度量方法所替代。有许多Ruby程序包(在google里搜索一下“ruby metrics”,最靠前的那些就是它),它们可以帮你评估代码遵循OOD原则的情况。这些度量软件通过扫描源代码,并对预测的质量进行统计。针对你自己的代码运行某个度量套件,可能会出现启发、羞辱以及担忧这三种状况。看似精心设计的应用程序会出现大量违背OOD的情况。

糟糕的OOD度量值无疑也标志着设计很糟糕:得分很低的代码将难以更改。不幸的是,得分很高的代码也不能就此证明它易于更改。即是说,这些度量无法保证你下一次的更改一定是轻松和廉价的。其中的问题在于,有可能创建出对未来过度预测的漂亮设计。虽然这些设计可能会得到非常高的OOD度量值,但如果它们对未来预测错误,那么当真正的未来最终到来时,想要进行修正则会付出昂贵的代价。OOD度量无法辨别出那些“在方法上正确而在做法却是错误”的设计。

因此,对SLOC所存在问题的警示也要扩展到OOD度量上。对它们要半信半疑。度量非常有用,因为它们没有偏执,而是会产生出一些数据。根据这些数据,你便可以推断出与软件有关的某些东西。不过,它们不是衡量质量的直接指标,而是更深层测量的“代理人”。终极的软件度量应该是:在起关键作用的那段时间间隔里,每一项功能所花费的成本。但它很不好计算。成本、功能和时间,都难以单独定义、跟踪和测量。

即使你有可能将某项单独的功能隔离起来,并跟踪与它相关的所有成本,但至关重要的时间间隔也会对代码应该如何评判产生影响。有时,现在拥有该项功能的代价非常大,以致它会超越未来所有成本的增长。如果今天缺失了某项功能会迫使你破产,那么明天处理这些代码会花费再多的成本也没有关系,你必须要尽最大努力保证按期完成。做这样的设计妥协就像是向未来借用时间,也就是众所周知的要承担技术债务。这是一笔最终必须归还的贷款,极有可能还会带上利息。

即使你并非故意想要承担技术债务,设计也要占用时间和花费成本。因为你的目标是要编写每一项功能成本都保证在最低水平的软件,所以具体要做多少设计才合适,取决于这样两件事情:你的能力和时间表。如果这个月的设计占用了你一半的时间,并且在这一年之内都无法体现出它的好处,那么这样的设计就不值得去做。当设计行为阻碍软件按时交付时,你便会输掉。一个优秀设计的应用程序,如果只能交付一半,那么与完全不能交付所导致的后果是一样的。不过,如果设计在今天早上占用了你一半的时间,而在今天下午它就能让你得到回报,并且在这个应用程序的整个生命周期里都不断采用这种做法,那么在时间方面你便都能获得一种日积月累的好处。这种设计努力会一直带来好处。

设计的盈亏临界点依赖于程序员。那些没有经验的程序员会做很多预先设计,但可能永远无法获得这样的结果:早期的设计努力会得到回报。对于那些熟练的设计师,它们在今天早上还在编写精心设计的代码,而在今天下午便能节省成本。你的经历可能介于这两种极端情况之间,本书接下来的章节将教给大家一些技巧。它们可用来权衡设计,并为你带来好处。

本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

时间: 2024-09-21 11:06:11

《面向对象设计实践指南:Ruby语言描述》—第1章 1.3节设计行为的相关文章

《面向对象设计实践指南:Ruby语言描述》目录—导读

内容提要 面向对象设计实践指南:Ruby语言描述 本书是对"如何编写更易维护.更易管理.更讨人喜爱且功能更为强大的Ruby应用程序"的全面指导.为帮助读者解决Ruby代码难以更改和不易扩展的问题,作者在书中运用了多种功能强大和实用的面向对象设计技术,并借助大量简单实用的Ruby示例对这些技术进行全面解释. 全书共9章,主要包含的内容有:如何使用面向对象编程技术编写更易于维护和扩展的Ruby代码,单个Ruby类所应包含的内容,避免将应该保持独立的对象交织在一起,在多个对象之间定义灵活的接

《面向对象设计实践指南:Ruby语言描述》—第1章 1.1节设计赞歌

第1章 面向对象设计 面向对象设计实践指南:Ruby语言描述 世界是过程式的.时间不停在向前流动,而事件也一个接一个地逝去.你每天早上的过程或许就是:起床.刷牙.煮咖啡.穿衣,然后上班.这些活动都可以使用过程软件来建模.因为了解事件的顺序,所以你可以编写代码来完成每一件事情,然后仔细地将这些事情一个接一个地串在一起. 世界也是面向对象的.与你互动的对象可能包括有你的老伴和猫,或者是车库里的旧汽车和一大堆的自行车零件,又或者是你的那颗扑通跳动的心脏,以及用来保持健康的锻炼计划.在这些对象中,每一个

《面向对象设计实践指南:Ruby语言描述》—第1章 1.2节设计工具

1.2 设计工具 面向对象设计实践指南:Ruby语言描述 设计可不是遵循一套固定规则就完事的动作.它是每次沿着一条分支前进的旅行,在这条路径上早期的选择关闭了某些选择,同时又会打开其他新的选择.在设计过程中,你会徘徊于各种错综复杂的需求中,这里的每个关键时刻都代表着一个决策点,它会对将来产生影响. 像雕塑家有凿子和文稿一样,面向对象的设计师也有自己的工具-原则和模式. 1.2.1 设计原则 SOLID原则首先由Michael Feathers提出,再由Robert Martin进行了推广.它代表

《面向对象设计实践指南:Ruby语言描述》—第1章 1.4节 面向对象编程简介

1.4 面向对象编程简介 面向对象设计实践指南:Ruby语言描述 面向对象的应用程序由对象和它们之间传递的消息构成.其中,消息相对更为重要.但在本节的简介里(以及在本书的前面几个章节里),这两个概念都同等重要. 1.4.1 过程式语言 相对于非面向对象(或过程式)的编程来说,面向对象编程是面向对象的.依据这两种风格的差异来考虑它们很有意义.假设有这么一种通用的编程语言,它可用来创建简单的脚本.在这门语言里,你可以定义变量(即组成多个名称),并将这些名字与少量的数据相关联.一旦进行了分配,便可以通

《面向对象设计实践指南:Ruby语言描述》—第1章 1.5节小结

1.5 小结 面向对象设计实践指南:Ruby语言描述 如果某个应用程序存活了很长时间(也就是说,如果它成功了),那么它最大的问题将是如何应对变化.通过代码编排有效地应对变化是设计的事情.最常见的设计要素是原则和模式.不幸的是,即使正确地运用了原则,并且也恰当地使用了模式,也无法保证能够很好地创建出易于更改的应用程序. OO度量能暴露出应用程序在遵循OO设计原则方面的情况.糟糕的度量值强烈地表明将来可能会遭遇困难:不过,好的度量值也发挥不了太大的作用.一个做法有问题的设计也可能产生出很高的度量值,

《面向对象设计实践指南:Ruby语言描述》—第8章 8.1节组合对象

第8章 组合对象 面向对象设计实践指南:Ruby语言描述 组合(composition)是指将不同的部分结合成一个复杂整体的行为,这样整体会变得比单个部分的总和还要大.例如,音乐就是组合而成的. 你可不能将软件当作是音乐,那只是一种类比.贝多芬的第五交响曲乐谱是一长串独特而又独立的记号.你只听一遍就会明白:尽管它包含的是一些记号,但它不是记号.它是另一回事. 你可以按同样的方式来创建软件,使用面向对象的组合技术来将简单.独立的对象组合成更大.更复杂的整体.在组合过程中,较大的那个对象通过"有一个

《面向对象设计实践指南:Ruby语言描述》—第8章 8.2节组合成Parts对象

8.2 组合成Parts对象 面向对象设计实践指南:Ruby语言描述 很明显,零件列表会包含一长串的单个零件.现在应该添加表示单个零件的类了.单个零件的类名显然应该为Part.不过,当你已拥有一个Parts类时,引入Part类会让交谈变得很困难.当同样的这个名字已经用于指代单个的Parts对象时,使用"parts"一词来指代一堆的Part对象很容易让人感到困惑.不过,前面的措辞说明了一种会顺带引起交流问题的技术.当在讨论Part和Parts时,你可以在类名之后带上"objec

《面向对象设计实践指南:Ruby语言描述》—第8章 8.3节制造Parts

8.3 制造Parts 面向对象设计实践指南:Ruby语言描述 回顾一下上面的第4-7行.那些Part对象存放在chain.mountain_tire等变量里面.它们都是很久以前创建的,你可能已经把它们给忘了.请仔细想想这四行所代表的知识主体.在应用程序里的某个地方,会有对象必须要知道如何创建这些Part对象.而在上面的第4-7行,在那个地方必须要知道与山地自行车一起的这四个特定对象. 这里包含了很多的知识,它很容易在应用程序里泄漏掉.这种泄漏情况,既不幸也没必要.虽然有很多不同的单个零件,但有

《面向对象设计实践指南:Ruby语言描述》—第8章 8.4节组合成Bicycle

8.4 组合成Bicycle 面向对象设计实践指南:Ruby语言描述 下面的代码展示了Bicycle使用组合的情况.它展示了Bicycle.Parts.PartsFactory,以及针对公路和山地自行车的设置数组. Bicycle有一个Parts,而Parts依次有一个Part对象集合.Parts和Part都可以以类形式存在,但包含它们的对象会把它们当成角色.Parts是一个扮演Parts角色的类,它实现了spares.而Part的角色则由OpenStruct扮演,它会实现name.descri