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