演化架构与紧急设计: 研究架构和设计

发现可维护性更好的设计和架构

软件架构和设计曾引起了大量热议,但是并没有产生太多新的亮点。本文将发起新一轮的有关软件架构和设计替代方法的讨论,并开启了这个 演化架构和紧急设计 系列文章。

演化架构(evolutionary architecture)和紧急设计(emergent design)都是将重要的决策推迟到最后责任时刻(Last Responsible Moment)的敏捷技术。在本系列的第一期文章中,系列作者 Neal Ford 将定义架构和设计,然后指明了一些关于整个系列的基本概念。

Neal Ford, 软件架构师/Meme Wrangler, ThoughtWorks Inc.

Neal FordNeal Ford 是一家全球性 IT 咨询公司 ThoughtWorks 的软件架构师和 Meme Wrangler。他的工作还包括设计和开发应用程序、教材、杂志文章、课件和视频/DVD 演示,而且他是各种技术书籍的作者或编辑,包括最近的新书 The Productive Programmer。他主要的工作重心是设计和构建大型企业应用程序。他还是全球开发人员会议上的国际知名演说家。请访问他的 Web 站点



2009 年 3 月 17 日

软件架构和设计一直都没有一个明确的定义,因为软件开发作为一门学科,尚未完全理解其中的复杂度和内涵。但是要发表关于这些主题的论述,您必须从某个位置开始。本系列涉及演化架构和紧急设计,因此将从一些定义、注意事项和其他基础设置入手。

关于本系列

系列 旨在从全新的视角来介绍经常讨论但是又难以理解的软件架构和设计概念。通过具体示例,Neal Ford 将帮助您在演化架构紧急设计 的灵活实践中打下坚实的基础。通过将重要的架构和设计决定推迟到最后责任时刻,您可以防止不必要的复杂度降低软件项目的质量。

定义架构

软件中的架构是开发人员谈论最多但是最难理解的概念之一。在会议中,关于架构的对话及相关讨论频繁出现,但是我们仍然只具有含糊的定义。在讨论架构时,我们实质上是在谈论几个不同但是相关的方面,这些方面通常可以划分为应用程序架构企业架构 这两个主要类别中。

应用程序架构

应用程序架构描述组成应用程序的主要部分。例如,在 Java 世界里,应用程序架构都描述两个内容:用于构建特定应用程序的框架组合 — 我称其为框架级架构— 以及更多传统的逻辑关注点分离,我一直称这些内容为应用程序架构。将框架架构作为一个独立部分,因为大多数面向对象语言的从业者已经发现单独的类不能实现良好的重用(您最后一次从 Internet 中下载一个单独的类以供某个项目使用是什么时候?)。目前面向对象语言中的重用部分都是库或框架。当您用提供丰富框架的语言(如 Java 语言)启动一个新项目时,首要的架构关注点之一就是应用程序的框架级架构。这种重用设计在 Java 世界中获得了巨大成功,以至于我已经开始认为我们应当停止把 Java 编程称为面向对象的语言,而应当称其为面向框架的语言。在许多方面,框架级架构代表特定构建块所描述的物理架构。

应用程序架构的另一个有趣的方面描述应用程序的逻辑部分如何整合在一起。这属于设计模式和其他结构描述的领域,并且因而趋向于更具抽象性和逻辑性,而非物理性。例如,您可以说 Web 应用程序遵循模型-视图-表示器(Model-View-Presenter)模式,而无需详细说明您使用哪个框架实现逻辑安排。这种逻辑安排是在开始处理应用程序的新组件时,最有可能增添到工作空间白板(whiteboard)中的内容之一。

企业架构

企业架构关注如何使企业作为一个整体(通常意味着在大型组织内运行的应用程序)来使用应用程序。关于企业架构与应用程序架构之间关系的常见比喻是把企业 比作城市规划,把应用程序 比作建筑结构。城市规划者必须考虑获得水、电、污水和其他服务才能使城市运转。一栋大楼使用的自来水不能多于提供给它的配额。企业架构需要为应用程序考虑同样的事情:您不可以允许一个应用程序使用所有网络带宽,而如果基础设施服务崩溃,就会出现大量问题。

企业架构在过去几年里得到了很多关注,这都是因为面向服务架构(Service-Oriented Architecture,SOA)。SOA 是一个独立的庞大主题,因此本系列未来几期文章将把它处理为特殊案例。它拥有自己的有趣方面,因为它在规定应用程序构造的特性时,它模糊了企业架构与应用程序架构之间的界限。

