软件架构中10个反模式

发现常见的架构反模式,学习如何避免它们并克服设计陷阱!获得宝贵的见解、实用的建议和实际示例,以构建更好的软件架构并改进现有架构。

反模式1:货物崇拜
在不了解流程、技术或方法为何以及如何运作的情况下采用它们,以期望获得与榜样相同的好处。

什么是货物崇拜?
“货物崇拜 "的起源可以追溯到生活在太平洋岛屿上的原住民,这些岛屿在二战期间被用作空军基地。这些基地大多通过空投获得补给。军队离开这些岛屿后,货物补给停止了,原住民开始模仿士兵的行为,例如雕刻木制耳机,就像塔台工作人员戴的那样,或者在夜间点燃跑道上的灯。通过这种方式,他们希望通过他们认为的士兵和货机的精神实体再次获得补给。

在现代信息技术中,我们还能看到这种行为。货物崇拜 "描述的是在不了解最初决策背后的原因和动机的情况下,为了获得同样的利益而采用榜样所展示的行为。

一般来说,学习好榜样的想法并不是什么坏事,因为他们大多比后来的追随者投入了更多的时间和金钱。不加质疑地做事的过程常常被用作学习新工艺、新技术或新方法的起点,就像日本武术中的 "Shu Ha Ri舒哈日 "原理一样。

  • 第一阶段 "Shu "可译为 "跟随",意思是学习者完全模仿老师的行为而不提出质疑。
  • 接下来的阶段 "Ha "是 "脱离 "的意思,即学习者遇到了适应行为的界限,需要找到自己的方法来进一步提高自己的技能。
  • 最后一个阶段 "Ri "的意思大致是 "离开",在这个阶段,学习者完全可以找到自己的方法来解决问题,因为他们已经理解了所学行为的含义和界限。

只要 "Ha "和 "Ri "阶段是采用新流程、技术或方法的一部分,"Shu "阶段就很可能不会导致货物崇拜的负面影响。不幸的是,这两个阶段常常被忽视,结果就会出现我们称之为反模式的 "货物崇拜"。

有哪些例子?

  • 适用于一切的 BPMN 引擎
  • 使用不适合您公司的架构方法,例如使用微服务,因为某些榜样(例如 Netflix)正在使用它们
  • 仅仅因为它是当前的敏捷“趋势”,就实施不适合您公司文化的流程,例如 Spotify 模式

为什么会出现这种情况?

  • 采用新行为需要花费大量时间,以至于跳过了反思和适应阶段。
  • 当员工发现新行为不适合公司文化或面临的问题时,开始采用新行为并向管理层传达初步结果的员工可能会担心丢面子。
  • 管理层对员工缺乏信任。如果员工提出有关新行为的问题,他们将不会被倾听,或者只是害怕不被倾听。
  • 员工缺乏反映和适应新行为的必要技能,因此他们只是继续做榜样所宣传的事情

我们怎样才能避免陷入这种情况呢?

  • 当采用新行为时,需要从一开始就分配反思和适应的时间。这需要清楚地传达给相关利益相关者,以便他们了解额外的时间和初始行为可能发生的变化。
  • 创建一种可以提出和讨论有关任何缺点(包括新行为)的问题的文化。
  • 鼓励员工进一步学习,使他们能够反思和适应行为。

如果我们最终陷入困境,有什么建议可以摆脱这种情况?

  • 开始反映行为并使其适应您的特定需求
  • 利用从事“新事物”工作的人员的优势,使他们能够反思和适应新事物

反模式2:领域过敏
一些架构师只关注技术,并认为业务问题领域是其他人的无聊问题。

很多时候,认为该领域很无聊的看法是完全错误的。几乎每个现实生活中的问题都有一些独特的方面,需要非常具体的解决方案。如果没有架构师认为该领域值得他们关注,那么它的一些具有挑战性的方面就不会及早暴露和解决。出于自身考虑而选择的技术架构(例如,因为它为创建它的人们提供了智力刺激和有趣的工作日)将无法很好地匹配当前的实际业务问题。它可能

  • 比问题实际需要的更复杂
  • 包括该领域中不存在的问题的解决方案
  • 忽略一些领域挑战,因为它们被忽视了

架构师将无法与业务利益相关者进行良好沟通,因为他们不会说自己的语言。这会导致双方的误解和挫败感。

为什么会出现这种情况?
架构师通常都热衷于解决技术上有趣且具有挑战性的问题。他们想要使用尚未使用过一千次的东西,想要构建真正创新的东西,并探索新的、令人兴奋的选项来解决问题。有些项目似乎没有为架构师提供领域或业务挑战,因为他们缺乏对业务领域更深入的了解,因为他们认为业务领域很无聊。

