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