前面几段内容提供了这些重要概念的表面定义,但是它们可用作其他更有趣、更细致的架构定义的出发点(包括通过其他定义得到的一些定义)。

目前的定义

许多有才识的人都曾试着去定义软件架构,因此我将从他们的成果中获取一些思路。在 Martin Fowler 的经典白皮书 “Who Needs an Architect?”(请参阅 参考资料)中,他讨论了几种定义。他引用了 Extreme Programming 邮件列表中的第一个定义:

“制定 IEEE 定义的 RUP 把架构定义为 ‘环境中最高级别的系统概念。软件系统的架构(在特定时间点上)是通过接口交互的重要组件的组织或结构,这些组件由越来越小的组件和接口组成。’”

如上所述,此定义在应用程序架构领域内非常恰当。虽然有些含糊,但是它捕捉到了架构职责的本质:最高级别的概念。

Fowler 随后引用了 Ralph Johnson 的话,Ralph Johnson 在此邮件列表的回复中对前面的定义提出了争议 :

“更好的定义是:‘在大多数成功的软件项目中,从事该项目的专家开发人员对设计系统的设计存在共识。这种共识被称为 “架构”。这种共识包括如何将系统分为组件以及组件如何通过接口进行交互。’”

Johnson 提出了一个很好的观点,强调软件开发对通信的依赖要多于对技术的依赖,而该架构实际上代表关于系统的共识,而不是特定于语言、框架和其他短暂存在的技术。

在前述论文中,Fowler 自己提供了一个我最喜欢的架构定义:

“架构是重要的事物,无论它是什么。”

这个定义有一点含糊,但同时也是描述性的。关于架构和设计的许多争论都反复思考对于重点的误解。对于业务分析师和企业架构师来说,他们所认为的重要内容是有差别的。此定义很好地概况了这样一个概念:您必须在自己的环境中 定义术语,然后才可以尝试定义其他内容。

Fowler 的定义还强调了定义像架构一样微妙的内容的另一个重要方面。“重要内容” 不仅因个人和团体而异;这些差别实际上可能互相排斥。例如,实际上所有 SOA 都在灵活性与速度之间实现了一种平衡。现在使用的旧客户机/服务器系统的速度几乎肯定比取代它的基于 Web 的、portlet 式引擎、基于服务的版本快。除非新应用程序是由很糟的开发人员编写的,否则提供灵活性的附加层意味着用户的响应时间将增加,使响应变得更慢。架构师可能会对用户说,“顺便说一句,我们正在安装的新 SOA 内容将为我们提供更多好处,但是您的工作现在将花费更多时间,抱歉”。可能那就是架构师比开发人员工资高的原因。

然后就剩下我特别喜欢的架构定义:

“以后很难更改的内容。”

此定义最符合演化架构的概念。演化架构中的核心理念之一是尽可能晚地推迟决定,这将允许您替换在最近的体验中表现更好的其他方法。许多此样式的架构的构建块出现在整个系列文章中并且推动了本系列的创作。

在停止讨论架构之前,如果我不讨论 “架构师” 这个职位,就是不负责任。令人力资源部门苦恼的是这个行业中有这样一个定义糟糕的职位。许多组织都希望提拔它们最好的开发人员 — 对于以后很难更改的内容作出重要决定的人员 — 但是除了 “架构师” 之外都没有一个恰当的行业术语,也没有通用的职位描述,因此每家公司都定义此角色的含义。某些架构师类似于电影黑客帝国第二部结束时的 Architect(Fowler 将其归类为 Architectus Reloadus)。这些架构师最后一次编写代码是在十多年以前,而现在他们为您的公司作出重要决定。他们使用的惟一一个软件开发工具是 Visio。

另一种架构师角色是 Fowler 称为 Architectus Oryzus(以我的一个同事 David Rice 命名)的角色。这些架构师与处理最困难部分的其他开发人员合作,积极地向项目贡献代码。他们的职责还包括与其他项目相关人员交流以确保每个人都具有相同的看法、使用相同的定义并且理解他们需要理解的系统的各部分。很明显,这种活跃的角色对于实现演化架构目标至关重要,并因此将在本系列中反复出现。


定义设计

大多数开发人员都已经有非常好的设计感觉,因此我不会花大量时间来定义它。它表示如何整合一款软件的具体细节。它包括常见的设计模式、重构、框架和开发人员的其他日常关注点。设计大致包括 BDUF(Big Design Up Front)与 Cowboy Hacking 之间的内容,如图 1 所示:

图 1. 设计图
设计图

