聊一聊代码重构——关于变量的代码实践
提炼变量
其目标是将一个复杂表达式或语句分解成更小的部分,并将其存储在变量中。提高代码可读性和复用性
复杂的表达式
有些时候为了方便我们会把业务处理的逻辑写在一起,如果参与处理的内容较多时我们就会创造出一个非常长且难以理解的表达式。当其他开发同学尝试接手这块代码的时候,这段复杂的逻辑将成为他理解代码的一个障碍。如果将长的表达式进行拆解,虽然代码会增多,但是通过一些中间变量我们可以更容易得理解代码。当一个表达式被赋值给一个变量后,在后续使用的时候,如果这段逻辑参与了后续的代码业务时,可以使用变量来替代,从而提高了代码复用程度。
是否需要提炼变量需要考虑下面的场景
- 需要多次使用相同的表达式或值时,避免重复代码并提高可读性。
- “魔法值”,一些在代码中被直接设置的字符串等。在未来当您需要更改代码中的某个业务涉及的值时,你可能需要修改多处。
- 代码中存在复杂的条件表达式时,需要将这些表达式进行拆分为这些部分分配有意义的变量名,从而提高可读性和可维护性。
- 表达式的内容会被其他方法使用,提炼变量将结果存储在变量中。
如何拆分表达式
- 选择需要重构的代码块
- 根据业务将表达式拆分成更小的部分,如果不是很容易拆分,可以先拆成较大的几部分然后单独细分。或者先将容易拆分的内容拆分出来。
- 选择一个有意义的名称来为新变量命名。名称应该清晰地描述变量的作用。
- 修改代码中相关逻辑内容,将变量插入到代码中。
- 测试
为变量起一个好名字
- 变量名应该直接说明变量所代表的内容,如果其返回的并非布尔类型,名称中需要包含其数据类型。
- 尽量避免缩写,缩写虽然让代码变得简洁,但如果不是众所周知的缩写,其他人阅读时可能出现理解偏差
- 如果需要,可以在变量名中添加适当的前缀或后缀。比如标记这个变量是个集合(List)是个时间(Date)
- 对于一些校验和计算表达式的变量,可以使用能描述之前表达式执行的业务的名字
提炼变量,并提供完整名字的例子
重构前
public class Main {public static void main(String[] args) {int b = 10;int h = 20;double hypotenuse = Math.sqrt((b * b) + (h * h));System.out.println("Hypotenuse of the triangle is: " + hypotenuse);}
}
重构后
public class Main {public static void main(String[] args) {int base = 10;int height = 20;int squareOfBaseAndHeight = (base * base) + (height * height);double hypotenuse = Math.sqrt(squareOfBaseAndHeight);System.out.println("Hypotenuse of the triangle is: " + hypotenuse);}
}
在旧方法中使用简写去描述一些内容,而且这些内容在一个超长的表达式中,对于阅读者一时间很难去理解其逻辑。新的方法中通过充分命名和提炼变量来让代码更加清晰。
删除多余变量
其目的是删除代码中的变量,并直接使用变量的值来代替它。这种技术的主要目的是简化代码并减少变量的使用,从而使代码更加清晰易读。
过度的提炼变量
既然有代码需要提取变量,那么就有代码需要移除变量。很多时候针对一些简单的表达式根本不需要进行变量进行参与,可能一个简单的比较大小和空值判断,本身已经很容易理解的。赋值变量除了增加代码复杂度,并不能给我们带来可读性的提高。
如果一个变量,逻辑并不复杂,而后续也没有被重复使用,那么就需要考虑是否需要移除掉这个变量。直接将使用变量的地方替换为实际值。
是否是多余变量需要考虑下面的场景
- 变量只使用一次。
- 变量只是中间步骤的结果,且整体表达式也不复杂。
- 变量的作用域很小,在其他的方法中并不需要使用。
简单的多余变量的例子
public class Main {public static void main(String[] args) {int x = 10;int y = 20;int result = x + y;System.out.println("Result is: " + result);}
}
上面的例子中,变量 result
被赋值了一次,而且在整个方法中只有这一个地方使用了。消除了这个变量的声明和赋值语句,会使代码更加简洁易懂。
最后直接使用System.out.println("Result is: " + (x + y));
替代
变量封装成对象
此方法是对变量进行封装来提高代码的可维护性和可读性的技术。它可以将变量从公共接口中隐藏,并通过访问器方法来访问它们,从而控制对变量的访问和修改。通过这种方式来保证变量的访问和修改是安全的。
控制变量的访问权限
有些时候业务需要多个方法参与,创建变量的地方和修改变量的地方并不在一个位置。随着使用这些变量地方增加,变量在其他方法中被意外的修改风险就随之增加。这个时候我们可以使用对象将数据进行封装。对变量进行封装后,我们可以。在对象中添加数据调用或修改的判断,这样对数据的使用的修改就可以进行限制。
什么时候需要将变量封装成对象
- 当变量被多个类或者方法使用时,将变量封装在一个类中,并通过该类提供的访问器方法来访问它。
- 当变量的值需要进行一些计算或验证时,可以将变量封装成对象,通过单独的方法控制对变量的访问和修改,这可以确保变量的值始终保持在有效的范围内。
- 某些构造后处理业务的类中如果存在过多的全局变量时,可以考虑将这些变量整合在一个类中,并将其作为该类的私有属性。
封装变量流程
- 确定哪些变量需要被封装起来
- 创建封装类,根据需要来确定哪些变量可以被访问和修改,以及修改时进行校验
- 替换变量访问,方法中的参数使用封装类替换,对参数的访问和修改使用
getter
和setter
方法 - 测试
封装变量的重构流程
例子
public class Main {public static void main(String[] args) {int length = 10;int width = 20;int height = 30;int volume = length * width * height;int surfaceArea = 2 * (length * width + width * height + height * length);System.out.println("Volume is: " + volume);System.out.println("Surface area is: " + surfaceArea);}
}
重构
public class Box {private int length;private int width;private int height;public Box(int length, int width, int height) {this.length = length;this.width = width;this.height = height;}public int getVolume() {return length * width * height;}public int getSurfaceArea() {return 2 * (length * width + width * height + height * length);}
}public class Main {public static void main(String[] args) {Box box = new Box(10, 20, 30);System.out.println("Volume is: " + box.getVolume());System.out.println("Surface area is: " + box.getSurfaceArea());}
}
在上面例子中,除了可以将变量封装在对象中使用,部分基于变量的表达式,也可以作为对象自身的特性,作为对象的内部方法进行实现。
为变量提供一个合适的名字
一个好的名字能显著提高代码可读性
如果我们面对一段陌生的代码,对它的第一印象就是名称。项目名、类名、方法名和变量名。一个好的名字可以极大的提高程序的可读性。
很多时候我们想给变量起一个合适的名字并不是那么容易,可能是需求的变化,也可能是我们代码还在完善中,我们暂时无法确定这段逻辑在最后承担的任务。
对于变量,我个人觉得对于变量的名字应该包含其所处上下文环境中的含义。作用阅读者我除了知道它是什么以外我也想知道它做什么。并且伴随业务和功能的需求的变化,变量的名称需要根据实际变化而改变。如果其关联值的业务含义发生变化了,但是名字还是用过时的名字,对于后续开发人员这将会是一个误导内容。
不同名字带来的感受
重构前
public class Main {public static void main(String[] args) {int a = 10;int b = 20;int c = a + b;System.out.println("Result is: " + c);}
}
重构后
public class Main {public static void main(String[] args) {int firstNumber = 10;int secondNumber = 20;int sum = firstNumber + secondNumber;System.out.println("Result is: " + sum);}
}
上面例子我们知道代码做了什么,但是上面内容写道有一大段复杂逻辑中,你会发现一个很尴尬的情况。你知道这段逻辑做了什么,但你不是很清楚计算出来的内容在业务中所代表的含义。当然上面例子是个比较极端的情况,但并非不可能出现。就像很多开发喜欢用user1
、user2
、user3
来区分相同对象一样
和上面类似的场景,很多时候在fori
循环中,i
作为变量会参与到循环体中的业务中,当存在嵌套循环、大量控制语句等交织在一起是,i
、j
、z
这种参数会让代码变的晦涩难懂。
不要让一个变量承担多种职责
一个变量多种职责
开发中代码循环中变量会伴随着循环被不断的赋值。除了这种情况以外,有些时候变量完成初始化后,在后来的某处代码中会对变量再次进行了赋值,这种赋值有时候代表着变量的职责或者当时的数据状态发生了变化。就好像一个人在同一个地方不同时间节点承担了多种任务,这使得调用者在使用变量的时候需要思考变量当时的职责是否符合自己需要。
这种职责的变化会使人阅读到这段代码重变得糊涂,对使用这个变量的开发来说也是一个负担。最好的方式是在进行后续赋值动作时单独赋值给一个新的变量。虽然看起来变量增多了,但是在后续调用的时候可以清晰的理解,这两个变量内容是不一致的,而不至于调用的时候产生混乱。
如何去调整这些变量
- 如果可能将新的变量声明为不可修改
- 在变量第二次修改的时候将其赋值给新的变量
- 以修改第二次赋值为界,将后续代码对旧变量的使用,指向到新的变量中。
- 如果后续依然存在赋值操作,则重复2、3步
变量被多次赋值的例子
double temp = 2 * (height + width);
System.out.println("Perimeter: " + temp);
temp = height * width;
System.out.println("Area: " + temp);
重构后
double perimeter = 2 * (height + width);
System.out.println("Perimeter: " + perimeter);
double area = height * width;
System.out.println("Area: " + area);
相比重构前的代码,在新的代码里面,使用temp
并不需要考虑这个变量此时应该保存的值内容。
相关文章:

