从一次复杂业务重构中,我重新认识了技术探索的价值
开篇:技术探索不是“玩花样”,而是解决真问题的底气

写这篇文章的时候,窗外的北京刚下完一场雨。空气里有股凉意,手机电量提醒也亮了起来。这让我想起几个月前那个项目上线的深夜,团队成员几乎都是在公司通宵过的夜。
那是我们一个老项目的重构任务——一个运行超过五年的核心功能模块。原本只是想做一次常规优化,结果随着深入分析,发现底层架构已经严重不适应当前的业务需求。于是我们决定大刀阔斧地进行重构。这个决策本身就很冒险,但最终不仅稳定上线,性能还提升了 60% 以上。
这次经历让我对“技术探索”这个词有了更深的理解。
很多人一听到技术探索,就觉得是搞点新框架、学些前沿概念。其实不然。真正的技术探索,是面对现实中的具体问题时,敢于跳出固有思维,去尝试更合适的解决方案,并承担其中的风险与不确定性。
而我想分享的就是这样一个真实的故事,以及在这过程中我的一些思考和成长。
问题描述:一个“卡脖子”的核心模块

我们的产品是一个金融类 App,其中一个核心模块是用户的账单详情页。页面结构看似简单:展示账单明细、分类统计、趋势图等信息。但在过去五年中,它经历了多次迭代、多人维护、各种临时方案叠加,逐渐变成了一块难啃的“骨头”。
当时的痛点包括:
- 数据加载慢:首次进入页面要加载多项数据源,用户常遇到白屏或闪现
- 逻辑耦合严重:一个 ViewModel 几乎承载了所有业务逻辑,动一处可能牵连全盘
- 难以扩展:新增图表类型、增加过滤条件等功能都需要大量改动
- 测试覆盖率低:几乎没有单元测试覆盖,回归成本极高
而且最要命的是——这个页面是整个 App 的流量入口之一,不能停服也不能灰度发布。任何改动都必须确保万无一失。
解决方案:不是为了新技术而重构,而是为了更好的未来

技术选型的权衡过程
在确定要做重构之前,我们开了好几轮会议。当时摆在面前的技术选项有不少:
- 继续使用 UIKit + MVC 架构:熟悉、可控,但显然没有解决根本问题
- 引入 SwiftUI:响应式 UI 很诱人,但考虑到兼容性、学习成本,加上需要支持 iOS 13 及以下版本,最终放弃
- 采用 MVVM + Coordinator 模式重构:这是相对保守但见效快的方向,能明显降低耦合程度
- 引入 Combine 或 RxSwift 等响应式框架:提升开发体验,但也增加了调试难度和团队协作门槛

综合评估后,我们选择了第3种方案:基于 MVVM + Coordinator 的架构模式进行重构。虽然听起来有点“传统”,但它是当下最适合团队现状和业务节奏的选择。
同时,我们也做了几个关键设计决策:
1. 分层解耦:真正意义上的职责划分
我们将原来的 ViewController 中的数据处理、网络请求、事件流转全部剥离出来。ViewModel 不再持有视图引用,只负责数据转换;Coordinator 负责路由跳转,不再混杂在业务逻辑中。
class BillDetailCoordinator: Coordinator {
func navigateToChart(filterType: ChartFilterType) {
let chartVC = ChartViewController(viewModel: ChartViewModel(filterType))
navigationController.pushViewController(chartVC, animated: true)
}
}