图 1 中的图谱左侧表示您可以预见在开发软件时出现的所有成百上千个关注点,并且尝试限制您对这些关注点的响应。您将在后续文章中阅读关于这句话的更多内容。因为我不花大量时间定义 设计并不表示我不会花大量时间讨论它。在编写第一行代码之前,本系列的大部分内容将介绍如何伴随着开发展开设计(而不是一成不变)的各个方面。


架构及设计关注点

比较演化突发

注意本系列称为演化架构和突发设计演化突发 为什么会有区别?我的同事向我指出,突发架构 不是一个热门的概念。如果您接受架构是以后难于更改的部分这一前提,就很难接受逐步显现的架构。架构关注在启动应用程序之前必须存在的基础元素。但是,仅仅因为您无法让架构显现并不意味着它无法演化。如果您已经创建了一个灵活的架构并且避免作出无法撤回的决定,则可以允许它在出现新关注点时演化。

使用手头现有的架构和设计定义,我希望深入研究一些全局性的关注点。所有这些主题都与基础级架构和设计有关,因此预先介绍这些内容使我可以在本系列后期文章中引用这些内容。首先,我将讨论技术债务(technical debt),然后讨论复杂度,最后讨论过度的一般性(rampant genericness)。

本金和利息

每位开发人员都开始注意到技术债务 的概念,您可能由此而迫于外部压力(例如日程压力)在设计中有所折衷。技术债务类似于信用卡债务:您目前没有足够的资金,因此您借用未来的资金。同样地,您的项目没有足够的时间来做正确的事情,因此您使用一种及时的解决方案并希望在未来的一段时间回过头来进行改进。糟糕的是,许多经理人似乎都不理解技术债务,因而不愿意重新完成旧的工作。

构建软件并不是挖壕沟。如果在挖沟时有所折衷,则只能得到一条宽度不均匀或者深度不同的沟。今天挖一条有缺陷的沟与您明天挖出一条好沟没什么关系。但是您今天构建的软件是您明天所构建内容的基础。现在为了权宜而做的折衷将导致软件中的 不断增大。在 The Pragmatic Programmer 一书中,Andy Hunt 和 Dave Thomas 谈到了软件中的熵以及为什么它有这样一种不利影响(请参阅 参考资料)。熵是复杂度的度量标准,而如果您现在由于某个问题的及时解决方案增加了复杂度,则必须在项目的剩余生命周期内为此付出一些代价。

假设您需要向一个现有的长期项目中添加新功能。这些新功能拥有某些内在的复杂度。但是,如果您已经拥有技术债务,则必须解决系统中那些折衷部分才能添加新功能。因此,添加新功能的成本将反映财务状况。图 2 显示了在设计简约的系统(例如,拥有很少技术债务或者没有技术债务的系统)中和在包含大量技术债务的典型系统中,在添加新功能所需付出的努力方面的差别。

图 2. 技术债务与利息
添加新功能所付出的努力

您可以把内在复杂度视为本金,而把前几条作为权宜之计的捷径所强加的额外工作视为利息。复杂度本身是一个非常有趣的题目。

本质复杂度与偶发复杂度的比较

我们在软件中解决的问题具有内在复杂度,我将它称为本质(essential)复杂度。由导致技术债务的折衷引发的复杂度是不同的。它包含使软件变得复杂的所有外部强加方法,并且这种复杂度是不应有的。我将此称为次要(accidental)复杂度。我在 The Productive Programmer 一书中定义并深入讨论了这些术语(请参阅 参考资料)。这些术语一般不是一成不变的:它们存在于图谱上,就像设计一样。一些示例将帮助阐明差别。

我的一个同事为一家具有工会组织的公司做过一个工资系统。该工会为其某些成员争取的一项福利是在狩猎季节开始时有一天额外的休假(嘿,他们一定是有优秀的谈判代表)。涉及的员工只在一家工厂工作,但是提供额外的休假是整个公司的工资系统的合法部分。这项更改给软件增添了许多复杂度,但是它是本质复杂度,因为它属于待解决的业务问题。

图谱中更远一点始终出现另一个示例:输入表单的字段级安全性。许多业务人员都认为 他们需要对每个字段的安全特性进行精细控制。实际上,一旦实现这样的精细控制,他们几乎总是很讨厌它,因为这将给需要定义和维护所有元数据的用户带来负担。我们的一个项目中的业务人员真的非常需要此功能,因此我们为他们在其中一个屏幕中实现了部分此功能。在他们粗略意识到使用此功能所需的工作后,他们决定,由于访问应用程序的惟一方法是通过一间上了锁的办公室,因此他们可以采用更粗粒度的安全性。这是一个在业务人员认识到他们认为必需的功能之后做出设计决定的优秀示例。