聊一聊代码重构——关于变量的代码实践
提炼变量 其目标是将一个复杂表达式或语句分解成更小的部分,并将其存储在变量中。提高代码可读性和复用性 复杂的表达式 有些时候为了方便我们会把业务处理的逻辑写在一起,如果参与处理的内容较多时我们就会创造出一个非常长且难以理解的表达式。当其他…...

Spring之基于注解方式实例化BeanDefinition(1)
最近开始读Spring源码,读着读着发现里面还是有很多很好玩的东西在里面的,里面涉及到了大量的设计模式以及各种PostProcessor注入的过程,很好玩,也很复杂,本文就是记录一下我学习过程中的主干流程。 在开始我们源码解读…...

【STM32】入门(十四):FreeRTOS-任务
1、简述 FreeRTOS应用程序由一组独立的任务构成。 在任何时间点,应用程序中只能执行一个任务,FreeRTOS调度器负责决定所要执行的任务。 每个任务在自己的上下文中执行,不依赖于系统内的其他任务或 FreeRTOS的调度器本身。 FreeRTOS调度器负责…...

apscheduler 的基本介绍和使用
APScheduler有四大组件: 1、触发器 triggers : 触发器包含调度逻辑。每个作业都有自己的触发器,用于确定下一个任务何时运行。除了初始配置之外,触发器是完全无状态的。 有三种内建的trigger: (1)date: 特定…...

