软件架构的风险消除策略

来源:互联网 发布:python socket编程tcp 编辑:程序博客网 时间:2024/05/16 07:49

1.2 软件架构的风险消除策略

 

架构设计的依据是什么?当然是需求,但架构设计思想中最有价值的东西,莫过于用风险分析来驱动架构设计,为什么这是个有价值的策略呢?因为一个产品设计中要考虑的问题很多,但只有在发现风险与消除风险的过程中,发现和抓住高风险的部分,才可以针对潜在威胁有重点地提出设计解决方案,甚至改变我们的设计思想,从而设计出更加良好的产品。

一、用风险分析驱动架构设计

1,在项目不同阶段风险的特点

1)通过风险分析发现问题

设计的要义是发现问题并解决问题,通过分析所面临的重点问题,找到解决方案。没有解决问题的设计并不是好设计,而通过识别和分析风险,可以帮助我们发现问题。一般来说,项目的开发可以分为四个大的阶段,包括初始阶段、细化阶段(也称架构阶段)、构建阶段、交付阶段。在不同阶段的风险曲线大致如下图所示。

2)风险的特征是分阶段的

有效的风险管理包括通过利益相关方的合作和参与,并主动地进行风险标识。从一般的规律上可以看出:初始阶段由于对信息掌握得不充分,项目的风险是比较大的。细化阶段的风险管理应处理可能危及关键目标实现的问题,在架构验证时风险达到最高,这是因为中间会多次改变方案,很可能重新选择技术路线,直到风险大幅下降。当风险下降到拐点的时候,也就是大部分风险都在细化阶段得到了解决,就可以进入构建阶段了。最后,在交付阶段,由于迭代开发是逐次交付的,所以风险不会变大。这与瀑布开发到了交付阶段风险突然增大形成明显的区别。

3)不同阶段的关注点

从风险的特点来看,大多数情况下细化阶段和构建阶段对风险处理的关注点是不一样的。

在细化阶段:主要关注对产品总体质量产生影响的风险,这种风险一般都比较大、比较关键、而且处理困难。

在构建阶段:主要关注为了在预算范围内完成项目的大量相关具体工作的风险,这种风险大多数与后期由于需求变更与频繁修改,对进度造成不良影响相关。

    这两种风险的差异导致了设计工作特征上的不同。由于细化阶段是一种典型的探索性工作,也是风险最高的阶段,所以从人员配备上看,需要有一个小型的、强于探索的、高水平的、能够解决风险的团队。这也是为什么任何项目开发都需要一个高水平的架构团队的原因,架构师的水平不足本身就是风险。

 

2,细化阶段的关注点

系统架构是早期化解高风险的重要媒介,细化阶段的主要关注点是:对初始阶段建议采用的技术路线进行证明,并且在这个技术路线上充实一些必要的细节,以保证将来能够按质量要求交付出解决方案。

1)架构并不完全是概要设计

很多人认为架构设计就是概要设计,这是不正确的。概要设计还是停留在图纸上,而架构必须证明这个技术路线可行,并且能够证明大多数质量风险已经得到了解决。

从另一方面讲,架构设计也并不完全指的是整体解决方案,事实上即使在很细节的组件设计层面,仍然需要根据构建阶段的风险,仔细设计每一部分的体系结构,并且体现出良好的设计思想。换句话说,设计中处处有架构,这和概要设计仅仅在概要层面想问题是不同的。

2)细化阶段需要实现一些必要场景

如何才能证明技术路线可行?这就需要在细化阶段额外实现一些必要的场景,这就是架构原型的作用。如果在细化阶段大家认为这个技术方案是合理的,并且解决了项目中如果不解决就会对项目产生威胁的问题,那么团队就会对这个项目充满信心。在这个消除技术风险的工作中,我们能够得到一些有价值的额外信息,使项目剩余阶段估计和计划的可靠性也高很多。

3)细化阶段的进度特征

正是由于细化阶段是高风险的,所以进度斜率会远远小于构建阶段,如下图所示。

