《软件工程》实验(二)——重构实验

《软件工程》实验报告(二)——重构实验

 

一、实验名称

重构实验

 

二、实验目的

1)理解重构在软件开发中的作用

2)熟悉常见的代码坏味道和重构方法

 

三、实验内容和要求

1)阅读:Martin Fowler 《重构-改善既有代码的设计》

2)掌握你认为最常见的6种代码坏味道及其重构方法

3)从你过去写过的代码或GitHub等开源代码库上寻找这6种坏味道,并对代码进行重构

 

四、实验思路

Martin Fowler 《重构-改善既有代码的设计》表明,代码有以下24种坏味道:神秘命名、重复代码、过长函数、过长参数列表、全局数据、可变数据、发散式变化、霰弹式修改、依恋情结、数据泥团、基本类型偏执、重复的switch、循环语句、冗赘的元素、夸夸其谈通用性、临时字段、过长的消息链、中间人、内幕交易、过大的类、异曲同工的类、纯数据类、被拒绝的遗赠、注释。

程序员为了快速完成任务,在没有完全理解整体架构之前就开始写代码,很可能会导致程序逐渐失去自己的结构。重构则帮助重新组织代码,重新清晰地体现。

程序结构和进一步改进设计。重构可以提高程序的可读性,容易理解的代码很容易维护和增加新功能。代码首先是写给人看的,然后才是计算机看的。

重构是一个Code Review 和反馈的过程。在另一个时段重新审视代码,会容易发现问题和加深对代码的理解。

重构不能再添加功能,只管改进程序结构;添加新功能时,不应该修改既有代码,只管添加新功能。

 

五、实验过程与结果

