读完重构后的一点感悟

284 阅读2分钟

最近花了一个月的时间将重构从头到尾读了一遍,颇有收获,推荐没读过的朋友都通读一遍,这样能让读你代码的朋友少掉点头发😏。

测试的重要性

测试是重构的前提,当我们在重构一个函数后,完善的单元测试可以使我们快速的知道重构后的函数是否改变了原始的行为。所以,在我们完成一个功能时,应添加完备的测试代码来保证后续重构。

何时重构

重构不一定非得等专门的一段时间,可以随时进行。当你开发一个新需求时,如果发现在现有的代码上无法便捷的添加新需求的代码,这就说明你应该重构代码,从而能轻松的添加新需求的代码。

或者,当你阅读之前的代码觉得以前的代码无法很好地表述现有的场景时,你也应该重构来使代码更好的表述现有的场景,从而使代码更易读、易理解。

拆分大函数

原因:当一个函数超过 50 行的时候,你就应该警惕它,问问自己是否该函数符合单一原则。若它同时处理了多件事,那你应该使用 Swift 中的嵌套函数来重构它。

待重构函数(举例函数的行数并不多,但是也应该重构):

func calculateScore(stus: [Student]) -> Double {
    // 获取所有学生的成绩
    var scores = [Double]()
    for stu in stus {
        scores.append(stu.score)
    }
    
    // 只计算 60 - 90 之间的平均值
    var valueScores = [Double]()
    for score in scores {
        if score > 60 && score < 90 {
            valueScores.append(score)
        }
    }
    
    // 计算平均值
    var totalScore = 0.0
    valueScores.forEach { totalScore += $0 }
    let aveScore = totalScore / Double(valueScores.count)
    return aveScore
}

重构后的函数:

func calculateScore(stus: [Student]) -> Double {
    // 获取所有学生的成绩
    func getScores() -> [Double] {
        return stus.map { $0.score }
    }
    
    // 只计算 60 - 90 之间的平均值
    func getValueScores(scores: [Double]) -> [Double] {
        return scores.filter { $0 > 60 && $0 < 90 }
    }
    
    // 计算平均值
    func calculate(valueScores: [Double]) -> Double {
        let totalScore = valueScores.reduce(0) { (res, cur) -> Double in
            return res + cur
        }
        return totalScore / Double(valueScores.count)
    }
    
    let valueScores = getValueScores(scores: getScores())
    return calculate(valueScores: valueScores)
}

拆分循环

原因:一个循环负责多种逻辑,会使代码难以理解。

疑惑:拆分后可能会对代码的性能造成影响?现在的硬件设施一般都不会造成可感知的影响。如果会有影响,逻辑清晰的代码也会比不清晰的代码更加好改。

待重构函数:

var count = 0
let name = "refactor"

for _ in 0..<5 {
    count += 1
    print(name)
}

重构后的函数:

for _ in 0..<5 {
    count += 1
}

for _ in 0..<5 {
    print(name)
}

用高阶函数代替循环

原因:高阶函数比 for-in 更加易读,且代码简洁。这意味着代码出错的几率更小。

待重构函数:

/// 添加的产品是否包含手机
func hasCellPhone(goodsItem: [GoodsItem]?) -> Bool {
     var result = false
     let cellPhoneType = "21"
     for item in goodsItem ?? [] {
         if let goodsType = item.goodsType {
             if goodsType.code == cellPhoneType {
                  result = true
                  break
             }
         }
    }
    return result
}

重构后的函数:

/// 添加的产品是否包含手机
func hasCellPhone(goodsItem: [GoodsItem]?) -> Bool {
     guard let items = goodsItem else { return false }
     let cellPhoneType = "21"
     let result = items.filter { $0.goodsType?.code == cellPhoneType }
     return !result.isEmpty
}

使用 guard 拆解嵌套if

原因:使用 guard 可以是代码逻辑更加清晰易读。

待重构函数:

func buyStock(buyer: Buyer) {
    if buyer.age > 18 {
        if buyer.accountIsValid {
            if buyer.accountMoney > 0 {
                buyer.buy(stock: "")
            }
        }
    }
}

重构后的函数:

func buyStock(buyer: Buyer) {
    guard buyer.age > 18 && buyer.accountIsValid && buyer.accountMoney > 0 else {
        return
    }
    buyer.buy(stock: "")
}

保持数据的不可变性

原因:如果数据是可变的,那么在未来的某个地方可能会修改它,而你并不知道,这样就会造成代码的错误(可能会发生的,就一定会发生)。如果数据开始就是不可修改的,那就能保证代码的正确性。

待重构函数:

var scores = [Double]()
for stu in stus {
    scores.append(stu.score)
}

重构后的函数:

let scores = stus.map { $0.score }

实践才是检验真理的唯一标准,希望看完这篇文章你能立马开始项目的重构。🤩