要注意,在细化阶段初期的一两次迭代中,风险总量会有所增加,这是由于在技术探索中会发现过去没有发现的问题。随着对技术风险的明确应对,风险在后期就开始下降。这中间会遇到多次返工,以及多个技术方案同时开发进行比对的情况,所以进度曲线比较缓慢,这可以理解。在风险曲线的拐点,也就是风险由急变缓的那一点,也就是细化阶段结束构建阶段开始的时候了。

 

为什么在细化阶段(或称架构阶段)主要的风险是质量风险呢?首先,产品的质量指标主要是由总体解决方案来决定的,架构是满足质量指标最关键的设计位置。另一方面,没有质量的产品永远不是好的产品。例如在验收的时候,产品的功能要求都达到了,但是客户认为产品可靠性、可恢复性、安全性、吞吐量、性能等方面没有达到要求,客户会怎么办?

二、质量风险对架构设计的影响

在细化阶段,我们需要关注的主要风险就是质量风险,并且希望通过合理的架构设计,使这些质量风险得以缓解。架构师在进行具体的架构设计之前,首先就需要花足够的精力来研究产品的质量要求,对质量的追求也往往会形成一些特殊的系统架构风格。

 

1,软件质量控制是一个系统

随着社现代社会运转的速度越来越快,人们对于自动化系统的依赖也越来越大,这就对软件质量提出了更苛刻的要求。但是,由于赶工缩短开发时间造成的质量问题不断出现,当工期和质量发生矛盾的时候,为了工期而放弃质量控制的案例比比皆是。

产品质量控制是一个系统,单点质量没什么意义,必须整体上每个控制点的质量都要好,这就需要付出巨大的努力。要注意到从系统的角度看质量是一个乘法,总体质量是每个单点质量乘在一起的,只要有一个点质量是是0,总的质量就是0。仅仅某一个部分质量好并不等于总体质量就很好,一个小小的bug引起了对整个体系质量的否定,关于质量的这个原理我们都要铭记于心。

质量的最高境界是什么呢?是尽善尽美,是“零缺陷”。这种境界似乎是不可达到的,但是我们不要忘了,软件是由人开发的,如果人们不能严于律己,堕落就会很快。软件企业应该根据自身实力和用户期望来设定质量目标,过低的质量将会毁坏企业声誉,而过高的质量目标有可能导致成本过高而拖累企业,这既需要平衡并且抓住重点。

 

2,质量属性决定了架构风格

在进行系统架构设计的时候,要注意到一种架构的风格,很大程度上与设计者如何满足质量要求的对策有关。需求的功能和非功能两方面都可能有质量要求,具体归纳如下。

1)与功能性有关的质量属性

和功能性有关的质量属性主要包括:

l 正确性(Correctness);

l 健壮性(Robustness);

l 可靠性(Reliability)。

正确性:是指软件按照需求正确执行任务的能力,这无疑是第一重要的软件质量属性。在软件设计之初,我们花大量的精力讨论业务分析与建模,讨论产品业务的定义,都是为了保证正确性这一最重要的质量指标。

健壮性:指的是在异常情况下,软件能够正常运行的能力。正确性与健壮性的区别在于,前者是在需求之内描述问题,后者是在需求之外描述问题。健壮性一般有两层含义:首先是容错能力,其次是恢复能力。容错指的是发生异常情况不出错误的能力,而恢复指的是软件发生错误以后能恢复到没有发生错误前的状态的能力。

可靠性:是一个与时间相关的属性,指的是在一定的环境下,在一定的时间段,系统不出现故障的概率。通常用平均无故障时间(MTTF,mean-time to fault)来衡量。

2)与非功能性有关的质量属性

非功能需求中有一些非常需要关注的质量属性,主要包括:

性能:性能是指软件的“时间-空间”效率,而不仅仅是指软件运行速度。换句话说是速度要快而占用资源要少。有人认为随着机器越来越好(CPU、内存),性能优化的必要性下降了,这是不全面的,因为随着机器的升级,软件系统也越来越庞大和复杂,性能优化的压力将更大。

易用性:指的是用户使用软件的容易程度,由于现代人的生活节奏加快,对软件易使用性提出越来越苛刻的要求是无可非议的。关键是易使用是站在用户的角度来说的,开发人员感觉易使用的软件,对用户来说未必。

