重构:改善既有代码的设计-笔记(二)

260 阅读9分钟

重构:改善既有代码的设计-购书链接

封装

  1. 封装记录

    用数据类取代记录型结构

    const organization = { name:'Acme', country: "GB" }
    
    class Organization {
        constructor(data){
            this._name = data.name
        }
        get name(){ return this._name }
        set name(name){ this._name = name }
    }
    const organization = new Organization({...})
    

    做法

    1. 对持有记录的变量使用封装变量,将其封装在一个函数中
    2. 创建一个类,将记录包装起来,并将记录变量的只替换成该类的一个实例,然后在类上定义访问和修改的函数
    3. 新建一个函数,让它返回该类的对象,而非原始记录
    4. 替换项目中的使用点
    const organization = { name:'Acme', country: "GB" }
    let result = ''
    result += `<h1>${organization.name} : ${organization.country}</h1>\n`
    organization.name = '121'
    organization.country = '121'
    result += `<h1>${organization.name} : ${organization.country}</h1>`
    console.log(result)
    
    class Organization {
        constructor(data){
            this._name = data.name
            this._country = data.country
        }
        get name(){ return this._name }
        get country(){ return this._country }
        set name(name){ this._name = name }
        set country(country){ this._country = country }
    }
    const organization = new Organization({ name:'Acme', country: "GB" })
    let result = ''
    result += `<h1>${organization.name} : ${organization.country}</h1>\n`
    organization.name = '121'
    organization.country = '121'
    result += `<h1>${organization.name} : ${organization.country}</h1>`
    console.log(result)
    
  2. 封装集合

    对可变数据进行封装,不返回源数据

    class Person {
        get courses() { return this._courses }
        set courses(courses) { this._courses = courses }
    }
    class Course {
        ...
    }
    
    class Person {
        get courses() { return this._courses.slice() }
        set courses(courses) { this._courses = courses.slice() }
        addCourse(aCourse) { ... }
        removeCourse(aCourse) { ... }
    }
    class Course {
        ...
    }
    

    做法

    1. 如果集合对引用尚未封装,则先用封装变量封装
    2. 在类上增加用于增删对函数
    3. 查找集合的引用点,修改后测试
    4. 每次只返回一份只读的副本
    class Person {
        constructor(name){
            this._name = name
            this._courses = []
        }
        get name(){return this._name }
        get courses() { return this._courses }
        set courses(courses) { this._courses = courses }
    }
    class Course {
        constructor(name, isAdvanced){
            this._name = name
            this._isAdvanced = isAdvanced
        }
        get name(){return this._name }
        get isAdvanced(){return this._isAdvanced }
    }
    const CoursesName = [{ name:'Math', isAdvanced: true }, { name:'Chinese', isAdvanced: true }, { name:'English', isAdvanced: false }]
    const p = new Person('xy')
    p.courses = CoursesName.map(({ name, isAdvanced }) => new Course(name, isAdvanced))
    p.courses.push(new Course('history', true)) // 无法监测的行为!
    
    class Person {
        constructor(name){
            this._name = name
            this._courses = []
        }
        get name(){return this._name }
        get courses() { return this._courses.slice() }
        set courses(courses) { this._courses = courses.slice() }
        addCourse(aCourse) { this._courses.push(aCourse) }
        removeCourse(aCourse){
            const index = this._courses.indexOf(aCourse)
            if(index === -1) throw new RangeError("not in")
            this._courses.splice(index, 1)
        }
    }
    class Course {
        constructor(name, isAdvanced){
            this._name = name
            this._isAdvanced = isAdvanced
        }
        get name(){return this._name }
        get isAdvanced(){return this._isAdvanced }
    }
    const CoursesName = [{ name:'Math', isAdvanced: true }, { name:'Chinese', isAdvanced: true }, { name:'English', isAdvanced: false }]
    const history = new Course('history', true)
    const p = new Person('xy')
    p.courses = CoursesName.map(({ name, isAdvanced }) => new Course(name, isAdvanced))
    p.addCourse(history)
    p.removeCourse(history)
    
  3. 以对象取代基本类型

    当基本数据类型不满足业务需求时,将其封装成类方便管理

    class Order{
        get priority(){return this._priority }
    }
    const priorities = ['low', 'normal', 'high', 'rush']
    
    class Order{
        constructor(data){
            this._priority = new Priority()
        }
    }
    class Priority{
        toString(){return this._value}
        static legalValues(){return ['low', 'normal', 'high', 'rush']}
    }
    const priorities = Priority.legalValues()
    

    做法

    1. 如果变量未被封装起来,则先封装变量
    2. 为数据值创建一个简单的类,保存数据并提供取值函数
    3. 修改第一步得到的设值函数,令其创建一个新类的对象将其存入字段
    4. 修改取值函数,令其调用新类的取值函数
    class Order{
        constructor(data){
            this._priority = data.priority
        }
        get priority(){return this._priority }
    }
    const priorities = ['low', 'normal', 'high', 'rush']
    let orders = new Array(10).fill(0).map((item, index) => new Order({ priority: priorities[index % 4] }))
    console.log(orders.filter(item => item.priority === 'high' || item.priority === 'rush'))
    
    class Order{
        constructor(data){
            this._priority = new Priority(data.priority)
        }
        get priorityString(){return this._priority.toString() }
        get priority(){return this._priority}
        set priority(aString){this._priority = new Priority(aString)}
    }
    class Priority{
        constructor(value){
            if(value instanceof Priority) return value
            if(Priority.legalValues().includes(value)){
                this._value = value
            }else{
                throw new Error('invalid for Priority')
            }
        }
        toString(){return this._value}
        static legalValues(){return ['low', 'normal', 'high', 'rush']}
        get _index(){ return Priority.legalValues().findIndex(item => item === this._value) }
    
        equals(order) { return this._index === order._index }
        higherThan(order) { return this._index > order._index }
        lessThan(order) { return this._index < order._index }
    }
    const priorities = Priority.legalValues()
    const normal = new Priority('normal')
    let orders = new Array(10).fill(0).map((item, index) => new Order({ priority: priorities[index % 4] }))
    console.log(orders.filter(item => item.priority.higherThan(normal)))
    
  4. 以查询取代临时变量

    将临时变量中的计算逻辑移动到类/函数中,通过查询获取

    class Order {
        get price() {
            let basePrice = ...
            let discountFactor = ...
            return basePrice * discountFactor
        }
    }
    
    class Order {
        get price() {...}
        get basePrice() { ... }
        get discountFactor() {...}
    }
    

    做法

    1. 检查变量在是否完全计算完毕,是否存在副作用
    2. 如果变量不是只读的,可以先改造成只读变量
    3. 将变量赋值代码提炼成设值函数
    class Order {
        constructor(quantity, item){
            this._quantity = quantity
            this._item = item
        }
        get price() {
            let basePrice = this._quantity * this._item.price
            let discountFactor = 0.98
            if(basePrice > 1000) discountFactor -= 0.03
            return basePrice * discountFactor
        }
    }
    
    class Order {
        constructor(quantity, item){
            this._quantity = quantity
            this._item = item
        }
        get price() {
            return this.basePrice * this.discountFactor
        }
        get basePrice() { return this._quantity * this._item.price }
        get discountFactor() {
            let discountFactor = 0.98
            if(this.basePrice > 1000) discountFactor -= 0.03
            return discountFactor
        }
    }
    
  5. 提炼类(内联类)

    将较大的类中的模块分离出去成为独立的类

    class Person {
        ...
    }
    
    class Person {
        constructor(name){
            this._telephoneNumber = new TelephoneNumber()
        }
    }
    class TelephoneNumber{
        ...
    }
    

    做法

    1. 决定如何分解类所负的责任
    2. 创建一个新的类,用以表现从旧类中分离出来的责任
    3. 构造旧类时创建一个新类的实例,建立联系
    4. 搬移字段,函数,去掉不再需要的函数接口
    class Person {
        constructor(name){
            this._name = name
        }
        get name(){return this._name }
        get telephoneNumber(){return `(${this.officeAreaCode}) ${this.officeNumber}` }
        get officeAreaCode(){return this._officeAreaCode }
        set officeAreaCode(arg){return this._officeAreaCode = arg }
        get officeNumber(){return this._officeNumber }
        set officeNumber(arg){return this._officeNumber = arg }
    }
    
    class Person {
        constructor(name){
            this._name = name
            this._telephoneNumber = new TelephoneNumber()
        }
        get name(){return this._name }
        get telephoneNumber(){return this._telephoneNumber.toString() }
        get officeAreaCode(){return this._telephoneNumber.areaCode }
        set officeAreaCode(arg){return this._telephoneNumber.areaCode = arg }
        get officeNumber(){return this._telephoneNumber.number }
        set officeNumber(arg){return this._telephoneNumber.number = arg }
    }
    class TelephoneNumber{
        get number(){return this._number}
        set number(arg){return this._number = arg}
        get areaCode(){return this._areaCode }
        set areaCode(arg){return this._areaCode = arg}
        toString(){return `(${this._areaCode}) ${this._number}`}
    }
    
  6. 内联类(提炼类)

    当一个类不再承担足够的责任时,将其直接内联到引用其的类中

    class Shipment {
        constructor(){
            this._trackingInformation = new TrackingInformation(company, number)
        }
    }
    class TrackingInformation {
        ...
    }
    
    class Shipment {
        constructor(){
            ...
        }
        ... //TrackingInformation
    }
    

    做法

    1. 对于内联类中的所有函数,在目标类中创建对应的函数
    2. 修改源类函数所有的引用点,令其调用目标类对应的委托方法
    3. 将所有的函数数据移动到目标类中
    class TrackingInformation {
        constructor(company, number){
            this._shippingCompany = company
            this._trackingNumber = number
        }
        get shippingCompany(){return this._shippingCompany}
        set shippingCompany(arg) {this._shippingCompany = arg}
        get trackingNumber(){return this._trackingNumber}
        set trackingNumber(arg) {this._trackingNumber = arg}
        get display(){ return `${this.shippingCompany} ${this.trackingNumber}` }
    }
    class Shipment {
        constructor(name, company, number){
            this._name = name
            this._trackingInformation = new TrackingInformation(company, number)
        }
        get trackingInfo(){return this._trackingInformation.display}
        get trackingInformation(){return this._trackingInformation}
    }
    
    class Shipment {
        constructor(name, company, number){
            this._name = name
            this._shippingCompany = company
            this._trackingNumber = number
        }
        get shippingCompany(){return this._shippingCompany}
        set shippingCompany(arg) {this._shippingCompany = arg}
        get trackingNumber(){return this._trackingNumber}
        set trackingNumber(arg) {this._trackingNumber = arg}
        get trackingInfo(){ return `${this.shippingCompany} ${this.trackingNumber}` }
        get trackingInformation(){return { _shippingCompany: this.shippingCompany, _trackingNumber: this._trackingNumber}}
    }
    
  7. 隐藏委托关系(移除中间人)

    隐藏跨级引用的情况,增加快速访问的接口

    class Person {
        get department(){return this._department }
    }
    class Department {
        get chargeCode(){return this._chargeCode }
    }
    
    class Person {
        get manager(){return this._department.manager }
    }
    class Department {
        get manager(){return this._manager}
    }
    

    做法

    1. 对于每个委托关系的函数,在服务对象端建立一个简单的委托函数
    2. 调整客户端,令其调用服务对象提供的函数
    class Person {
        constructor(name){
            this._name = name;
        }
        get name(){return this._name }
        get department(){return this._department }
        set department(arg){this._department = arg}
    }
    class Department {
        get chargeCode(){return this._chargeCode}
        set chargeCode(arg){this._chargeCode = arg}
        get manager(){return this._manager}
        set manager(arg){this._manager = arg}
    }
    
    class Person {
        constructor(name){
            this._name = name;
        }
        get name(){return this._name }
        set department(arg){this._department = arg}
        get manager(){return this._department.manager }
    }
    class Department {
        get chargeCode(){return this._chargeCode}
        set chargeCode(arg){this._chargeCode = arg}
        get manager(){return this._manager}
        set manager(arg){this._manager = arg}
    }
    
  8. 移除中间人(隐藏委托关系)

    类中存在大量的委托函数,服务类变成类一个中间人

    class Person {
        get manager(){return this._department.manager }
    }
    class Department {
        get manager(){return this._manager}
    }
    
    class Person {
        get department(){return this._department }
    }
    class Department {
        get chargeCode(){return this._chargeCode }
    }
    

    做法

    1. 为受托对象创建一个取值函数
    2. 对于每个委托函数,是客户端转为连续的调用访问
    class Person {
        constructor(name){
            this._name = name;
        }
        get name(){return this._name }
        set department(arg){this._department = arg}
        get manager(){return this._department.manager }
        get chargeCode(){return this._department.chargeCode}
    }
    class Department {
        get chargeCode(){return this._chargeCode}
        set chargeCode(arg){this._chargeCode = arg}
        get manager(){return this._manager}
        set manager(arg){this._manager = arg}
    }
    
    class Person {
        constructor(name){
            this._name = name;
        }
        get name(){return this._name }
        get department(){return this._department }
        set department(arg){this._department = arg}
    }
    class Department {
        get chargeCode(){return this._chargeCode}
        set chargeCode(arg){this._chargeCode = arg}
        get manager(){return this._manager}
        set manager(arg){this._manager = arg}
    }
    
  9. 替换算法

    使用更清晰的方式替换复杂的方式

    function findPerson(people){
        for(let i = 0; i < people.length; i++){
            if(people[i] === 'Don') return 'Don'
        }
        return ''
    }
    
    function findPerson(people) {
        return people.find(p => ['Don'].includes(p)) || ''
    }
    

    做法

    1. 整理代替换的算法,确保它以及被抽取到一个独立的函数中
    2. 为函数准备测试,固定其行为
    3. 准备好另一个算法进行替换
    function findPerson(people){
        for(let i = 0; i < people.length; i++){
            if(people[i] === 'Don') return 'Don'
            if(people[i] === 'Join') return 'Join'
            if(people[i] === 'Kent') return 'Kent'
        }
        return ''
    }
    
    function findPerson(people) {
        const candidates = ['Don', 'Join', 'Kent']
        return people.find(p => candidates.includes(p)) || ''
    }
    