我们怎样才能避免陷入这种情况呢?
对业务领域和语言(即通用语言UL)的透彻理解应该是每个从事系统工作的架构师的共同目标。这将不可避免地使他们成为以业务为导向的利益相关者和同事更好的沟通伙伴。它还可以帮助他们更早地识别和解决技术问题。它使他们能够构建符合实际需求而不是理论需求的解决方案。它还可能通过展示新的或尚未使用的技术可能在哪些领域带来新的机会来帮助推动业务领域的发展。因此,架构师应该与用户和利益相关者交谈、提出问题、阅读文档、查看现有系统,并总体上努力精通他们所使用的业务领域。

如果我们最终陷入困境,有什么建议可以摆脱这种情况?
最有可能的是,还会出现其他反模式的表现,例如

  • 情感依恋
  • 过度设计
  • 由于领域过敏而误用通用性。这些将需要对现有解决方案进行重大重构。

相比之下,领域过敏本身“只”需要架构师处理工作的方式改变想法。

反模式3:情感依恋
很多时候,参与软件开发的人们对他们所坚持的框架、编程语言或架构方法有一定的偏好,无论这些偏好是否能解决当前的问题。同样,人们可能会对某种解决方案产生情感依恋,因为他们过去曾与其他方法作过斗争,并且不想重复这种经历。或者反过来,人们可能会对解决方案产生情感依恋,因为团队提出了一个好的解决方案并希望坚持下去。这里的关键方面是情感部分,它导致坚持某些解决方案。

值得一提的是,情感纽带也有积极的一面。例如,对某人参与的产品的积极情感依恋通常会对产品本身产生积极影响。当出于情感原因做出决定而忽视客观事实时,就会出现问题。

有哪些例子?

  • 绑定到生态系统
  • 构建一个自制的框架而不是使用现有的框架
  • 坚持运作不佳的平台/决定
  • 开发人员将系统视为他们的“宝贝”
  • 基于过时技术的遗留系统
  • 重用众所周知但不适合新任务的软件

为什么会出现这种情况?

  • 害怕在改变架构方法或技术时丢面子
  • 害怕必须学习新东西
  • 某种类型解决方案的痛苦经历
  • 过度自信自己可以做得更好
  • 害怕扔掉现有的东西并“浪费”时间
  • 对某人的解决方案感到自豪并渴望得到赞赏
  • 无需评估具体解决方案即可对制造商深信不疑

我们怎样才能避免陷入这种情况呢?
  • 为做出的决策创建定期评估/审查循环
  • 创造安全空间以减少情绪方面
  • 使用客观/中立的评估模板
  • 获得中立意见
  • 澄清重新评估并不是个人贬低现有解决方案
  • 引入错误文化可以消除恐惧

如果我们最终陷入困境,有什么建议可以摆脱这种情况?
  • 同上(避免陷入这种情况)

反模式4:对基础设施的无知
忽略目标环境,例如硬件性能、网络拓扑和质量、(初始)设计或/和开发期间的功率限制。这会导致系统不满足要求,并且以后很难甚至不可能修复。

开发人员往往过于关注业务逻辑。基础设施是整个应用程序的重要组成部分,今天比以前更是如此。

  • 忽略网络延迟和物理布局可能会导致性能不佳。
  • 从一开始就应该尊重安全考虑。以后通常不可能重新设计。
  • 从一开始就应考虑法律因素,例如,每次从 Kafka 流中删除个人数据可能会很困难,以遵守当地法律 (GDPR)。
  • 盲目地使用云技术可能​​会导致安全和/或性能问题。例如,对所有通信进行加密将提高安全性,但会对性能和延迟产生负面影响。

有哪些例子?

  • 仅在开发人员笔记本电脑上开发自助服务终端系统,而不是在真实硬件上开发自助服务终端系统。
  • 盲目使用所有云基础设施,例如 istio.io
  • 尝试在速度较慢的 Intranet 上使用高度分布式的服务。

为什么会出现这种情况?

  • 过于关注开发代码,忽视目标环境
  • 盲目信任云技术等技术标准
  • 从某事开始,快速下手,希望以后能够解决这些其他问题
  • 解决目标环境为时已晚

我们怎样才能避免陷入这种情况呢?
  • 定义(重要的)质量目标和具体的质量场景
  • 在目标硬件(行走骨架)上尽早并定期进行测试/部署
  • 检查系统的每个组件是否符合法律限制,例如删除个人数据
  • 从一开始就规划部署,例如定义/测试通信路径
  • 寻求外部专家的帮助
  • 实现质量目标的策略和行动
    • 负载、性能和压力测试
    • 威胁建模
    • 混沌工程
    • ……