清晰性:意味着工作成果易读、易理解。一个臃肿不堪的软件系统迟早要出问题。所以简洁是人们“精益求精”结果,而不是潦草应付的结果。

安全性:它的目的是系统应该具备防止非法入侵的能力,这既属于技术问题也属于管理问题。随着软件规模越来越大,出现安全漏洞的机率也就随之升高。

对于安全的要求,可以用如下C-I-A三元组表达:

l 机密性(Confidentiality):信息或资源对非授权用户隐藏;

l 完整性(Integrity):数据或资源的可信度,数据完整性和来源完整性;

l 可用性(Availability):确保授权用户对信息或资源的使用。

对于大多数系统而言,完全杜绝非法入侵是不可能的,一般来说,如果非法入侵所花费的代价(时间、费用、风险等)高于所得到的好处,就可以认为系统是安全的。

可扩展性:这反映软件适应“变化”的能力,包括需求、设计的变化、算法的改进和变化。当软件规模很大的时候,可扩展性就成为一个非常重要的质量属性,也是系统设计的时候必须着重考虑的问题。

可移植性:指的是软件不经修改(或者稍加修改)就可以在不同软硬件环境中使用的能力。在互联网时代,这个要求所占的重要性越来越大。

三、进行关键质量属性分析

1,用两点论分析质量

1)对质量不能同等关注

在产品需求中,对质量的需求可能很多。但如果设计中对每一个质量需求都同等的关注,很可能是一个面面俱到的设计。这不但可能是一个没有特色的产品,也是一个可能花费巨资但用户并不满意的产品。什么是风险?如果客户最看重最关注的东西没有达成,这样的产品客户绝不会接受。如果我们能够针对客户最关注的少数质量属性进行设计,就会形成一种独特的架构风格,并且更容易取得成功。

2)如何发现关键质量需求?

这就需要在前期架构设计的时候,仔细分析在众多的质量属性里面,哪一个(或几个)质量要求对于客户来说是最重要和最关键的?这就是关键质量属性分析。发现这点很重要,这是成功架构设计的基本保障。那么如何发现我们最值得关注的质量属性呢?有两个两个非常好的问题:

何谓成功?这个问题可以帮助我们理解软件质量最少达到什么状态时,客户感觉满意,这种基于问题宽度的分析可以帮助我们不丢失关键质量。

何谓失败?这个问题可以让我们知道如果什么软件质量没有达到要求,客户坚决不能接受,这种基于问题深度的分析可以帮助我们找出设计质量绝不能越过的底线。

    通过对这两个问题的分析,我们就可以得出质量属性表并进行排序,并有了明确的设计指向。

   3)不同角色对质量的关注点

在考虑关键质量属性的时候,我们还会发现有些属性是客户关注的,例如:可靠性、性能、安全性等。还有一些质量属性是开发组织关注的,例如:可维护性、可移植性、可扩展性等,对这些不同的关注点都需要引起重视,并不仅仅是满足客户需要,例如对于可维护性的关注,最终受益者还是客户。

 

    2,质量分析场景

1)用场景分析质量要素

在筛选出关键质量属性之后,就需要对这些关键质量属性进行进一步的分解和研究,从而找到针对具体质量属性的解决方案,并由此综合出架构策略,这种对具体质量属性的细化描述,我们称之为质量分析场景,在具体设计之前我们需要仔细思考和描述这个质量属型的场景。针对具体的质量属性需求,质量属性场景一般由以下六个部分组成。

注意:对于不同的系统,对于质量的关注点是不一样的,例如某个设计强调了高性能,另一个设计可能更强调高可靠性,针对这些质量属性就可以形成基本设计重点,我们称之为“解决方案”,解决方案是一种影响质量属性的设计决策,把各种解决方案打包集合,我们称之为“架构策略”。这种针对某些质量属性的设计结果,就形成了这个产品的架构风格。

    2)发现系统的核心功能

在软件质量标准中功能性是个非常重要的属性,如果功能不全,那性能再好也是没有意义的。因此在架构设计中,发现系统的核心功能非常重要。为了发现这些核心功能,在设计一个系统的时候,我们需要回答如下一些问题:

