为什么要重构,因为我们总是不可能在一开始就写出简洁的代码
为了类更小,函数更小,越小,意味着它所承担的少,求求你了,给代码减负吧!!(甘哥你在看吗?)
如何借助 idea来实现快速重构
一个函数几百行,需要依靠注释才能看懂,这种时候我们就要用 大名鼎鼎的提炼函数,在代码整洁之道这样说:阅读一段好的代码就像看看报一样!
鼠标选中,右键
public class ExtractMethodExampleOriginal {
private String _name;
private Hashtable orders;
void printOwing() {
Enumeration e = orders.elements();
double outstanding = 0.0;
//打印横幅
System.out.println("************************************");
System.out.println("***********Customer Owes***********");
System.out.println("************************************");
//计算未完成
while (e.hasMoreElements()) {
Order each = (Order) e.nextElement();
outstanding += each.getAmount();
}
//打印详细信息
System.out.println("name:" + _name);
System.out.println("amount" + outstanding);
}
}
public class ExtractMethodExampleOriginal2 {
private String _name;
private Hashtable orders;
void printOwing() {
printBanner();
double outstanding = calculateOutstanding();
printDetails(outstanding);
}
private void printDetails(double outstanding) {
System.out.println("name:" + _name);
System.out.println("amount" + outstanding);
}
private double calculateOutstanding() {
double outstanding = 0.0;
while (true) {
Enumeration elements = orders.elements();
if (!elements.hasMoreElements()) break;
Order each = (Order) elements.nextElement();
outstanding += each.getAmount();
}
return outstanding;
}
private static void printBanner() {
System.out.println("************************************");
System.out.println("***********Customer Owes***********");
System.out.println("************************************");
}
}
当函数代码和名称一样好懂时,需要内联,一个临时变量被简单的赋值了一次,需要内联,,刚刚把代码提出去,现在又放回去了=。=
isCurrentOutstandingBiggerThanZero方法的内容是 getCurrentOutstanding() > 0 ;我一看就知道是大于0啊,反复套娃是吧!这时候就要内联函数
public class InlineMethodAndTempExampleOriginal {
double calcPreviousOutstanding() {
return isCurrentOutstandingBiggerThanZero() ? 2 : 1;
}
double calcAfterOutstanding() {
return isCurrentOutstandingBiggerThanZero() ? 20 : 10;
}
boolean isCurrentOutstandingBiggerThanZero() {
return getCurrentOutstanding() > 0;
}
double getCurrentOutstanding() {
return 10;
}
}
选中方法,右键重构,选择
内联操作(Ctrl+Alt+N) 内联方法和内联变量的快捷键都是Ctrl+Alt+N
double calcPreviousOutstanding() {
return getCurrentOutstanding() > 0 ? 2 : 1;
}
double calcAfterOutstanding() {
return getCurrentOutstanding() > 0 ? 20 : 10;
}
double getCurrentOutstanding() {
return 10;
}
就是将一个临时变量的计算过程,放到一个函数中。然后变量去引用函数。
这样提有什么好处?
1.是扩大了范围,本来临时变量是只在函数内可见。
2.更容易提方法
public class ReplaceTempWithQueryExampleOriginal {
private int quantity;
private int itemPrice;
public double calcPrice() {
int basePrice = quantity * itemPrice;
double discountFactor;
if (basePrice > 1000) {
discountFactor = 0.95;
} else {
discountFactor = 0.98;
}
return basePrice * discountFactor;
}
}
1.首先将 quantity * itemPrice 提取函数
2. 选中 提取函数
double discountFactor;
if (basePrice > 1000) {
discountFactor = 0.95;
} else {
discountFactor = 0.98;
}
3.这时候 是不是 发现discountFactor 只被用了一次,一看到这种情况,DNA就触动了,TM直接将 discountFactor 内联变量,
public double calcPrice() {
int basePrice = getBasePrice();
double discountFactor = getDiscountFactor(basePrice);
return basePrice * discountFactor;
}
private static double getDiscountFactor(int basePrice) {
double discountFactor;
if (basePrice > 1000) {
discountFactor = 0.95;
} else {
discountFactor = 0.98;
}
return discountFactor;
}
public class ReplaceTempWithQueryExample2 {
private int quantity;
private int itemPrice;
public double calcPrice() {
int basePrice = getBasePrice();
return basePrice * getDiscountFactor(basePrice);
}
private static double getDiscountFactor(int basePrice) {
double discountFactor;
if (basePrice > 1000) {
discountFactor = 0.95;
} else {
discountFactor = 0.98;
}
return discountFactor;
}
private int getBasePrice() {
return quantity * itemPrice;
}
}
当你有一个复杂的表达式时,将它的结果或者其中一部分的结果放进一个临时变量
private void introduceVariableConditionalMethod() {
if (platform.toUpperCase().indexOf("MAC") > -1 &&
browser.toUpperCase().indexOf("IE") > -1 && isInitialized() && resize > 0) {
//do something
}
}
private double calcPrice() {
return quantity * itemPrice - Math.max(0, quantity - 500) * itemPrice * 0.05 + Math.min(quantity * itemPrice * 0.1, 100.0);
}
private boolean isInitialized() {
return false;
}
上面introduceVariableConditionalMethod函数中的if中有 大量的一眼看过去不知道意思的,这时候就要将一个个表达式的结果用 临时变量保存,专业术语叫 引入解释性变量(Ctrl+Alt+V),干就完事了,兄弟们!!
1.我们先对introduceVariableConditionalMethod函数下手
private void introduceVariableConditionalMethod() {
boolean isMacos = platform.toUpperCase().indexOf("MAC") > -1;
boolean isIe = browser.toUpperCase().indexOf("IE") > -1;
boolean wasResized = resize > 0;
if (isMacos && isIe && isInitialized() && wasResized) {
//do something
}
}
2.calcPrice函数,我k这么长,这谁写的代码!我写的,那没事了,哈哈哈哈!!
private double calcPrice() {
return quantity * itemPrice - Math.max(0, quantity - 500) * itemPrice * 0.05 + Math.min(quantity * itemPrice * 0.1, 100.0);
}
先引入解释性变量 这样有助于我们理解,下面代码是不是一目了然
private double calcPrice() {
int totalPrice = quantity * itemPrice;
double discount = Math.max(0, quantity - 500) * itemPrice * 0.05;
double shippingCost = Math.min(quantity * itemPrice * 0.1, 100.0);
return totalPrice - discount + shippingCost;
}
其实到这已经可以了,不过还记得上面我们说了啥吗?
难道是以查询来取代临时变量,没错,不过我就不写,就是懒。
使用引入解释性变量 还有助于提取函数(以查询来取代临时变量本质上就是提取函数,不要说你没看出来)
private void introduceVariableConditionalMethod() {
if (platform.toUpperCase().indexOf("MAC") > -1 &&
browser.toUpperCase().indexOf("IE") > -1 && isInitialized() && resize > 0) {
//do something
}
}
private double calcPrice() {
int basePrice = quantity * itemPrice;
double discount = Math.max(0, quantity - 500) * itemPrice * 0.05;
double shippingCost = Math.min(quantity * itemPrice * 0.1, 100.0);
return basePrice - discount + shippingCost;
}
private boolean isInitialized() {
return false;
}
当有一个临时变量,被多次赋值(一个变量被承担了多个责任),但它既不是循环变量,也不是计算结果。我从业这么多年还未见到,有人这样写!
double calcDistanceTravelled(int time) {
double result;
// 加速度 /力除以质量
double acc = primaryForce / mass;
int primaryTime = Math.min(time, initPrimaryTime);
result = 0.5 * acc * primaryTime * primaryTime;
int secondaryTime = time - initPrimaryTime;
if (secondaryTime > 0) {
double primaryVel = acc * initPrimaryTime;
acc = (primaryForce + secondaryForce) / mass;
result += primaryVel * secondaryTime + 0.5 * acc * secondaryTime * secondaryTime;
}
return result;
}
例子中可以看出acc(加速度)被赋值多次,
1.先将acc第二次 赋值改为二次加速度
2.然后将第一次acc 使用 shift+F6 可以快速将acc 全部重命名。
当然这份代码还能优化~
double calcDistanceTravelled(int time) {
double result;
// 加速度 /力除以质量
double primaryAcc = primaryForce / mass;
int primaryTime = Math.min(time, initPrimaryTime);
result = 0.5 * primaryAcc * primaryTime * primaryTime;
int secondaryTime = time - initPrimaryTime;
if (secondaryTime > 0) {
double primaryVel = primaryAcc * initPrimaryTime;
double secondaryAcc = (primaryForce + secondaryForce) / mass;
result += primaryVel * secondaryTime + 0.5 * secondaryAcc * secondaryTime * secondaryTime;
}
return result;
}
代码中对入参赋值
public int changeSimpleTypeParameterValueAndReturn(int inputVal) {
if (inputVal < 50) {
inputVal = inputVal * 2;
}
if (inputVal > 60) {
inputVal = inputVal + 1;
}
return inputVal;
}
用一个临时变量取代该参数
注意java 中是按值传递,对参数赋值是无意义的
那没有意义,那我直接复用这个变量不行吗?
我看你之前说说的都忘了,分解临时变量,一个变量被多次赋值,一个人承担多个责任不累吗?
public int changeSimpleTypeParameterValueAndReturn(int inputVal) {
int result=inputVal;
if (inputVal < 50) {
result = inputVal * 2;
}
if (inputVal > 60) {
result = inputVal + 1;
}
return result;
}
当你有一个大型函数,局部变量很多,提取函数变得很困难, 就是将函数放到一个对象中,其中的局部变量变为对象的字段,这样就更容易的将大型函数拆成多个 小的
还没看到合适的
将原本不容易理解的算法替换成成一个更清晰的算法
public String findPerson(String[] people) {
for (int i = 0; i < people.length; i++) {
if (people[i].equals("Tom")) {
return "Tom";
}
if (people[i].equals("Sam")) {
return "Sam";
}
if (people[i].equals("Tim")) {
return "Tim";
}
}
return "";
}
啊这,没啥说的
public String findPerson(String[] people) {
List candidates = List.of("Tom", "Sam", "Tim");
for (String person : people) {
if (candidates.contains(person)) {
return person;
}
}
return "";
}
当一个类中的函数与另一个的的关系更强,这时候通常将该函数提取到此类中
例子
public class Account {
private AccountType type;
private int daysOverdrawn;
public Account(AccountType type, int daysOverdrawn) {
this.type = type;
this.daysOverdrawn = daysOverdrawn;
}
public double calcOverdraftCharge() {
if (type.isPremium()) {
double result = 10;
if (daysOverdrawn > 7) {
result += (daysOverdrawn - 7) * 0.85;
}
return result;
} else {
return daysOverdrawn * 1.75;
}
}
public double calcBankCharge() {
double result = 4.5;
if (daysOverdrawn > 0) {
result += calcOverdraftCharge();
}
return result;
}
}
public class AccountType {
public static final int ACCOUNT_TYPE_PREMIUM = 1;
public static final int ACCOUNT_TYPE_SENIOR = 2;
private int accountType;
public AccountType(int accountType) {
this.accountType = accountType;
}
public boolean isPremium() {
return ACCOUNT_TYPE_PREMIUM == accountType;
}
}
我们可以看见 calcOverdraftCharge这个函数用来计算透支费用,它会根据不同的AccountType(账号类型) 来进行计算,可以感觉到这个函数和AccountType类的关系更强。
不管是重构代码还是使用设计模式,都需要对业务有一定的了解,不然就是纸上谈兵
开始对calcOverdraftCharge重构
1. 将 daysOverdrawn变为形参,选中daysOverdrawn变量 使用引入形参(CTRL+ALT+P) 然后代理方法(ALT+SHIFT+O)
有的时候因为快捷键冲突可能和导致失效,使用右键重构(CTRL+ALT+SHIFT+T)
因为后面我们要搬移函数,但是它依赖daysOverdrawn这个变量
public double calcOverdraftCharge() {
return calcOverdraftCharge(daysOverdrawn);
}
public double calcOverdraftCharge(int daysOverdrawn) {
if (type.isPremium()) {
double result = 10;
if (daysOverdrawn > 7) {
result += (daysOverdrawn - 7) * 0.85;
}
return result;
} else {
return daysOverdrawn * 1.75;
}
}
public class Account {
private AccountType type;
private int daysOverdrawn;
public Account(AccountType type, int daysOverdrawn) {
this.type = type;
this.daysOverdrawn = daysOverdrawn;
}
public double calcOverdraftCharge() {
return type.calcOverdraftCharge(daysOverdrawn);
}
public double calcBankCharge() {
double result = 4.5;
if (daysOverdrawn > 0) {
result += calcOverdraftCharge();
}
return result;
}
}
public class AccountType {
public static final int ACCOUNT_TYPE_PREMIUM = 1;
public static final int ACCOUNT_TYPE_SENIOR = 2;
private int accountType;
public AccountType(int accountType) {
this.accountType = accountType;
}
public boolean isPremium() {
return ACCOUNT_TYPE_PREMIUM == accountType;
}
public double calcOverdraftCharge(int daysOverdrawn) {
if (isPremium()) {
double result = 10;
if (daysOverdrawn > 7) {
result += (daysOverdrawn - 7) * 0.85;
}
return result;
} else {
return daysOverdrawn * 1.75;
}
}
}
类中字段被其它类用的更多,这就像自己的老婆老往别人家跑,你说这你能受的了吗?这不让她搬出去?
和上面搬移方法一样 F6(只适用于静态字段)
沉默是今夜的康桥!
一个类承担的过多不属于它的职责
public class Person {
private final TelephoneNumber telephoneNumber = new TelephoneNumber();
private String name;
public Person(String name, String officeAreaCode, String officeNumber) {
this.name = name;
this.telephoneNumber.officeAreaCode = officeAreaCode;
this.telephoneNumber.officeNumber = officeNumber;
}
public String getTelephoneNumber() {
return telephoneNumber.getTelephoneNumber();
}
public class TelephoneNumber {
String officeAreaCode;
String officeNumber;
public TelephoneNumber() {
}
public String getTelephoneNumber() {
return "(" + officeAreaCode + ")" + officeNumber;
}
}
}
当类的职责过少时,就将改类内联到和它联系紧密的类
首先查看调用点(Alt+F7),然后重构
客户通过委托类来调用另一个对象,应该在服务类上直接返回客户想要的.这样的好处就是频闭了内部的实现,日后做变动时用户是无感知的
类中做了大量简单的委托,这时候应该让客户直接调用
关于字段的访问要么直接访问,要么通过函数来间接访问.
间接访问的好处:
在我们公司中算法端的业务中存在大量的 return 数组,通过不同的下标来表明其含义
其实应该 用对象来取代数组
在阿里巴巴开发手册中明确要求不能出现魔法值,如果一个常量具有特殊意义那就应该创建一个常量并为其命名
你的各个子类中唯一差别只是在返回常量数据的函数上
public interface Client {
PtyOsType getTtyOsType();
}
public class CmdClient implements Client{
@Override
public PtyOsType getTtyOsType() {
return PtyOsType.CMD;
}
}
public class LinuxBashClient implements Client {
@Override
public PtyOsType getTtyOsType() {
return PtyOsType.LINUX_BASH;
}
}
public abstract class Client {
public final PtyOsType PtyOsType ;
public Client(com.example.core.client.PtyOsType ptyOsType) {
PtyOsType = ptyOsType;
}
public PtyOsType getTtyOsType(){
return PtyOsType;
}
}
public class ClientFactory {
public Client getClient(PtyOsType ptyOsType) {
switch (ptyOsType) {
case CMD -> {
return SingletonClient.CMD_CLIENT.getInstance();
}
case LINUX_BASH -> {
return SingletonClient.BASH_CLIENT.getInstance();
}
default -> throw new IllegalStateException("没有该客户端");
}
}
public Client getClient(Integer id) {
return getClient(PtyOsType.getById(id));
}
public enum SingletonClient {
CMD_CLIENT(new CmdClient(PtyOsType.CMD)),BASH_CLIENT(new LinuxBashClient(PtyOsType.LINUX_BASH));
private final Client instance;
SingletonClient(Client client){
instance = client;
}
public Client getInstance(){
return instance;
}
}
}
重构:改善即有代码的设计