突破架构瓶颈:克服软件系统中的漂移和侵蚀
一种常见但不完美的比喻是将软件系统中的架构漂移和侵蚀与物理建筑的架构相比。虽然这个比喻很直观,但它存在一个根本性的误解,这也常常引发软件开发中的架构问题。

试想一下,一个设计良好的摩天大楼或房屋建成后,我们期望它基本保持不变,顶多因为偶尔的现代化或扩建而发生变化。
令人惊讶的是,如今许多工程师(甚至可能是无意识地)将同样的逻辑套用到软件架构上:认为一旦系统架构设计完成,如果设计得当,它就不需要进一步修改,直到需求变化和遗留代码迫使进行大规模重写。
这是一个关键的误解。与物理结构不同,软件本质上是动态的,不断变化,需要定期更新以保持活力。一旦软件停止演变,就会开始衰亡。
此外,这种比喻通常强调软件系统的结构和行为,但忽略了同样重要的决策、权衡和妥协,这些因素共同塑造了架构。理解架构决策背后的原因对于未来的修改以及管理和演变软件架构至关重要。
本文旨在加深你对架构技术债务的理解,并强调有效管理架构漂移和侵蚀的关键因素。
架构技术债务概述
“在软件密集型系统中,技术债务指的是那些在短期内权宜的设计或实现,这些构造设置了一个技术背景,使得未来的变更更为昂贵甚至不可能。技术债务是一种或有负债,其影响主要限于系统内部质量,特别是可维护性和可演化性。” ——Avgeriou等人,2016年
技术债务总结了软件开发中过去决策和捷径累积的后果,包括低质量代码、缺失的文档和严重耦合等问题。这些问题可能源自多种原因,如战略性权衡或需求的意外变化等。
尽管许多工程团队记录了他们管理技术债务的策略——如谷歌和 ThoughtWorks 的做法——但关于特定类型的技术债务,即架构技术债(ADT),讨论较少。
ADT 源于系统设计过程中的有意或无意决策,导致维护性降低、复杂性增加、性能下降和可扩展性受限等问题。由于软件架构定义了系统的关键属性和约束,ADT 对系统演变及组织实现目标的能力构成重大风险。
ADT 是不可避免的,特别是在目标是快速交付和后续迭代时,有时甚至是必要的。因此,团队必须识别 ADT 并实施有效管理策略,以防止架构退化——即逐渐变得过时、不可靠,无法适应不断变化的业务需求或技术进步。
首先,关键的是在 ADT 的广泛范围内区分两个独特的现象:系统架构漂移和系统架构侵蚀。
架构漂移与架构侵蚀
架构漂移指的是在系统中引入不在原始架构计划中的设计决策,但这些决策并不一定会违反基础架构原则。架构侵蚀是指引入的新设计直接与系统的预期架构相冲突,破坏了系统的指导原则。
以建筑架构为比喻,架构漂移就像是建造一栋地中海风格的房子,然后添加一个哥特式的塔楼和一个后现代的扩建。这虽然导致了风格混杂(可能并不美观),但不会破坏结构的完整性。
在软件工程中,一个系统可能以干净的架构开始,但由于架构漂移,最终演变成包含多种架构范式、不一致编码实践、冗余组件和依赖项的复杂结构。

深入探讨架构漂移 by Vladi Stevanovic
另一方面,架构侵蚀类似于进行改造时破坏了房屋的结构完整性。例如,为了创建开放式布局而拆除承重墙却没有适当的支撑,或者在没有考虑原始墙体承重能力的情况下加建一层楼。
在软件架构中,架构侵蚀引入了违反系统基础原则和预期设计模式的行为,使系统变得脆弱,最终导致劣质架构,未来出现问题。
这些违规行为可能表现为紧密耦合的模块、绕过安全协议、忽略性能约束,或在无状态系统中引入有状态组件等。