l 这个系统有哪些核心功能?

l 这些核心功能分布在哪些系统级的框架之内?

l 是什么样的质量要求才使我们这样分配功能分布?

l 这些系统功能互相交互的时候考虑了哪些质量要求?

利用这些步骤,我们就可能在一定程度上,把一个系统架构构建成核心功能与基础框架规划清楚,特别是把核心功能与其它功能的交互关系,或者它与基础框架的交互关系更加清楚的表达出来,这种清晰的架构也有利于进一步重构和优化。

四、从“可行走骨架”开始设计

进行风险化解也不能仅仅依靠前期风险分析。由于人们认识事物的特点,是不可能在一开始就把所有的风险都发掘出来的,这就需要从根本上改变长期以来形成的僵化的软件开发方法论,从而创建出新的符合实际情况的方法论。

 

1,在不断培育软件的过程中完成架构

我们应该非常透彻的理解,软件是一种思维,而思维是培育出来的,这一点很重要。

1)软件是一个活物

在很长的时间里,我们习惯于用构建硬件的方式开发软件,比如把架构比照盖房子,这至少是不全面的。软件是一个“活物”,它与人的思维一样,会随着时间的增长发生变化,要返工修改,要和新的系统交互。这一切都会以我们无法预见的方式发生。这就是说,架构师不是建筑师,优秀的软件不是构建出来的,而是培育出来的。

2)抵制前期进行庞大设计的诱惑

一开始就设计庞大的无所不能的系统毫无好处可言。大型项目更可能失败、更无法验证、更可能脆弱不堪,更容易产生不必要和无用的产物,成本可能无比高昂,而且,还会招致不利的项目还值不值得继续下去的质疑。因此,无论多么诱人,都要抵制住前期进行庞大、完整、僵化的架构设计的诱惑。要有宏伟的远景(grand vision),但不要有庞大的设计。软件开发是一种学习过程,因此环境和需求的变化不可避免,我们和我们自己的系统都要学会适应变化。   

如何做到这一点呢?首先架构师要有爱心,要象对待自己孩子一样的面对自己开发的软件系统,这样才会有激情。初期的架构就像一个姗姗学步小孩,要不断鼓励它、理解它、发掘它的潜能:从一个“小的但可以使用的系统”开始,这个系统可以是预期架构的一个子集,亦即最简单的可行实现。然后,引导系统进行演化。这个新生系统拥有很多期望的特征,在完成这个系统的时候我们可以学到很多东西,这些是一个庞大而恐怖的系统以及一大堆架构文档无法给予我们的。

在生活中处处有这样的现象,一棵小树我们可以尽早的培育与修正,也比较容易发现问题和纠正发展的方向。但是一个扭曲的参天大树,要改变就很困难了。在架构的这种演化过程中,由于系统体积较小,将更容易测试,耦合度也会更低,开发团队可以更小,协调的成本也因此降低,系统也更容易部署。

3)在不断培育软件的过程中完善软件

在不断培育软件的过程中,团队可以更早的知道什么可行什么不可行,也能早期揭示出系统哪里已经僵化(不容易变化)并修改之,而不是早期无休止的争论。更重要的是,利益相关方从一开始就参与到整体设计中来,系统对他们会显得实在而易于理解。注意,不要把这种演化方法和消减需求、规范混乱、生产废品以及放弃管理混为一谈,演化是一种策略,它依靠的是更强大的管理水平。

 

2,从“可行走骨架”开始开发应用

上面所说从一个“小的但可以使用的系统”实现、验证和不断发展应用架构,并不是一个随意的选择,而是一个精心考虑的策略:这就是从“可行走骨架”开始架构设计。目标是要在培育骨架成长的过程中,保持系统一直是可运行可用的。

1)骨架系统(skinny system)

“可行走骨架”是对系统的最简单实现,它贯穿头尾,把所有主要的架构组件联接                                 起来。从可工作的最小系统来训练全部的通讯路径,可以带来“朝着正确方向前进的信心”。

