聊一聊代码重构——我们为什么要代码重构
代码重构
事情的起因是在去年下半年,我们终于无法承受往年的历史包袱而决定开始进行代码重构。
在以前我们尝试过进行代码重构但是从来没有系统性的考虑过如何重构。在对代码重构的过程中很多经验都是来自《重构:改善既有代码的设计》这本书,但是很多内容在实际操作中可能并不是那么美好。而且里面介绍的很多技巧在实际中有些技巧非常有用,而有些技巧并不是那么容易操作的。
我会陆续把实际重构时用到的技巧在后面文章中列出来
为什么要重构
为什么要重构?很多时候在改了无数个BUG后,在某次简单的业务调整却需要修改几十个文件后。估计很多人都会有个想法这沙雕代码推倒重来算了。代码能跑就不要动、代码和程序员能跑一个就可以。
这些调侃大家都听过,但是实际上如果能遇见一个不会对旧功能进行调整的项目那可太难了。很多时候我们不得不在这些屎山代码里继续开发。直到有一天这部分内容再也无法扩展了,我们不得不尝试重构。
怎么去重构
很多时候在改代码改的头疼的时候,我们只是想要一个更好的代码。但是真的尝试去写更好的代码时,又会产生手足无措。因为重构代码前我们还需要有很多工作需要准备。首先一条是我们为什么要重构,或者说重构的目标是什么?
我工作中尝试开始重构代码的时候通常会从下面的角度去考虑。
是否能够改进我的业务逻辑
很多时候相比最初的设计,随着功能越来越多,业务设计会进行膨胀,可能业务只是膨胀了一点点,但是后端的代码却成倍的增加。随着经手的开发人员越来越多,很多时候后来的开发者无法理解之前开发者的逻辑,只能在后续的开发中通过重新实现或者复制一份之前代码来保证代码逻辑在自己负责的那一部分是正确的。日积月累之下会发现一个大的业务中,某些操作被重复的执行。而重构就是为了消除这部分内容。
是否让我的代码能够被更多其他同事所理解
代码是开发者写的,是给机器使用的,但最终代码是给别人看的。随着时间流逝很多开发者连自己写的代码都无法理解,更别说去理解其他开发者的代码。如果一段代码谁也无法理解,它就像是一个泥潭,拉扯着整个业务的扩展。重构就是为了清晰代码逻辑,让这些无法理解的逻辑变得可以被理解。
如果某处混乱的代码经常出问题是不是需要重构
很多时候业务设计的范围比用户操作习惯还要广。一些边缘功能被人忽视,而在进行某些涉及相关功能的调整时,可能是开发者的疏忽、可能是文档的缺失,也可能是测试单元的缺失导致这些功能没有被调整。而这些边缘功能就像床底下的灰尘会伴随着业务很久。而整理逻辑的过程,就像是打扫屋子的过程,将这些隐秘的问题一一发现
这部分也会现在、未来会有很多扩展,现有代码是否能够支撑
随着功能增加,系统里面逻辑相互耦合增多。我们会发现当新增一个功能的时间消耗会越来越多。而且开发新功能后出现的BUG也越来越多。而且随着经手的开发者增加,我们越来越难以从代码中看到业务的真实脉络。这就像是在一个沼泽地中前行,你看到的都是一片平坦,但是踩下去的都是一个一个深坑。目标明明不远但是走的却是非常艰难。
重构的时机
想清楚目标后就可以开始为目标准备计划。并非对已经上线的代码进行调整才是重构。重构可以是专门安排的时间,也可以是每时每刻。
非计划的重构
工作中我们很难去申请到专门的时间去进行代码重构,但是随着时间代码质量又在下降,所以大部分的重构都发生在平时。一般在开发新功能或者修复BUG的时候,对代码进行重构。这个时候代码重构的效率最高,一方面是代码并未真正上线一些调整都还是可能得,另外一方面刚完成的逻辑这个时候记忆最清晰。在提交代码前再浏览一下自己的代码,就像是写论文后在最终截止的时候从头到尾再读一遍自己写的东西总是能发现一些问题的。
代码评审
代码评审也是重构的一个好时机,代码评审可以帮助团队中经验的传播,一些有经验的开发者可以将开发经验传播给经验欠缺的人。并且可以让更多人了解到新功能的实现逻辑。更重要的是通过别人角度我可以意识到哪些逻辑对于我来说是清晰的,对于其他人来说是模糊的,通过收集这些反馈,对于如果写出可以被他人理解的代码大有裨益。并且通过互相交流,开发团队中最终可以形成一个统一的代码风格,这样对于理解团队其他开发者逻辑有所帮助。
计划性重构
很多时间比较长的项目中存在一些庞大且无法理解的核心代码。这些代码所处的位置非常重要,其逻辑非常复杂且不可理解。这些代码无法通过日常进行调整。这个时候我们需要专门安排时间和开发人员对代码进行重构。计划性重构中这部分逻辑应该也是高优先级的内容。
不要重构
并非所有烂代码都需要重构的。在资源很紧张的时候,这部分内容并没有成为业务前进上的绊脚石,这部分逻辑可以被保留下来。我们要把时间留给更重要的内容上。另外一种情况是相比重构,重写似乎更容易一些,这个时候就不要再去重构了。但是这是一个非常大胆的决定。这个时候你需要充分理解这段代码的业务逻辑,并且有足够的测试保证重写后的逻辑正常。
重构的风险
很多时候我们以为重构面对的问题是:什么是坏代码、如何调整他们。但是实际上开始动手会发现面对的挑战更加复杂。重构过程中会出现一些其他模块的限制而产生妥协,重构过程中被不断扩大的边界,重构后代码是否准确以及不断被调整的开发计划。
时间,最需要的是时间,最少的也是时间
重构最大的问题就是挤占了新功能的开发时间。重构不仅没有产生新的功能,伴随着重构出现的BUG,会消耗大量的开发时间。实际开发中经常遇见“时间紧先提供一个临时方案,后续有时间后再优化”,“这是一个临时的功能,并不会成为正式功能”这些说辞,这无疑给重构带来巨大挑战。然后随着这类修改的增加,开发新功能变得非常慢。而重构就是为了提高开发效率,虽然没有实际产生新的功能,但是对于后续新功能的开发是节约了时间的。提高后续开发效率,这也是重构的最终目标。我们之前说的优化逻辑、让代码易于理解都是为了这个最终目标服务的。
欠缺的单元测试
足够的测试是重构的安全保证,但是很遗憾,至少我接触的项目中,大多数测试都是严重不足的。单元测试是一个很麻烦的工作,尤其是一个覆盖率足够高的单元测试,其编写消耗的时间甚至比开发功能需要的时间还要多的多。有些比较懒的开发者在添加一些功能的时候有意无意的忽略掉单元测试添加和编写。这些行为都导致重构这些遗留代码的时候我们无法预估最终的结果是否和以前保持一致。所以很多时候在重构之前我们需要将这些功能的测试补充进来。这无疑大大增加了重构的工作量。
不断扩大的边界,不断妥协的修改
重构之前需要明确这次重构的目的和边界。如果不能明确目的和边界,整个重构产生的工作量则无法估计。在重构某个业务的时候可能会发现其他业务代码存在相同的问题,这个时候如果盲目的修改结果可能发现修改的内容越来越多,越来越偏离最开始的目标。另外一个干扰地方就是性能,很多时候按照规则去重构会发现这回导致性能下降。我个人经验是如果不是下降的非常夸张则不需要在意。另外在进行代码重构的会发现有些逻辑可以进行性能优化,如果它不会非常影响重构进度的话可以尝试进行优化。如果明显影响了重构的进度,那么这个时候我建议是,记录但是不进行修改。需要明确重构和性能优化的区别,重构是为了提高后续开发效率,一切以这个为目标。
重构的计划
当我们明确了重构的目标、也申请到了重构的时间准备面对各种挑战。就要开始准备重构的计划了。
分析代码
在尝试重构前我们需要足够了解需要被重构的内容。包括:这段代码的作用、其上层对应了哪些业务,它底层涉及那些数据。只有真正的了解这部分业务才能保证重构的业务和之前保持一致。
定位代码中的问题。重构的最好的方式还是在原基础上进行优化。除非真的无法挽回尽量不要重写。那么进行优化就需要找到那些问题代码,并确定改进方案。
设计重构方案
根据上面的分析开始指定代码重构的计划,这部分需要考虑:
- 代码应该使用哪种方式进行重构。
- 目前的资源能够重构那些内容。
- 一个大的重构计划拆分为多个小的里程碑,这样能够阶段性的检查重构成果。
关于重构方式,我实际经验中我习惯将重构分为三种,根据重要性进行排序结果是:可读性重构、可维护性重构、性能重构。
可读性重构是最容易启动的,也是最重要的。当代码需要进行可读性重构的时候证明这块代码在阅读上都已经出了问题。而重构代码的前提是需要理解代码,而开发者是否真的完全了解其逻辑,那很难给一个确实的回答。
可维护性重构主要是针对代码的结构是否支持后续业务扩展,是否是BUG频出的地方。可维护性重构真真正正需要对代码逻辑进行调整,针对有些问题甚至需要进行代码结构上的变化。这部分也是重构比较耗时的内容。
性能重构和性能优化还是不同的。性能优化需要对数据存储和访问进行结构调整,而大多数时候重构是针对可读性和可维护性进行调整,代码重构中性能并非其目标(当然实际上因为代码更加简洁大部分时间会让代码效率更高)。但是有些时候我们面对低效率的代码的调整也会让性能提高,比如将循环中出现的数据库访问操作放在循环外、低效率的循环判断等等。这些调整并非为了提高可读性和可维护性,但是考虑到其只需要对一小段代码进行调整就能实现性能的提高,我觉得这些内容应该也被认为是代码重构的目标之一。
推荐书籍
关于重构的书籍有很多,而且这些书籍都很有用。和一些开发组件类书籍不同,这些书籍并不会时间流失而变得过时。所以可以买来是不是的看一看,每次都有会新的体会。而我在工作中很多对重构最开始的理解都是源于这些书籍。
《重构:改善既有代码的设计》(Martin Fowler):这本书是代码重构的经典之作,全面介绍了重构的概念、方法和技巧,提供了大量的实例和案例,是一本不可多得的参考书。
《代码整洁之道》(Robert C. Martin):这本书介绍了如何写出干净、优雅、可维护的代码,涵盖了很多代码重构的技巧和方法,讲解深入浅出,适合各个层次的开发人员。
相关文章:
聊一聊代码重构——我们为什么要代码重构
代码重构 事情的起因是在去年下半年,我们终于无法承受往年的历史包袱而决定开始进行代码重构。 在以前我们尝试过进行代码重构但是从来没有系统性的考虑过如何重构。在对代码重构的过程中很多经验都是来自《重构:改善既有代码的设计》这本书,…...
【Python学习笔记】第二十九节 Python2 和Python3发生了哪些变化
Python 版本分为两大流派,一个是 Python 2.x 版本,另外一个是 Python 3.x 版本,Python 官方同时提供了对这两个版本的支持和维护。2020 年 1 月 1 日,Python 官方终止了对 Python 2.7 版本(最后一个 Python 2.x 版本&a…...

[oeasy]python0099_雅达利大崩溃_IBM的开放架构_兼容机_oem
雅达利大崩溃 回忆上次内容 个人计算机浪潮已经来临 苹果公司迅速发展微软公司脱离mits准备做纯软件公司IBM用大型机思路制作的5100惨败 Commodore 64 既做计算机又做游戏机 计算机行业和游戏行业 跟随着底层技术不断迭代已经进入了战乱纷纷的年代最终又会如何呢?…...
学术论文投稿之同行评审过程中可能会遭遇哪些偏见?
同行评审过程的顺利进行,在很大程度上取决于学术界的积极参与和相互信任,以及需要参与各方都以负责任的态度行事。作为审稿专家,向作者提供公正、客观的评价是至关重要的。同行评审过程中,若有任何偏离客观性的行为,均…...

Python写一个自动发送直播弹幕的工具,非常简单
哈喽大家好,今天给大家用Python整一个可以在直播间自动发弹幕的工具,来为喜欢的主播疯狂扣6 ! 事情原由昨晚回家,表弟在看LOL直播,看得我气不打一处来,差点就想锤他。 身为程序员的表弟,看直…...

学生档案管理系统的设计与实现
技术:Java、JSP等摘要:本设计是为托普学院学生档案的管理实现电子化而设计的,系统开发采用J2EE技术,数据库采用了SQL Server 2005,因而系统具有很好的扩展性、可移植性,实现了教学资源的信息化管理。主要功…...

JavaEE学习笔记-SpringBoot快速上手、部分注解解释
SpringBoot快速上手 一、快速创建SpringBoot应用1.1利用IDEA提供的Spring Initializr创建Spring Boot应用1.2Spring Boot生成的项目结构介绍1.3初步测试后端是否OK(建立一个controll类)二、热部署2.1 添加依赖2.2 Setting处项目自动化设置2.3 具体项目设置2.4 待选步骤三、注…...

【Python学习笔记】第二十六节 Python PyMySQL
一、什么是 PyMySQL?PyMySQL 是在 Python3.x 版本中用于连接 MySQL 服务器的一个库。可以用它来连接Python和MySQL。如果你追求速度,这是一个很好的选择,因为它比mysql-connector-python快。PyMySQL 遵循 Python 数据库 API v2.0 规范&#x…...

Android问题笔记 -关于Kotlin插件版本的问题
专栏分享点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册点击跳转>Scratch编程案例 👉关于作者 众所周知,人生是一个漫长的流程,不断克服困难,不断…...

【同步工具类:Phaser】
同步工具类:Phaser介绍特性动态调整线程个数层次Phaser源码分析state 变量解析构造函数对state变量赋值阻塞方法arrive()awaitAdvance()业务场景实现CountDownLatch功能代码测试结果实现 CyclicBarrier功能代码展示测试结果总结介绍 一个可重复使用的同步屏障,功能…...
Linux命令·rmdir
今天学习一下linux中命令: rmdir命令。rmdir是常用的命令,该命令的功能是删除空目录,一个目录被删除之前必须是空的。(注意,rm - r dir命令可代替rmdir,但是有很大危险性。)删除某目录时也必须具…...
从0开始自制解释器——综述
作为一个程序员,自制自己的编译器一直是一个梦想。之前也曾为了这个梦想学习过类似龙书、虎书这种大部头的书,但是光看理论总有一些云里雾里的感觉。看完只觉得脑袋昏昏沉沉并没有觉得有多少长进。当初看过《疯狂的程序员》这本书,书里说&…...

【spring】spring5特性
1、整个 Spring5 框架的代码基于 Java8,运行时兼容 JDK9,许多不建议使用的类和方 法在代码库中删除 日志框架 2、Spring 5.0 框架自带了通用的日志封装 (1)Spring5 已经移除 Log4jConfigListener,官方建议使用 Log4j…...

曹云金回归、于谦电影杀青,德云社想不火都难
说起民间最大的相声社团,首屈一指的要属德云社,之所以说德云社最大,主要是优秀相声演员够多。德云社在郭德纲的带领下,如今已经是人才济济,听说最近队伍会进一步壮大,前徒弟曹云金也要回归了。 当年曹云金作…...
从入门到精通:数据库设计规范指南
当我们开始设计数据库时,我们需要确保它是可靠和可扩展的。为了实现这一目标,我们需要遵循一些数据库设计规范。本文将介绍一些数据库设计规范,以确保您的数据库能够满足当前和未来的业务需求。 目录 一、命名规则 二、数据类型 三、索引…...
js 求解《初级算法》8.字符串转换整数(atoi)
一、题目描述 请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数 算法如下: 读入字符串并丢弃无用的前导空格 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符ÿ…...
Vue学习笔记(5)
5.1 其他常用内置指令 5.1.1 v-text v-text是Vue.js中常用的内置指令之一,用于将数据绑定到DOM元素的文本内容。与双花括号({{ }})类似,v-text指令也可以将Vue实例中的数据渲染到页面上。 使用v-text指令时,Vue会将指…...
LeetCode 面试题 05.02. Binary Number to String LCCI【字符串,数学】中等
本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章…...

数据结构 “串“ 的补充提升与KMP算法及其优化的具体实现
❤️作者主页:微凉秋意 ✅作者简介:后端领域优质创作者🏆,CSDN内容合伙人🏆,阿里云专家博主🏆 ✨精品专栏:C面向对象 🔥系列专栏:数据结构与课程设计 文章目录…...
如何使用Spring Cloud搭建MQ(Message Queue)消息队列
Spring Cloud是一个开源框架,用于构建基于微服务架构的应用程序。它提供了多种工具和技术,用于实现各种微服务模式,并使它们易于管理和部署。MQ(消息队列)则是一种重要的异步通信机制,用于在不同的应用程序…...

C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...

【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error
在前端开发中,JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作(如 Promise、async/await 等),开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝(r…...