如果我们最终陷入困境,有什么建议可以摆脱这种情况?
  • 回顾、分析当前状态
  • 参见上文(定义质量目标、策略和行动)
  • (在基础设施方面重新考虑整个系统的模块化,然后可能重构组件。例如合并过于依赖的微服务,使得一个功能的执行总是涉及另一个服务。)

反模式5:恶性生长
恶性增长描述了软件系统不受控制/管理不善的增长,这导致系统难以维护且容易出错。

软件系统往往会随着时间的推移而增长。添加了新功能,集成了其他应用程序。应用程序变得更加复杂并且更难以推理。由于多个模块或应用程序的紧密耦合,更改和测试需要更多时间来实施。错误更容易被忽视。

有哪些例子?

  • 大泥球
  • 毒液

为什么会出现这种情况?

  • 缺乏规划和协调,增长不受控制
  • 当问题通过临时解决方案解决时
  • 对原有架构改动太少

当设计人员面临选择是从头开始构建优雅的东西,还是破坏现有系统的架构以快速解决问题时,架构通常会失败。零碎成长
  • 缺乏架构和技术知识/经验

在软件世界中,我们在生命周期的早期部署最熟练、最有经验的人员。后来,当资源稀缺时,维护工作就交给了初级员工。所谓的维护阶段是生命周期中真正为总体规划的虚构付出代价的部分。维护程序员需要承担起应对固定设计和不断变化的世界之间不断扩大的分歧的重担。如果架构洞察力在生命周期后期出现的假设是正确的,那么应该重新考虑这种做法。零碎成长
  • 时间压力
  • 低预算

我们怎样才能避免陷入这种情况呢?
一定量的前期规划和设计不仅重要,而且不可避免的零碎增长

  • 定期进行内部和外部审查,以获得有关您的系统的反馈
  • 使用反馈来引导系统的发展
  • 以可管理的小块进行改变零碎增长
  • 持续集成
  • 没有单独的开发和维护团队
  • 重构,只要需要就重构

如果我们最终陷入困境,有什么建议可以摆脱这种情况?
  • 整合与重构
  • 获得外部架构审查,应用改进建议
  • 重新起家

反模式6:通用性的误用
构建一个过于通用的解决方案,最终无法令任何人满意。

我们有一个非常具体的问题要解决,而我们并不只想解决这个非常具体的问题。那样就太简单了。我们要解决的是我们的问题所属的整个问题类别。这样,我们今后的生活就会更轻松。我们对第一个问题的通用解决方案可以解决该问题类的所有未来问题。我们只需要在这里写一个小小的扩展点,或者在那里添加一些配置,我们未来的问题就会迎刃而解。

不幸的是,事情并非如此,我们最终会得到一个非常糟糕的具体问题解决方案,以及一个非常糟糕的未来问题解决方案。当我们看研究人员是如何找到一类问题的通用解决方案时,我们就会发现,他们总是能找到一种特定的解决方案,而这种解决方案只适用于一个特定的问题。如果这个办法行得通,他们就会尝试用这个办法来解决同一类问题中的另一个问题。

如果成功了,通常在经过几次调整后,他们会发现解决方案的空间更加清晰了。但他们仍会找到该问题类别中的另一个问题,并尝试解决它,直到他们知道自己找到了通用解决方案。

在软件领域,我们经常听到 "先使用后重用 "的说法,其原理与此类似。首先要解决非常具体的问题,如果这个问题能够解决,你就可以考虑可能可以重复使用的解决方案。

如果我们过早地从一个我们还不完全了解的问题的通用解决方案开始,我们就不可能找到一个好的解决方案。

有哪些例子?

  • 适用于 100 多个国家/地区的金融公司应用程序产品线
  • 物流中的超通用框架
  • 全球电子商务 B2B 服务
  • 12 种保险产品的通用产品模型
  • 使用跨平台框架进行应用程序开发

为什么会出现这种情况?
利益相关者也有同样的想法:“我们可以以通用的方式构建此功能,以便我们将来可以在其他地方轻松地重用它”。一次解决问题,然后在各处重复使用该解决方案。一些利益相关者,例如项目经理,看到了节省未来成本的机会。其他利益相关者,例如开发人员,喜欢解决非常困难的问题。要开始通用开发的螺旋式下降,您只需要一个利益相关者要求通用解决方案。很难说服支持这个想法的利益相关者放弃它,因为收益很容易被高估,而成本很容易被低估。