可行走的骨架是最终系统的一个早期版本,我们也称之为骨架系统(skinny system),也称之为架构基线。这个骨架系统包含了项目结束时的“完整”系统所具有的模型的一个版本,它包含了相同的子系统、组件和节点的“骨架(skeleton)”,但并非所有的“肌肉(musculature)”都已齐全。骨架系统(架构基线)通常只包含5%~15%的最终代码,但他已经足够验证我们所做的关键设计了。骨架一旦就绪,就该进入“健身”环节了。通过“全身锻炼”使系统不断成长起来,这就是增量实现,逐步增加贯穿一体的功能。

2)缩短反馈回路

庞大完整的架构调整是困难而昂贵的,初期的架构设计历时越久越庞大,调整的代价就越高。因此,越早发现错误进行调整,这个方法论的价值就越高。“从可行走的骨架”开始这种方法论,能够创建很短的反馈回路,可以更快速的对系统进行调整。以迭代方式按优先级列表上的次序,逐步满足全部的业务需求,而这种业务需求又必然在验证质量属性和架构策略。如果早期阶段发现了问题,这时在实现上投入还不太多,架构的演化和发展会更容易和客观。

系统越庞大,使用这个策略就越显重要。对于小型系统,少量开发人员就可以从头到尾实现全部功能,变更也相对容易。但一个涉及多个团队(很可能是异地的)共同完成,一旦发现初始架构的错误,就会发生大量的协调很返工,有时候甚至不可能完成。

3)需要验证风险已经得到化解

一个良好的架构在建立成本很小的时候建立起来,就会获得良好的投资回报,它减少了项目执行过程中的重新设计和做无用功。为了确认架构已经成功化解了风险,整个架构体系应该是完成的经过测试和验证的系统。这就是说,首先创建的应该是一组核心的功能,或者对于项目至关重要的最高优先级的系统,或者是能够降低风险的系统。随后基于核心功能反复扩展,逐步增加功能以提高功能性。这样一来,就可能避免一个困境的选择:是中止还是继续?而从“可行走骨架”开始,并且保证系统可运行,增量式的培育,可以很大程度上避免这个风险。

五、进度风险对架构设计的影响

在细化阶段已经化解了主要质量风险以后,就可以进入构建阶段了。在这个阶段,风险的主要矛盾将发生转移,人们的关注点开始转向进度(交付时间)。而良好的架构和精细的设计对于缓解这些风险扮演着重要的角色。

为了发现进度风险,首先需要关注的就是哪些因素会影响进度?这些因素很对,但什么才是影响进度最大的风险呢?无数事实告诉我们,初期过于乐观的计划,过程中的需求变更,项目开发人员的、变动,都有可能对按期交付这个目标造成很大的风险。在这些因素中排第一位的,当属人们的思维方式。

 

1,处理庞大软件项目软件工程方法

很多组织都喜欢挑战复杂的大型项目,殊不知规模越大,项目失败的可能性就越大,一般规模扩大一倍,失败的可能性往往会增加十倍。但是我们所遇到的项目往往正是由于其规模和复杂性才具有价值,哪些策略可以帮助我们控制项目的规模呢?

抓住真正的需求:软件项目的交付目标表现为一组需求,需求定义了软件的功能和质量。但客户真正关注的需求是什么?不能为客户创造价值的需求应该受到质疑,很多情况下这类需求应该考虑推迟或者放弃。

分而治之:寻找机会把大项目分成小项目,比起由大量相互依赖的庞大功能组成的大项目,几个项目独立的小项目更容易管理。

设置优先级:业务环境瞬息万变。大型项目在完工之前,需求会改变很多,有些需求会随着业务的变化甚至被取消,但是关键需求通常会维持不变。理清需求的优先级,优先实现最关键的需求。

尽快交付:在看到演示产品之前,多数客户都不知道自己想要什么。由于不同的人对需求不同的理解,往往会把需求想得过于复杂,很可能与真正的需求不沾边。让客户试用演示的产品,没准会发现其实事情没那么复杂,可能会有更简单的解决方案。首先实现最重要的需求,尽快获得客户反馈,越快越好。