Oracle中merge Into的用法
Oracle中merge Into的用法 使用场景 在操作数据库时,数据存在的情况下,进行update操作;不存在的情况下,进行insert操作;在Oracle数据库中,能够使用merge into来实现。 基本语法 merge into table_name …...

JDK19下载、安装与测试的完整图文教程
一、下载JDK 1、官网获取:https://www.oracle.com/ 1.1 点击“Products”; 1.2 选择“Java”; 1.3 选择“Download Java”; 1.4 选择“Java downloads”,这里以最新版(JDK19)为例ÿ…...

Vector - CAPL - 获取相对时间函数
在自动化开发中,无论是CAN通信测试,还是网络管理测试,亦或是休眠唤醒等等存在时间相关的,都可能会使用相关的时间函数;今天主要介绍的就是获取当前时间,我们知道vector工具的最大优势就是稳定和精确度高&am…...

C++编程语言STL之unordered_map介绍
本文主要介绍 C 编程语言的 STL(Standard Template Library) 中 unordered_map 的相关知识,同时通过示例代码介绍 unordered_map 的常见用法。1 概述C标准库提供了四个无序关联容器(unordered associated container)&a…...

【独家】华为OD机试 - 最快检测效率-核酸(C 语言解题)
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧文章目录 最近更新的博客使用说明本期…...