我们怎样才能避免陷入这种情况呢?
当开发人员要求利益相关者实施这样的通用解决方案时,我们需要解释为什么通用解决方案从一开始就不起作用,并与科学家如何找到通用解决方案相关。开发人员还需要意识到,这些利益相关者会感到失望,并可能会寻找其他只会说“是的,我们可以为您做到这一点”的人。因此,开发人员需要真正令人信服。相反,开发人员应该始终首先解决具体问题。每当同一问题类别的类似问题再次出现时,开发人员需要仔细考虑是否应该采用通用解决方案。有时,实施通用解决方案从来都不是至关重要的,因为它要么不存在,要么实施和维护成本太高。

如果我们最终陷入困境,有什么建议可以摆脱这种情况?
当解决方案过于通用时,使其更加具体就至关重要。这可以通过从头开始开发一个全新且非常具体的解决方案(重写)或找到中间解决方案来完成。大多数情况下,完全重写是一个糟糕的主意,请参阅“你不应该做的事情”或“大重写”。我们需要小心重写,因为重写的努力几乎总是被完全低估。如果您的通用解决方案有许多客户,并且您需要为每个客户进行许多特定的重写,那么成本会变得更加昂贵。重写的另一个问题是你的利益相关者可能不再信任你。您已经在一个不起作用的解决方案上花费了大量资金,现在您再次需要大量时间和金钱来解决您最初造成的问题。

利益相关者通常希望很快看到结果,因此了解您最大的问题并首先解决它至关重要。然后是第二大问题,依此类推。构建一个原型来验证您的改进想法,然后对其进行扩展。

良好的软件工程实践对您有好处:增量和迭代地改进和交付。增量工作而不深入思考你的架构可能会导致你把自己困在一个不容易退出的角落。因此,只有在您对整体解决方案有了很好的了解后才开始改进。换句话说:做经典的建筑工作。

反模式7:永远不要改变正在运行的系统
“永远不要改变正在运行的系统”——可能每个从事 IT 工作的人都听过这句话。当存在“系统”并且没有人因为担心破坏某些东西而愿意触碰它时,这可能是一种反模式。
该系统要么非常关键,要么非常复杂,因此人们担心在改变它时会破坏它。通常,人们甚至不愿意尝试改变系统,这导致了头脑的垄断,甚至没有人能够再改变系统。人们也经常开始围绕系统工作并在其周围包装额外的层以添加功能甚至修复错误。这会导致一个更加脆弱和复杂的系统。

没有人愿意在这样的系统上工作,很难找到开发人员,也很难激励他们。那么问题将会进一步加剧。当软件不再定期维护和更新时,库和框架很快就会过时,并且它们将不再与周围的系统很好地配合。其他系统元素将开始围绕它建立反腐败层。这通常会导致重新实现/逆向工程系统。由于缺乏信息,这会带来高昂的成本,甚至更高的风险。

为什么会出现这种情况?

  • 当项目团队开发了一个复杂的系统,然后交给不属于开发过程的维护团队时(通常不会发生良好的交接,因为文档和交接通常是在截止日期到来时被取消的第一个工作步骤)关闭)
  • 该系统被忽视,因为它已经完成并启动并运行,一段时间后就没有人觉得有责任了,也没有人认为有必要维护该系统
  • 对于决策者来说,头部垄断的风险并不像节省金钱和时间那么重要

我们怎样才能避免陷入这种情况呢?

  • 编写单元测试并为您的软件提供持续集成环境,这样您就可以减少对破坏系统的恐惧
  • 仔细记录。
  • 实施和维护测试用例
  • 定期更新技术和知识
  • 让您的持续交付系统检查过时的库和存在安全风险的库
  • 当开发团队和维护团队不重叠时,有一个移交的概念,或者尝试根本不让不同的团队进行开发和维护(“你构建它 - 你运行它”)
  • 请注意,整个系统中没有任何部分是不重要的
  • 定期选择一个随机遗留系统,该系统必须实施最小的更改并将其投入生产。

如果我们最终陷入困境,有什么建议可以摆脱这种情况?
通常首选的方法是重新实施系统。但由于巨大的成本和风险,这并不是理想的解决方案。采取如此激进的步骤可能是有原因的,例如,如果环境像主机环境上的 COBOL 代码一样过时,但大多数时候最好保持系统的活力和运行。更好的方法是简单地命名但很难做到。开始减少技术债务。为此,以下步骤通常很有用:

  • 实施测试或完成测试。从最重要的开始。这让人们对整个系统更有信心。该测试甚至可以用来证明所实现的功能和副作用的不确定性。有时测试不再起作用 - 重新访问它们并更新。
  • 弥补文档中的空白。甚至记录您(尚)不知道的内容。
  • 更新库,找到死的过时代码 - 删除它。
  • 重构代码等
  • 开始改变系统,尝试实施移动测试并建立持续集成环境
  • 鼓励人们在系统上工作
  • 停止围绕系统构建包装器以避免更改它

