0%
这是一片思考的空间 -- arthinking
Spring 重构&代码整洁之道 软件设计 JVM 并发编程 数据结构与算法 分布式 存储 网络 微服务 设计模式
Java技术栈 - 涉及Java技术体系

重新组织函数

提炼函数

重新组织函数的前提:过长函数Long Methods

有一段代码可以被组织在一起并独立出来。可以通过 Extract Method(提炼函数) 把一段代码从原先函数中提取出来,并让函数名称解释该函数的用途。

1
2
3
4
5
6
7
void printOwing() {
printBanner();

// Print details.
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
1
2
3
4
5
6
7
8
9
void printOwing() {
printBanner();
printDetails(getOutstanding());
}

void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}

内联函数

有时候遇到某些函数,其内部代码和函数名称同样清晰易读,可能你重构了该函数,使得其内容和名称变得同样清晰,果子如此,你就应该通过 Inline Method(内联函数) 去掉这个函数。

1
2
3
4
5
6
7
8
9
class PizzaDelivery {
// ...
int getRating() {
return moreThanFiveLateDeliveries() ? 2 : 1;
}
boolean moreThanFiveLateDeliveries() {
return numberOfLateDeliveries > 5;
}
}
1
2
3
4
5
6
class PizzaDelivery {
// ...
int getRating() {
return numberOfLateDeliveries > 5 ? 2 : 1;
}
}

内联临时变量

如果有一个临时变量,只被简单表达式赋值一次,而它妨碍了其他重构手法,那么可以通过 Inline Temp(内联临时变量) 对该变量所有的引用动作替换为对他赋值的那个表达式自身。

1
2
3
4
boolean hasDiscount(Order order) {
double basePrice = order.basePrice();
return basePrice > 1000;
}
1
2
3
boolean hasDiscount(Order order) {
return order.basePrice() > 1000;
}

以查询取代临时变量

如果有一个临时变量,只被简单表达式赋值一次,也可以通过 Replace Temp with Query(以查询取代临时变量) 将这个表达式提炼到一个独立函数中,将这个临时变量所有引用点替换为对新函数的调用,方便后续重构。 Inline Temp(内联临时变量) 多半是作为 Replace Temp with Query(以查询取代临时变量) 的一部分来使用,所以真正的动机出现在后者那儿(查询取代临时变量方便方法重构)。

1
2
3
4
5
6
7
8
9
double calculateTotal() {
double basePrice = quantity * itemPrice;
if (basePrice > 1000) {
return basePrice * 0.95;
}
else {
return basePrice * 0.98;
}
}
1
2
3
4
5
6
7
8
9
10
11
double calculateTotal() {
if (basePrice() > 1000) {
return basePrice() * 0.95;
}
else {
return basePrice() * 0.98;
}
}
double basePrice() {
return quantity * itemPrice;
}

引入解释性变量

当处理一个拥有大量局部变量的算法的时候,直接使用 Extract Method(提炼函数) 绝非易事。这种情况下可以使用 Introduce Explaining Variable(引入解释性变量) 来理清代码,梳理清楚逻辑之后,再使用 Replace Temp with Query(以查询取代临时变量) 把中间引入的解释性临时变量去掉,方便重构。况且,如果我最终使用 Replace Method with Method Object(以函数对象取代函数) ,那些引入的解释性临时变量也有其价值。

1
2
3
4
5
6
7
8
void renderBanner() {
if ((platform.toUpperCase().indexOf("MAC") > -1) &&
(browser.toUpperCase().indexOf("IE") > -1) &&
wasInitialized() && resize > 0 )
{
// do something
}
}
1
2
3
4
5
6
7
8
9
void renderBanner() {
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIE = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResized = resize > 0;

if (isMacOs && isIE && wasInitialized() && wasResized) {
// do something
}
}

分解临时变量

假如有某个临时变量被赋值超过一次,它既不是循环变量,也不被用于收集计算结果,那么可以使用 Split Temporary Variable(分解临时变量) 针对每次赋值,创造一个独立、对应的临时变量。

1
2
3
4
double temp = 2 * (height + width);
System.out.println(temp);
temp = height * width;
System.out.println(temp);
1
2
3
4
final double perimeter = 2 * (height + width);
System.out.println(perimeter);
final double area = height * width;
System.out.println(area);

移除对参数的赋值