在图谱中朝向偶发复杂度的最远端是纯管道实践,如 Enterprise JavaBeans(EJB)技术的前两个版本和 BizTalk 等工具。许多项目都需要这些工具所引入的额外负载,但是它们只是给使用它们的大多数项目增加了复杂度而已。

有三个问题可能会产生偶发复杂度。我已经讨论了第一个:由于日程或其他外部压力而导致临时大量削减代码。第二个是复制,The Pragmatic Programmers 中称其为对 DRY(不要重复自己,Don't Repeat Yourself)原则的违背。复制是软件开发中最能让人在不知不觉中降低软件质量的因素,因为在开发人员甚至还没有觉察的情况下,它会蔓延到许多位置。一个明显的例子是复制并粘贴代码,但是也存在大量更复杂的示例。例如,几乎所有使用对象-关系映射工具(例如 Hibernate 或 iBatis)的项目都存在大量复制。您的数据库模式、XML 映射文件和后端 POJO 拥有略为不同但是重复的信息。通过创建这些信息的标准来源并生成其他部分有可能解决此问题。复制对项目有害,因为它不利于做出结构更改或重构为更好的代码。如果您知道需要在三处不同的位置更改某个内容,那么即使有利于代码的长期运行,也应该避免这样做。

偶发复杂度的第三个诱因是不可逆性。您做出的无法逆转的所有决定都将最终导致某种程度的偶发复杂度。不可逆性将同时影响架构和设计,尽管它的影响在架构级别上比较常见并且比较有破坏性。尽量避免作出不可逆转或者难于逆转的决定。我的一些同事信奉在最后责任一刻 做决定。这并不表示您应当把决定推迟得太久,只要推迟得比较久就足够了。什么时候才是决定某些架构关注点的最后责任时刻?做决定的时间拖得越晚,您为自己留下的可能性就越多。问问您自己:“我是不是现在就需要做那个决定?” 以及 “我做什么能让我推迟那个决定?” 如果在决策过程中应用一些技巧,那么您将会对您可以推迟的内容感到十分惊讶。

前面所述的框架级架构和应用程序架构之间的区别将与 “最后责任时刻” 原则联系起来。应用程序架构一般为逻辑架构。例如,假定您知道需要分离模型-视图-表示器(Model-View-Presenter)关注点。通常,通过选择满足部分或全部需求的框架,您可以成功实现这种逻辑架构的物理实现。看看您是否可以推迟该决定,因为一旦物理实现就位后,它将限制必须考虑的其他类型的决定。尽可能推迟框架决定将使您获得受实际情况影响较小的更好选择。

过度的一般性

架构与设计的最后一个全局关注点,我将之称为过度的一般性。Java 世界里似乎有一个弊病:通过尝试使解决方案尽可能一般化来过度设计解决方案。这样做的动机十分明显:如果我们构建许多扩展层,我们稍后可以在其上更轻松地构建更多层。但是,这是一个危险的陷阱。因为一般性将增加熵,所以您将破坏在项目初期中通过有趣的方式演化设计的能力。增加过多灵活性将使对代码库的每一次更改都变得更加复杂。

当然,您不可以忽略可扩展性。敏捷的移动性在决定如何实现功能时很重要:YAGNI(You Ain't Gonna Need It)。这是避免过度设计简单功能的信条。只实现目前需要的功能,在以后您需要更多功能时,可以再进行添加。我看到过某些 Java 项目为了实现一般性和可扩展性,在架构与设计方面使用了大量折衷,最后导致项目失败。这是个令人感到讽刺的教训,因为本来希望尽可能延长项目的生命周期,结果反而缩短了生命周期。了解如何把握可扩展性与过度设计之间的微妙界限十分困难,而且它是我将反复说到的主题。


路线图

本文包含大量文字叙述而没有源代码,因此不同于本系列中所有其他后续文章。讨论像架构与设计这样的复杂主题时,固有的问题之一就是必须具备可以确保所有人都在同一个情境中的上下文设置。我已经为本系列的其余文章打好了基础,我将在其中深入探究与演化架构和紧急设计相关的具体领域。每篇文章将深入探究这些概念中的一个或两个概念的特别说明,其中包含大量详细信息和源代码。下一部分:我将通过测试驱动开发(我已经将其命名为 测试驱动设计)讨论紧急设计。

参考资料

学习

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology
ArticleID=376320
ArticleTitle=演化架构与紧急设计: 研究架构和设计
publish-date=03172009