第一组:纯数据类(Data Class

1.特征:只包含字段和访问它们的getter和setter函数的类。这些仅仅是供其他类使用的数据容器。这些类不包含任何附加功能,并且不能对自己拥有的数据进行独立操作。

2.代码示例:

class Person {  
  public String name;  
}  
//类中存在public字段  

3.问题原因:

当一个新创建的类只包含几个公共字段(甚至可能几个getters / setters)是很正常的。但是对象的真正力量在于它们可以包含作用于数据的行为类型或操作。

4.重构方法:

● 封装字段(Encapsulated Field):将它声明为private,并提供相应的访问函数。

● 封装集合(Encapsulated Collection):让该函数返回该集合的一个只读副本,并在这个类中提供添加、移除集合元素的函数。

● 搬移函数(Move Method):在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。

● 提炼函数(Extract Method):移动这段代码到一个新的函数中,使用函数的调用来替代老代码。

● 移除设置函数(Remove Setting Method):去掉该字段的所有设值函数。

● 隐藏函数(Hide Method):将这个函数修改为private。

5.重构后的代码:

//将它声明为private,并提供相应的访问函数。  
class Person {  
  private String name;  
  public String getName() {  
    return name;  
  }  
  public void setName(String arg) {  
    name = arg;  
  }  
}  

6.重构收益:

● 提高代码的可读性和组织性。特定数据的操作现在被集中在一个地方,而不是在分散在代码各处。

● 帮助你发现客户端代码的重复处。

第二组:夸夸其谈通用性(Speculative Generality

1.特征:存在未被使用的类、函数、字段或参数。

2.代码示例:

class PizzaDelivery {  
  //...  
  int getRating() {  
    return moreThanFiveLateDeliveries() ? 2 : 1;  
  }  
  boolean moreThanFiveLateDeliveries() {  
    return numberOfLateDeliveries > 5;  
  }  
}  
//一个函数的本体比函数名更清楚易懂。  

3.问题原因:

有时,代码仅仅为了支持通用的特性而产生,然而却一直未实现。结果,代码变得难以理解和维护。

4.重构方法:

● 折叠继承体系(Collapse Hierarchy):将它们合为一体。

● 将类内联化(Inline Class):将这个类的所有特性搬移到另一个类中,然后移除原类。

● 内联函数(Inline Method):在函数调用点插入函数本体,然后移除该函数。

● 移除参数(Remove Parameter):将该参数去除。

5.重构后的代码:

//在函数调用点插入函数本体,然后移除该函数。  
class PizzaDelivery {  
  //...  
  int getRating() {  
    return numberOfLateDeliveries > 5 ? 2 : 1;  
  }  
}  

6.重构收益:

● 减少代码量。

● 更易维护。

第三组:数据泥团(Data Clumps

1.特征:有时,代码的不同部分包含相同的变量组(例如用于连接到数据库的参数)。这些绑在一起出现的数据应该拥有自己的对象。

2.代码示例:

//从某个对象中取出若干值,将它们作为某一次函数调用时的参数(Java)  
int low = daysTempRange.getLow();    
int high = daysTempRange.getHigh();    
boolean withinPlan = plan.withinRange(low, high);   

3.问题原因:

通常,数据泥团的出现时因为糟糕的编程结构或“复制-粘贴式编程”。

有一个判断是否是数据泥团的好办法:删掉众多数据中的一项。这么做,其他数据有没有因而失去意义?如果它们不再有意义,这就是个明确的信号:你应该为它们产生一个新的对象。

4.重构方法:

● 提炼类(Extract Class):建立一个新类,将相关的字段和函数从旧类搬移到新类。

● 引入参数对象(Introduce Parameter Object):以一个对象来取代这些参数。

● 保持对象完整(Preserve Whole Object):改为传递整个对象。

5.重构后的代码:

boolean withinPlan = plan.withinRange(daysTempRange);    
//改为传递整个对象  

 6.重构收益:

● 提高代码易读性和组织性。对于特殊数据的操作,可以集中进行处理,而不像以前那样分散。

● 减少代码量。

第四组:依恋情结(Feature Envy)

1.特征:一个函数访问其它对象的数据比访问自己的数据更多。

2.代码示例:

void printOwing() {  
  printBanner();  
  //print details  
  System.out.println("name: " + name);  
  System.out.println("amount: " + getOutstanding());  
}  
//有一段代码可以组织在一起  

3.问题原因:

这种坏味道可能发生在字段移动到数据类之后。如果是这种情况,你可能想将数据类的操作移动到这个类中。

4.重构方法:

● 搬移函数(Move Method):在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。

● 提炼函数(Extract Method):移动这段代码到一个新的函数中,使用函数的调用来替代老代码。

5.重构后的代码:

//移动这段代码到一个新的函数中,使用函数的调用来替代老代码  
void printOwing() {  
  printBanner();  
  printDetails(getOutstanding());  
}  
void printDetails(double outstanding) {  
  System.out.println("name: " + name);  
  System.out.println("amount: " + outstanding);  
}  

6.重构收益:

● 减少重复代码(如果数据处理的代码放在中心位置)。

● 更好的代码组织性(处理数据的函数靠近实际数据)。

第五组:异曲同工的类(Alternative Classes with Different Interfaces)

1.特征:两个类中有着不同的函数,却在做着同一件事。

2.代码示例:

class Person {  
  public String getsnm();  
}  
//函数的名称未能恰当的揭示函数的用途  

3.问题原因:

这种情况往往是因为:创建这个类的程序员并不知道已经有实现这个功能的类存在了。

4.重构方法:

● 函数改名(Rename Method):修改函数名。

● 搬移函数(Move Method):在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。

● 添加参数(Add Parameter):为此函数添加一个对象函数,让改对象带进函数所需信息。

● 令函数携带参数(Parameterize Method):建立单一函数,以参数表达哪些不同的值。

● 提炼超类(Extract Superclass):为这两个类建立一个超类,将相同特性移至超类。

5.重构后的代码:

//修改函数名。  
class Person {  
  public String getSecondName();  
}  

6.重构收益:

● 消除了不必要的重复代码,为代码瘦身了。

● 代码更易读(不再需要猜测为什么要有两个功能相同的类)。

第六组:临时字段(Temporary Field)

1.特征:临时字段的值只在特定环境下有意义,离开这个环境,它们就什么也不是了。

2.代码示例:

class Order {  
  //...  
  public double price() {  
    double primaryBasePrice;  
    double secondaryBasePrice;  
    double tertiaryBasePrice;  
    // long computation.  
    //...  
  }  
}  
//有一个过长函数,它的局部变量交织在一起,以致于无法应用提炼函数(Extract Method)   

3.问题原因:

有时你会看到这样的对象:其内某个实例变量仅为某种特定情况而设。这样的代码让人不易理解,因为你通常认为对象在所有时候都需要它的所有变量。在变量未被使用的情况下猜测当初设置目的,会让你发疯。

通常,临时字段是在某一算法需要大量输入时而创建。因此,为了避免函数有过多参数,程序员决定在类中创建这些数据的临时字段。这些临时字段仅仅在算法中使用,其他时候却毫无用处。

这种代码不好理解。你期望查看对象字段的数据,但是出于某种原因,它们总是为空。

4.重构方法:

● 提炼类(Extract Class):建立一个新类,将相关的字段和函数从旧类搬移到新类。

● 以函数对象取代函数(Replace Method with Method Object):将函数移到一个独立的类中,使得局部变量成了这个类的字段。然后,你可以将函数分割成这个类中的多个函数。

● 引入 Null 对象(Introduce Null Object):将null值替换为null对象。

5.重构后的代码:

//将函数移到一个独立的类中,使得局部变量成了这个类的字段。然后,可以将函数分割成这个类中的多个函数  
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 order object.  
    //...  
  }  
  public double compute() {  
    // long computation.  
    //...  
  }  
}  

6.重构收益:

● 更好的代码清晰度和组织性。

 

六、实验体会

所谓优雅的代码,或者恶心的代码,很多时候是见仁见智的。也同时是看个人喜好或者习惯的。优雅的代码并不是说这个代码写的有多神,多么让人惊叹。能够让人清晰的去阅读去理解就是好的代码。代码并不是艺术,更多的是严谨的表达出自己的思路。在这个过程中代码的易读性是第一位的,然后是正确性,然后是运行效率。让人感到恼火的代码也并一定是写的多么凌乱。它们可能非常的中规中矩,甚至是以大量高精尖的设计模式为基础的。但是,阅读这样的代码绝对不是一件轻松的事情。以封装为例,让使用者去知道更多的概念,去注意更多的细节,让代码的功能更加脆弱,这样的封装意义何在?封装需要的是能够简化概念,让使用者使用某个功能变得更加容易、更加健壮,让使用者不必操心底层细节。总而言之,代码的坏味道就意味着需要重构。

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.1. 编写目的 从现在应用的技术方面和用户的操作方式方面研究图书管理系统用来统一管理,记录图书的荆楚信息,图书管理系统为用户建立一个账户,并给借阅者发放借阅卡以及对应的账号。账户中存储借阅者的个人信息、借阅信息和预定信息,从而使管理员进行管理。 1.2. 参考资料 《软件工程技术与应用》 《数据结构》 《软件工程实践教程》 《软件工程概论》 2. 任务概述 2.1. 目标 在该系统中,图书管理员要为每个借阅者建立一个账户,并给借阅者发放借阅卡以及对应的账号。账户中存储借阅者的个人信息、借阅信息和预定信息。持卡者(即拥有账户的个人)可以借阅书刊、返回书刊、查询书刊信息、预定书刊并取消预订。这些操作均由管理员代理执行,即借阅者不与管理系统直接交互。借阅书刊时,借阅者需要提供书刊名、ISBN/ISSN号,以及借阅者的图书卡号和姓名。完成输入后,系统需检查账户是否存在。若有效,系统查询书看是否存在,若存在则可借出,建立并在系统中存储借阅记录。借阅者返还书刊后,删除相应借阅记录。如果所借书刊被借出,借阅者可预定书刊,一旦预定的书刊可以获取,就直接将书刊借给预定者。为简化系统,预定书刊可获取时则直接借出,也不考虑借阅期限。 2.2. 条件与约束 1. 图书管理员建立的借阅者账号的卡号唯一性,主键约束 2. 借阅者借出的书刊的ISBN/ISSN号应该唯一,主键约束 3. 借书模块中的借阅者账号为外键 4. 对于借阅者对于借书实践也应该进行记录 3. 功能需求 图书管理员: 1. 管理借阅者账号(增加用户、删除用户、修改用户、查询用户) 2. 管理图书系统(增加书刊、删除书刊、修改书刊、查询书刊) 借阅者: 1. 借阅书刊 2. 返还书刊 3. 查询书刊信息 4. 预定书刊 5. 取消预订书刊 4. 性能需求 时间要求: 要求三个月内能够制定出初步的系统程序提供用户使用 存储要求: 要求使用oracle数据库进行存储管理 建表、设计数据流图、实体图、状态转换图、表的约束与关系 5. 接口需求 输入输出需求: 系统管理员的输入输出: 对图书的录入、删除、修改、查询 对借阅者信息的删除、查询、修改、删除 借阅者的输入输出: 对图书的查询 数据库需求: 系统管理员对图书操作系统、对借阅者信息的管理权限 借阅者对图书的查询权限 程序接口需求: 系统管理员对图书操作系统、对借阅者信息的接口(8088) 借阅者对图书的查询接口(8086) 6. 将来可能需求 1.借阅者可能会增加权限,系统更加开放。 2.系统管理员可能会分级别,例如,一级管理员、级管理员、三级管理员等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值