《重构实验》

一、实验目的
1.理解重构在软件开发中的作用
2.熟悉常见的代码坏味道和重构方法

二、实验内容和要求
1.阅读:Martin Fowler 《重构-改善既有代码的设计》
2.掌握你认为最常见的8种代码坏味道及其重构方法
3.从你过去写过的代码或Github等开源代码库上寻找这8种坏味道,并对代码进行重构

三、主要实验内容·
代码坏味道一:Duplicated Code(重复的代码)

1.	ParkLocal pl = BeanContext.getPark();  
2.	//设置F15任务  
3.	ObjectBean ob0 = pl.create("Fan", "F15", "true");  
4.	String Task0 = ob0.toString();  
5.	if(Task0.equals("Fan.F15:true")){
    
      
6.	System.out.println("设置任务F15成功");  
7.	}  
8.	//设置F16任务  
9.	ObjectBean ob1 = pl.create("Fan", "F16", "true");  
10.	String Task1 = ob1.toString();  
11.	if(Task1.equals("Fan.F16:true")){
    
      
12.	System.out.println("设置任务F16成功");  
13.	}  
14.	//设置F17任务  
15.	ObjectBean ob2 = pl.create("Fan", "F17", "true");  
16.	String Task2 = ob2.toString();  
17.	if(Task2.equals("Fan.F17:true")){
    
      
18.	System.out.println("设置任务F17成功");  
19.	}  
20.	//设置F18任务  
21.	ObjectBean ob3 = pl.create("Fan", "F18", "true");  
22.	String Task3 = ob3.toString();  
23.	if(Task3.equals("Fan.F18:true")){
    
      
24.	System.out.println("设置任务F18成功");  
25.	}  
26.	//设置F20任务  
27.	ObjectBean ob4 = pl.create("Fan", "F20", "true");  
28.	String Task4 = ob4.toString();  
29.	if(Task4.equals("Fan.F20:true")){
    
      
30.	System.out.println("设置任务F20成功");  
31.	}  
32.	//后面要使用Task0,Task1,Task2,Task3,Task4  
	同一个class内的多个Task片段含有相同表达式(expression),这时候采用Extract Method提炼出重复的代码,然后让这几个地点都调用被提炼出来的那一段代码。
	重构后的代码如下:
1.	String Task0 = setTask("F15");  
2.	String Task1 = setTask("F16");  
3.	String Task2 = setTask("F17");  
4.	String Task3 = setTask("F18");  
5.	String Task4 = setTask("F20");    
6.	  
7.	public String setTask(String str){
    
      
8.	ParkLocal pl = BeanContext.getPark();  
9.	ObjectBean ob0 = pl.create("Fan", str, "true");  
10.	String Task0 = ob0.toString();  
11.	if(Task0.equals("Fan."+str+":true")){
    
      
12.	System.out.println("设置任务"+str+"成功");  
13.	}  
14.	return Task0;  
15.	}   

代码坏味道二:Large Class(过大类)

1.	class Person{
    
      
2.	   private String _name;  
3.	   private String _officeAreaCode;  
4.	   private String _officeNumber;  
5.	     
6.	   public String getName() {
    
      
7.	       return _name;  
8.	   }  
9.	  
10.	   public String getTelephoneNumber() {
    
      
11.	       return ("(" + _officeAreaCode + ") " + _officeNumber);  
12.	   }  
13.	  
14.	   String getOfficeAreaCode() {
    
      
15.	       return _officeAreaCode;  
16.	   }  
17.	  
18.	   void setOfficeAreaCode(String arg) {
    
      
19.	       _officeAreaCode = arg;  
20.	   }  
21.	  
22.	   String getOfficeNumber() {
    
      
23.	       return _officeNumber;  
24.	   }  
25.	  
26.	   void setOfficeNumber(String arg) {
    
      
27.	       _officeNumber = arg;  
28.	   }      
29.	}  

如果想利用单一class做太多事情,其内往往就会出现太多instance变量,可以将「与电话号码相关」的行为分离到一个独立class中, 建立「从旧class访问新class」的连接关系。
重构后的代码如下:

1.	class TelephoneNumber{
    
        
2.	   private String _number;  
3.	   private String _areaCode;    
4.	   public String getTelephoneNumber() {
    
      
5.	       return ("(" + _areaCode + ") " + _number);  
6.	   }    
7.	   String getAreaCode() {
    
      
8.	       return _areaCode;  
9.	   }    
10.	   void setAreaCode(String arg) {
    
      
11.	       _areaCode = arg;  
12.	   }    
13.	   String getNumber() {
    
      
14.	       return _number;  
15.	   }    
16.	   void setNumber(String arg) {
    
      
17.	       _number = arg;  
18.	   }    
19.	}  

1.	class Person...    
2.	   private String _name;  
3.	   private TelephoneNumber _officeTelephone = new TelephoneNumber();    
4.	 public String getName() {
    
      
5.	       return _name;  
6.	   }    
7.	   public String getTelephoneNumber(){
    
      
8.	       return _officeTelephone.getTelephoneNumber();  
9.	   }    
10.	   TelephoneNumber getOfficeTelephone() {
    
      
11.	       return _officeTelephone;  
12.	   }  

