我不是个伟大的程序员;我只是个有着一些优秀习惯的好程序员而己
本人比较直接,不说虚的,直接上干货。
目录
Duplicated Code(重复的代码)
Long Method(过长函数)
Long Parameter List(过长参数列)
Large Class(过大类)
提前总结就是四招:
一、重复的代码提炼成函数
二、把过长的函数变小
三、参数列太长或变化太频繁,参数对象化
四、大招:类的代码行数太多,要考虑提炼子类。
第一种情况是:同一个class内的两个函数含有相同表达式(expression)。
void printOwing(String _name) {
Enumeration e =_orders.elements();
double outstanding = 0.0;
// print banner
System.out.println ("**************************");
System.out.println ("***** Customer Owes ******");
System.out.println ("**************************");
// calculate outstanding
while (e.hasMoreElements()) {
Order each = (Order) e.nextElement();
outstanding += each.getAmount();
}
//print details
System.out.println ("name:" + _name);
System.out.println ("amount" + outstanding);
}
实际上这三部分都可以提炼。
优化后的结果
void printOwing(String _name) {
printBanner();
double outstanding = getOutstanding();
printDetails(_name,outstanding);
}
void printBanner() {
// print banner
System.out.println ("**************************");
System.out.println ("***** Customer Owes ******");
System.out.println ("**************************");
}
void printDetails (String _name,double outstanding) {
System.out.println ("name:" + _name);
System.out.println ("amount" + outstanding);
}
double getOutstanding() {
Enumeration e = _orders.elements();
double result = 0.0;
while (e.hasMoreElements()) {
Order each = (Order) e.nextElement();
result = each.getAmount();
}
return result;
}
第二种情况:两个subclasses有相同的表达式,或者是相似的表达式
优化的方法是:抽取相同的表达式(属性和方法),放在父类里,两个子类再去继承。
**********************************************************************************
如果是相似的表达式,好抽出共性的,则用模板函数设计模式来处理。
这里用到了JAVA的两个特性,继承和多态。
1、在各个subclass 中分解目标函数,把有差异的部分变成入参,封装成一个模板函数。
2、把模板函数放到父类中。
3、子类根据需要输入不同的入参,得到需要的结果。
*********************************************************************************
如果是相似的表达式,差异的地方不好抽共性,则用模板函数设计模式来处理。
这里用到了JAVA的两个特性,继承和覆写(overrides)。
1、在各个subclass 中分解目标函数,使分解后的各个函数要不完全相同,要不完全不同。
2、父类有一个主函数包含完全相同的函数和完全不同的函数:相同的函数,抽到父类中,不相同的函数在父类中定义一个函数。
3、子类继承父类,然后覆写完全不同的函数,再调用主函数可得到期望的结果。
百分之九十九的场合里,要把函数变小,只需使用Extract Method(第一招)。找到函数中适合集在一起的部分,将它们提炼出来形成一个新函数。
如果函数内有大量的参数和临时变量,它们会对你的函数提炼形成阻碍。这时就要用Replace Temp with Query来消除这些临时变量
Replace Temp with Query(以查询取代临时变量)
1、找出只被赋值一次的临时变量。
2、将该临时变量声明为final
3、编译:这可确保该临时变量的确只被赋值一次。
4、将临时变量等号右侧部分提炼到一个独立函数中;
5、首先将函数声明为private。日后你可能会发现有更多class需要使用 它,彼时你可再放松对它的保护。
6、编译,测试:确保提炼出来的函数无任何连带影响(副作用),结果不变;
7、把临时变量全替换成独立出来的函数;
以上,over!
例子:未优化代码
double getPrice() {
int basePrice = _quantity * _itemPrice;
double discountFactor;
if (basePrice > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice * discountFactor;
}
开始优化 1~3步骤
double getPrice() {
final int basePrice = _quantity * _itemPrice;
final double discountFactor;
if (basePrice > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice * discountFactor;
}
4~6步骤
double getPrice() {
final int basePrice = basePrice();
final double discountFactor;
if (basePrice > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice * discountFactor;
}
private int basePrice() {
return _quantity * _itemPrice;
}
7步骤
double getPrice() {
final double discountFactor;
if (basePrice() > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice() * discountFactor;
}
private int basePrice() {
return _quantity * _itemPrice;
}
搞定basePrice之后,再以类似办法提炼出一个discountFactor():
double getPrice() {
final double discountFactor = discountFactor();
return basePrice() * discountFactor;
}
private double discountFactor() {
if (basePrice() > 1000) return 0.95;
else return 0.98;
}
最后的效果
double getPrice() {
return basePrice() * discountFactor();
}
private double discountFactor() {
if (basePrice() > 1000) return 0.95;
else return 0.98;
}
private int basePrice() {
return _quantity * _itemPrice;
}
通过以上的优化,一个大函数,已经变成了多个小函数,重点是代码的可读性提高了,顺带的代码量变少。
当你看到一个函数的入参有四,五个,甚至更多时,且好几个函数都使用这组入参,这时就要用参数对象化来优化代码。这些函数可能隶属同一个class,也可能隶属不同的classes 。这样一组参数就是所谓的Date Clump (数据泥团)」。这时用一个对象封装这些参数,再用对象取代它们。
1、入参有四,五个,甚至更多时,就要着手优化;
2、用一个新的class封装入参,并把这些参数设置为private严格保护起来,写这些参数的get方法和set方法。
3、原函数的入参变成这个新的class对象,函数里的参数用class对象对应的属性替换。
4、编译测试;
5、将原先的参数全部去除之后,观察有无适当函数可以运用Move Method 搬移到参数对象之中。
例子:未优化的代码
@Autowired
private AddressService addressService;
public List inquireAddressListAccount( Integer pageNum ,Integer pageSize,String addressName,String mobile,String zipCode,String consignee){
return addressService.inquireAddressList(pageNum,pageSize,addressName,mobile,zipCode,consignee);
}
优化
@Autowired
private AddressService addressService;
public List inquireAddressListAccount( Integer pageNum ,Integer pageSize,InquireAddressListInput output){
return addressService.inquireAddressList(pageNum,pageSize,output);
}
public class InquireAddressListInput(){
private String addressName;
private String mobile;
private String zipCode;
private String consignee;
public String getConsignee() {
return consignee;
}
public void setConsignee(String consignee) {
this.consignee = consignee;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
public String getAddressName() {
return addressName;
}
public void setAddressName(String addressName) {
this.addressName = addressName;
}
}
如果想利用单一class做太多事情,其内往往就会出现太多instance变量。一旦如此,Duplicated Code也就接踵而至了。
Extract Class 是Extract Subclass 之外的另一种选择,两者之间的抉择其实就是委托(delegation)和继承(inheritance)之间的抉择。
情况一:某个class做了应该由两个classes做的事。(Extract Class)
1、明确每个class所负的责任,该做什么事情;
2、建立一个新class,用以表现从旧class中分离出来的责任;
3、建立「从旧class访问新class」的连接关系;
4、每次搬移后,编译、测试。
5、决定是否让新的class曝光。
例子:未优化的代码
class Person{
private String _name;
private String _officeAreaCode;
private String _officeNumber;
public String getName() {
return _name;
}
public String getTelephoneNumber() {
return ("(" + _officeAreaCode + ") " + _officeNumber);
}
String getOfficeAreaCode() {
return _officeAreaCode;
}
void setOfficeAreaCode(String arg) {
_officeAreaCode = arg;
}
String getOfficeNumber() {
return _officeNumber;
}
void setOfficeNumber(String arg) {
_officeNumber = arg;
}
}
优化1~2步骤
可以将「与电话号码相关」的行为分离到一个独立class中
class TelephoneNumber{
private String _number;
private String _areaCode;
public String getTelephoneNumber() {
return ("(" + _areaCode + ") " + _number);
}
String getAreaCode() {
return _areaCode;
}
void setAreaCode(String arg) {
_areaCode = arg;
}
String getNumber() {
return _number;
}
void setNumber(String arg) {
_number = arg;
}
}
优化3步骤
class Person...
private String _name;
private TelephoneNumber _officeTelephone = new TelephoneNumber();
public String getName() {
return _name;
}
public String getTelephoneNumber(){
return _officeTelephone.getTelephoneNumber();
}
TelephoneNumber getOfficeTelephone() {
return _officeTelephone;
}
情况二:class 中的某些特性(features)只被某些(而非全部)实体(instances)用到。Extract Subclass(提炼子类)
1、为source class 定义一个新的subclass
2、为这个新的subclass 提供构造函数。
简单的作法是:让subclass 构造函数接受与superclass 构造函数相同的参数,并通过super 调用superclass 构造函数;
3、找出调用superclass 构造函数的所有地点。如果它们需要的是新建的subclass , 令它们改而调用新构造函数。
如果subclass 构造函数需要的参数和superclass 构造函数的参数不同,可以使用Rename Method 修改其参数列。如果subclass 构造函数不需要superclass 构造函数的某些参数,可以使用Rename Method 将它们去除。
如果不再需要直接实体化(具现化,instantiated)superclass ,就将它声明为抽象类。
4、逐一使用Push Down Method 和 Push Down Field 将source class 的特性移到subclass 去。
5、每次下移之后,编译并测试。
例子:未优化代码
--用来决定当地修车厂的工作报价:
class JobItem ...
public JobItem (int unitPrice, int quantity, boolean isLabor, Employee employee) {
_unitPrice = unitPrice;
_quantity = quantity;
_isLabor = isLabor;
_employee = employee;
}
public int getTotalPrice() {
return getUnitPrice() * _quantity;
}
public int getUnitPrice(){
return (_isLabor) ?
_employee.getRate():
_unitPrice;
}
public int getQuantity(){
return _quantity;
}
public Employee getEmployee() {
return _employee;
}
private int _unitPrice;
private int _quantity;
private Employee _employee;
private boolean _isLabor;
class Employee...
public Employee (int rate) {
_rate = rate;
}
public int getRate() {
return _rate;
}
private int _rate;
优化1步骤
class LaborItem extends JobItem {}
优化2步骤
class LaborItem extends JobItem {
public LaborItem (int unitPrice, int quantity, boolean isLabor, Employee employee) {
super (unitPrice, quantity, isLabor, employee);
}
}
这就足以让新的subclass 通过编译了。但是这个构造函数会造成混淆:某些参数是LaborItem 所需要的,另一些不是。稍后我再来解决这个问题。
优化3步骤
清理构造函数参数列
class JobItem...
protected JobItem (int unitPrice, int quantity, boolean isLabor, Employee employee) {
_unitPrice = unitPrice;
_quantity = quantity;
_isLabor = isLabor;
_employee = employee;
}
public JobItem (int unitPrice, int quantity) {
this (unitPrice, quantity, false, null)
}
外部调用应该使用新构造函数:
JobItem j2 = new JobItem (10, 15);
测试通过后,再使用 Rename Method 修改subclass 构造函数:class LaborItem
public LaborItem (int quantity, Employee employee) {
super (0, quantity, true, employee);
}
可以将JobItem 的特性向下搬移。先从函数幵始,我先运用 Push Down Method 对付getEmployee() 函数:
class LaborItem extends JobItem {
public LaborItem (int unitPrice, int quantity, boolean isLabor, Employee employee) {
super (unitPrice, quantity, isLabor, employee);
}
public LaborItem (int quantity, Employee employee) {
super (0, quantity, true, employee);
}
public Employee getEmployee() {
return _employee;
}
}
//因为_employee 值域也将在稍后被下移到LaborItem ,所以我现在先将它声明为protected。
class JobItem...
protected Employee _employee;
将_employee 值域声明protected 之后,我可以再次清理构造函数,让_employee 只在「即将去达的subclass 中」被初始化:
class JobItem...
protected Employee _employee;
protected JobItem (int unitPrice, int quantity, boolean isLabor) {
_unitPrice = unitPrice;
_quantity = quantity;
_isLabor = isLabor;
}
class LaborItem ...
public LaborItem (int quantity, Employee employee) {
super (0, quantity, true);
_employee = employee;
}
下一个优化_isLabor 值域,_isLabor 在JobItem是值为false,在LaborItem值为true。
可以用多态常量函数。所谓「多态常量函数」会在不同的subclass 实现版本中返回不同的固定值
class JobItem...
protected boolean isLabor() {
return false;
}
class LaborItem...
protected boolean isLabor() {
return true;
}
就可以摆脱_isLabor 值域了
通过多态代替条件的方式,重构代码
class JobItem ...
public int getUnitPrice(){
return (isLabor()) ?
_employee.getRate():
_unitPrice;
}
将它重构为:
class JobItem...
public int getUnitPrice(){
return _unitPrice;
}
class LaborItem...
public int getUnitPrice(){
return _employee.getRate();
}
使用某项值域的函数全被下移至subclass 后,我就可以使用 Push Down Field 将值域也下移。
最后的结果就是:
public class JobItem {
protected JobItem (int unitPrice, int quantity) {
_unitPrice = unitPrice;
_quantity = quantity;
}
public int getTotalPrice() {
return getUnitPrice() * _quantity;
}
public int getUnitPrice(){
return _unitPrice;
}
public int getQuantity(){
return _quantity;
}
private int _unitPrice;
private int _quantity;
}
//
public class LaborItem extends JobItem {
private Employee _employee;
public LaborItem(int quantity, Employee employee) {
super(0, quantity);
_employee = employee;
}
public Employee getEmployee() {
return _employee;
}
public int getUnitPrice() {
return _employee.getRate();
}
}
//public class Employee {
public Employee(int rate) {
_rate = rate;
}
public int getRate() {
return _rate;
}
private int _rate;
}
一个class如果拥有太多代码,也适合使用 Extract Class和 Extract Subclass。
作者:小虚竹
欢迎任何形式的转载,但请务必注明出处。
限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。
我不是个伟大的程序员,我只是个有着一些优秀习惯的好程序员而己