软件架构构成了一个系统的骨架。它定义了当面对不同的功能性和非功能性需求时的系统行为。一方面,传统瀑布式方法对项目开发的所有阶段提出了硬性约束要求,因此传统瀑布式方法显得僵化。另一方面,敏捷运动让我们拥抱改变,即使是处于开发阶段后期的改变。尽管我们正推动自己从僵化的开发模式迈向更灵活的模式,软件架构由于其系统骨架的定位,天然地对变化敏感。因此关键之处在于,敏捷运动拥抱的软件架构必须是可持续的——具备可持续概念的软件架构,支持在项目复杂度不断增加的同时,系统能以渐进式的、简单的以及可维护的方法进行扩展。
在这篇文章里,我回顾了自己在传统瀑布式软件架构和敏捷软件架构下的工作经历。描述了两者在以下三个方面表现出来的相似性及差异性:
软件架构扮演的具体角色;
软件架构的时间跨度;
软件架构的输出。
什么是软件架构?
软件架构的定义(实际上你也能添加你自己下的定义)成百上千。存在这么多种定义的原因在于每个人都是基于自身情境下定义。我对IEEE给出的定义特别推崇,这个定义描述的基本概念非常形象化。此外,该定义描述出了软件架构的精髓本质,同时适用于瀑布式和敏捷流程,而不是只能匹配某一个。在本文的后续部分,我会引述到该定义:
一个系统的基本组织结构、基本组成构件和互相之间的关系,以及构件于外部环境间的关系。同时,软件架构为后续的设计和架构演化提供了指导性原则。
瀑布式软件架构
传统瀑布式开发的特征在于其由一系列有明确的开始和结束时间的阶段构成,每个阶段包含确定的活动集。所有阶段串接在一起,每个阶段严重依赖于前一个阶段的交付产出。图1阐述了瀑布式开发过程涉及到的常见阶段。
图1:传统的瀑布式模型
软件架构工作通常在软件需求确定后开始启动,认为在此时,关于系统应做什么,已经确定好了。下一步是审查负责确定软件架构的人以及当前阶段的实际输出结果。
传统软件架构
软件架构实践中通常由软件架构师完成。软件架构师拥有丰富的技术知识和经验——往往是由公司中已经达到一定等级的开发人员晋升而来的。软件架构师负责分析软件需求,并基于这些需求为未来系统的演化做某些技术决策。许多公司通常是一个项目对应一个软件架构师,但在一些更大的公司里,软件架构师们可能以团队的形式共同合作。当项目所在领域非常复杂或者项目的周期很长,比如长达2到3年甚至更久时,通常情况下就会有软件架构师团队。不是所有的公司都有指定的软件架构师角色——许多公司把这部分职责委托给他们的高级开发人员承担。在本文的后续部分,我所谈的是指由软件架构师执行的具体活动项,而不是执行活动项的人,除非另有明确说明。
传统的软件架构师有4大主要特征:
关注大格局——软件架构师应当思考,系统在未来的几个月(有时间甚至是几年)会变成什么样子。此外,他还应当一并考虑到所有其他相关的系统(比如第三方系统和数据库),以及系统间的通讯问题。
遵从性导向——软件架构师应考虑到可能的遵从性问题。可能的有法律规范、许可证、标准或其它,身为软件架构师他应确保系统在未来能够满足这些至关重要的标准。
绘制蓝图——软件架构师的一项重要交付是从不同角度描述架构设计的文档和图表集。开发团队利用这些交付件开始进行系统构建。
不太多的实际操作经验——软件架构师的工作目标是产出最终文档以供开发人员使用。尽管软件架构师通常拥有开发经验,但他很少参与到开发过程中去——而是指导开发人员构建已设计好的系统。在某些时候他甚至可能调去参加另一个项目,留下开发人员自己完成开发。
真实世界之痛
我曾经为全球最大的啤酒公司的其中之一做一个软件项目。项目用了2到3年的时间,使用典型的瀑布式方法,在不同的阶段有对应的负责人——软件架构师、开发人员、测试人员。我是一个小团队里的开发,我们根据软件架构师传达的指示和指导意见,执行系统的开发工作。最初软件架构师提供在场支持,但过了不久他转到另一个项目去了,因此就减小了在这个项目上的工作量。当新的依赖不断出现,有时候很难照着拟定的软件架构推进,因为和已有架构设计的规定不相符。尽管尝试过让我们的高级开发人员接管架构设计,但项目还是逐渐成为所谓的意大利面条式代码(spaghetti
code),每个人都害怕去改代码,因为很可能在哪个地方就出问题了。遗憾的是,项目已经来不及做任何重大的改变了,无法回到正轨,所以尽管项目最终发布了,但日后仍然被停掉了。
敏捷运动
传统的瀑布式架构的性质是一次性活动,活动有明确的起止时间,而敏捷软件架构是一个持续不断的过程,也许没有终点。敏捷软件架构使我们可以对架构设计实施更改,如果需要的话,可以定期实施。拥抱变化的一大机制是在项目里运用迭代和增量开发。在Scrum里,这些迭代被称之为sprint,如图2所示,典型的一个sprint的周期约为2-4星期。周期窗口如此之小,所以能对提出的任何改变做快速讨论。此外,敏捷非常关注团队的协作,团队成员之间存在的任何问题应立即解决掉,以防止出现误解及沟通不畅的情况。
图2:Scrum中的一个典型Sprint
敏捷运动使得人们可以拥抱项目中的变化,但它并没有告诉你应当以多快的速度响应变化。软件架构设计作为系统的骨干支柱,对变化非常敏感。比如说,在项目中期,你觉得你能更改项目使用的平台或编程语言吗?这样的更改,即便很罕见,也需要通过多轮的迭代才能完成。这种改变甚至能把你重新拉回到项目的启动阶段。当牵扯到软件架构设计时,有些类型的变化就比较苦楚,需要较多的执行时间。
敏捷软件架构师
Scrum定义了三类角色:
产品负责人(product owner)——负责提供具体业务领域的信息
scrum master——负责推进团队的沟通和协作
开发团队(development team)——负责实现用户story以及软件编写
为了将传统软件架构师角色转换为适配敏捷世界,我们需要先分析下一些可能的变种。构建Scrum团队的一个方法是让开发团队和一个单独的软件架构师团队一起紧密地合作。图3说明了多Scrum团队的构建场景。
图3:一个单独的软件架构师团队和多个开发团队的合作
凭这种方法确实可以完成团队的构建,但存在两个问题:
软件架构师团队可能会变成需求方,而开发团队成为实现方,这正是瀑布式方法下出现的典型情况。最后呈现的结果是,软件架构师定义项目的前景,留给开发人员去完成实现。如果开发人员没有足够的参与,不被尊重对待,他们的积极性被下降。
上面这样做的后果,导致一部分开发人员可能也想加入软件架构师团队,这样就能够给其他开发人员分配工作。Trustpilot提供了一个例子,他们有一个核心团队和开发团队,但总的来说却破坏了工作风气和彼此的协作,于是他们把核心团队的所有成员都移到一个开发团队中去,收到了积极的效果。
另一种做法是将软件架构师直接置于开发团队中,如图4所示。
图4:每个开发团队都有一名软件架构师
这种情况下,敏捷软件架构师的责任发生了一些变化:
在当前和宏图愿景之间保持平衡——敏捷软件架构师需要同时思考两点,当前开发过程中发生的事情以及将其与整个系统的宏图愿景进行对齐。
实际操作经验——敏捷软件架构师同时也是开发人员,参与系统的实现工作。这样使得敏捷软件架构师能够获得关于做出的架构决策的第一手反馈信息。
创建原型,明智决策——当需要做出重大的技术决策时,快速创建原型可以揭示这个决策是否可行以及它会如何影响到现有系统。再者,与全体开发团队的沟通非常关键,团队合作的效果远好于独自一人埋头苦干。
关注可持续性——极其重要的一点是,架构设计决策造就可持续的软件架构,从长期来看,可持续的软件架构能很好地支撑起项目。个人责任感和情怀是其中不可或缺的一部分。敏捷软件架构师是开发团队的一份子,所以如前所述他能得到自己所做决策的第一手反馈。相比瀑布式方法,软件架构师的决策传递到开发团队并由开发团队负责执行,敏捷方法提升了个人责任感。
如果想在不同的开发团队(也可能是不同的项目)之间共享软件架构师资源,可以选择构建拥有独立软件架构师团队的组织结构。除此之外,如果从事的领域很复杂,需要考虑的视角很多,也可以采用拥有独立软件架构师团队的组织结构。在这类情况,必须保证软件架构师与开发人员的合作紧密,并展现出了对开发人员的支持。敏捷方法关注协作,将软件架构师从开发人员从分离出来,使得协作变得困难了。结果开发过程变得更贴近于瀑布式模型。我个人更青睐第二种变体,在那软件架构师处于开发团队中,因此团队成员之间的沟通交流更有效。在一个更高的层次上架构师仍能(也应当能)协调一致。
敏捷软件架构的时间跨度
敏捷软件架构的一个重要方面是何时开始进行架构设计。不同于瀑布式模型对每一个阶段都做了明确定义,在敏捷的世界里,不存在一个所有人都同意开始的确定的时间点。一个典型的做法是引入sprint
#0,这是一个特殊的sprint,开发环境已经配置好了,一些重要的决策已确定(比如编程语言、平台、数据库等等)。
这种方法有个常见的陷阱,即人们倾向于延长sprint #0,因为总会发现事情“几乎就快准备好了”。经常听到“再给一个星期,我们就能开始进入常规的sprint”这样的话。很多时候你会发现自己已经在开发系统了,但用户story还没见着,因为“提前帮忙完成功能实现真的很酷”。这种情况你应当预先商定出sprint
#0的结束日期,可以设置在一个常规sprint的持续周期内,或者类似相近的时间。
可能有人会疑惑,万一到常规sprint应启动的时候,还没完成架构设计,要怎么办。嗯,其实这也没关系。事实上有可能永远不会有准备好的一天。那也没关系。软件架构设计是一个持续不断的过程。你应当经常性地重新看回来,去修正系统的骨干。在架构设计不能给予支持保证时,你是无法进行系统开发的。Simon
Brown说:
敏捷团队没必要创建敏捷软件架构。但一个好的架构确能做到敏捷。
控制原则
我们生活在一个复杂的世界,每一个业务领域也都是如此复杂。当构建一个软件的架构时,真的很容易从一开始就把事情复杂化了,进而让后续开发更容易出错。以下两条原则是做决策时事实上的标准:
保持简单,愚笨(KISS,Keep It Simple, Stupid)
你不需要它(YAGNI,You Aren’t Gonna Need It)
如果在那一刻我们真的需要一个具体的功能和做成决策,这两条原则试图让我们对此做慎重的思考。如果我们把做决策推迟到一个更晚的时段,就能保持架构的简单,并因此在一个更长的时间里方便管理。软件架构变得复杂的一个通常做法是引入抽象——可能变成一个新的花式层,以一种格式复制数据,然后转换为另一种格式,或者为了让代码具备可测试性,可能创建出许许多多的类、接口、工厂等等。
不过,在运用这两条原则时还要提防一处陷阱,我们倾向于把所有事情都延缓处理直到最后一刻。到那个时候,可能已经变得太难实施所需的变更了。为了避开陷阱,我们的任务难得多,因为
我们不应在最后一刻做出决策,而应在最有责任这么做的时刻做出决策。
当我准备做出架构上的决策决定时,如果做这个决定的确定性很高,我通常的做法是先做一些不那么花时间的小的准备。我也会去咨询我的同事,我们一起讨论问题。
真实世界之痛
我曾经做过一个轮渡票务在线销售的项目。这是一个复杂的系统,它需要和4个其它第三方系统进行通讯。一开始我们的首要关注点是基于用户story完成功能实现。尽管我们知道我们需要一个更复杂巧妙的缓存机制,但那时还不必要——我们得先完成当前的用户story,于是我们选择了延缓处理。然后到后面,由于与其它第三方系统的大量通讯,系统变慢了。我们别无选择,只能停止用户story开发,一门心思扑在缓存机制上——但这时事情已经不好办了。
如何组织文档
瀑布式方法要求编写大量的文档,因为需要用文档来在不同的阶段(以及每个阶段的参与者)之间进行信息传递。编写文档的过程不仅耗时间,而且由于文档存在对功能的不当描述,还经常造成误解。更进一步,难以保持文档的及时更新,因为开发倾向于快速推进,很多时候不会再理会文档了。正如我们使用敏捷来迭代地编写代码,同样可以如此处理文档。我们开始只描述系统的重要方面,然后在需要时持续地加入更多新的信息。
哪些内容应写入文档
切勿对同一个东西以不同的方式做多次的文档化处理。举个例子,有工具能帮助你从代码中生成图表——和创建独立的图表相比,这样做方便很多,图表很容易过时的。除此之外,假如你为了描述系统的某一方面而创建了可视化工件,就没必要再使用文字(除非你想要增加一些不能用视觉方式表达的细节)创建一大堆文本文档去描述同一件事情。这里有一点很重要,你和你的团队应对使用的绘图符号有一致的理解。
怎样文档化可能会想到使用UML来对软件架构进行文档化。UML是标准语言,每个人都能理解,大学里也教,因此UML一定是团结起组织内每个人的不二选择。我的经验却显示,实践中很少有人使用UML。原因之一可能是UML提供的描述系统的方法非常多,可以从不同的视角进行描述,所以不经常使用UML的人会感到挫败。
在敏捷业界,没有特定的工具用来文档化软件架构。可以使用涂写白板、便利贴、文本文档、wiki等等(见图5)。实践中,只使用2到3种不同的格式会比较稳妥些,要不然信息可能会变得难以存储和检索。比如说,在白板上涂写一阵后,你可能需要对其进行拍照,这样就保存下来了图表的电子版本。如果后面你要再次编辑,这时你得选择到底是直接数字编辑照片,还是重新在白板上画一遍然后再照一张相。
我用图表工具来生成系统构件的简图。通常用Microsoft Visio或draw.io(已集成在Google
Drive中),不过还有大量的其它工具可选,在线和离线的都有。如果开会时在白板上做了绘制,我会在会议结束后用图表工具重画一遍一模一样的图,以保持我画的东西的格式统一,彼此不存在大的差异。如果我需要对图表添加额外的注解,我一般会另行创建文本文档。
图5:白板画和便利贴形式的文档化
总结
软件架构定义了未来系统的骨架。它不只是由线点组成的图画,而是一系列管理支配着系统开发的完整决策,包括代码本身。应细致地考虑每一个做出的决策与决定,它们都是一种权衡折衷。敏捷理念要求对变化持开放心态,甚至是来自项目晚期的变化,和传统的瀑布式模型不同,瀑布式模型希望需求比较稳定。不过,系统骨架的变更往往并不容易实施,甚至可能把你拉回到开发阶段。因此,不要等到最后一刻才做出该做的决策决定,这点很重要,而应该在最有责任这么多的时候做出决定,为此甚至可以不惜冒点小风险,实现某些在那个时间点不做要求的东西。再者,敏捷软件架构要求综合全面地看待宏图愿景和“现在”,构建出一个每个人都能在上面添砖加瓦的可持续的平台。
|