代码坏味道三:Long Method(过长函数)

1.	double getPrice() {
    
      
2.	  
3.	       int basePrice = _quantity * _itemPrice;  
4.	       double discountFactor;  
5.	       if (basePrice > 1000) discountFactor = 0.95;  
6.	       else discountFactor = 0.98;  
7.	       return basePrice * discountFactor;  
8.	  
9.	   }  

找出只被赋值一次的临时变量,将该临时变量声明为final,编译:这可确保该临时变量的确只被赋值一次,将临时变量等号右侧部分提炼到一个独立函数中,首先将函数声明为private,可能会发现有更多class需要使用它,彼时再放松对它的保护,把临时变量全替换成独立出来的函数。
重构后的代码如下:

1.	double getPrice() {
    
        
2.	       return basePrice() * discountFactor();    
3.	   }  
4.	   private double discountFactor() {
    
        
5.	       if (basePrice() > 1000) return 0.95;  
6.	       else return 0.98;    
7.	   }  
8.	   private int basePrice() {
    
        
9.	   	  return _quantity * _itemPrice;    
10.	   }  

代码坏味道四:Data Clumps(数据泥团)

1.	@Autowired  
2.	    private AddressService addressService;  
3.	    public List inquireAddressListAccount( Integer pageNum ,Integer pageSize,String addressName,String mobile,String zipCode,String consignee){
    
      
4.	  
5.	        return addressService.inquireAddressList(pageNum,pageSize,addressName,mobile,zipCode,consignee);  
6.	    }  

用一个新的class封装入参,并把这些参数设置为private严格保护起来,写这些参数的get方法和set方法,原函数的入参变成这个新的class对象,函数里的参数用class对象对应的属性替换,将原先的参数全部去除之后,观察有无适当函数可以运用Move Method 搬移到参数对象之中。
重构后的代码如下:

1.	@Autowired  
2.	    private AddressService addressService;  
3.	    public List inquireAddressListAccount( Integer pageNum ,Integer pageSize,InquireAddressListInput output){
    
      
4.	  
5.	        return addressService.inquireAddressList(pageNum,pageSize,output);  
6.	    }   
7.	  
8.	    public class InquireAddressListInput(){
    
      
9.	        private String addressName;  
10.	        private String mobile;  
11.	        private String zipCode;  
12.	        private String consignee;  
13.	          
14.	    public String getConsignee() {
    
      
15.	        return consignee;  
16.	    }    
17.	    public void setConsignee(String consignee) {
    
      
18.	        this.consignee = consignee;  
19.	    }    
20.	    public String getMobile() {
    
      
21.	        return mobile;  
22.	    }  
23.	    public void setMobile(String mobile) {
    
      
24.	        this.mobile = mobile;  
25.	    }   
26.	    public String getZipCode() {
    
      
27.	        return zipCode;  
28.	    }    
29.	    public void setZipCode(String zipCode) {
    
      
30.	        this.zipCode = zipCode;  
31.	    }    
32.	    public String getAddressName() {
    
      
33.	        return addressName;  
34.	    }    
35.	    public void setAddressName(String addressName) {
    
      
36.	        this.addressName = addressName;  
37.	    }  
38.	    }  

代码坏味道五:Long Parameter List(过长参数列)

1.	public double getPrice() {
    
        
2.	      int basePrice = _quantity * _itemPrice;  
3.	      int discountLevel;  
4.	      if (_quantity > 100) discountLevel = 2;  
5.	      else discountLevel = 1;  
6.	      double finalPrice = discountedPrice (basePrice, discountLevel);  
7.	      return finalPrice;  
8.	  }  	  
9.	  private double discountedPrice (int basePrice, int discountLevel) {
    
      
10.	      if (discountLevel == 2) return basePrice * 0.1;  
11.	      else return basePrice * 0.05;  
12.	  }  

如果有必要,将参数的计算过程提炼到一个独立函数中,将函数内有使用参数的地方替换成独立函数,全部替换完成后,最后把这个参数删除。(优化参数basePrice,去掉参数basePrice,去掉discountLevel参数,独立函数返回值要为discountLevel 最后赋值的值,可以发现getPrice函数直接调用discountedPrice 函数,所以可用Inline Method(将函数内联化))
重构后的代码如下:

1.	public double getPrice() {
    
      
2.	    if (getDiscountLevel() == 2) return getBasePrice() * 0.1;  
3.	      else return getBasePrice() * 0.05;    
4.	  }      
5.	  private double getDiscountLevel(){
    
      
6.	      if (_quantity > 100) return 2;  
7.	      else return 1;            
8.	  }    
9.	  private double getBasePrice(){
    
      
10.	    return _quantity * _itemPrice;  
11.	  }  

代码坏味道六:Switch Statements(switch惊悚现身)