封装.png

搬移特性

  1. 搬移函数

    合理的把函数放置在它应该出现的位置

    function trackSummary(points){
        ...
        function distance(p1, p2){...}
    }
    
    function trackSummary(points){ ... }
    function distance(p1, p2){...}
    

    做法

    1. 检查函数在当前上下文引用的元素,考虑是否将他们一起搬移
    2. 检查待搬移函数是否具有多态性
    3. 将函数复制一份到目标的上下文中,调整函数
    function trackSummary(points){
        const totalTime = calculateTime()
        const totalDistance = calculateDistance()
        const pace = totalTime / 60 / totalDistance
        return {
            time: totalTime,
            distance: totalDistance,
            pace
        }
        function calculateDistance(){
            let result = 0
            for(let i = 1; i < points.length; i++){
                result += distance(points[i - 1], points[i])
            }
            return result
        }
        function distance(p1, p2){
            const EARTH_RADIUS = 3959
            const dLat = radians(p2.lat) - radians(p1.lat)
            const dLon = radians(p2.lon) - radians(p1.lon)
            const a = Math.pow(Math.sin(dLat / 2), 2) + Math.cos(radians(p2.lat)) * Math.cos(radians(p1.lat)) * Math.pow(Math.sin(dLon / 2), 2)
            const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
            return EARTH_RADIUS * c
        }
        function radians(degrees){
            return degrees * Math.PI / 180
        }
        function calculateTime(){
            return 6
        }
    }
    
    function trackSummary(points){
        const totalTime = calculateTime()
        const distance = totalDistance(points)
        const pace = totalTime / 60 / distance
        return {
            time: totalTime,
            distance,
            pace
        }
        function calculateTime(){
            return 6
        }
    }
    function totalDistance(points){
        let result = 0
        for(let i = 1; i < points.length; i++){
            result += distance(points[i - 1], points[i])
        }
        return result
    }
    function distance(p1, p2){
        const EARTH_RADIUS = 3959
        const dLat = radians(p2.lat) - radians(p1.lat)
        const dLon = radians(p2.lon) - radians(p1.lon)
        const a = Math.pow(Math.sin(dLat / 2), 2) + Math.cos(radians(p2.lat)) * Math.cos(radians(p1.lat)) * Math.pow(Math.sin(dLon / 2), 2)
        const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
        return EARTH_RADIUS * c
    }
    function radians(degrees){
        return degrees * Math.PI / 180
    }
    
  2. 搬移字段

    如果更新一个字段,需要同时在几个结构中进行修改,则表明该字段需要被搬移到一个集中的地点

    class Customer {
        constructor(name, discountRate){
            this._discountRate = discountRate
            this._contact = new Contact()
        }
    }
    class Contact { ... }
    
    class Customer {
        constructor(name, discountRate){
            this._contact = new Contact(discountRate)
        }
    }
    class Contact {
        constructor(discountRate){
            this._discountRate = discountRate
        }
    }
    

    做法

    1. 确保源字段已经得到良好封装
    2. 在目标对象上创建一个字段
    3. 确保源对象能正常引用目标对象
    4. 调整源对象的访问函数,令其使用目标对象的字段
    class Customer {
        constructor(name, discountRate){
            this._name = name
            this._discountRate = discountRate
            this._contact = new Contact()
        }
        get discountRate(){return this._discountRate}
        becomePreferred(){
            return this._discountRate += 0.03
        }
        applyDiscount(amount){
            return amount - amount * this.discountRate
        }
    }
    class Contact {
        constructor(){
            this._startDate = new Date()
        }
    }
    
    class Customer {
        constructor(name, discountRate){
            this._name = name
            this._contact = new Contact(discountRate)
        }
        get discountRate(){return this._contact.discountRate}
        _setDiscountRate(aNumber){return this._contact.discountRate = aNumber}
        becomePreferred(){
            return this._setDiscountRate(this._contact.discountRate += 0.03)
        }
        applyDiscount(amount){
            return amount - amount * this.discountRate
        }
    }
    class Contact {
        constructor(discountRate){
            this._startDate = new Date()
            this._discountRate = discountRate
        }
        get discountRate(){return this._discountRate}
        set discountRate(arg){this._discountRate = arg}
    }
    
  3. 搬移语句到函数(搬移语句到调用者)

    某些语句与一个函数放在一起更加像一个整体的时候,就可以将其搬移到函数中

    function renderPerson(person){
        [`<p>title: ${aPhoto.title}</p>`, ...emitPhotoData(person.photo)]
    }
    function emitPhotoData(aPhoto){
        return [`<p>date: ${aPhoto.date}</p>`]
    }
    function renderPhoto(aPhoto){
        return [`<p>title: ${aPhoto.title}</p>`,
            `<p>date: ${aPhoto.date}</p>`].join('/n')
    }
    
    function renderPerson(person){
        [...emitPhotoData(person.photo)]
    }
    function emitPhotoData(aPhoto){
        return [`<p>title: ${aPhoto.title}</p>`, `<p>date: ${aPhoto.date}</p>`]
    }
    function renderPhoto(aPhoto){
        return [...emitPhotoData(person.photo)].join('/n')
    }
    

    做法

    1. 如果重复的代码距离目标函数有些距离,先用移动语句
    2. 如果目标函数仅被唯一一个源函数调用,则只需要将源函数中重复的代码段剪切复制到函数中
    3. 如果由多个调用点,则先选择对一个调用点应用提炼函数,将待搬移语句与目标函数提炼成一个新函数
    function renderPerson(person){
        const result = []
        result.push(`<p>${person.name}</p>`)
        result.push(renderPhoto(person.photo))
        result.push(`<p>title: ${person.photo.title}</p>`)
        result.push(emitPhotoData(person.photo))
        return result.join('\n')
    }
    function emitPhotoData(aPhoto){
        const result = []
        result.push(`<p>location: ${aPhoto.location}</p>`)
        result.push(`<p>date: ${aPhoto.date.toDateString()}</p>`)
        return result.join('\n')
    }
    function renderPhoto(aPhoto){
        const result = []
        result.push(`<p>name: ${aPhoto.name}</p>`)
        result.push(`<p>color: ${aPhoto.color}</p>`)
        return result.join('\n')
    }
    function renderDiv(person){
        return [
            "<div>", 
            `<p>title: ${person.photo.title}</p>`,
            emitPhotoData(person.photo),
            "</div>"
        ].join('\n')
    }
    
    function renderPerson(person){
        const result = []
        result.push(`<p>${person.name}</p>`)
        result.push(renderPhoto(person.photo))
        result.push(emitPhotoData(person.photo))
        return result.join('\n')
    }
    function renderPhoto(aPhoto){
        const result = []
        result.push(`<p>name: ${aPhoto.name}</p>`)
        result.push(`<p>color: ${aPhoto.color}</p>`)
        return result.join('\n')
    }
    function renderDiv(person){
        const result = ["<div>", emitPhotoData(person.photo), "</div>"]
        return result.join('\n')
    }
    function emitPhotoData(aPhoto) {
        return [
            `<p>title: ${aPhoto.title}</p>`,
            `<p>location: ${aPhoto.location}</p>`,
            `<p>date: ${aPhoto.date.toDateString()}</p>`
        ].join('\n')
    }
    
  4. 搬移语句到调用者(搬移语句到函数)

    当函数边界发生偏移,在某些调用点表现出不同的行为

    function renderPerson(person){
        [...emitPhotoData(person.photo)]
    }
    function emitPhotoData(aPhoto){
        return [`<p>title: ${aPhoto.title}</p>`, `<p>date: ${aPhoto.date}</p>`]
    }
    function renderPhoto(aPhoto){
        return [...emitPhotoData(person.photo)].slice(1).join('/n')
    }
    
    function renderPerson(person){
        [`<p>title: ${aPhoto.title}</p>`, ...emitPhotoData(person.photo)]
    }
    function emitPhotoData(aPhoto){
        return [`<p>date: ${aPhoto.date}</p>`]
    }
    function renderPhoto(aPhoto){
        return [...emitPhotoData(aPhoto)].join('/n')
    }
    

    做法

    1. 如果源函数很简单,且调用者也不多,则只需把要搬移的代码复制回去
    2. 若调用点不止一个,则先用提炼函数把不想搬移的代码提炼成一个新函数
    3. 对源函数应用内联函数,对提炼函数进行修改
    function renderPerson(person){
        const result = []
        result.push(`<p>${person.name}</p>`)
        result.push(renderPhoto(person.photo))
        result.push(emitPhotoData(person.photo))
        return result.join('\n')
    }
    function renderPhoto(aPhoto){
        const result = []
        result.push(`<p>name: ${aPhoto.name}</p>`)
        result.push(`<p>color: ${aPhoto.color}</p>`)
        return result.join('\n')
    }
    function renderDiv(person){
        const result = ["<div>", emitPhotoData(person.photo), "</div>"]
        return result.join('\n')
    }
    function emitPhotoData(aPhoto) {
        return [
            `<p>title: ${aPhoto.title}</p>`,
            `<p>location: ${aPhoto.location}</p>`,
            `<p>date: ${aPhoto.date.toDateString()}</p>`
        ].join('\n')
    }
    
    function renderPerson(person){
        const result = []
        result.push(`<p>${person.name}</p>`)
        result.push(renderPhoto(person.photo))
        result.push(emitPhotoData(person.photo))
        result.push(`<p>location: ${person.photo.location}</p>`)
        return result.join('\n')
    }
    function renderPhoto(aPhoto){
        const result = []
        result.push(`<p>name: ${aPhoto.name}</p>`)
        result.push(`<p>color: ${aPhoto.color}</p>`)
        return result.join('\n')
    }
    function renderDiv(person){
        const result = ["<div>", 
        emitPhotoData(person.photo),
        `<p>location: ${person.photo.location}</p>`,
        "</div>"]
        return result.join('\n')
    }
    function emitPhotoData(aPhoto) {
        return [
            `<p>title: ${aPhoto.title}</p>`,
            `<p>date: ${aPhoto.date.toDateString()}</p>`
        ].join('\n')
    }
    
  5. 函数调用取代内联代码

    通过函数的方式去消除重复的逻辑

    let appliesToMass = false;
    let states = ['MA']
    for(let s of states) {
        if(s === 'MA') appliesToMass = true;
    }
    
    let states = ['MA']
    let appliesToMass = states.includes('MA');
    

    做法

    1. 内联代码替代为对一个既有函数的调用
    let appliesToMass = false;
    let states = ['MA']
    for(let s of states) {
        if(s === 'MA') appliesToMass = true;
    }
    
    let states = ['MA']
    let appliesToMass = states.includes('MA');
    
  6. 移动语句 让存在关联的代码一起出现,使代码更加容易理解

    if(stack.length === 0){
        ...
        stack.push(i)
    }else{
        ...
        stack.push(i)
    }
    
    if(stack.length === 0){
        ...
    }else{
        ...
    }
    stack.push(i)
    

    做法

    1. 确定待移动的代码片段应该搬完何处,搬移后是否会影响正常工作
    2. 搬移代码
    let result, stack = []
    for(let i = 0; i < 5; i++){
        if(stack.length === 0){
            result = 0
            stack.push(i)
        }else{
            result = stack[stack.length - 1]
            stack.push(i)
        }
    }
    
    let result, stack = []
    for(let i = 0; i < 5; i++){
        if(stack.length === 0){
            result = 0
        }else{
            result = stack[stack.length - 1]
        }
        stack.push(i)
    }
    
  7. 拆分循环

    拆分身兼多职的循环,让代码更加易读

    for (const p of people) {
        if(p.age < youngest) youngest = p.age
        totalSalary += p.salary
    }
    
    let youngest = Math.min(...people.map(p => p.age))
    let totalSalary = people.reduce((prev, item) => prev + item.salary, 0)
    

    做法

    1. 复制一遍循环代码
    2. 识别移除循环中的重复代码,使得每个循环只做一件事
    3. 拆分后使用提炼函数
    const people = [
        { age: 20, salary: 10000 },
        { age: 30, salary: 10000 },
        { age: 25, salary: 20000 },
        { age: 22, salary: 50000 },
        { age: 26, salary: 60000 },
    ]
    let youngest = people[0] ? people[0].age : Infinity;
    let totalSalary = 0
    for (const p of people) {
        if(p.age < youngest) youngest = p.age
        totalSalary += p.salary
    }
    
    let youngest = Math.min(...people.map(p => p.age))
    let totalSalary = people.reduce((prev, item) => prev + item.salary, 0)
    
  8. 管道取代循环

    使用管道优化迭代结构,可读性更强

    function acquireData(input) {
        for (const line of lines) {
           ...
        }
    }
    
    function acquireData(input) {
        lines.slice(1).filter(line => line.trim() !== '')...
    }
    

    做法

    1. 创建一个新变量,用于存放和参与循环过程的集合
    2. 从顶部开始,将循环每一块行为都搬移出来,在上一步创建的集合变量用一种管道运算替代
    const input = `office, country, telephone
    
    Chicago, USA, +1 312 373 1000
    Beijing, China, +86 4000 900 505
    Chicago, USA, +1 312 373 1000
    Beijing, China, +86 4000 900 505
    Chicago, USA, +1 312 373 1000
    Beijing, China, +86 4000 900 505`;
    function acquireData(input) {
        const lines = input.split('\n')
        let firstLine = true
        const result = []
        for (const line of lines) {
            if(firstLine) {
                firstLine = false
                continue
            }
            if(line.trim() === '') continue
            const record = line.split(',')
            if(record[1].trim() === 'China'){
                result.push({
                    city: record[0].trim(),
                    phone: record[2].trim(),
                })
            }
        }
        return result
    }
    
    function acquireData(input) {
        const lines = input.split('\n')
        return lines
                .slice(1)
                .filter(line => line.trim() !== '')
                .map(line => line.split(','))
                .filter(record => record[1].trim() === 'China')
                .map(record => ({
                    city: record[0].trim(),
                    phone: record[2].trim(),
                }))
    }
    
  9. 移除死代码

    没用的东西大胆删除了他,不然越来越多

搬移特性.png