越复杂的项目越难成功实现。良好的架构有助于缩小项目规模,通常也会降低设计与实现的复杂性,这是成功交付的最有效途径。从工程方法来说:

首先,不论什么情况,必须把一个大项目分成若干相对独立能够持续交付的部分,这样就可以把大问题分成若干小问题,在每一个小部分可以用线性方法来处理,这样就简化了问题,提高了效率。

其次,如果前期需求比较清楚,则可以使用下面的分段线性化模型。由于限制了每个阶段的规模和复杂度,在每个里程碑阶段中都可以用线性方法来处理。在每个里程碑必须进行一次集成,这也是持续集成这个概念提出来的科学原理,被称之为架构驱动的增力量开发。需求的开发需要支持这种分阶段处理问题的方式。

最后,如果前期需求不是太清楚,项目带有强烈的创新成分,可以使用下面具有强迭代的逐步求精的模型:在每个迭代期间,由于限制了范围,线性模型仍然在起作用,被称之谓问题驱动的迭代开发,需求开发就需要支持这种逐步求精的方式。

这两种工程方法都可以做得很正规,而且是由计划来控制。这两种方法的共同特点就是通过分解,把大问题变为小问题,而且代表着两种极端情况,大部分项目处于这两者之间,形成一种混合体。

由于每一个阶段是一个小型的独立体,就需要考虑每一部分的关系,通过事先定义稳定的接口,使每一部分的变化不至于辐射到其它部分去,在这个背景下,接口也成为一种需求。

这种通过可演示的结果来评估自己目前身在何处,也使我们对于项目的实际进展更加有把握,对需求的理解也更透彻。迭代开发的评估比传统方法会更频繁,客户的参与程度会更高,而不是把问题全部堆积到项目后期来解决,这样就降低了项目的风险,通过早期发现需求得不确切之处,引领项目达到一个更加成功的目标。

 

2,利用持续集成缓解交付风险

我们经常遇到的困惑就是:产品集成是最后统一集成,还是分阶段持续集成?我经常看到开发人员在交付阶段在没日没夜的加班,为什么到了交付阶段就要加班?我提的问题是:在交付阶段不断加班、修修补补、赖赖巴巴的满足要求,这样能保证产品质量吗?

1)早期交付持续集成

将集成活动放到每个迭代周期,就可以利用每次迭代结束的演示来推动项目的进展。缺陷就可以在早期暴露出来,便于早期修复缺陷而不至于到后期发现缺陷束手无策。这些所谓缺陷有的是由于设计上的,更多的是由于双方对需求的理解不同造成的。在这样的迭代过程中,架构也会有一个不成熟的原型逐步成长为一个稳定的骨架,最后成长为一个可发布的产品。从认识论的角度,软件开发就是一个人们认识事物的过程,这也是一个对问题的理解逐步深入螺旋形上升的过程,符合人们认识事物的规律。

2)架构级别的需求与设计

如何避免后期大范围长周期的系统集成呢?利用架构驱动的开发是一个值得推荐的方法。在大型复杂项目中使用迭代开发的时候,软件开发团队应该首先完成架构,架构是一个产品并且早期经过验证,这样一来就可以早期检测出设计的瑕疵并且得到解决。这种开发风格的结果,就是每个迭代周期快要结束的时候都有集成活动,以避免到了后期那种爆炸式的集成。所以在项目开始之初,我们应该首先透彻理解架构级别的需求,进行详细的架构设计,并且把它稳定下来,这样就能降低废品率和返工率,而且早期缓解高风险。

3)架构的思想与过程的思想

这些过程思想能够得以实施的先决条件,就是架构设计思想和方案要与这种过程思想匹配。事实上设计思想与管理过程上的不匹配,往往是项目面临的最大风险。无数事实告诉我们,越是早期发现和解决风险,项目的成功率就越高,项目的成本也就越低。

六、尽早发现软件的腐化

软件的腐化是开发进度的大敌,当你在项目后期不断的修修补补,一个问题的出现引发大量的点跟着变动,这样的系统是不可能按时交付的。更重要的是,当需求发生变化的时候,对正在开发的产品简直是一场灾难,这样的系统又怎么可能按时交付?这就需要设计。