函数的参数都应该被当成final类型来处理,假设代码中对一个参数进行了赋值,请通过 Remove Assignments to Parameters(移除对参数的赋值) 以一个临时变量取代该参数的位置。如果函数体很长,可以尝试在参数前添加final关键词,检查方法体中是否有对参数进行重新赋值。

1
2
3
4
5
6
int discount(int inputVal, int quantity) {
if (inputVal > 50) {
inputVal -= 2;
}
// ...
}
1
2
3
4
5
6
int discount(int inputVal, int quantity) {
if (inputVal > 50) {
inputVal -= 2;
}
// ...
}

以函数对象取代函数

如果有一个大型函数,变量太混乱,难以替换,其中对局部变量的使用是你无法采用 Extract Method(提炼函数) ,可以通过 Replace Method with Method Object(以函数对象取代函数) 将这个函数放进一个单独的对象中,如此一来,局部变量就编程了对象内的字段,这样就可以很方便的在同一个对象中将这个大型函数分解为多个小型函数了。

1
2
3
4
5
6
7
8
9
class Order {
// ...
public double price() {
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
// Perform long computation.
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Order {
// ...
public double price() {
return new PriceCalculator(this).compute();
}
}

class PriceCalculator {
private double primaryBasePrice;
private double secondaryBasePrice;
private double tertiaryBasePrice;

public PriceCalculator(Order order) {
// Copy relevant information from the
// order object.
}

public double compute() {
// Perform long computation.
}
}

替换算法

针对不够好的算法,可以使用 Substitute Algorithm(替换算法) 引入更加清晰的算法。

使用这项朝那个狗手法之前,请先确定自己已经尽可能分解了原先函数。替换一个巨大而复杂的算法是非常困难的,只有先降它分解为较简单的小型函数,才能更有把握地进行算法替换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String foundPerson(String[] people){
for (int i = 0; i < people.length; i++) {
if (people[i].equals("Don")){
return "Don";
}
if (people[i].equals("John")){
return "John";
}
if (people[i].equals("Kent")){
return "Kent";
}
}
return "";
}
1
2
3
4
5
6
7
8
9
10
String foundPerson(String[] people){
List candidates =
Arrays.asList(new String[] {"Don", "John", "Kent"});
for (int i=0; i < people.length; i++) {
if (candidates.contains(people[i])) {
return people[i];
}
}
return "";
}

欢迎关注我的其它发布渠道

订阅IT宅
内功修炼
Java技术栈
Java架构杂谈是IT宅精品文章公众号,欢迎订阅:
📄 网络基础知识:两万字长文50+张趣图带你领悟网络编程的内功心法 📄 HTTP发展史:三万长文50+趣图带你领悟web编程的内功心法 📄 HTTP/1.1:可扩展,可靠性,请求应答,无状态,明文传输 📄 HTTP/1.1报文详解:Method,URI,URL,消息头,消息体,状态行 📄 HTTP常用请求头大揭秘 📄 HTTPS:网络安全攻坚战 📄 HTTP/2:网络安全传输的快车道 📄 HTTP/3:让传输效率再一次起飞 📄 高性能网络编程:图解Socket核心内幕以及五大IO模型 📄 高性能网络编程:三分钟短文快速了解信号驱动式IO 📄 高性能网络编程:彻底弄懂IO复用 - IO处理杀手锏,带您深入了解select,poll,epoll 📄 高性能网络编程:异步IO:新时代的IO处理利器 📄 高性能网络编程:网络编程范式 - 高性能服务器就这么回事 📄 高性能网络编程:性能追击 - 万字长文30+图揭秘8大主流服务器程序线程模型
📄 Java内存模型:如果有人给你撕逼Java内存模型,就把这些问题甩给他 📄 一文带你彻底理解同步和锁的本质(干货) 📄 AQS与并发包中锁的通用实现 📄 ReentrantLock介绍与使用 📄 ReentrantReadWriteLock介绍与使用 📄 ReentrantLock的Condition原理解析 📄 如何优雅的中断线程 📄 如何优雅的挂起线程 📄 图解几个好玩的并发辅助工具类 📄 图解BlockingQueue阻塞队列
📄 消息队列那么多,为什么建议深入了解下RabbitMQ? 📄 高并发异步解耦利器:RocketMQ究竟强在哪里? 📄 Kafka必知必会18问:30+图带您看透Kafka
📄 洞悉MySQL底层架构:游走在缓冲与磁盘之间 📄 SQL运行内幕:从执行原理看调优的本质 📄 洞悉Redis技术内幕:缓存,数据结构,并发,集群与算法