【Redis应用】基于Redis实现共享session登录(一)
🚗Redis应用学习第一站~ 🚩本文已收录至专栏:数据库学习之旅 👍希望您能有所收获 👉相关推荐:使用短信服务发送手机验证码进行安全校验 一.引入 在开发项目过程中,我们常常能碰到需要登录注…...

Android framework系列2 - Init进程
1、源码 入口:system/core/init/main.cpp2 流程图 https://note.youdao.com/s/EtnCswft 3、代码详解 主入口共三步,如流程图所示,我们主要看下最后一步 入口在init.cpp下,这个阶段主要来解析init.rc并执行此文件下的命令 看到…...

2023年“网络安全”赛项江苏省淮安市选拔赛 任务书
任务书 一、竞赛时间 共计3小时。 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 第一阶段单兵模式系统渗透测试 任务一 服务器内部信息获取 任务二 网站渗透测试 任务三 Linux系统渗透提权 任务四 Web渗透测试 第二阶段分组对抗 备战阶段 攻防对抗准备工作 系统加…...

2023年Wireshark数据包分析——wireshark0051.pcap
Wireshark数据包分析 任务环境说明: 服务器场景:FTPServer220223服务器场景操作系统:未知(关闭连接)FTP用户名:wireshark0051密码:wireshark0051从靶机服务器的FTP上下载wireshark0051.pcap数据包文件,找出黑客获取到的可成功登录目标服务器FTP的账号密码,并将黑客获…...