人们总是问什么是正确的设计?实际情况是,假定你一切都做对了,但只要一件错事,就全盘皆输。当人们获得的成功时,很少有人列出成功的原因,因为要列的东西太多了。换句话说,做正确的事情并不能保证良好的设计,我们必须避免做错误的事情。换句话说,关注错误比关注正确重要,关注软件的腐化原因本质上就是关注错误。

 

    1,腐化软件的气味

从系统的角度来看,好的系统与差的系统差别很大。一个差的系统一般具有如下一些特征:首先,差的系统大多来自于经验主义,没有理论支撑;其次,差的系统缺乏规律,不可复制;最后,差的系统大多会越来越混乱。事实上一个工作正常的软件并不认为不可交付,但有的软件会散发出一种气味,有经验的架构师会从中看到危险。归纳一下,当软件出现下面任何一种气味时,就表明软件正在腐化:

僵化性:很难对系统进行改动,因为每个改动都会迫使许多对系统其他部分的其他改动。

脆弱性:对系统的改动会导致系统中和改动的地方在概念上无关的许多地方出现问题

牢固性:很难解开系统的纠结,使之成为一些可在其他系统中重用的组件。

粘滞性:做正确的事情比做错误的事情要困难。

不必要的复杂性:设计中包含有不具任何直接好处的基础结构。

不必要的重复:设计中包含有重复的结构,而该重复的结构本可以使用单一的抽象进行统一。

晦涩性:很难阅读、理解。没有很好地表现出意图。

有腐化气味的软件并不一定就是有问题,但它给我们提供了一个指向,避免了漫无目的地去寻找,提醒我们需要关注其中的某些问题,闻到坏味也就意味着重构的开始。那么这些气味的表现形式是什么呢?

 

2,缺乏正交性

正确的设计应该保证每个事实应该是单一的、不可分解的单元。每个事实必须独立于其它的事实。当改变发生的时候(这是不可避免的),只有一个地方需要修改,这就是架构设计的正交性原则。正交性是从几何学中借来的术语,表示两条轴线相交成直角,互不依赖,沿着某一条直线移动,你投影到另一条直线上的投影不变。

在软件中,正交性表示了不相依赖性和解耦性。如果在设计中一个事物的变化还会影响其它事物,我们就称他们是非正交的。实践证明,软件模块设计的非正交性带来很多问题:

生产率问题:如果改动是非局部化的,使开发时间延长,而且单元测试很困难,在增加新代码的时候,还需要改动其它的代码。

软件难以复用:如果组件职责不清稀,就难以和新组件结合在一起,组件的开发难以做到并行化。

增加风险:有问题的区域被相互关联在一起,某个模块的毛病会扩散到其它模块。

为了保证正交性,就是提早构建合理的抽象,把实现封装到抽象中去。抽象可以为未来的修改预留余地。

 

3,概念不一致

好的架构应该让系统反映一组设计思想,而不是让系统包含许多好的思想。这种概念的不一致性,使开发者在理解了系统一部分以后,难以迅速理解系统的另一部分,这对升级和维护带来了附加的困难。

架构团队的挑战在于:在创建架构的时候保持同一种思考方式和同一种哲学,架构团队要尽可能小,让他们在充分沟通、高度协调的环境下工作。当发生思考方式和哲学上的争议时,让首席架构师作最后的裁决(而不是无限期的争论),称之为“简单雷同原则”。对于任何复杂系统,简单雷同原则都可以使复杂的问题变简单,变得可以操作,非此不能保证完满的协调。

 

4,完美的危险

片面追求“最佳”会带来所谓“完美的危险”。每一种情况下可能都有它的“最佳”,但可能造成整体上极其繁乱难以理解,最终是架构变得极其脆弱。应该用尽可能少的机制,将有可能整体上得到更小的、更快的和更健壮的系统。

什么是足够好的软件呢?问题是什么是“好”?完美无缺的产品是不存在的,这就需要一个目标:什么样的情况意味着软件成功。这就需要非功能性需求的收集。我们更要思考的问题是:什么情况发生软件就是失败?理解了失败的场景,我们的设计才更有指向性。

