Refactoring 一直以来都是项目开发中的热点和难点,考虑到更通俗的易懂,本文是什么(what),为什么(why)以及怎么做(how)的三个点进行展开讲解。因为重构不是独立的对某一块代码优化,而是让系统以及代码的相互协调作用表现最佳的改进过程,所以文章的内容可能存在交集的部分,而已理解的情况下,大家可自行跳过。
重构是在不创建新的功能方法前提下,改进系统代码的过程,让代码逻辑和架构设计变得更加干净和清晰
重构主要的目的在于解决项目存在的技术债务,将原有的代码和设计更清晰简单,提高系统性能。
在重构之前,请先理解项目并罗列需要重构的清单
// 优化前
double calculateTotal() {
double basePrice = quantity * itemPrice;
if (basePrice > 1000) {
return basePrice * 0.95;
}
else {
return basePrice * 0.98;
}
}
// 优化后:
double calculateTotal() {
if (basePrice() > 1000) {
return basePrice() * 0.95;
}
else {
return basePrice() * 0.98;
}
}
double basePrice() {
return quantity * itemPrice;
}
// 优化前
funcDemo (int a,String b,double c,Map<String,String>d,List<Object> e){}
// 优化后
funcDemo (Params p){}
// 参数聚合
class Params {
int a
String b
double c
Map<String,String>d
List<Object> e
}
/**
当然这样做的好处就是方法的入参通过一个对象来描述,这样就很清晰,
但是还有一个缺点就是你需要维护多个struct对象结构体,
因为每有一个方法需要将入参聚合成对象的时候,
你就需要创建一个新的对象。
*/
很多人会担心如果方法过多,这样会影响性能吗?其实几乎在所以情况下,影响微小的可以忽略不计
与上述长方法一致,对于开发人员来说,将一个新功能放置在现有类中比在该功能上创建一个新类精神上来看更简单和轻松。
// 优化前
class Soldier {
public int health;
public int damage;
public int weaponStatus;
public int getDamage() {
// ...
}
public void attack() {
// ...
}
}
//优化后
class Soldier {
public int health;
public Weapon weapon;
public void attack() {
// ...
}
}
class Weapon {
public int damage;
public int weaponStatus;
public int getDamage() {
// ...
}
}
//优化前
//优化前
String[] row = new String[2];
row[0] = "Liverpool";
row[1] = "15";
//优化后
Performance row = new Performance();
row.setName("Liverpool");
row.setWins("15");
//优化前
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
//优化后
boolean withinPlan = plan.withinRange(daysTempRange);
//优化前
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
//优化后
boolean withinPlan = plan.withinRange(daysTempRange);
int basePrice = quantity * itemPrice;
double seasonDiscount = this.getSeasonalDiscount();
double fees = this.getFees();
double finalPrice = discountedPrice(basePrice, seasonDiscount, fees);
int basePrice = quantity * itemPrice;
/**
*在上述查询seasonalDiscount和Fees的字段完全可以在discountedPrice内部分方法调用,而是不是通过
*值传递的方式透传给方法
*double seasonDiscount = this.getSeasonalDiscount();
*double fees = this.getFees();
*/
double finalPrice = discountedPrice(basePrice);
// 优化前
queryA(String id){
db = initDB(mysqlIp,mysqlHost,userName,passWord)
db.query(id)
}
deleteA(String id){
db = initDB(mysqlIp,mysqlHost,userName,passWord)
db.delete(id)
}
//优化后
class DBConfig {
int port
String host
String userName
String Password
}
queryA(String id){
db = initDB(DBConfig.host,DBConfig.port,DBConfig.userName,DBConfig.Password)
db.query(id)
}
deleteA(String id){
db = initDB(DBConfig.host,DBConfig.port,DBConfig.userName,DBConfig.Password)
db.delete(id)
}
代码中出现一个方法里面大量的if 或者switch的语句,严重的话这样的判断语句甚至超过了10个以上。
在项目跌代中,产品的需求在同一个方向做了不同的逻辑,并且随着版本迭代增加,该方向的个性化逻辑也就逐渐增加
// 优化前
void funcA(String A){
if A="a"{
doSomethingA();
}else if A="b"{
doSomethingB();
}else if A="c"{
doSomethingC();
}else if A="d"{
doSomethingD();
}else {
//default
}
}
void doSomethingA()
void doSomethingB()
void doSomethingC()
void doSomethingD()
// 优化后
static Map<String,DoService> MethodFatory = new HashMap<String,DoService>();
static{
MethodFatory.put("a",ALogic);
MethodFatory.put("b",BLogic);
MethodFatory.put("c",CLogic);
MethodFatory.put("d",DLogic);
}
//调用条件方法
void funcA(String A){
MethodFatory.get(A).doSomething();
}
interface Logic {
void doSomething();
}
class ALogic implement Logic {
void doSomething();
}
class BLogic implement Logic {
void doSomething();
}
class CLogic implement Logic {
void doSomething();
}
class DLogic implement Logic {
void doSomething();
}
// 优化前
void setValue(String name, int value) {
if (name.equals("height")) {
height = value;
return;
}
if (name.equals("width")) {
width = value;
return;
}
Assert.shouldNeverReachHere();
}
// 优化后
void setHeight(int arg) {
height = arg;
}
void setWidth(int arg) {
width = arg;
}
//优化前,也许大部分的人都这样做
if (customer == null) {
plan = BillingPlan.basic();
}
else {
plan = customer.getPlan();
}
//优化后
//创建空的之类对象
class NullCustomer extends Customer {
boolean isNull() {
return true;
}
// 空的之类对象负责创建新对象
Plan getPlan() {
return new NullPlan();
}
}
//如果是空就使用空子类对象
customer = (order.customer != null) ?
order.customer : new NullCustomer();
// 如果非空直接使用正常对象,如果空则使用空的之类的创建的对象来处理.
plan = customer.getPlan();
通常开发人员在局部方法中调用下游的函数,且需要传大量的字段的时候,这个时候他们往往使用局部临时变量。
将方法转换为一个单独的类,以便局部变量成为该类的字段。然后,您可以将方法拆分为同一类中的多个方法。
// 优化前
class Order {
// ...
public double price() {
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
}
}
//优化后
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) {
}
public double compute() {
}
}
代码结构变得更加清晰和容易组织
如果子类仅使用从其父类继承的某些方法和属性,则层次结构是不合理的。不需要的方法可以简单地不使用或重新定义并释放异常。
// 优化前
class Manager extends Employee {
public Manager(String name, String id, int grade) {
this.name = name;
this.id = id;
this.grade = grade;
}
// ...
}
// 优化后
class Manager extends Employee {
public Manager(String name, String id, int grade) {
super(name, id);
this.grade = grade;
}
// ...
}
通俗的说就是存在两个类,接口名称不一样,但是实现的功能基本一致
开发者在创建接口功能的时候,未了解已经存在一个类实现了该接口需要做的功能
有时你只需要给某一个类增加一个字段的时候,而不得不去同时修改其他几个与本次改动不相关的代码。
通常这一类现象产生的原因是代码的编写的时候代码结果不良导致
class People {
public int age;
public int height;
public int weight;
public String Name;
public String getName() {
// ...
}
public void getAge() {
// ...
}
}
//优化后
class PeopleBody {
public String Name;
public BodyInfo bodyInfo;
public String getName() {
// ...
}
}
class BodyInfo {
public int age;
public int height;
public int weight;
public int getAge() {
// ...
}
}
// 如果该字段是公有的话,这里需要特殊优化
class Person {
public String name;
}
/**
*优化后
*需要将公有改成私有,这样修改后以后重构改动将变得更简单
*/
class Person {
private String name;
public String getName() {
return name;
}
public void setName(String arg) {
name = arg;
}
}
每当为一个类创建一个子类时,就会发现自己需要为另一个类创建一个子类
随着项目需求迭代,新的类逐渐增加,后续一些的变动将越来越复杂
可以分两步对并行类层次结构进行重复数据删除。首先,使一个层次结构的实例引用另一个层次结构的实例。然后使用上述的“当一个类的某个方法调用的频率在其他类中比在自己类中还要高的时候,则创建一个新类,把旧类实现的所以方法都转移过去,最后并删除旧类里的这个方法”和“当然一个类的成员变量使用频率在其他类的地方调用更高的时候,则需要创建一个新的类,把这些成员变量转移过去,最后并删除旧类里的这些成员变量。”的方式解决
一种方法充满过度性的注释
当方法创建者意识到自己的代码不直观或不明显时,通常会出于最佳意图创建注释,从而导致了过多的无效注释,如果你觉得没有注释就无法理解代码片段,请尝试以无需注释的方式更改代码结构。
// 优化前
void renderBanner() {
if ((platform.toUpperCase().indexOf("MAC") > -1) &&
(browser.toUpperCase().indexOf("IE") > -1) &&
wasInitialized() && resize > 0 )
{
// do something
}
}
//优化后
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
}
}
// 优化前
void printOwing() {
printBanner();
// Print details.
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
// 优化后
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
// 优化前,我们进程需要处理的
if (message== null || message.equls("")) {
throw new IllegalArgumentException("输入信息错误!");
}
// 优化后 使用断言我们可以自己这样使用
Assert.hasText((message, "输入信息错误!");
两块代码几乎完全一致
当多个程序员同时在同一程序的不同部分上工作时,通常会发生复制。由于他们正在执行不同的任务,因此他们可能不知道自己的同事已经编写了类似的代码,这些代码可以重新用于他们自己的需求。
// 优化前
public class Manager: Employee
{
public Manager(string name, string id, int grade)
{
this.name = name;
this.id = id;
this.grade = grade;
}
// ...
}
// 优化后
public class Manager: Employee
{
public Manager(string name, string id, int grade): base(name, id)
{
this.grade = grade;
}
// ...
}
// 优化前
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 String.Empty;
}
// 优化后
string FoundPerson(string[] people)
{
List<string> candidates = new List<string>() {"Don", "John", "Kent"};
for (int i = 0; i < people.Length; i++)
{
if (candidates.Contains(people[i]))
{
return people[i];
}
}
return String.Empty;
}
// 优化前
double DisabilityAmount()
{
if (seniority < 2)
{
return 0;
}
if (monthsDisabled > 12)
{
return 0;
}
if (isPartTime)
{
return 0;
}
// Compute the disability amount.
// ...
}
// 优化后
double DisabilityAmount()
{
if (IsNotEligibleForDisability())
{
return 0;
}
// Compute the disability amount.
// ...
}
bool IsNotEligibleForDisability(){
if (seniority < 2)
{
return true;
}
if (monthsDisabled > 12)
{
return true;
}
if (isPartTime)
{
return truetrue;
}
// 优化前
if (IsSpecialDeal())
{
total = price * 0.95;
Send();
}
else
{
total = price * 0.98;
Send();
}
// 优化后
if (IsSpecialDeal())
{
total = price * 0.95;
}
else
{
total = price * 0.98;
}
Send();
理解和维护一个类的时间和成本是非常高的,如果对于一个经常不实用或者很少值得你去额外关注的话,请删除这个类
经常一些类被设计为具有全部功能,但经过一些重构后,它变得很小了。也许它旨在支持从未完成的未来开发工作。
数据类是指仅包含字段和用于访问它们的原始方法(获取器和设置器)的类。这些只是其他类使用的数据的容器。这些类不包含任何其他功能,并且不能独立地对其拥有的数据进行操作
当新创建的类仅包含几个公共字段(甚至可能有少数getter / setter)时,这是很正常的事情。但是对象的真正力量在于它们可以包含行为类型或对其数据的操作。
不再使用变量,参数,字段,方法或类。
当软件要求发生变化或进行了更正时,没有人有时间清理旧代码
总有一些用不上的类,方法以及变量或参数
有时会以“以防万一”的方式创建代码,以支持从未实现的预期未来功能。结果,创建的代码几乎没有被使用,到后来维护变得难以理解
// 优化前
class PizzaDelivery {
int getRating() {
return moreThanFiveLateDeliveries() ? 2 : 1;
}
boolean moreThanFiveLateDeliveries() {
return numberOfLateDeliveries > 5;
}
}
// 优化后
class PizzaDelivery {
int getRating() {
return numberOfLateDeliveries > 5 ? 2 : 1;
}
}
当前类方法访问另一个对象的数据比访问其自身的数据更多
将过多的字段定义在数据类中,在这种情况下,您可能还希望将对数据的操作移至此类。
一个类使用另一个类的内部的成员变量和方法
开发中把所有的集中点都放到一个类中,这样导致其他的类与该类有了过多的密切联系,譬如依赖该类的方法和该类的成员变量
在代码中,也许经常看到funcA()->funcB()->func()C-funcD(),这样串行的依赖
当客户端请求另一个对象,该对象又请求另一个对象,依此类推,就会发生消息链。这些链意味着客户端依赖于沿类结构的导航。这些关系中的任何更改都需要修改客户端
如果一个类只有一个简单的方法,调用方式却是通过一个代理类给其他人提供调用
如果一个方法的大部分的方法都是代理到另一个类的话,删除代类,强直接调用另一个类的最终方法方法
领取专属 10元无门槛券
私享最新 技术干货