重构改善既有代码的设计 --方法篇

重构改善既有代码的设计 --方法篇

重新组织数据

  • 目的:解决不易理解的错综复杂的长函数,促使代码更清晰更易维护。

  • 解决思路:长函数变成短函数、算法结构优化

  • 常用方法:抽离函数(Extract Method),通过查询的方式来获取值 (Replace Temp With query), 优化算法 (Substitute Algorthm)

  • AF老项目中有很多行数过百的函数,导致维护起来很费时费力,很多函数都可以用Extract Method方法,拆成小函数,无论是后期增加逻辑还是要代码复用都都能帮助理解。另外函数很长的原因的是场景特别多有很多出来了函数,但是这些个处理函数都写在一个入口中,还是希望用Replace Temp With query方式来处理数据

  1. 抽离函数(Extract Method)
  • 一个函数如果需要写很多注释来让人理解的时候,这个函数应该是可以被拆解成小函数。良好的函数命名可以让人更容易理解代码

  • 场景说明


    let infoArr = Array(10).fill().map((item, index) => {
        return {
            name: 'VFrank' + index,
            age: Math.floor(Math.random() * 10 + 20)
        }
    });

    function printInfo() {

        // 输出一个横幅
        console.log('**********************')
        console.log('*****Hello VFrank*****')
        console.log('**********************')

        // 获取所有name和age字段数组统计项
        let temp = { nameArr: [], ageArr: [] };

        infoArr.forEach(item) {
            let { name, age } = item;
            temp.nameArr.push(name);
            temp.ageArr.push(age);
        }

        // 打印统计数据
        console.log(nameArr);
        console.log(ageArr);
    }

  • 上面的printInfo函数,其实每个注释都可以是独立的一个函数,这里说明抽离函数中的几个问题
  1. 第一块注释,那无对内部变量的引用,可以直接方法抽离为一个小函数
  2. 第二块注释,属于数据处理层可以单独抽离出来,以返回值形式
  3. 第三快注释,打印逻辑对内部变量有引用关系,需要以参数形式传入

修改后如下


    let infoArr = Array(10).fill().map((item, index) => {
        return {
            name: 'VFrank' + index,
            age: Math.floor(Math.random() * 10 + 20)
        }
    });

    function printInfo() {

        printBanner();
        let result = getOutStand(); // 这里用Replace Temp With query方法修改
        printDetail(result);

    }

    // 无引用内部变量关系直接抽离
    function printBanner() {
        console.log('**********************')
        console.log('*****Hello VFrank*****')
        console.log('**********************')
    }

    // 计算逻辑代码抽离,
    function getOutStand() {

        let result = { nameArr: [], ageArr: [] };
        infoArr.forEach(item) {
            let { name, age } = item;
            result.nameArr.push(name);
            result.ageArr.push(age);
        }
        return result;

    }

    // 对内部变量有引用关系的通过参数传入
    function printDetail(result) {
        console.log(result.nameArr);
        console.log(result.ageArr);
    }

  1. 通过查询的方式来获取值(Replace Temp With query)
  • 函数中存在很多临时变量,这些变量只在这个函数中使用,这个导致一个函数过长而不易理解。换成一个函数可以增加函数的颗粒度,给后续维护带来极大的帮助

  • 场景


    // 一个计算价格的函数, 基本价格 * 折扣
    getPrice() {

        let basePrice = this.quantity * this.itemPrice;
        let discountFactor = 0;
        if (basePrice > 1000) {
            discountFactor = 0.95;
        } else {
            discountFactor = 0.98;
        }

        return basePrice * discountFactor;
    }

  • 第一次修改把basePrice抽取出来
    getPrice() {

        let discountFactor = 0;
        if (this.getBasePrice() > 1000) {
            discountFactor = 0.95;
        } else {
            discountFactor = 0.98;
        }

        return this.getBasePric() * discountFactor;
    }

    getBasePric() {
        return this.quantity * this.itemPrice;
    }

  • 修改后发现discountFactor其实也可以抽出来,再次修改如下

    getPrice() {

        return this.getBasePric() * this.discountFactor();
    }

    getBasePric() {
        return this.quantity * this.itemPrice;
    }

    getDisCountFactor() {

        if (this.getBasePrice() > 1000) {return 0.95;}
        return 0.98;
    }

在对象之间搬移特性

  • 目的:解决对象因为承担过多的责任而变得臃肿不堪的场景(比如业务代码Mgr经常作了Panel或者Form应该做的事情)

  • 解决思路:梳理对象的责任,移动对应的函数。(决定把责任放在哪)

  • 常用方法:搬移方法(Move Method),提炼类(ExTract Class)

搬移方法(Move Method)

  • 一个类如果与另外一个类有着高度耦合的关系,就需要搬移函数,使得每个类更简单

  • 小原则:函数与哪个对象交流比较多,就移入这个对象

重新组织数据

  • 目的:轻松处理数据的重构

  • 解决思路:魔数改成常量、数组转换成对象

  • 常用方法:搬移方法(Move Method),提炼类(ExTract Class)

