2018-08-26

2018-09-04  本文已影响0人  兴兴不息

重构常用手法(一)

重构:使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。

重构的最终为了使项目达到clean code,不管项目在开发中还是维护中都有可能需要对项目进行重构,本文列举了项目中常见的重构的坏味道和常见的重构手法。�

项目中常见的问题:

重构常用的方法:

1. 提炼函数

代码都是以函数为单位,提炼函数使项目中常用的手法。提炼函数的常用的几个场景:

void printOwing() {
    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(double previousAmount) { 
      printBanner(); 
      double outstanding = getOutstanding(previousAmount * 1.2);               
      printDetails(outstanding); 
}

void printBanner() { 
    System.out.println ("**************************"); 
    System.out.println ("***** Customer Owes ******"); 
    System.out.println ("**************************"); 
} 
double getOutstanding(double initialValue) { 
    double result = initialValue; 
    Enumeration e = _orders.elements(); 
    while (e.hasMoreElements()) { 
        Order each = (Order) e.nextElement(); 
        result += each.getAmount(); 
    } 
    return result;
}

void printDetails (double outstanding) { 
    System.out.println ("name:" + _name); 
    System.out.println ("amount" + outstanding); 
}

在表达式复杂让人难以理解时,可以提取函数,通过函数名来解释表达式的用途。

double price() { 
    // price is base price - quantity discount + shipping 
    return _quantity * _itemPrice - Math.max(0, _quantity - 500) * _itemPrice * 0.05 + Math.min(_quantity * _itemPrice * 0.1, 100.0); 
}

=>

double price() { 
    return basePrice() - quantityDiscount() + shipping(); 
}
private double quantityDiscount() { 
    return Math.max(0, _quantity - 500) * _itemPrice * 0.05; 
} 
private double shipping() { 
    return Math.min(basePrice() * 0.1, 100.0); 
} 
private double basePrice() {
    return _quantity * _itemPrice; 
}

总结:

2. 卫语句

在复杂业务场景下,一个函数难免会出现复杂的条件逻辑判断,让人难以理解函数的执行路径,应该使用卫语句来表现所及特殊情况。

double getPayAmount() { 
    double result; 
    if (_isDead) 
        result = deadAmount(); 
    else { 
        if (_isSeparated) 
            result = separatedAmount(); 
        else { 
            if (_isRetired) 
                result = retiredAmount(); 
            else result = normalPayAmount(); 
        }; 
    } 
    return result; 
};

=>

double getPayAmount() { 
    if (_isDead) 
        return deadAmount(); 
   if (_isSeparated) 
        return separatedAmount(); 
    if (_isRetired) 
        return retiredAmount(); 
    return normalPayAmount(); 
};

为使用卫语句,可以将条件表达式逆反,

public double getAdjustedCapital() { 
    double result = 0.0; 
    if (_capital > 0.0) { 
        if (_intRate > 0.0 && _duration > 0.0) { 
            result = (_income / _duration) * ADJ_FACTOR; 
        } 
    } 
    return result; 
}

=>

public double getAdjustedCapital() { 
    double result = 0.0; 
    if (_capital <= 0.0) 
        return result; 
    if (_intRate <= 0.0 || _duration <= 0.0) 
        return result; 
    return (_income / _duration) * ADJ_FACTOR; 
}

总结:

3. 引入解释性变量:

在表达式复杂情况下,可将表达式进行分解、各部分替换为临时变量,以此变量名称来解释表达式用途。

if ((platform.toUpperCase().indexOf("MAC") > -1) && (browser.toUpperCase().indexOf("IE") > -1) && wasInitialized() && resize > 0 ){ 
      // do something 
 }

=>

final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1; 
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1; 
final boolean wasResized = resize > 0; 
if (isMacOs && isIEBrowser && wasInitialized() && wasResized) { 
    // do something 
}

总结:

4. 提取类:

一个类的设计应该符合单一指责原则。一个类如果过于复杂,做了好多的事情,违背了“单一职责”的原则,所以需要将其可以独立的模块进行拆分,当然有可能由一个类拆分出多个类。

Extract Class - Before.png
class Person... 
    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; 
    } 
    private String _name; 
    private String _officeAreaCode; 
    private String _officeNumber;
Extract Class - After.png
class Person... 
    public String getName() { 
        return _name; 
    } 
    public String getTelephoneNumber(){ 
        return _officeTelephone.getTelephoneNumber(); 
    } 
    TelephoneNumber getOfficeTelephone() { 
        return _officeTelephone; 
    } 
    private String _name; 
    private TelephoneNumber _officeTelephone = new TelephoneNumber(); 

class TelephoneNumber... 
    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; 
     } 
    private String _number; 
    private String _areaCode;

总结:

5. 业务与显示分离

GUI类中,用户界面显示代码和业务逻辑代码应该进行分离。用户界面代码不包含任何业务数据,可以达到很好的复用;业务逻辑代码不包含界面显示代码,使得同一业务逻辑代码的多种展现方式成为可能。
以如下一个展示评论列表的控件为例:

class CommentList extends React.Component {
  constructor() {
    super();
    this.state = { comments: [] }
  }
  componentDidMount() {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: function(comments) {
        this.setState({comments: comments});
      }.bind(this)
    });
  }
  render() {
    return <ul> {this.state.comments.map(renderComment)} </ul>;
  }
  renderComment({body, author}) {
    return <li>{body}—{author}</li>;
  }
}

在该控件中数据在componentDidMount函数中通过ajax请求获取,界面显示在render和renderComment中完成,因此该组件具备较高的定制性,更换位置和变量之后需要重写一份相似的函数,难以重用。
同时组件对于comments数据也没有专门的数据检查,难以达到复用的效果。为了达到代码复用和数据检查的效果,将业务逻辑与显示分离,可以得到如下的代码:

class CommentListContainer extends React.Component {
  constructor() {
    super();
    this.state = { comments: [] }
  }
  componentDidMount() {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: function(comments) {
        this.setState({comments: comments});
      }.bind(this)
    });
  }
  render() {
    return <CommentList comments={this.state.comments} />;
  }
}
class CommentList extends React.Component {
  constructor(props) {
    super(props);
  }
  render() { 
    return <ul> {this.props.comments.map(renderComment)} </ul>;
  }
  renderComment({body, author}) {
    return <li>{body}—{author}</li>;
  }
}

这样就做到了数据提取和渲染分离,CommentList可以复用,同时CommentList可以设置PropTypes判断数据的可用性。

总结:

上一篇 下一篇

猜你喜欢

热点阅读