重构笔记-Extra Method

96
sindri的小巢
2016.02.23 22:01* 字数 1218

前言

这算是我的读书笔记吧,最近在看Martin Fowler大神的《重构 改善既有代码的设计》,决定将书中的代码换成熟悉的代码来实现。对于重构的概念,我直接引用书中的原话来科普下:

所谓重构是这样一个过程:在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。重构是一种经千锤百炼形成的有条不紊的程序整理方法,可以最大限度地减少整理过程中引入错误的几率。本质上说,重构就是在代码写好之后改进它的设计。

学习重构的意义何在呢?更多的是为了提高自己对于软件工程的见解,提高自己的技术。

提炼函数

Extra Method(提炼函数)是一种常见的重构手法,当你觉得你的代码需要添加一段注释才能让人理解用途的时候,不要犹豫,直接将这些代码提炼出来,放在独立的函数中。提炼函数的做法步骤如下:

  • 创建一个新函数,根据这个函数的意图来对它命名(以“做什么”而不是“怎么做”来命名,比如重写一个数组的访问,使得不发生越界。那么我会命名为func safeObjectAtIndex(index: Int)而不是func MakeIndexLessThanCountToGetObject(index: Int),这个例子有点蠢)
  • 将提炼出来的代码从原函数中复制到新建的目标函数里
  • 检查提炼出的代码,看看其中是否需要使用到在原函数中的局部变量或者函数参数
  • 检查是否有任何局部变量的值被新提炼的函数改变,如果有,看看能否将提炼代码处理为查询操作,并返回改变的值。如果很难这么做,需要使用其它方式进行修改
  • 将被提炼的代码段的局部变量或者函数参数传入给新提炼的函数
  • 处理完所有的局部变量,进行编译、测试

1、初始代码

func printOwing() {
    var outstanding: CGFloat = 0.0

    // print banner
    print("********************************")
    print("******* Customer Owes ********")
    print("********************************")

    // calculate outstanding
    for order in _orders {
        outstanding += order.amount
    }

    // print details
    print("name: \(name)")
    print("amount: \(outstanding)")
}

2、提炼功能单一的代码块

这一步骤包括把功能相同单一的代码提炼出来(比如日志输出代码),以及简单的接收原函数局部变量作为函数参数的提炼函数(修改局部变量在提炼函数中的命名使其更符合要求)

func printOwing() {
    var outstanding: CGFloat = 0.0

    printBanner()
    // calculate outstanding
    for order in _orders {
        outstanding += order.amount
    }
    printDetails(outstanding)
}

func printBanner() {
    print("********************************")
    print("******* Customer Owes ********")
    print("********************************")
}

func printDetails(amount: CGFloat) {
    print("name: \(name)")
    print("amount: \(amount)")
}

3、对局部变量进行再赋值

对需要进行计算的局部变量进行提炼,首先检查局部变量是否会在原函数跟提炼代码中一并发生改变,如果不会,那么将局部变量作为提炼函数的返回值使用

func printOwing() {
    printBanner()
    var outstanding: CGFloat = getOutstanding()
    printDetails(outstanding)
}

func printBanner() {
    print("********************************")
    print("******* Customer Owes ********")
    print("********************************")
}

func printDetails(amount: CGFloat) {
    print("name: \(name)")
    print("amount: \(amount)")
}

func getOutstanding() -> CGFloat {
    var result: CGFloat = 0.0
    for order in _orders {
        result+= order.amount
    }
    return result
}

ps:在swift中存在元组类型,但是建议不要让函数返回多种不同值的元组,这会导致函数的复用性降低

实战例子

今天在项目中自定义的一个tableView表尾视图需要在数据源变化的时候改变自身的高度,且高度有一个最低值。已知表尾视图有个belongView的对tableView的弱引用,屏幕高度为kScreenHeigth,导航栏总体高度为kNavigationBarHeight(在打开个人热点时这个值会发生改变),其中最低高度值取决于最下方按钮addButton的位置。我的重构步骤如下:

1、初始代码

func updateViewHeight() {
    let tableView: UITableView! = belongView!
    var headerHeight = tableView.tableViewHeaderView?.frame.height
    headerHeight = (headerHeight == nil ? 0 : headerHeight)
    let cellCount = tableView.dataSource.numberOfRowsInSection(tableView: tableView, section: 0)
    var viewHeight: CGFloat = kScreenHeight - kNavigationBarHeight - CGFloat(cellCount) * tableView.rowHeight - headerHeight
    let buttonBottomDistance: CGFloat = 15.0
    let minHeight: CGFloat = CGRectGetMaxY(addButton) + buttonBottomDistance
    self.frame.height = max(minHeight, viewHeight)
}

2、将局部变量提炼成函数获取

我先把单元格个数、表头高度以及最低高度提炼出来

