从 Windows Forms 到微服务的经验教训
Photo by Dan Counsell on Unsplash
如果说软件开发中有什么不变的东西,那就是变化。
在 .NET 生态系统中摸爬滚打的这二十年里,我见证了各种框架的起起落落,目睹了容器化的崛起,也曾为微服务架构摇旗呐喊——而在几年前,微服务对许多人来说还只是晦涩难懂的概念。
然而,如果说这个领域还有另一件永恒的事,那就是技术债务——它始终存在,而且如果不加以管理,就会从一个微不足道的小麻烦演变成巨大的绊脚石。
幸运的是(或者不幸,取决于你怎么看),我几乎经历过所有形式的技术债务。我最早接触的是 Windows Forms 项目,当时这可是最前沿的技术。后来,我开始构建 ASP.NET MVC 的单体应用,接着又挑战过连接到庞大单体数据库的嵌入式软件,之后将旧的单体架构迁移到微服务,最终踏上了可扩展分布式系统的架构之路。
在每个阶段,技术债务的表现形式都在变化,但其根本原因和解决方案却惊人地一致:短视的决策、忽视最佳实践、赶工期,以及缺乏清晰的沟通——这些因素共同推动了一个恶性循环,如果不加遏制,即便是最优秀的团队也会被拖垮。
在这里,我想分享自己这些年来积累的个人见解:技术债务是如何产生的,如何尽早识别它,以及如何在不影响新功能开发进度的前提下加以应对。
早期岁月
刚开始接触 .NET 时,我在做 Windows Forms 应用开发。
那时候很激动,因为我们正在从 VB5、VB6 和 Visual J++ 这些老旧的客户端-服务器技术,过渡到(在当时看来)更现代化的 C# 环境。Windows Forms 带来的 UI 可能性让我们兴奋不已——拖拽控件、窗体事件、集成数据库连接,相比于老框架而言,这一切都让人觉得是一次革命。
但这些项目也让我第一次接触到了技术债务的问题。那时候,我们很容易把所有东西都写在同一个项目文件里,或者只用少量几个解决方案。
每个窗体都带着超大的代码隐藏文件,逻辑和 UI 交织在一起,改动一个地方就可能弄崩六七个功能。到处可见所谓的“上帝类”(God class),它们从数据访问到业务逻辑再到 UI 渲染,什么都管。
我们甚至会直接在按钮点击事件里写数据访问代码。随着时间推移,这些代码隐藏文件变得越来越庞大,调试它们简直就是一场噩梦。
我们是走一步算一步地写代码,只关注当下能跑起来,而不是为未来的扩展做好规划。
起初,这种技术债务是隐形的,毕竟所有东西都集中在一起。但一旦应用需要扩展,我们就得重写大量代码才能适应新的需求。这是我第一次真正意识到,在软件开发中短视思维会带来怎样的后果。
Photo by Glenn Carstens-Peters on Unsplash
Web 开发的崛起
最终,世界大规模转向了 Web,.NET 开发者也随之跟进。ASP.NET Web Forms,后来又有了 ASP.NET MVC,成为了新的前沿技术。
有了 MVC,我们终于有了一个结构化的方法,可以更好地组织代码。但那些从 Windows Forms 过渡过来的开发者很快发现,仅仅遵循 MVC 模式并不能保证代码的整洁。
我们仍然需要严格执行这些分离。
尽管 MVC 结构天然地引导开发者实现更好的关注点分离,但当需要添加重大新功能或适应新的业务需求时,大型单体应用依然会遇到问题。
在压力之下,人们往往会忍不住把控制器和业务逻辑混在一起,让数据访问层变得混乱不堪。一旦应用需要集成一个新系统——比如第三方 API 或新的支付网关——我们就会发现,现有的数据模型和控制器根本不够灵活。
结果呢?仓促的补丁,让本就不堪重负的代码库变得更加复杂。
那个时候,我意识到了一些至关重要的道理。
首先,采用像 MVC 或者 MVVM 这样的成熟模式,并不能自动消除技术债务。
其次,真正的关键在于持之以恒的纪律——定期回顾代码,重构、优化,保持模块化。
第三,如果不腾出时间去重构和维护代码库,你积累的技术债务会很快超过你赶工开发所节省的时间。
转向嵌入式系统和大型数据库
在我的职业旅途中,有一段时间我开始接触嵌入式系统,而这些系统仍然依赖于庞大的单体数据库。这种环境带来了独特的挑战,尤其是在性能和资源限制方面。
Web 应用可以把部分任务卸载出去,或者通过增加服务器实现横向扩展,但嵌入式系统的硬件资源有限,因此必须精打细算地进行优化。
在这些项目中,技术债务往往表现为性能债务。为了赶工期,我们会在编写高效代码时偷懒,结果就是,等到设备在真实场景下运行时,整个系统慢得像乌龟爬。
另一个大问题是,整个应用(包括业务逻辑,甚至 UI 逻辑)都和数据库模式紧紧耦合在一起。改动一个表结构,就意味着要重写大量代码。数据迁移和版本管理变成了一场噩梦,而一个小小的失误就可能让整个系统彻底崩溃。
我对这些经历记忆犹新,它让我深刻体会到了在数据层保持模块化设计的重要性。
即使你没有在构建微服务架构,把数据库模式和应用逻辑解耦,依然能带来极大的好处。
使用存储过程、合理的数据访问层,以及精心设计的数据契约,可以帮助你避免层层连锁的崩溃。简单来说,关注点分离的原则,在数据层和应用层同样重要。
Photo by cottonbro studio
从单体架构迁移到微服务
当微服务开始流行时,我开始参与一些项目,目标是把大型单体应用拆分成更小、更容易独立部署的服务。
这是我职业生涯中最具启发性的经历之一。微服务架构承诺解决很多问题——可扩展性、可维护性,以及更快的发布周期。但它同时也带来了新的复杂性,从分布式系统管理,到服务间通信和数据一致性问题。
事实证明,在微服务环境中积累技术债务,跟在单体架构里一样容易。事实上,如果没有强大的 DevOps 实践、自动化测试,以及清晰的服务边界,风险甚至更高。
此外,我见过一些团队只是“表面上”在做微服务,每个服务仍然严重依赖共享数据库或一堆共享的数据模型库。这种“半解耦”很快就会导致一张纠缠不清的依赖网,讽刺的是,这样的系统可能比原来的单体架构还难维护。
尽管存在潜在的陷阱,但如果做得正确,微服务架构确实能带来真正的优势。把应用拆分成更小、更清晰定义的服务,可以减少变更和故障的影响范围。如果某个服务崩了,它可能不会拖垮整个系统。
从技术债务的角度来看,你可以单独重构或重新设计某个服务,而不会影响整个应用。关键在于严谨的规划——明确边界,确保服务是真正独立的,并且在监控、日志记录和 CI/CD 基础设施上做好投资。
扩展架构与云原生方法
近年来,我一直在深入参与基于 Azure 和 AWS 构建可扩展的云原生解决方案(尽管我个人更偏向 Azure,因为它与 .NET 的集成更流畅)。
如今的解决方案通常涉及 Docker 容器化,以及 Kubernetes 编排,或者按需启动的无服务器函数。这种环境提供了前所未有的灵活性,但同时也要求有严格的架构纪律,以防技术债务悄然积累。
在基础设施层面控制技术债务,最大的帮手之一就是 IaC(基础设施即代码)工具,比如 Terraform 或 Azure Resource Manager (ARM) 模板。显然,通过像管理代码版本一样管理基础设施版本,你可以跟踪变更,必要时轻松回滚,并确保开发、测试和生产环境的一致性。
微服务和云原生架构的复杂性意味着你需要高级的监控能力。像 Azure Insights、ELK 和 Grafana 这样的工具,能让你在问题变成紧急情况之前先发现它们。这种前置性的洞察可以大大降低大规模技术债务的可能性,因为你能更早发现性能瓶颈或架构上的反模式。
此外,记住云原生解决方案是自然演进的。
当你启动一个新容器,或者改变函数应用的触发方式时,你其实是在迭代架构。只要有意为之——监控使用情况、衡量性能、收集反馈并相应调整,你就能控制住技术债务。
及早识别技术债务
我在职业生涯中学到的最宝贵的经验之一就是:及早发现技术债务,等于赢了一半。 早一点修复 bug 或重构代码,成本永远比晚了再处理要低。但在紧迫的截止日期和不断增加的功能需求下,怎么才能察觉到技术债务正在悄悄堆积?
• 代码异味(Code Smells):这些都是明显的红旗,比如超长的方法、重复的逻辑、魔法字符串,或者那些无所不包的类。
• 新成员上手缓慢:如果新开发者需要很长时间才能理解代码库,或者经常在复杂的依赖关系中迷失,那基本可以确定系统存在架构问题。
• 回归 bug 过多:如果每次新增功能都会无缘无故弄崩其他地方,那很可能是隐藏的耦合问题,或者代码本身过于脆弱。
• 总是在救火:如果你们的日常工作一直是在修复线上问题,而不是主动优化系统,那就是技术债务积累过多的警告信号。
留意这些迹象。它们是你的早期预警系统,提醒你需要腾出时间来重构、优化测试,或者甚至重新设计某些组件。
Photo by Tara Winstead
应对技术债务的策略
管理技术债务,既是技术问题,也是思维方式的问题。我想分享一些对我来说行之有效的核心策略。
我发现一个有效的做法是,在每个 Sprint 或发布周期里专门留出时间处理技术债务任务。这样可以确保你在交付新功能的同时,也能逐步削减旧有问题。而自动化测试能让你放心地进行重构,而不会破坏已有功能。
再配合持续集成(CI)流水线,每次提交代码时都会自动触发测试,这样可以尽早发现问题。
另外,记住要践行“童子军原则”(Boy Scout Rule)!这是从 Robert C. Martin 那里借来的一个原则,它的意思是:每次修改代码时,都应该让代码库比你接手时更干净一点。即使是微小的改进,长期积累下来也会产生巨大的变化。
同时,使用 Architecture Decision Record (ADR) 或者 Wiki 页面,记录下为什么做了某些架构决策。这些背景信息可以防止未来的团队重蹈覆辙。
最后一个建议是:向非技术人员传达忽视技术债务的代价。他们不需要了解所有细节,但应该明白,如果不解决技术债务,未来的成本会更高,交付速度也会变慢。
真实案例
让我分享几个(已经脱敏处理的)故事,展示技术债务如何在不同的管理方式下,决定一个项目的成败。
有一次,我参与了一个大型 ASP.NET MVC 应用的重构,这个应用经过多年开发,变得越来越难以维护。我们决定来一次彻底大重构,把它拆成微服务。
但我们没有明确的服务边界,没有合理的 DevOps 策略,也没有系统化的测试方案。
结果?项目被拖延了一整年,成本超支,团队士气低落。最终,我们不得不推倒重来,回到单体架构,然后以业务能力为指导,逐步拆分成一个个独立的微服务。
另一边,在另一个项目里,我们面对的是一个每天都在运行的关键业务系统。我们没有一上来就推倒重写,而是先识别出最棘手的模块,并优先把它们拆分成独立服务。我们为这些新服务搭建了 CI/CD 流水线,并逐步引入现代架构设计。
在一年时间里,我们成功将整个系统的 50% 迁移到微服务架构,而没有影响日常业务运行。每次小步前进,都给团队带来了信心,也让企业感到安心。
这两个案例形成了鲜明对比,揭示了一个核心道理:应对技术债务,需要有明确的计划,并且要让所有人理解你的策略。
一刀切的大重构听起来很有吸引力,但往往会因其复杂性而失败。
最终思考
从 Windows Forms 应用,到 MVC 单体架构,再到带着庞大数据库的嵌入式系统,最终到云上的现代微服务架构,我深刻认识到:技术债务是软件开发生命周期中无法避免的一部分。
问题不是你会不会积累技术债务,而是你能多快意识到它的存在,以及你是否有足够的纪律去应对它。
从我的经验来看,.NET 生态系统提供了丰富的工具和框架,来帮助我们提高代码质量和架构规范。从高层次的设计模式,到自动化测试框架,我们拥有足够的手段来将技术债务控制在可接受的范围内。
从 Windows Forms 到微服务的经验教训的更多相关文章
- 放弃Dubbo,选择最流行的Spring Cloud微服务架构实践与经验总结
http://developer.51cto.com/art/201710/554633.htm Spring Cloud 在国内中小型公司能用起来吗?从 2016 年初一直到现在,我们在这条路上已经 ...
- 微服务架构-选择Spring Cloud,放弃Dubbo
Spring Cloud 在国内中小型公司能用起来吗?从 2016 年初一直到现在,我们在这条路上已经走了一年多. 在使用 Spring Cloud 之前,我们对微服务实践是没有太多的体会和经验的.从 ...
- centos7下docker发布第一个微服务应用(Eureka)
1.在windows下打包 微服务应用通过maven进行打包,在项目的pom.xml执行mvn clean package,或者直接通过idea或者eclipse进行maven打包 之上操作将在项目的 ...
- Martin Fowler谈微服务的优缺点
很多开发团队已经认识到微服务架构比单体架构更优越.但是也有其他团队感觉到这是一种消弱生产力的负担.就像任何软件架构,微服务架构同样有利弊.为了能做出一个明智的选择,你必须了解这些应用并将它们运用到你特 ...
- Spring Cloud 微服务的那点事
什么是微服务 微服务的概念源于2014年3月Martin Fowler所写的一篇文章“Microservices”. 微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调 ...
- Spring Cloud与微服务构建:Spring Cloud简介
Spring Cloud简介 微服务因该具备的功能 微服务可以拆分为"微"和"服务"二字."微"即小的意思,那到底多小才算"微&q ...
- Spring Cloud及微服务简介
最近在看微服务编排的东西,看到一篇入门博客,私以为不错,再次分享下:https://blog.csdn.net/w05980598/article/details/79007194 什么是微服务 微服 ...
- [跨数据库、微服务] FreeSql 分布式事务 TCC/Saga 编排重要性
前言 FreeSql 支持 MySql/SqlServer/PostgreSQL/Oracle/Sqlite/Firebird/达梦/Gbase/神通/人大金仓/翰高/Clickhouse/MsAcc ...
- google和ebay微服务经验
摘自:http://www.infoq.com/cn/articles/ecosystems-of-microservices 多元化(polyglot)微服务是终极游戏 大规模系统和多元化微服务最终 ...
- 【微服务No.1】Consul服务发现在windows下简单使用
基本介绍: 安装: 下载地址:https://www.consul.io/downloads.html 运行: consul agent -dev 显示这个界面说明已经开启成功. 页面显示: 然后访问 ...
随机推荐
- 03C++顺序结构(2)
一.变量.赋值语句与表达式 1.天安门广场在北京市中心,它南北长880米,东西宽500米,试编一程序,计算天安门广场面积是多少平方米. 点击查看代码 1 //试编程,计算天安门广场的面积是多少平方米 ...
- 1.mysql部署文档
部署方式 1.docker上面部署 ● 部署最新版本 docker pull mysql:latest # 拉取最新版本的镜像文件 docker images # 查看已经安装的镜像文件 docker ...
- Qt/C++编写跨平台的推流工具(支持win/linux/mac/嵌入式linux/安卓等)
一.前言 跨平台的推流工具当属OBS最牛逼,功能也是最强大的,唯一的遗憾就是多路推流需要用到插件,而且CPU占用比较高,默认OBS的规则是将对应画布中的视频画面和设定的音频一起重新编码再推流,意味着肯 ...
- 【OpenGL ES】GLSL基础语法
1 前言 本文将介绍 GLSL 中数据类型.数组.结构体.宏.运算符.向量运算.矩阵运算.函数.流程控制.精度限定符.变量限定符(in.out.inout).函数参数限定符等内容,另外提供了一个 ...
- 深度解析Mamba与状态空间模型:一图带你轻松入门
1.概述 Transformer架构无疑是大型语言模型(LLMs)成功背后的核心动力.从开源的Mistral到封闭的ChatGPT,几乎所有主流的LLM都在使用这一架构.然而,随着技术的不断进步,研究 ...
- 阿里IM技术分享(八):深度解密钉钉即时消息服务DTIM的技术设计
本文引用自InfoQ社区"5亿用户如何高效沟通?钉钉首次对外揭秘即时消息服务DTIM"一文,作者陈万红等.策划褚杏娟,有修订和改动. 一.引言 本文是国内企业IM的事实王者钉钉首次 ...
- Python 抽象基类 ABC :从实践到优雅
今天我们来聊聊 Python 中的抽象基类(Abstract Base Class,简称 ABC).虽然这个概念在 Python 中已经存在很久了,但在日常开发中,很多人可能用得并不多,或者用得不够优 ...
- 内华达大地测量实验室GNSS数据tenv3格式下载
GNSS时序形变位移数据下载 引言 下载方式 注意事项 引言 目的:教大家如何下载GNSS时序形变位移数据,本人主要是利用GNSS位移结果进行InSAR相关成果的精度验证工作.若大家需要在自己的研究领 ...
- 今天记录一下vue更改时间格式的js
首先定义js文件,我这边定义为date.js,里面包含了增加零的处理 //date.jsexport function formatDate(date, fmt) { if (/(y+)/.test( ...
- 10.3 - AM - 模拟赛 总结
复盘 T1 很水,一道异或求和,但是某两位仁兄因没打括号而死. T2 很水,一道字符串处理,但是我和某位仁兄因没特判而死(虽然没有 hack 掉我,所以我理论上还是满分). T3 不水,看了很久,没想 ...