重构笔记-Extra Method

前言

这算是我的读书笔记吧,最近在看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
}

大功告成

文集:开发日记

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容