我不是个伟大的程序员;我只是个有着一些优秀习惯的好程序员而己
本人比较直接,不说虚的,直接上干货。
目录
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
1、在各个subclass 中分解目标函数,把有差异的部分变成入参,封装成一个模板函数。
2、把模板函数放到父类中。
3、子类根据需要输入不同的入参,得到需要的结果。
*********************************************************************************
如果是相似的表达式,差异的地方不好抽共性,则用模板函数设计模式来处理。
这里用到了JAVA的两个特性,继承和覆写(overrides)。
优化的思路2
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
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(提炼子类)
优化思路2
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。
想重构代码,直接把以上四招看情况用上,更多精彩内容,请等待后续更新。
作者:小虚竹
欢迎任何形式的转载,但请务必注明出处。
限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。