基于风险驱动的恰如其分的软件架构
随着岁月的推移,软件系统的规模、功能、复杂度都在呈现数量级增长,随之而来的变化带给软件开发者的压力与日俱增,更不要说各种敏捷方法和快速开发方法的流行极大地吊起了客户对交付时间和质量的期望。开源软件的日趋完善和其对应社区的指数级增长带来了数量庞大的框架、库等基本构造块,开发者可以借助各种成熟的基础设施和成熟完善的开发工具在很短的时间内完成复杂的功能。然而软件架构和抽象洞察能力这一无形的武器依然只为少数资深的开发者所掌握,所谓的软件架构随着软件工程的进展也变得和以往更加不同,闭门造车式的旷日持久的分析-设计-编码-测试-集成-交付流程逐渐被绝大多数组织所抛弃。
可惜任何具体技术上的进展都依然没有能从根本上轻易解决软件的核心复杂性,即所谓没有银弹;软件架构的演进也是如此,传统的大规模预先设计的方式固然慢慢变得不合时宜,站在较高的系统层次上,合适的软件架构仍然有巨大的现实意义。
假设教练和新手(初出茅庐的新队员)正在观看同一场比赛,教练所能察觉到的内容远远超过新手。这并非是因为教练火眼金睛,而是因为他掌握了某种无形的武器。通过一整套思维抽象,教练能够投过现象看到本质,把对原始现象的感知转换为对目前局势简明扼要的理解。例如,教练看到传球的一瞬间,就会联想到某种战术的成功。尽管教练与新手观看了同一场比赛,但是教练可以更好的理解比赛。
Gegore Fairbanks博士在其《恰如其分的软件架构》一书的概述中反复采纳了上述的隐喻来阐释抽象思维和精炼模型的重要性;因为这是软件架构最为根本的部分,是复杂软件系统最为本质和核心的部分。计算机科学经过几十年的发展,形成了一些非常基本的解决问题的思路,包括分治,抽象和知识积累;合格的架构师则需要审时度势,深入的理解所要解决的具体问题领域的最本质的问题,适时的选择合理的抽象,对系统做合理的划分从而能有效的分而治之又不引入过多的边界耦合问题,同时还要巧妙地将领域知识映射到具体的软件实现上来。
知识的重要性不应被低估
这里知识的重要性往往容易被忽略,尤其是容易被一些断章取义的对爱因斯坦关于想象力比知识更重要的主张的引用所蒙蔽;因为软件工程问题比较不同于前沿物理理论的探索,实际软件工程需要解决很多关于人的协作的问题,而不仅仅是纯粹探索具体的软件技术本身。现实的工程实践中,有太多的软件工程师将聪明才智花费在编程语言细节的追求和优化上,却对大的软件系统的职责分割和协作交互方式投入关注太少,从这个角度上来说,掌握再多的具体变成语言的细节可能对整体的帮助也不会帮助太多,因为大部分编程语言在一些细节问题的处理上其实是大同小异,而对于粗粒度的业务逻辑的抽象处理能力上,编程语言的支持其实又相当有限。设计模式试图从纯技术的角度来提炼一些公用的范式,然而终究和具体需要解决的领域问题关系甚远 - 我们需要的更多是关于如何思考整个系统的分割和协同解决整体问题的知识,譬如同样是用于解决一个关于日志文件的存储问题,可以有一些不同的解决思路
- 采用本地的日志文件
- 采用中央数据库的方法来存储
- 采用分布式文件系统加索引来支持快速检索的技术
对于软件架构师而言,不了解这些具体技术及其背后的优缺点和应用场景,自然无法对真实需要解决的领域问题选择合适的解决方法。这里了解相关的知识是解决问题的重要前提条件,如果不具备相关的知识而是仅仅选择一个理论上可能可以解决当前问题的方案,然后寄希望于之后发现问题再行逐步解决不是一种负责任的做法。所谓脱离了具体知识的想象力,只能是纸上谈兵光说不练假把式,将架构师应该负的责任都推给了救火队长。
起码从知识积累的角度来说,软件架构的选择和维护不是一个容易的工作,需要大量的现有知识和对业务场景的仔细分析,才能从众多可能的解决方法中找到一个最适合具体场景的解决方案出来,将其转化为合理的软件概念和抽象,最终生成可以运行的代码。
为什么需要风险驱动的架构
按照上述的思路,软件架构是一个成本相当高又相当费时的工作,架构师需要对所处理的领域问题做深入的探讨、分析和分解,根据已有的解决方案做合理的评估,选出最合适的匹配才能力保最终实现出来的软件系统满足目标客户的需求,为客户创造价值的同时自己的组织也从中获取收益。可惜在敏捷开发思想日益深入人心的今天,绝大部分组织没有可能有这么多的时间让架构师做太完备的分析而不产出一行代码,你晚发布一个月,可能你的竞争对手就已经占领了更多的市场,取得更多客户的订单。
最理想的情况是,你的项目可能刚好是一个有很多成功先例的项目,那么仅需要重用那些历经考验的架构做合适的适配即可投入使用;如果你面临的是一个相对很新的领域而没有现成的架构方式可以参考,你就需要特别小心谨慎的分析思考了。更可能的情况是,一个系统中有一些领域是相对成熟的有现成的方案,而另外一些子领域则可能是问题重重,需要比较大的投入去分析和考量。风险驱动的方式采用和项目优先级对齐的架构设计方式,通过对项目优先级的分析识别出相对合理的风险优先级,再基于这些风险优先级来决定做多少架构的工作,以及需要选择什么样的技术来解决高风险的问题。
如何决定架构的重要性
具体的就是如何判断对于某些具体的问题架构是至关重要的,而那些情况下不是。譬如开发一个现实商品细节的手机呈现页面,架构就是是无关紧要的,只需要开发者有足够的软件知识和清晰的界面布局约束,就可以以较低的风险完成编码测试并交付给客户;相反当你开发一个实现自动驾驶汽车的刹车子系统的软件模块的时候,则需要时刻关注整个自动驾驶系统的架构,因为一个微小的错误判断可能轻则导致车辆失控撞上马路旁边路灯,重则撞上前方静止等待红灯的车辆,引发严重的事故。
对于大规模或者复杂度高的(有很多相关开发/测试等人员牵涉其中)系统,我们需要格外重视其软件架构,具体而言
- 如果可能的解决方案空间很少,或者很难设计出满足要求的解决方案的系统,软件架构的风险也非常高,譬如设计一个对吞吐量、可靠性、实时性都有相当要求的系统,需要仔细的权衡那些具体的技术可以采用,可能会有哪些现在的风险
- 生死攸关的系统或者失败风险极高的系统,譬如你在开发一个网络中间设备控制器软件,改软件下游有相当多的终端设备相连,任何时候改控制器软件发生故障或者性能下降,下游的设备都会受到波及从而招致客户的抱怨;这种情况下架构的决策以及权衡也变得相当重要
- 对质量有相当高要求的系统,譬如高可用性约束的系统,对系统发生故障导致不可用的时间和故障修复时间有明确的高可靠性要求,那么具体设计中就需要考虑使用那些高可用技术既保证系统的一致性得以满足又能够有适当的备份机制提供热切换,始终尽量保持服务处于不中断状态
如何做:风险驱动模型
既然风险驱动架构的目的是期望用最小的架构技术去降低最紧迫的风险以达到事半功倍的效果。随之而来的就是具体采用怎样的步骤来达到目标,概括起来可以用如下的步骤
- 识别系统的风险,对这些风险排定优先级;这些在专门的风险管理知识中有专门的介绍,包括定性分析方法和定量分析法等,比较正规的办法是使用风险矩阵,包括美国的军方标准MIL-STD-882D,当然大部分软件组织在设计计算机系统的时候不会这么正规。
- 选择运用一组技术,这些技术必须是可以用来合理的降低风险而不是增加系统的风险,同时能完成功能需求
- 评估风险减低的程度
风险驱动模型的核心在于始终将风险放在极为显著的位置,充分将风险暴露出来,考虑其带来的影响并采用合理的技术解决之。很多情况下,特性驱动的开发模型过于关注对基本功能的完成情况而选择对系统重要的非功能风险置之不理,直到系统功能完成差不多了才来审视这些质量属性和约束,往往这个时候已经到了项目的维护期而为时已晚,早期对这些风险的缺乏重视导致项目的维护难以为继而失败甚至推倒重来。
有可能出现的一个问题是,不同的开发人员对风险的评估和认识和你的不同,这个时候可能就需要仔细的评审和和基于事实的一些评估;甚至要做一些简单的原型验证工作。这些工作都是恰当而必要的,因为你始终工作在方法优先级较高的风险上。
选用什么样的技术
在这个语境下,技术是个很宽泛的字眼,往往关注的是一些独立于具体领域问题的技术,如设计模式或架构模式来分解系统,对领域进行建模采用领域驱动设计,做原型测试等等。任何特定的技术都只是擅长于解决某些类型的风险,但并不一定适合于解决其它风险,譬如吞吐量分析和原型测试有利于降低性能风险,但并不能解决系统可靠性的风险。有些风险可能有多种技术手段可以解决,而有些特定的风险则必须结合多种技术才能解决,因此不能一概而论或寄期望于有一个“万能手册”
如果你面临
风险,请使用 技术降低它。
有意义的是这种根据风险来选择技术的思路,它可以帮助我们提高工作的效率,避免在一些低效的技术上浪费时间或者资源,但同时又不忽略那些真正关乎项目成败的风险。
具体实践上我们也不能根除项目的所有风险,因为消除风险的成本是巨大的,所以必须结合项目的优先级,权衡工程风险和非工程风险,合理采用技术手段和非技术(项目管理)手段,综合考虑技术因素和非技术因素最终确保项目的成功。
何时停止?
合理把握设计和架构的度是个复杂的问题,因为时间成本是此消彼长的关系;用在设计和分析的时间太多了,能分配在构建和测试上的时间就会相应减少。因此我们需要一些中庸之道,采用恰如其分的软件设计,即不做过多又不能忽视可能是实际项目实施陷入困境的风险。因而一个简单的模型指导原则是
为架构所付出的努力应该与相应失败的风险相称
譬如如果性能风险微不足道,就没必要在性能测试或者分析优化数据结构上浪费太多的时间。然而当性能变的直观重要的时候,就必须从一开始尽量想清楚目标环境的各种性能约束条件,选择合适的算法和数据结构,仔细权衡各种网络和IO开销,及早开始做性能测试甚至可以在开始阶段多做原型验证,确保设计没有大的缺陷。
架构和设计的演进
正如马丁·福勒在其演进式架构中所说,系统的设计会随着具体的实现的增长而增长;过往的软件工程历史表明,演进式设计一直充满了争议,局部而又不协调的设计决策会带来一系列的混乱,从而制造出一个大杂烩系统,导致系统的可维护性下降,进一步阻碍系统的进一步演进。甚至于一不小心还会陷入大泥球架构的泥潭。
当然随着敏捷开发方式的日渐流行和各项重构技术的发展,演进式架构以及渐渐克服了上述这些缺点从而再度焕发生机,奥妙就在于各种更为敏捷的工程实践,包括重构、测试驱动开发、持续集成等越来越深入人心。
传统的方法上来说,也有预先计划式设计这种从建筑行业借鉴来的方式(架构师这个名称也是从建筑行业来的),要求实现做好周密的设计然后再开工写代码做测试;只是时至今日一见很少有人主张采用预先大量设计(Big Design Up Front)。
介于两者之间的是预先小量的设计,采用这种方式可能是更好的一种这种,譬如在一开始阶段,通过对系统风险的分析对最大的风险采用预先设计的方式防范最首要的风险,然后随着项目的逐步推进,采用演进式设计来逐步完善整个系统的整体设计图景;当然这里最重要的前提是,确保重构、持续集成、测试驱动开发等关键的敏捷开发实践能够落到实处。
T.B.D
Leave a Comment
Your email address will not be published. Required fields are marked *