这样的分层让代码清晰了不少。每个人都知道应该去哪改什么,谁也不用再对着一堆 @objc 和 Selector 干瞪眼。
2. 数据流标准化:定义统一的数据结构和接口
我们为数据源抽象出了标准的协议:
protocol BillDataSource {
func loadBillSummary(completion: @escaping (Result<Summary, Error>) -> Void)
func loadTransactions(completion: @escaping (Result<[Transaction], Error>) -> Void)
}
这样带来的好处是,不管是本地缓存还是远程拉取,对外暴露的接口是一致的,极大提高了可测性和灵活性。
3. 增量重构 + 功能开关控制(Feature Toggle)
为了避免一次性全面重构带来的风险,我们采用了增量重构的方式:
- 第一天先拆出 Summary 相关模块
- 第二天替换 Transaction 列表的实现
- 同时保留旧逻辑作为 fallback 机制
此外,我们还使用了一个简单的 feature toggle 控制器来控制新旧逻辑切换,万一出现问题可以秒级回滚。
if FeatureFlags.isNewBillDetailEnabled {
presentNewUI()
} else {
presentLegacyUI()
}
这套机制在后续几次上线中救了我们不止一次。
效果总结:重构之后的变化
重构上线后,我们观察了以下几个指标:
| 指标 | 改造前 | 改造后 | 提升幅度 |
|---|---|---|---|
| 页面首屏加载时间 | 900ms - 1200ms | 450ms - 600ms | ✅ 平均提升约 55% |
| 逻辑修改所需平均开发时长 | 2人日 | 0.5人日 | ✅ 缩短至1/4 |
| 回归 Bug 数量 | 上线后平均 3~5 个 | 上线后 0~1 个 | ✅ 稳定性显著提升 |
| 新功能接入速度 | 首次需一周 | 最快可当天完成 | ✅ 扩展性大幅提升 |
更重要的是,我们通过这次重构,建立起了一套规范的开发流程,也为其他模块的演进提供了模板和参考。
经验分享:技术探索不是为了炫技,而是解决问题
结合这几年的工作经验,特别是这一次项目重构的经历,我想给正在面临类似问题的朋友几点建议:
1. 不要为了新技术而拥抱新技术
我曾经也非常热衷于把项目改成各种“时髦”的技术栈,比如强制全项目上 SwiftUI,或者非要引入某个复杂的函数响应式库。结果往往是前期投入巨大,后期维护困难。
记住一句话:合适的技术 > 最新的技术。
技术选型从来不是一个人的英雄主义表演,而是团队协同、可维护性、学习成本、产品节奏的综合权衡。
2. 重构一定要有目标感和阶段性成果
很多重构之所以失败,是因为一开始就追求完美主义,恨不得一口吃成胖子。
正确的做法是:小步快跑,持续交付价值。哪怕只是一个组件、一个接口的规范化,只要能让后续开发变得更轻松,那就是值得的。
3. 学会“讲故事”,让非技术人员也能理解你的技术选择
有时候你在工程层面做出的改进,在产品经理眼里就是“没变化”。这时候你需要讲清楚背后的意义,比如“现在我们可以更快地上新功能”,或者“这次改动会避免未来出现重大故障”。
技术沟通的本质,其实是建立信任。
4. 保持开放心态,多听听别人的想法
即使是经验丰富的工程师,也不是永远正确。在那次重构中,我曾坚持某些细节不该妥协,但后来发现年轻同事提出的替代方案反而更优雅、更轻量。
团队的成长来自于每个人的输入,而不是一个人说了算。
尾声:技术人的路,走得越久越敬畏
如今再回想那次重构,我依然觉得它是我职业生涯中很重要的一次实践。不是因为它带来了多么高深的技术突破,而是它让我更加明白:技术探索的核心,不是炫技,而是解决问题的能力。
我们每天都在跟设备打交道,跟代码对话。但我们真正的对手,其实是不断变化的业务需求、日益复杂的系统环境,还有——我们自己有限的认知边界。
所以,我想说,作为一名开发者,最重要的是:
别怕犯错,也不要急于求成。技术的精进,是一场耐力赛,而不是百米冲刺。
希望这篇分享,能带给你一点点启发,也希望我们都能成为更冷静、更有判断力的技术人。
如果某一天你也面对一个“该不该重构”的抉择时刻,请记得问问自己:
“这件事,是在为未来铺路,还是仅仅为了解决眼前问题?”
共勉。

评论 0