func updateViewHeight() {
    let headerHeight: CGFloat = getTableViewHeaderViewHeight()
    let cellCount: Int= getTableViewCellsCount()
    var viewHeight: CGFloat = kScreenHeight - kNavigationBarHeight - CGFloat(cellCount) * belongView!.rowHeight - headerHeight
    let minHeight: CGFloat = getViewMinHeight()
    self.frame.height = max(minHeight, viewHeight)
}

func getTableViewHeaderViewHeight() -> CGFloat {
    var headerHeight = 0.0
    if belongView!.tableViewHeaderView {
        headerHeight = (belongView!.tableViewHeaderView?.height)!
    }
    return headerHeight
}

func getTableViewCellsCount() -> Int {
    return (belongView!.dataSource.tableView(tableView: belongView!, numberOfRowsInSection: 0))!
}

func getViewMinHeight() -> CGFloat {
    let buttonBottomDistance: CGFloat = 15.0
    return CGRectGetMaxY(addButton) + buttonBottomDistance
}

3、将相同功能的代码再次提炼

由于代码中获取单元格数量的目的是为了获取所有单元格高度,因此可以提炼一个获取单元格总高度的方法getCellsTotalHeight

func updateViewHeight() {
    let headerHeight: CGFloat = getTableViewHeaderViewHeight()
    var viewHeight: CGFloat = kScreenHeight - kNavigationBarHeight - getCellsTotalHeight() - headerHeight
    let minHeight: CGFloat = getViewMinHeight()
    self.frame.height = max(minHeight, viewHeight)
}

func getTableViewHeaderViewHeight() -> CGFloat {
    var headerHeight = 0.0
    if belongView!.tableViewHeaderView {
        headerHeight = (belongView!.tableViewHeaderView?.height)!
    }
    return headerHeight
}

func getCellsTotalHeight() -> CGFloat {
    let cellCount: Int= getTableViewCellsCount()
    return CGFloat(cellCount) * belongView!.rowHeight
}

func getTableViewCellsCount() -> Int {
    return (belongView!.dataSource.tableView(tableView: belongView!, numberOfRowsInSection: 0))!
}

func getViewMinHeight() -> CGFloat {
    let buttonBottomDistance: CGFloat = 15.0
    return CGRectGetMaxY(addButton) + buttonBottomDistance
}

4、提炼功能相近的函数

可以看到updateViewHeight的目的是为了更新表尾视图的高度,那么里面还存在一些计算高度的代码,将这些代码做最后的提炼成函数getAdjustHeight

func updateViewHeight() {
    self.frame.height = getAdjustHeight()
}

func getAdjustHeight() -> CGFloat {
    let headerHeight: CGFloat = getTableViewHeaderViewHeight()
    var viewHeight: CGFloat = kScreenHeight - kNavigationBarHeight - getCellsTotalHeight() - headerHeight
    let minHeight: CGFloat = getViewMinHeight()
    return max(viewHeight, minHeight)
}

func getTableViewHeaderViewHeight() -> CGFloat {
    var headerHeight = 0.0
    if belongView!.tableViewHeaderView {
        headerHeight = (belongView!.tableViewHeaderView?.height)!
    }
    return headerHeight
}

func getCellsTotalHeight() -> CGFloat {
    let cellCount: Int= getTableViewCellsCount()
    return CGFloat(cellCount) * belongView!.rowHeight
}

func getTableViewCellsCount() -> Int {
    return (belongView!.dataSource.tableView(tableView: belongView!, numberOfRowsInSection: 0))!
}

func getViewMinHeight() -> CGFloat {
    let buttonBottomDistance: CGFloat = 15.0
    return CGRectGetMaxY(addButton) + buttonBottomDistance
}

5、将临时变量替换成提炼的函数

最后,我们将必要的局部变量变成函数,并且将计算高度的临时变量变成函数调用
可以看到updateViewHeight的目的是为了更新表尾视图的高度,那么里面还存在一些计算高度的代码,将这些代码做最后的提炼成函数getAdjustHeight

func updateViewHeight() {
    self.frame.height = getAdjustHeight()
}

func getAdjustHeight() -> CGFloat {
    var viewHeight: CGFloat = kScreenHeight - kNavigationBarHeight - getCellsTotalHeight() - getTableViewHeaderViewHeight()
    return max(viewHeight, getViewMinHeight())
}

func getTableViewHeaderViewHeight() -> CGFloat {
    var headerHeight = 0.0
    if belongView!.tableViewHeaderView {
        headerHeight = (belongView!.tableViewHeaderView?.height)!
    }
    return headerHeight
}

func getCellsTotalHeight() -> CGFloat {
    return CGFloat(getTableViewCellsCount()) * belongView!.rowHeight
}

func getTableViewCellsCount() -> Int {
    return (belongView!.dataSource.tableView(tableView: belongView!, numberOfRowsInSection: 0))!
}

func getViewMinHeight() -> CGFloat {
    return CGRectGetMaxY(addButton) + getButtonBottomDistance()
}

func getButtonBottomDistance() -> CGFloat {
    return 15.0
}

大功告成

文集:开发日记

开发日记
Web note ad 1