当你从用户那里获取需求的时候,你是不是问过:你认为这个软件很好是因为什么?是效率?还是容量?还是安全性?这就会得到不同的约束。如果你早期给了用户某种东西,尽管不太好,但客户的反馈会把你引向更好的解决方案。

不要由于过度修饰和过分求精而毁损原本很好的产品。知道何时止步,让软件凭着自己的质量在原地站立一会儿,不用担心,他可能并不完美,但足以应用。总比一层又一层,细节复细节的叠加,最后让软件迷失在自己绘制的迷宫之中要好。

 

5,需求蠕变:熵

需求蠕变对我们的影响很大,好的架构应该设计一条最容易维护的路线,随着时间的推移仍然能够保持架构,这样一来就延缓了“熵增定律”。

不要留着“破窗户”(低劣的设计,错误的决策,糟糕的代码)不修,发现一个就修一个,如果没有时间修理,就把它注释出来(用木板把窗户钉起来),留待将来处理,说明其实还在你的控制之下。我们经常看到一个整洁运行良好的系统,一旦出现破窗户,就相当迅速的恶化。所以,你不应该成为第一个弄脏东西的人。

 

    6,烟囱系统

    “烟囱”是一个即兴设计的软件系统的术语,就像一个烧木头的炉子,需要经常修理(通烟囱)来保证烟道的畅通,还会用任何手头上的材料来维修烟管。这样构建起来的系统,慢慢就会成为随意修理的大杂烩。形成烟囱系统最根本的原因是缺乏概念的一致性。也就是在一个系统内部,一组子系统和模块之间缺乏协调和规划。

有很多原因会产生烟囱系统:

l 用了多种架构机制来集成子系统:由于缺乏共同的机制,所以难以说明和修改架构。

l 缺乏抽象:每个子系统的接口都是独一无二的。

l 对元数据的使用不足:没有可用的元数据来支持在不改变软件的情况下对系统进行扩展和重新配置。

l 实现的类之间采用紧耦合,要求过多的与特定服务相关的客户端代码。

l 缺乏架构的远景。

形成烟囱系统关键的原因就是抽象不足,各个子模块是以十分随意的方式拼接在一起的,而不是仔细寻找共性形成抽象。事实上我们强调架构设计必须以极大的精力关注接口,也就是要关注抽象,这需要架构师有极强的抽象思维能力。

我们现在很多开发企业总是说忙,总是说乱,但能不能把开发停一下,仔细想想这是为什么呢?我们有多少工作时间是在做那些似曾相识但又略有不同的产品?如何制定一个标准,使开发变得更有序和更有效呢?

我们来看一个简单的例子,如图一个典型烟囱系统,包括3个客户端子系统和6个服务子系统。每个子系统都有一个独特的软件接口并且是一个类,如果增加或替换子系统,就需要额外的代码修改客户端来集成新的独特接口。

这个子系统改进方案考虑了子系统之间的共同抽象,通过包装每个特定服务来支持共同接口。如果添加额外设备,可以把它们透明的集成到已有的系统软件内。添加的交易服务增加了发现和区分抽象服务的能力。

 

7,是什么导致了软件腐化?

    是什么造成了软件的腐化?在传统的工程方法中,由于需求没有按照初始设计预见的方式进行变化,从而导致了设计的退化。通常,后期发生的改动都很急迫,并且进行改动的开发人员对于原始的设计思路并不熟悉。因而,虽然对设计的改动可以工作,但是它却以某种方式违反了原始的设计。随着改动的不断进行,这些违反渐渐地积累,设计开始出现坏味。

要注意的问题是,我们不能因为设计的退化而责怪需求的变化。作为软件开发人员,我们对于需求变化应该有非常好的了解。事实上,我们中的大多数人都认识到需求是项目中最不稳定的要素。如果我们的设计由于持续、大量的需求变化而失败,那就表明我们的设计和实践本身是有缺陷的。我们必须要设法找到一种方法,使得设计对于这种变化具有弹性,并且应用一些实践来防止没计腐化。


原创粉丝点击