SpringMVC的自定义配置和自动化配置
SpringBoot的自动配置MVC处理加载逻辑基于Spring Boot的MVC自动化配置由WebMvcAutoConfiguration类完成,部分关键源码:AutoConfiguration(after { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,ValidationAutoConf…...

画图说透 ZooKeeper如何保证数据一致性:选举和ZAB协议
1、zookeeper是什么? zookeeper能被各个牛逼的中间件项目中所依赖,已经说明了他的地位。一出手就是稳定的杀招。zookeeper是什么?官网中所说,zookeeper致力于开发和维护成为一个高度可靠的分布式协调器。 开局一张图,…...

错误异常捕获
1、React中错误异常捕获 在 React 中,可以通过 Error Boundaries(错误边界)来捕获错误异常。Error Boundaries 是一种 React 组件,它可以在其子组件树的渲染期间捕获 JavaScript 异常,并且可以渲染出备用 UI。React 提…...

js垃圾回收机制
内存的生命周期 ]S环境中分配的内存,一般有如下生命周期 1.内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存 2.内存使用:即读写内存,也就是使用变量、函数等 3.内存回收: 使用完毕,由垃圾回收器自动回收不再…...

YApi分析从NoSQL注入到RCE远程命令执行.md
0x00 前提 这个是前几个月的漏洞,之前爆出来发现没人分析就看了一下,也写了一片 Nosql注入的文章,最近生病在家,把这个写一半的完善一下发出来吧。 0x01 介绍 YApi是一个可本地部署的、打通前后端及QA的、可视化的接口管理平台…...

【C++】stl_list介绍和实现,list和vector区别,list vector string 迭代器失效
本篇博客详细介绍list的实现&细节讲解,并且在文章末对list和vector,string进行区分和复习 list的基本结构就是双向带头循环链表,链表和顺序表的差别我们在前面数据结构的时候早就学过了,不再赘述 在使用stl库里面list时&…...

linux-kernel-ecmp-ipv4
当使用ip route add/del添加或者删除路由时,通过触发netlink发送信息到各协议路由系统注册的netlink处理函数,如add时调用函数为inet_rtm_newroute。Equal Cost Multi Path,在ip交换网络中存在到达同一目的地址的多条不同的路径,而且每条路径…...

蒙特卡洛树搜索(MTCS)
一、目标 一种启发式的搜索算法,在搜索空间巨大的场景下比较有效 算法完成后得到一棵树,这棵树可以实现:给定一个游戏状态,直接选择最佳的下一步 二、算法四阶段 1、选择(Selection) 父节点选择UCB值最…...

【Verilog】——Verilog简介
目录 1.简介 2.什么是HDL以及HDL的功能 3.Verilog和C语言的比较 4.Verilog的用途 5.数字系统的抽象层次 1.系统级 2.算法级 3.RTL级(寄存器变换级) 6.数字系统抽象层级 7.自顶向下的结构化设计方法 8.Verilog建模 9.Verilog概述 10.Verilog模块的基本…...

【Python从入门到进阶】10、流程控制语句-循环语句(for-while)
接上篇《9、流程控制语句-条件语句(if-else)》 上一篇我们学习了Python的控制流语句的概念,以及其中的条件语句(if/else),本篇我们来学习控制流语句中的循环语句(for/while)。 一、Python中的循环 Python的循环结构就是让程序“杀个回马枪”࿰…...

超全的命令(代码)执行漏洞无回显的姿势总结(附带详细代码和测试分析过程)
目录 漏洞代码 突破方式 重定向 dnslog外部通信 burpsuite burpcollaborator外部通信 日志监听 netcat监听 反弹shell的各种姿势 漏洞代码 <?php shell_exec($_GET[a]); ?>这里使用了无回显的shell执行函数shell_exec,给html目录的权限是777 突破方…...

STM32MP157-Linux音频应用编程-简易语音助手
文章目录前言STM32MP157简易语音助手alsa-lib简介:移植alsa-lib库:libcurl库简介:移植libcurl库:API调用修改asrmain.c文件修改token.c文件录音文件IO打开音频文件硬件控制sysfs文件系统数据解析和控制多线程主循环实现效果及注意…...

Python-OpenCV图像处理:学习图像算术运算,如加减法、图像混合、按位运算,以及如何实现它们
目录 目标 图像添加 图像混合算法 按位运算 目标 学习对图像的几种算术运算,如加法、减法、位运算等。了解这些功能:cv.add()、...

并发编程——ReentrantLock
如果有兴趣了解更多相关内容,欢迎来我的个人网站看看:耶瞳空间 一:基本介绍 从Java 5开始,引入了一个高级的处理并发的java.util.concurrent包,它提供了大量更高级的并发功能,能大大简化多线程程序的编写…...

English Learning - L2 第 3 次小组纠音 [ʌ] [ɒ] [ʊ] [ɪ] [ə] [e] 2023.3.4 周六
English Learning - L2 第 3 次小组纠音 [ʌ] [ɒ] [ʊ] [ɪ] [ə] [e] 2023.3.4 周六共性问题小元音 [ʌ]小元音 [ɒ]小元音 [ʊ]小元音 [ɪ]小元音 [ə]小元音 [e]我的发音问题纠音过程共性问题 小元音 [ʌ] 口型容易偏大 解决办法:因为嘴角没有放松,…...

STM32之关门狗
看门狗介绍在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会造成整个系统的陷入…...

Apollo控制部分1-- ControlComponent组件介绍
Apollo控制部分1-- ControlComponent组件介绍摘要一、ControlComponent1、启动文件解析2、ControlComponent()组件函数解析1)ControlComponent::ControlComponent() 构造函数2)ControlComponent::Init() 初始化函数(执行一次)3&am…...