代码重构相关内容
提炼变量
其目标是将一个复杂表达式或语句分解成更小的部分,并将其存储在变量中。提高代码可读性和复用性
复杂的表达式
有些时候为了方便我们会把业务处理的逻辑写在一起,如果参与处理的内容较多时我们就会创造出一个非常长且难以理解的表达式。当其他开发同学尝试接手这块代码的时候,这段复杂的逻辑将成为他理解代码的一个障碍。如果将长的表达式进行拆解,虽然代码会增多,但是通过一些中间变量我们可以更容易得理解代码。当一个表达式被赋值给一个变量后,在后续使用的时候,如果这段逻辑参与了后续的代码业务时,可以使用变量来替代,从而提高了代码复用程度。
是否需要提炼变量需要考虑下面的场景
- 需要多次使用相同的表达式或值时,避免重复代码并提高可读性。
- “魔法值”,一些在代码中被直接设置的字符串等。在未来当您需要更改代码中的某个业务涉及的值时,你可能需要修改多处。
- 代码中存在复杂的条件表达式时,需要将这些表达式进行拆分为这些部分分配有意义的变量名,从而提高可读性和可维护性。
- 表达式的内容会被其他方法使用,提炼变量将结果存储在变量中。
如何拆分表达式
- 选择需要重构的代码块
- 根据业务将表达式拆分成更小的部分,如果不是很容易拆分,可以先拆成较大的几部分然后单独细分。或者先将容易拆分的内容拆分出来。
- 选择一个有意义的名称来为新变量命名。名称应该清晰地描述变量的作用。
- 修改代码中相关逻辑内容,将变量插入到代码中。
- 测试
为变量起一个好名字
- 变量名应该直接说明变量所代表的内容,如果其返回的并非布尔类型,名称中需要包含其数据类型。
- 尽量避免缩写,缩写虽然让代码变得简洁,但如果不是众所周知的缩写,其他人阅读时可能出现理解偏差
- 如果需要,可以在变量名中添加适当的前缀或后缀。比如标记这个变量是个集合(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
并不需要考虑这个变量此时应该保存的值内容。