1.	public void getPrice(String type){
    
      
2.	        if ("Benz".equals(type)) {
    
      
3.	            System.out.println("奔驰车价格");  
4.	        } else if ("BMW".equals(type)) {
    
      
5.	            System.out.println("宝马车价格");  
6.	        } else if ("audi".equals(type)) {
    
      
7.	            System.out.println("奥迪车价格");  
8.	        }  
9.	    }  
10.	  
11.	public void getStock(String type){
    
      
12.	        if ("Benz".equals(type)) {
    
      
13.	            System.out.println("奔驰车的剩余库存。。。");  
14.	        } else if ("BMW".equals(type)) {
    
      
15.	            System.out.println("宝马车的剩余库存。。。");  
16.	        } else if ("audi".equals(type)) {
    
      
17.	            System.out.println("奥迪车的剩余库存。。。");  
18.	        }  
19.	    }  

加一个汽车的类型,那么需要把两段代码的if else同时加一个分支,这时候不仅仅出现了第一个坏味道,重复的代码,还伴随着霰弹式修改;下面用工厂+策略模式重构上面代码,把不同品牌的车抽象出了不同的类,用工厂根据类型帮我们创建不同的汽车类。
重构后的代码如下:

1.		// 工厂类  
2.	public class CarFactory {
    
      
3.	    public CarService instance(String type){
    
      
4.	        if ("Benz".equals(type)) {
    
      
5.	            return new BenzService();  
6.	        } else if ("BMW".equals(type)) {
    
      
7.	            return new BWMService();  
8.	        } else if ("audi".equals(type)) {
    
      
9.	            return AudiService();  
10.	        }  
11.	    }  
12.	}  
13.	// 不同品牌的汽车接口,定义汽车的所有方法  
14.	public interface CarService {
    
      
15.	    Integer getPrice();  
16.	}   
17.	// 奔驰车实现类  
18.	public class BenzService implements CarService {
    
      
19.	    @Override  
20.	    public Integer price() {
    
      
21.	        // 返回奔驰车的价格  
22.	        return 1000000;  
23.	    }  
24.	}    
25.	// 宝马车实现类  
26.	public class BMWService implements CarService {
    
      
27.	    @Override  
28.	    public Integer price() {
    
      
29.	        // 返回宝马车的价格  
30.	        return 2000000;  
31.	    }  
32.	}  
33.	// 客户端根据品牌取价格,无需再用if else  
34.	public static void main(String[] args) {
    
      
35.	    String type = "Benz";  
36.	    CarFactory factory = new CarFactory();  
37.	    Integer price = factory.instance(type).getPrice();  
38.	    System.out.println(price);  
39.	}  

代码坏味道七:Primitive Obsession(基本型别偏执)

1.	String[] row = new String[3];  
2.	  row [0] = "Liverpool";  
3.	  row [1] = "15";  

写代码时总喜欢用基本类型来当参数,而不喜欢用对象。当要修改需求和扩展功能时,复杂度就增加了。
优化思路:如果你有一组应该总是被放在一起的属性或参数,可以用提炼类的方式来处理;如果你在参数列中看到多个基本型数据,可以引用参数对象;如果你发现自己正从array中挑选数据,可以用对象取代数组。
重构后的代码如下:

1.	//对象取代数组  
2.	  Performance row = new Performance();  
3.	  row.setName("Liverpool");  
4.	  row.setWins("15");  

代码坏味道八:Middle Man(中间转手人)

1.	class Person {
    
      
2.	   Department _department;  
3.	   public Department getDepartment() {
    
      
4.	       return _department;  
5.	   }  
6.	   public void setDepartment(Department arg) {
    
      
7.	       _department = arg;  
8.	   }  
9.	 }  
10.	 class Department {
    
      
11.	   private String _chargeCode;  
12.	   private Person _manager;  
13.	   public Department (Person manager) {
    
      
14.	       _manager = manager;  
15.	   }  
16.	   public Person getManager() {
    
      
17.	       return _manager;  
18.	}  

如果客户希望知道某人的经理是谁,他必须先取得Department对象: manager = john.getDepartment().getManager();这样的编码就是对客户揭露了Department的工作原理,于是客户知道:Department用以追踪「经理」这条信息。如果对客户隐藏Department,可以减少耦合(coupling)。 为了这一目的,我在Person中建立一个简单的委托函数:
public Person getManager() {
return _department.getManager();
}
现在,我得修改Person的所有客户,让它们改用新函数: manager = john.getManager();
只要完成了对Department所有函数的委托关系,并相应修改了Person的所有客户,我就可以移除Person中的访问函数getDepartment()了。
重构后的代码如下:

1.	class Person...  
2.	   Department _department;          
3.	   public Person getManager() {
    
      
4.	       return _department.getManager();  
5.	 class Department...  
6.	   private Person _manager;  
7.	   public Department (Person manager) {
    
      
8.				_manager = manager;  
9.	   }  

四、实验感想
通过本次实验,我对自己写代码的坏习惯有了清楚的了解,知道了该怎么改正这些习惯;同时,我对各种代码坏味道有了初步的了解,对以后编程大有补益。

猜你喜欢

转载自blog.csdn.net/weixin_45341339/article/details/112414236