2025年,AR眼镜迎来了爆发期。我们公司的AR眼镜应用,用户量在几个月内涨了十几倍。
用户量涨了是好事,但我们的代码库却扛不住了。这个项目是两年前快速启动的,当时为了赶进度,写了很多"能跑就行"的代码。现在用户量上来了,各种问题暴露出来:bug频发、性能差、新功能开发慢、团队成员不敢改代码。
领导决定做一次全面的代码重构。我作为技术负责人,牵头了这次重构。花了三个月时间,把一个"烂代码"项目,改造成了一个结构清晰、易于维护的项目。
这篇文章,我想分享一下这次重构的经历。从识别代码坏味道、制定重构策略、逐步实施到效果验证,聊聊如何把烂代码改造成优雅代码。希望能给正在做或者打算做代码重构的朋友一些参考。
为什么要重构
先说说为什么要做这次重构。
我们的AR眼镜应用,主要功能是实时识别物体、显示信息叠加、空间交互、多人协作。这些功能对实时性和稳定性要求很高。
随着用户量增长,问题越来越多:
第一个问题是,bug频发。线上经常出各种奇怪的bug,而且很难定位。因为代码结构混乱,一个功能的逻辑散落在好几个文件里,改一个地方可能影响另一个地方。团队成员每天都在救火,没有时间做新功能。
第二个问题是,性能差。AR应用对性能要求很高,需要实时渲染和低延迟。但我们的代码里有很多性能问题,比如不必要的重复计算、内存泄漏、渲染阻塞。用户反馈卡顿、发热、耗电快。
第三个问题是,新功能开发慢。代码耦合严重,加一个新功能要改很多地方,而且容易引入新bug。一个本来一周能做完的功能,经常要做两三周。团队的开发效率很低。
第四个问题是,团队成员不敢改代码。因为代码没有测试,结构不清晰,改了不知道会不会出问题。大家都倾向于在现有代码上打补丁,而不是重构,导致代码越来越烂,形成恶性循环。
第五个问题是,人员流动的影响。项目早期的核心成员走了几个,留下的代码没人能完全看懂。新人入职,光熟悉代码就要一两个月,而且很容易踩坑。
这些问题已经严重影响了产品的发展和团队的士气。重构,势在必行。
识别代码坏味道
重构的第一步,是识别代码中的"坏味道"。我们用了几天时间,对整个代码库做了一次全面的"体检"。
主要发现了这些问题:
第一个坏味道是,上帝类(God Class)。有几个类特别大,几千行代码,什么都干。比如有一个叫ARManager的类,既负责相机管理、又负责渲染、又负责空间计算、又负责用户交互。这个类几乎是整个应用的核心,但也最难维护。改任何功能都要动这个类,很容易出问题。
第二个坏味道是,长方法。很多方法几百行,做了很多事情。比如一个处理用户手势的方法,既判断手势类型、又处理坐标转换、又更新UI、又发送网络请求。这样的方法很难理解,也很难测试和修改。
第三个坏味道是,重复代码。很多逻辑在不同的地方重复实现。比如坐标转换的逻辑,在三四个文件里都有类似的实现,而且还不完全一样,有的还有bug。改一个地方,其他地方忘了改,就会出现不一致。
第四个坏味道是,魔法数字和字符串。代码里到处都是硬编码的数字和字符串,比如"if (type == 3)"、"if (status == 'active')"。这些魔法值没有注释,不知道是什么意思,改的时候很容易改错。
第五个坏味道是,深层嵌套。很多方法里有四五层if-else嵌套,像金字塔一样。比如先判断有没有权限,再判断设备类型,再判断网络状态,再判断用户状态……这样的代码很难读懂,也很容易漏掉某些分支。
第六个坏味道是,职责不清。很多类和方法的职责不清晰,一个类既做数据获取又做业务逻辑又做UI渲染。MVC或者MVVM的分层没有做好,View里有业务逻辑,Model里有UI代码。
第七个坏味道是,没有测试。整个项目几乎没有单元测试,集成测试也很少。改代码之后,只能靠手动测试,很难保证没有引入新bug。这也是大家不敢改代码的主要原因。
第八个坏味道是,注释少或者注释过时。关键的算法和业务逻辑没有注释,或者注释和代码不一致(代码改了但注释没改)。看代码的时候,只能靠猜。
把这些问题整理出来之后,我们对代码库的状况有了清晰的认识。接下来就是制定重构策略。
重构策略
面对这么多问题,我们没有选择"推倒重写",而是选择了"渐进式重构"。
原因有几个:第一,推倒重写风险大,而且在重写期间,旧代码还要维护,相当于做两份工作;第二,业务还在快速发展,不能停下来几个月只做重构;第三,渐进式重构可以随时验证效果,出问题也容易回滚。
我们的重构策略是:
第一,先加测试,再重构。这是最重要的原则。没有测试的代码,重构就是赌博。我们先给核心模块加测试,确保重构前后行为一致。
第二,从最痛的地方开始。不是所有代码都要重构,先从问题最严重、改得最频繁的模块开始。比如那个上帝类ARManager,还有性能瓶颈模块。
第三,小步快跑,每次重构一个小部分。不要一次改太多,改完就测试、就提交、就验证。这样出问题容易定位,也容易回滚。
第四,重构和新功能开发结合。不要为了重构而重构,在做新功能的时候,顺便重构相关的代码。这样既不影响业务进度,又能逐步改善代码质量。
第五,建立代码规范。重构的过程中,建立统一的代码规范、架构规范、命名规范。所有新写的代码都要遵守规范,旧代码逐步迁移到规范。
第六,持续监控。重构之后,监控性能、稳定性、开发效率等指标,确保重构确实带来了改善,而不是越改越糟。
重构的实施
按照这个策略,我们开始了重构。整个过程分成了几个阶段。
第一阶段:加测试
重构之前,先给核心模块加测试。
我们的项目是用Swift写的iOS应用(AR眼镜的配套App),用XCTest做单元测试。我们优先给这些模块加测试:
- 数据模型和业务逻辑层:这些是核心,测试价值最高。
- 工具类和扩展:这些被很多地方引用,确保它们的正确性很重要。
- 算法模块:比如空间计算、坐标转换,这些逻辑复杂,容易出bug。
加测试的过程中,我们发现了很多隐藏的bug。比如,坐标转换的方法,在某些边界条件下返回错误的结果;手势识别的逻辑,在特定的手势序列下判断错误。这些bug之前没发现,是因为没有测试覆盖。
加测试花了大概三周时间。虽然花了时间,但这是值得的。有了测试,后面的重构就有了安全网。
第二阶段:拆分上帝类
接下来,我们处理最大的问题:上帝类ARManager。
这个类有3000多行代码,负责了太多事情。我们的拆分思路是,按照职责把它拆分成多个小类:
- 相机管理类:负责相机的初始化、配置、帧捕获。
- 渲染管理类:负责Metal渲染、场景管理、材质和纹理。
- 空间计算类:负责平面检测、坐标转换、空间锚点。
- 手势识别类:负责手势的识别和分发。
- AR会话协调器:负责协调上面各个模块,提供统一的接口。
拆分的过程中,我们遵循了几个原则:
- 单一职责:每个类只做一件事。
- 高内聚低耦合:相关的逻辑放在一起,类之间通过清晰的接口通信。
- 依赖注入:类之间通过协议(Protocol)依赖,而不是直接依赖具体实现,方便测试和替换。
- 逐步迁移:不是一次性把ARManager删掉,而是先把功能逐步移到新的类里,ARManager变成一个空壳,最后再删除。
拆分花了大概两周时间。拆分之后,每个类都不超过500行,职责清晰,容易理解和修改。而且,因为有测试保护,拆分过程中没有引入大的bug。
第三阶段:提炼方法和消除重复
拆分完大类之后,我们开始处理长方法和重复代码。
对于长方法,我们用了"提炼方法"的重构手法:把一个长方法中相关的代码块,提炼成独立的小方法,给它起一个能描述功能的名字。比如,原来一个处理手势的方法有200行,我们提炼成了recognizeGesture()、calculateTouchPosition()、updateUI()、sendAnalytics()几个小方法,主方法只需要调用这些小方法,逻辑清晰了很多。
对于重复代码,我们用了"提炼函数"和"提炼类"的手法:把重复的逻辑提炼成公共函数或者公共类,所有地方都调用同一个实现。比如坐标转换的逻辑,原来在三四个地方有不同的实现,我们提炼成了一个CoordinateConverter类,所有地方都用它,既消除了重复,又统一了行为。
这个阶段,我们还处理了魔法数字和字符串。把硬编码的值,改成了有意义的常量或者枚举。比如,把"if (type == 3)"改成了"if (type == .object)",用枚举代替魔法数字,代码的可读性大大提升。
第四阶段:优化架构分层
之前的代码,架构分层很混乱。我们重新梳理了架构,采用了MVVM + Coordinator的模式:
- Model层:数据模型和业务逻辑,纯Swift代码,不依赖UI。
- ViewModel层:处理视图逻辑,把Model的数据转换成View可以显示的格式。
- View层:UI渲染和用户交互,只做和UI相关的事情。
- Coordinator层:负责页面路由和跳转,把路由逻辑从ViewController中抽出来。
- Service层:负责网络请求、传感器数据获取、AR会话管理等外部依赖。
分层之后,各层的职责清晰,依赖关系明确。View不直接访问Model,而是通过ViewModel;ViewController不负责跳转,而是通过Coordinator。这样的架构,更容易测试,也更容易维护。
我们还引入了依赖注入容器,管理各个对象的创建和依赖关系。这样,在测试的时候可以很方便地替换依赖(比如用Mock的网络层代替真实的网络请求)。
第五阶段:性能优化
架构重构完成之后,我们开始做性能优化。AR应用对性能要求很高,我们重点优化了几个方面:
第一,渲染性能。用Instruments的Metal System Trace工具,分析了渲染管线,找到了瓶颈。主要问题是有很多不必要的draw call和状态切换。我们做了批处理(把多个小的渲染合并成一个)、减少了状态切换、优化了纹理格式,渲染帧率从45fps提升到了稳定的60fps。
第二,内存管理。用Leaks工具检测内存泄漏,发现了几个循环引用的问题(闭包捕获了self,没有用weak)。修复之后,内存占用稳定了很多,不会用着用着就闪退了。
第三,计算性能。空间计算和坐标转换是CPU密集型的操作,我们做了几个优化:把重复计算的结果缓存起来、用更高效的算法、把一些计算放到后台线程,避免阻塞主线程。优化之后,CPU使用率降了20%左右。
第四,启动性能。优化了启动流程,把非必要的初始化延迟到启动之后,启动时间从3秒降到了1.5秒。
性能优化的效果很明显,用户反馈卡顿和发热的问题少了很多,应用的口碑也变好了。
第六阶段:建立持续改进机制
重构不是一次性的项目,而是一个持续的过程。我们建立了一些机制,确保代码质量不会再次恶化:
第一,代码审查(Code Review)。所有代码合并到主分支之前,必须经过至少一个人的审查。审查的重点包括:是否符合架构规范、有没有明显的坏味道、有没有测试、命名是否清晰。
第二,静态分析。在CI(持续集成)中加入了SwiftLint静态分析,代码不符合规范的话,CI会失败,不能合并。这样可以自动检查代码风格和一些常见问题。
第三,测试覆盖率要求。新代码必须有单元测试,核心模块的测试覆盖率要求达到70%以上。CI会统计测试覆盖率,低于阈值的话会告警。
第四,定期重构时间。每个迭代留出20%的时间,专门用来做技术债务的偿还和代码重构。这样,团队不会因为赶进度而完全忽略代码质量。
第五,技术分享。定期组织技术分享,讨论架构设计、最佳实践、重构经验。让团队成员都有代码质量意识,而不是只有少数人关心。
重构的效果
三个月的重构,效果是显著的。
第一个效果是,bug率下降了。线上bug的数量,比重构前下降了60%左右。因为代码结构清晰了,测试覆盖了,很多潜在的问题在开发阶段就被发现了。
第二个效果是,性能提升了。渲染帧率稳定在60fps,启动时间缩短了一半,CPU和内存占用都降了。用户的负面反馈少了很多,应用商店的评分也提升了。
第三个效果是,开发效率提升了。新功能的开发周期,从平均两三周缩短到了一周左右。因为代码耦合低了,加新功能不需要改很多地方,而且有测试保护,不容易引入bug。
第四个效果是,团队士气提升了。大家不再每天面对烂代码,不再天天救火。开发新功能变得更顺畅,也更有成就感。新人入职的熟悉时间,从一两个月缩短到了两三周。
第五个效果是,技术债务减少了。代码库的结构清晰了,规范建立了,持续改进机制也有了。虽然还有一些历史遗留问题,但整体的代码质量已经上了一个台阶。
当然,重构也不是没有代价。我们花了三个月时间,投入了两个全职开发,期间新功能的开发速度确实慢了一些。但从长远来看,这些投入是值得的。重构之后,团队的开发效率提升了,产品质量也更好了。
重构的经验和教训
分享几个这次重构中总结的经验和教训。
第一个经验是,先加测试再重构。这是最重要的一条。没有测试的重构,就是在裸奔。有了测试,你才能放心地改代码,改完知道有没有破坏原有功能。如果实在加不了测试(比如有些UI代码很难测试),至少要做集成测试或者手动测试用例。
第二个经验是,渐进式重构优于推倒重写。推倒重写听起来很美好,但实际风险很大。你可能在重写的过程中,发现旧代码里有很多你没注意到的边界情况和业务逻辑,重写的版本反而不如旧的。渐进式重构,每一步都小步验证,风险可控,而且不影响业务发展。
第三个经验是,从最痛的地方开始。不要试图一次性重构所有代码,那样工作量太大,也容易失去动力。先从问题最严重、改得最频繁的模块开始,快速看到效果,建立信心,然后再逐步扩展。
第四个经验是,重构和业务结合。不要为了重构而重构,把重构和新功能开发结合起来。做新功能的时候,顺便重构相关的代码。这样,业务和技术都不耽误,也更容易获得领导和团队的支持。
第五个教训是,不要过度设计。重构的时候,很容易为了"优雅"而过度设计,引入很多不必要的抽象和层级。记住,简单是美。重构的目标是让代码更容易理解和维护,而不是展示架构技巧。能用简单方案解决的,就不要用复杂方案。
第六个教训是,注意重构的范围。每次重构的范围要可控,不要改着改着就扩大了范围,最后改了一大堆代码,出了问题不知道是哪里引起的。每次只改一个点,改完测试、提交,再改下一个。
第七个教训是,团队共识很重要。重构不是一个人的事,需要整个团队的认同和配合。在重构之前,要和团队充分沟通,让大家理解为什么要重构、重构的目标是什么、每个人的角色是什么。如果团队不认同,重构很难推进。
写在最后
代码重构是一件痛苦但有价值的事情。
看着烂代码一点点变成优雅代码,看着系统从问题不断变成稳定高效,那种成就感是很强的。而且,重构的过程也是一个学习的过程,你会对代码、对架构、对业务有更深的理解。
但重构不是目的,而是手段。我们重构代码,是为了让产品更好、让开发更高效、让团队更快乐。不要为了重构而重构,也不要追求完美的代码。够用、好维护、能支撑业务发展,就是好的代码。
如果你也在面对一个烂代码的项目,不要害怕,也不要急于求成。从加测试开始,从最痛的地方开始,小步快跑,持续改进。时间会给你回报。
希望我们的重构经历,能给你一些启发。如果你也有代码重构的经验或者问题,欢迎在评论区交流。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录