简化条件表达式

  • 目的:简化一些条件逻辑复杂的判断

  • 解决思路:将“分支逻辑”和“操作细节”进行分离

  • 常用方法:分解条件表达式(Decompose Conditioal),合并条件表达式(Consolidate Conditional Expression),以卫语句代替嵌套表达式(Replace Nested Conditional withGuard Clauses)

分解条件表达式(Decompose Conditioal)

  • 复杂条件逻辑是导致复杂度上升的原因之一,编写不同的条件分支,根据不同的分支做不同的事情,但是复杂的条件判断会让你弄不清楚为什么要这样做,可读性大大的降低,所以需要拆解为多个独立的判断函数,根据用途来命名区分,突出条件逻辑。

  • 场景


    // dealWithData(num) {
    //     if (num < this.minNum || num > this.maxNum) {
    //         // doSomeThing
    //     } else {
    //         // doSomeThing
    //     }
    // }


    function dealWithData(num) {
        if (this.isOverFlow(num)) {
            // doSomeThing
        } else {
            // doSomeThing
        }
    }

    isOverFlow(num) {
        return num < this.minNum || num > this.maxNum
    }

合并条件表达式(Consolidate Conditional Expression)

  • 有一系类的判断条件,得到的结果都是一致的

    // function dealWithData(num) {
    //     if (num < 2) {
    //         return 0;
    //     }

    //     if (num > this.maxNum) {
    //         return 0;
    //     }

    //     if (this.isFlag(num)) {
    //         return 0;
    //     }
    // }

    // 修改后
    dealWithData(num) {
        if (this.isZero(num)) {
            return 0;
        }
    }

    isZero(num) {
        return num < 2 || num > this.maxNum || this.isFlag(num);
    }

以卫语句代替嵌套表达式(Replace Nested Conditional withGuard Clauses)

  • 函数中的条件逻辑使人难以看清正常的执行路径的情况下,使用卫语句理清

//   dealWithData() {
//         let result;

//         if (this.isManage()) {
//             result = this.getManage();
//         } else {
//             if (this.isEmploy()) {
//                 result = this.getEmploy();
//             } else {
//                 if (this.isReired()) {
//                     result = this.getReired();
//                 } else {
//                     result = this.getDefault()
//                 }
//             }
//         }
//     }

    // 修改后
    dealWithData() {

        if (this.isManage()) {
            return this.getManage();
        }
        if (this.isEmploy()) {
            return this.getEmploy();
        }

        if (this.isReired()) {
            return this.getReired();
        }
        result = this.getDefault()
    }

简化函数调用

  • 目的:让接口变得更容易理解和使用

  • 解决思路:对函数的返回值、函数名、参数进行优化

  • 常用方法:函数改名(Rename Method),添加参数(Add Parameter),分离查询函数和修改函数(Spaaeate Query form Modifier),保持对象完整(Preserve Whole Object)

函数改名(Rename Method)

  • 将复杂的处理过程分解成小函数,小函数命名不好,很难弄清楚函数式做什么的,没有达到预期的效果

添加参数(Add Parameter)

  • 某个函数需要从调用端的到更多的信息,为此可以添加一个对象参数,让该对象带进函数所需信息。但是过多的参数要注意数据泥团的问题。

分离查询函数和修改函数(Spaaeate Query form Modifier)


    // doSomeCode(nameArr) {
    //     for (let i = 0; i < nameArr.length; i++) {
    //         if (name === 'VFrank') {
    //             showMsg();
    //             return 'VFrank'
    //         }
    //     }
    //     return ''
    // }


    // 修改后
    doSomeCode(nameArr) {
        if (findPerson(nameArr)) {
            showMsg();
        }
    }

    findPerson(nameArr) {
        for (let i = 0; i < nameArr.length; i++) {
            if (name === 'JhoVFrankn') {
                return 'VFrank'
            }
        }
    }

处理概括关系

  • 目的:梳理继承关系,划分函数应该在父类还是在子类中

  • 解决思路:将函数方法在继承体系同上下移动

  • 常用方法: 函数上移(Pull Up Method), 函数下移(Push Down Method),塑造模板函数(Form Template Method), 提炼父类,提炼子类, 提炼接口

函数上移(Pull Up Method), 函数下移(Push Down Method)

  • 有些函数在子类中实现的都是相同的功能,这些函数应该抽离到父类中,反之,如果父类函数中某个函数只是与某一个子类有关系,这个函数应该移到子类中去

构造函数本体上移

  • 在各个子类中拥有一些构造函数, 本体几乎一样,可以在超类中构造一个函数,并在子类中调用。

  • 这一点Ext里面用的很多,比如Ext的field组件,在initComponent的时候addEvent(‘focus’, ‘blur’)等事件,把子类共有的操作都提到父类中来执行

塑造模板函数

  • 子类中,相应的某些函数以相同的顺序来执行类似的操作,但是每个函数的操作细节上有所不同,可以把这些操作放到独立的函数中,用同样的函数名表示。

  • Ext的组件的基本上都是这种模式来实现的, 每个组件都有initComponent,onRender,afterRender等函数,就是 用了这种方式,由此也有了钩子函数,也就有了生命周期概念。

重构改善既有代码的设计 --原则篇
重构改善既有代码的设计 --方法篇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值