DALL-E 对架构侵蚀的诠释
应对架构技术债务的策略
架构技术债务积累过多会导致架构全面退化。团队通常会采取两种策略之一:不断调整代码以应对突发问题,或者进行大规模重构。不幸的是,这两种策略常常失败,甚至可能加剧现有的技术债务。
调整代码通常只是表面解决方案。如果团队缺乏对系统架构的全面了解或对问题根源的理解,他们只能被动应对,这难以解决根本问题。
另一方面,即使是有意的重构——无论是渐进式还是一次性重构——如果不解决导致债务的根本原因,仍可能失败,技术债务也会再次出现。
最有效的方式是摒弃这些被动措施,转向整体的、主动的方法。在开发过程中整合持续的、前置的系统设计审查,使团队能够更持续地管理技术债务。例如,与其通过快速修复强行将新需求加到现有系统架构中,或不断替换遗留系统,不如采取更有效的方法,使系统设计始终包含新特性,然后无缝集成实际特性。
正如敏捷宣言的签署者之一、极限编程创始人 Kent Beck 所言:“对于每一个期望的变更,先让变更变得容易(警告:这可能很难),然后再进行容易的变更。”
架构恢复的可持续策略
许多团队误以为采用敏捷方法就能确保持续的系统设计审查,并防止架构技术债务的积累。然而,现实情况往往与这种期望存在差距。
敏捷团队注重频繁交付功能增量,可能无意中忽视了长期的架构完整性。快速交付模式还可能导致文档和设计不够清晰,使开发人员难以理解系统的整体架构及其组件的交互方式。这种疏忽会使系统维护和扩展越来越困难,最终导致技术债务的积累。
应对已累积的架构技术债务(ADT)并防止其进一步增加,需要采取以下关键步骤:
- 实施架构可观测性。首先,对现有架构进行彻底检查,了解应用程序在生产环境中的行为,并列出其最关键的问题。这一步对于评估系统设计的架构漂移程度至关重要。
- 现代化开发流程。架构漂移和侵蚀往往源于缺乏有效的流程,而不是缺乏技能。随着业务环境和软件需求的演变,缺乏系统化的方法来引入新变化以及处理团队成员的入职和离职,会使软件架构偏离其预期设计。制定系统设计、管理和文档的最佳实践,对于长期维护架构完整性至关重要。
最后的思考
在技术变革加速和竞争加剧的背景下,适应性是现代技术世界的关键。拥有一个积累了大量技术债务的复杂系统,就像是背负沉重的枷锁。在依赖关系和错误的迷宫中穿梭,使得适应变化的世界变得越来越困难,机会也因此流失。
从财务角度来看,修改负担沉重的架构债务系统的成本,总是高于那些经过深思熟虑的前期设计的系统。
虽然适量的技术债务是可管理的,并且可以通过战略性方法解决,但过度积累往往会导致系统瘫痪,带来重大挑战。
驾驭架构技术债务的复杂性,必须采取有意识且主动的策略。团队必须优先进行持续的架构评估,并整合强大的可观测性工具,以准确监控系统演变。此外,通过严格的设计、管理和文档实践来现代化开发流程,这对于维护系统的完整性和可扩展性至关重要。
管理技术债务的最有效方法是将软件变更和演化置于开发过程的核心。
相关文章:
突破架构瓶颈:克服软件系统中的漂移和侵蚀
一种常见但不完美的比喻是将软件系统中的架构漂移和侵蚀与物理建筑的架构相比。虽然这个比喻很直观,但它存在一个根本性的误解,这也常常引发软件开发中的架构问题。 试想一下,一个设计良好的摩天大楼或房屋建成后,我们期望它基本保…...
每日练题(py,c,cpp).6_19,6_20
检验素数 from math import sqrt a int(input("请输入一个数:")) for i in range(2,int(sqrt(a))):if a%i 0:print("该数不是素数")breakelse: print("该数是素数")# # 1既不是素数也不是合数 # #可以用flag做标志位 # b int(…...
居中显示-css样式
在微信小程序中,要让一个盒子(子元素)在另一个盒子(父元素)内部居中显示,可以使用以下几种方法: 1. 使用 Flex 布局 微信小程序支持使用类似于 CSS Flexbox 的布局方式。以下是使用 Flex 布局的…...
生骨肉冻干喂猫比较好?热门、口碑好、值得入手生骨肉冻干力荐
随着科学养猫的普及,生骨肉冻干喂养越来越受欢迎,生骨肉冻干喂养对猫的好处很多,它符合猫咪的天性,可以提供全面的营养,保持牙齿和牙龈的健康,还有助于维持健康的消化系统。虽然许多猫主人看到了生骨肉冻干…...
【安卓13 源码】RescueParty救援机制
RescueParty机制正是在这个背景下诞生的,当它注意到系统或系统核心组件陷入循环崩溃状态时,就会根据崩溃的程度执行不同的救援行动,以期望让设备恢复到正常使用的状态。 开机后会自动重启,进入Recovery界面。经查找,是…...
详细介绍iutils.dll丢失的多个解决方法,一键快速修复丢失的iutils.dll文件
当用户遭遇“iutils.dll缺失”的提示时,这通常预示着依赖该库文件的程序将面临启动失败或功能受限的风险。DLL(Dynamic Link Library,动态链接库)文件无疑占据了核心地位。这些文件就如同建筑师手中的蓝图,为软件的构建…...
基于SpringBoot+Vue的美容美发在线预约系统的设计与实现【附源码】
毕业设计(论文) 题目:基于SpringBootVue的美容美发在线预约系统的设计与实现 二级学院: 专业(方向): 班 级: 学 生: 指导教师ÿ…...
语言的数据结构:树与二叉树(二叉树篇)
语言的数据结构:树与二叉树(二叉树篇) 前言概念特别的二叉树满二叉树完全二叉树 存储结构顺序存储链式存储 查找方式 前言 上文说到了树,有人认为二叉树是树的每一个分支都有两个子节点。其实这也对。但二叉树在此基础上还做了限…...
若以框架学习(3),echarts结合后端数据展示,暂时完结。
前三天,参加毕业典礼,领毕业证,顿时感到空落落的失去感,没有工作,啥也没有,总感觉一辈子白活了。晚上ktv了一晚上,由于我不咋个唱歌,没心情,听哥几个唱了一晚上周杰伦&am…...
Spring Boot循环依赖(解决)
类与类之间的依赖关系形成了闭环,就会导致循环依赖问题的产生。举例来说,假设存在两个服务类A和服务类B,如果A通过依赖注入的方式引用了B,且B通过依赖注入的方式引用了A,那么A和B之间就存在循环依赖。 换成如下方法获…...
emqx4.4.3关于如何取消匿名登录,添加认证用户这件事
emqx4.4.3如何取消匿名登录,添加认证用户 emqx版本:4.4.3 背景:使用docker搭建完emqx后,使用 MQTTX 连接总是超时: 检查Java项目 是否有接口:https://XXXX:80/mqtt/auth? 若有,则具体逻辑查询…...
七天速通javaSE:第三天 程序控制结构:练习题
文章目录 前言一、基础1.计算从0~100之间奇数之和和偶数之和2. 用for循环输出0~1000之间能被5整除的数,每行输出三个 二、进阶1. 九九乘法表2.等边三角形 前言 本文主要讲解三种基本程序控制结构的练习题,以期熟练掌握顺序、选择、循环三种基本结构 一、…...
新增题目接口开发
文章目录 1.基本设计2.生成CRUD代码1.生成五张表的代码1.subject_info2.subject_brief3.subject_judge4.subject_multiple5.subject_radio 2.将所有的dao放到mapper文件夹3.将所有实体类使用lombok简化4.删除所有mapper的Param("pageable") Pageable pageable5.删除所…...
国内怎样使用GPT4 turbo
GPT是当前最为熟知的大模型,它优越的性能一直遥遥领先于其它一众厂商,然而如此优秀的AI在中国境内却是无法正常使用的。本文将告诉你4种使用gpt4的方法,让你突破限制顺利使用。 官方售价是20美元/月,40次提问/3小时,需…...
【语义分割】1-标注数据集-【单张图片】labelme标注json文件转mask
声明:我学习了b站:标注自己的语义分割数据集_哔哩哔哩_bilibili 并且复现了,记录了所思所得。 主要是说了: 做语义分割,数据集怎么用labelme标注成json文件,以及,json文件怎么转成mask 流程…...
c++: 理解编译器在背后所做的工作-工具篇
理解C模板以及编译器的优化是深入掌握C编程的重要部分。有一些其他工具和技术可以帮助你更好地理解编译器在背后所做的工作,特别是优化方面。以下是一些有用的工具和技术: 1. Compiler Explorer (Godbolt) Compiler Explorer 是一个非常流行的在线工具…...
Verilog HDL语法入门系列(三):Verilog的语言操作符规则(上)
目录 1.操作符优先级2.Verilog中的大小(size)与符号3.算术操作符4.按位操作符5.逻辑操作符6.逻辑反与位反的对比 微信公众号获取更多FPGA相关源码: 1.操作符优先级 下表以优先级顺序列出了Verilog操作符。 2.Verilog中的大小(size)与符号 Verilog根据表达式中变…...
IT营大地老师是谁,怎么什么都会?
很多学员都很好奇大地老师到底是谁,怎么什么都会?每过一段时间就会出一门新课程,涉足深耕不同的领域。经反馈常有童鞋私聊IT营官网客服咨询这个问题,也有很多人在b站大地老师的免费课程里私信,有好奇也有崇拜ÿ…...
【python013】pyinstaller打包PDF提取脚本为exe工具
1.在日常工作和学习中,遇到类似问题处理场景,如pdf文件核心内容截取,这里将文件打包成exe可执行文件,实现功能简便使用。 2.欢迎点赞、关注、批评、指正,互三走起来,小手动起来! 3.欢迎点赞、关…...
VUE div的右上角的角标/标签
一、效果图 二、代码 <div class"comp-overview"><div class"overview-item" v-for"(item,index) in overviewInfoList" :key"index"><div class"angle_mark"><span>{{item.label}}</span>&…...
CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...