反模式8:过度设计
系统具有不必要的复杂架构。更简单的方法足以满足业务需求。
建筑师可能倾向于为新项目选择尖端技术、框架和概念。这可能会导致以下问题:

  • 开发人员必须花费更多的时间和脑力来理解所使用的概念和技术。因此,开发可能需要更长的时间。
  • 逻辑相关的代码可能会分散在多个文件或模块中。额外的框架特定代码可能会降低可读性。这使得代码更难推理、更脆弱并且更难维护。

为什么会出现这种情况?
许多开发人员对新概念、技术和框架有着天生的好奇心。他们喜欢尝试这些方法而不考虑它们是否适合问题领域。此外,一些开发人员过早地优化软件:

  • 灵活性:例如过于通用的软件误用通用性
  • 可扩展性:例如构建框架而不是单一解决方案
  • 可扩展性:例如,在没有电流需求的情况下针对高负载进行优化

其他开发人员也需要做出改变。他们不想一遍又一遍地使用相同的方法。他们希望获得新技术的实践经验。

我们怎样才能避免陷入这种情况呢?

  • 使解决方案尽可能简单 (KISS)
  • 仅在需要时进行优化
  • 仅针对当前需求实施,而不是针对未来可能的需求
  • 审核要求,解决企业实际问题
  • 使用创新代币

有时问题不需要用软件来解决。相反,业务流程可以改变。

如果我们最终陷入困境,有什么建议可以摆脱这种情况?

  • 重构或重写应用程序。

反模式9:过度模块化
软件项目在运行时(例如服务、lambda)或构建时(例如Maven 模块、包)被分为多个模块。与模块化为更少的模块相比,这种模块化具有缺点。它增加了不必要的复杂性。因此,项目对于开发人员来说变得更难理解和调试。过度模块化可能会导致编译时间更长。此外,与模块化为更少的模块相比,运行时的性能可能较差。

有哪些例子?

  • 将 Maven 模块或包剪得太小。
  • 将微服务/纳米服务/lambda 削减得太小。
  • 模块的时间耦合。
  • 将结帐系统拆分为太多服务

为什么会出现这种情况?
  • 过早优化:
    • 开发人员计划进行未来理想的模块化。
    • 这些模块应该在未来提供更好的构建性能/可维护性/可测试性。
    • 这些模块将来可以重复使用。
    • 开发者担心以后进一步的模块化会变得困难。
  • 货物崇拜:开发人员将软件模块化,因为其他人不明白为什么这样做。
  • 开发人员在模块化不足方面经历了糟糕的经历。
  • 开发人员缺乏领域知识。

我们怎样才能避免陷入这种情况呢?
  • 尝试使用一些有意义的模块来强调重要的架构边界。通过有界上下文来切割模块。(DDD 领域驱动设计)
  • 定期重新评估项目的模块化并相应地重构您的软件。早点开始,晚点就更难了。

如果我们最终陷入困境,有什么建议可以摆脱这种情况?
  • 根据当前的问题和经验制定目标模块化。与当前的模块化进行比较。
  • 基于模块化评估的重构模块。
  • 合并通常一起部署/修改的模块。

反模式10:模块化不足
软件系统在运行时(例如容器)或构建时(例如Maven模块、包)被划分为太少的模块。与模块化为更多模块相比,这种模块化有缺点。这些模块太大,因此难以理解、维护、保护、部署和水平扩展。由于代码中缺少边界,不同功能之间的依赖性和数据共享的风险增加。

为什么会出现这种情况?

  • 旧的项目结构。例如某些 COBOL 应用程序。
  • 不受控制的软件增长。
  • 时间压力。
  • 低预算。
  • 组织问题:整体模块化没有明确的愿景。
  • 缺乏架构和技术知识/经验。
  • 缺乏领域知识。

我们怎样才能避免陷入这种情况呢?
  • 创建强调重要架构边界的有意义的模块。通过有界上下文来切割模块。(领域驱动设计)
  • 定期进行内部和外部审查,以便尽早获得模块化的反馈。
  • 一旦您从中受益,就将您的项目拆分为更多模块。例如,当:
    • 代码被重用
    • 代码由另一个团队维护
    • 构建/运行时性能不足
    • 模块变得太大/太复杂

如果我们最终陷入困境,有什么建议可以摆脱这种情况?
  • 根据模块化审查拆分模块。
  • 重写或重构系统或其部分。