zoukankan      html  css  js  c++  java
  • 【重构笔记02】重新组织函数

    前言

    重构过程中,还是有一定标准可循的,每个重构手法有如下五个部分:

    首先是名称(name),建造一个重构词汇表,名称是非常重要的 然后是一个简短概要,介绍重构手法适用的场景,以及他干的事情,这样我们可以快速找到所需重构方法

    然后,介绍为什么需要这个重构,或者什么情况下适用这个重构做法,简明扼要的介绍如何一步步重构

    最后,以一个十分简单的例子说明此重构如何运作

    所以今天我们进入重构的学习吧!

    提炼函数

    我们重构是,重头戏就是处理函数,以js而言,函数重要性更是终于“类”的概念。

    如何恰当的包装代码,如何减少过长的代码,这是我们多数时刻需要思考的。

    但是要消除函数过长是不易的,一个函数过长说明这个函数所完成的业务很复杂,而且可能关联性很高,要将这样的代码拆分,就不止是重构的事情了

    在此提炼函数变得十分考验一个人的水平,如何将一段代码从原先函数提取出来,如何将一个单数调用替换为函数本体,这些都不简单

    最后改完,时常发现提炼的某些函数实际意义不大,我们还得考虑如何回溯原来的函数

    难在何处

    提炼函数不易,难在处理局部变量,临时变量尤其突出

    处理一个函数时,我们可以先使用查询取代变量的方法取代临时变量

    如果一个临时变量多次使用,可以使用分解临时变量的方法将它变得容易替换

    但是,多数时候临时变量确实混乱,难以替换,这时候我们可以使用以函数对象取代函数的方法,这样的代价就会引入新类

    参数带来的问题比临时变量少一点,前提是不在函数内为他赋值(不对参数赋值,对js来说就是一个传说,因为我们队参数赋值可以保证程序更健壮),但是移除对象赋值,也许能带给你不一样的感受

    说了这么多,我们来好好审视下,我们的一些手段吧!!!

    光说无码不行,我们先上一个例子,我们现在将这段代码放到一个独立的函数中,注意函数名需要解释函数用途哦

     1 var log = function (msg) { console.log(msg); };
     2 
     3 var printOwing = function (amount) {
     4     printBanner();
     5     log('name:' + _name);
     6     log('amount:' + _amount);
     7 };
     8 
     9 var printOwing = function (amount) {
    10     printBanner();
    11     printDetails(amount)
    12 };
    13 
    14 var printDetails = function (amount) {
    15     log('name:' + _name);
    16     log('amount:' + _amount)
    17 };

    这是比较常见的重构手法,当我们看到一个过长的函数或者一段需要注释才能看懂的代码时,这段代码可能就需要放进独立的函数了

    如果每个函数的粒度都很小,那么函数被复用的机会就大,这样高层函数看上去就像被函数名注释似的,这样函数复写也相对简单

    如何做?

    ① 创造一个新函数,根据这个函数的意图来对它命名(以它“做神马”来命名,而不是以它"怎么做"命名)

    PS:即使我们要提炼的代码非常简单,哪怕只是一个消息或者一个函数调用,只要新函数能更好的表示代码意图,就可以提炼,否则就不要动他了

    ② 将提炼的代码拷贝到新建函数中

    ③ 检查提炼的代码,看看其中是否引用了“作用域限于原函数”的变量(局部变量、原函数参数)

    ④ 检查是否包含“仅用于被提炼代码段”的临时变量,如果有,在目标函数中将之声明为局部变量

    ⑤ 检查被提炼代码段,看看是否有任何局部变量的值被他改变,如果一个临时变量的值被修改了,看看是否可以将提炼的代码变为一个查询,将结果给相关变量

    如果这样不好做,或者被修改的变量不止一个,拷贝的方式可能就不适用了,这个时候可能还需要用到(分解临时变量/以查询替换变量)等手段了

    ⑥ 将被提炼代码段中需要被读取的局部变量,当参数传给目标函数

    ⑦ 处理结束后检查测试之,便结束!

    好了,我们再来几个例子

    无局部变量

     1 var log = function (msg) { console.log(msg); };
     2 var printOwing = function (amount) {
     3     //var productList = [];//这个数据你懂的
     4     var outstanding = 0;
     5     log('*****************');
     6     log('****Cunstomer Owes*****');
     7     log('*****************');
     8 
     9     for (var k in productList) {
    10         outstanding += productList[k].getAmount();
    11     }
    12 
    13     log('name:' + _name);
    14     log('amount:' + outstanding);
    15 };

    这个重构比较简单

     1 var printOwing = function (amount) {
     2     //var productList = [];//这个数据你懂的
     3     var outstanding = 0;
     4     printBanner();
     5 
     6     for (var k in productList) {
     7         outstanding += productList[k].getAmount();
     8     }
     9 
    10     log('name:' + _name);
    11     log('amount:' + outstanding);
    12 };
    13 
    14 var printBanner = function () {
    15     log('*****************');
    16     log('****Cunstomer Owes*****');
    17     log('*****************');
    18 };

    但是没有局部变量只是一个传说,比如上处最后log的内容,于是来一个(简单的)

     1 var printOwing = function (amount) {
     2     //var productList = [];//这个数据你懂的
     3     var outstanding = 0;
     4     printBanner();
     5 
     6     for (var k in productList) {
     7         outstanding += productList[k].getAmount();
     8     }
     9 
    10     printDetails(outstanding);
    11 };
    12 
    13 var printBanner = function () {
    14     log('*****************');
    15     log('****Cunstomer Owes*****');
    16     log('*****************');
    17 };
    18 
    19 var printDetails = function (outstanding) {
    20     log('name:' + _name);
    21     log('amount:' + outstanding);
    22 }

    PS:此处的_name,在js里面应该是this._name

    这个也相对比较简单,如果局部变量是个对象,而被提炼代码调用了会对该对象造成修改的函数,也可以这样做,只不过需要将这个对象作为参数传递给目标函数,只有在被提炼函数会对变量赋值时,有所不同,下面我们就会看到这个情况。

    局部变量赋值

    这个情况较复杂,这里我们看看临时变量被修改的两种情况,

    比较简单的情况是这个变量只在被提炼代码段中使用,这样源代码中的这个变量就可以被消除,

    另一种情况就是源代码中改了,提炼处代码也改了, 这个时候如果是之前改的就不用管了,之后会发生变化需要返回这个值。

    这里我们将上述代码计算的代码提炼出来:

     1 var log = function (msg) { console.log(msg); };
     2 var printOwing = function (amount) {
     3     //var productList = [];//这个数据你懂的
     4     printBanner();
     5     printDetails(getOutStanding());
     6 };
     7 
     8 var printBanner = function () {
     9     log('*****************');
    10     log('****Cunstomer Owes*****');
    11     log('*****************');
    12 };
    13 
    14 var printDetails = function (outstanding) {
    15     log('name:' + _name);
    16     log('amount:' + outstanding);
    17 };
    18 
    19 var getOutStanding = function () {
    20     var result = 0;
    21     for (var k in productList) {
    22         result += productList[k].getAmount();
    23     }
    24     return result;
    25 };

    这个例子中outstanding变量只是单纯被初始化一个明确的值,但如果其他地方做过处理,就必须作为参数传入

     1 var log = function (msg) { console.log(msg); };
     2 var printOwing = function (amount) {
     3     //var productList = [];//这个数据你懂的
     4     var outstanding = amount * 2;
     5     outstanding = getOutStanding(outstanding)
     6     printBanner();
     7     printDetails(getOutStanding());
     8 };
     9 
    10 var printBanner = function () {
    11     log('*****************');
    12     log('****Cunstomer Owes*****');
    13     log('*****************');
    14 };
    15 
    16 var printDetails = function (outstanding) {
    17     log('name:' + _name);
    18     log('amount:' + outstanding);
    19 };
    20 
    21 var getOutStanding = function (result) {
    22     result = result || 0;//注意这种写法如果result为0可能导致我们程序BUG,所以数字要注意
    23     for (var k in productList) {
    24         result += productList[k].getAmount();
    25     }
    26     return result;
    27 };

    如果其中改变的变量不止一个,就返回对象变量吧,这个东西就暂时说到这里了,后面看实例吧。

    内联函数

    该方法用于消除函数,先来个代码看看

     1 var getRating = function () {
     2     return moreThanFive() ? 2 : 1;
     3 };
     4 
     5 var moreThanFive = function () {
     6     return num > 5;
     7 }
     8 
     9 var getRating = function () {
    10     return num > 5 ? 2 : 1;
    11 };

    本来我们常以简单的函数表现动作意图,这样会使代码更为清晰,但有时候会遇到某些函数,内部代码很简单,这种情况就应该去掉这个函数

    PS:这个界限不是很好把握,另一种情况是手上有一群组织不合理的函数,我们可以将它组织到一个大函数中,再从新提炼成小函数,这种情况更多见。

    如果我们使用了太多中间层,使得系统所有的函数都是对另一个函数的委托,这个时候,函数会让我们晕头转向,这个时候可以去掉中间层

    如何做?

    ① 检查函数,确定其不具有多态(如果有继承关系就不要搞他了)

    ② 找出函数所有调用点

    ③ 复制为函数本体

    ④ 检查,删除函数本身

    内联函数比较复杂,递归调用,多返回点,

    内联临时变量

    你有一个临时变量,只被一个简单的表达式赋值一次,而他影响了其它重构手法,那么将所有对该变量的引用动作,替换为对它赋值的那个表达式自身

    1 var basePrice = anOrder.basePrice();
    2 return basePrice > 100;
    3 
    4 return anOrder.basePrice() > 100

    以查询取代临时变量

    这个方法比较实用,程序以一个临时变量保存某一个表达式的结果时,那么将这个表达式提炼到一个独立的函数中

    将这个临时变量的变量的所有引用点替换为新函数的调用,这样的话,新函数就可以被其它函数使用了

     1 function amount() {
     2     var basePrice = _quantity * _itemPrice;
     3     if (basePrice > 1000) return basePrice * 0.95;
     4     else return basePrice * 0.98;
     5 }
     6 
     7 
     8 function amount() {
     9     if (basePrice() > 1000) return basePrice() * 0.95;
    10     else return basePrice() * 0.98;
    11 }
    12 
    13 function basePrice() {
    14     return _quantity * _itemPrice;
    15 }

    这样做的好处是,消除临时变量,因为临时变量是暂时的,只能存在所属函数,所以为了能够访问到变量,有可能我们会写出更长的函数,

    如果把临时变量替换为一个查询,那么同一个类中的所有函数都可以获得这个数据,类的结构会更加清晰

    查询替换变量一般会在提炼函数时候用到,该方法要用好还是不易的

    怎么做?

    ① 找出只被赋值一次的临时变量(多次赋值需要分解临时变量了),注意这在js中可能不易

    ② 提炼临时变量等号右边到独立函数

    ③ 测试

    我们常常使用临时变量保存循环中的信息,这个情况下就把整个循环提炼出来,

    PS:这个时候我们可能会关心性能问题,据说这个性能不会对我们的程序有多大的影响

     1 function getPrice() {
     2     var basePrice = _quantity * _itemPrice;
     3     var discountFactor;
     4     if (basePrice > 1000)
     5         discountFactor = 0.95;
     6     else
     7         discountFactor = 0.98;
     8 }
     9 此时我们想替换两个临时变量
    10 function getPrice() {
    11     return basePrice() * discountFactor();
    12 }
    13 
    14 function basePrice() {
    15     return _quantity * _itemPrice;
    16 }
    17 
    18 function discountFactor() {
    19     return basePrice() > 1000 ? 0.95 : 0.98
    20 }

    引入解释性变量

    如果我有一个复杂表达式,将该表达式(或者一部分)的结果放进一个临时变量,将此临时变量名用来解释表达式用途

     1 if(platform.toLocaleUpperCase().indexOf('MAC') > -1 && location.href.toLocaleUpperCase().indexOf('IE') > -1 && ......){
     2 //do someting
     3 }
     4 
     5 这种很长的条件判断很难读,这个时候临时变量反而能帮助你阅读
     6 var isMac = platform.toLocaleUpperCase().indexOf('MAC') > -1;
     7 var isIE = location.href.toLocaleUpperCase().indexOf('IE') > -1;//这个代码有问题,不必关注
     8 
     9 if(isMac && isIE && ......){
    10 //do someting
    11 }

    但有个问题是,原作者并不推荐增加临时变量,所以,一般我们就会提炼为函数了,这样也增加重用性

    分解临时变量

    我们的程序有某个临时变量被赋值超过一次,他既不是循环变量又不被用于收集计算结果,那么针对每次赋值新建一个对应的临时变量

    1 var temp = 2 * (_height + _width);
    2 temp = _height * _width;
    3 
    4 var perimeter = 2 * (_height + _width);
    5 var area = _height * _width;

    这样做的好处,其实就是为了避免一个变量被无意义多次使用

    除了循环变量或者用于收集结果的临时变量应该被多次使用,还有一些临时变量用于保存冗长的结果会被稍后使用

    如果一个变量被赋值超过一次,那么他就担任了过多的职责了 其实这样做的好处,是为了我们方便提炼函数,或者以查询替换变量的操作

     1 function getDistanceTravelled(time) {
     2     var result;
     3     var acc = _primaryForce / _mass;
     4     var primaryTime = Math.min(time, _delay);
     5 
     6     result = 0.5 * acc * primaryTime * primaryTime;
     7     var secondaryTime = time - _delay;
     8 
     9     if (secondaryTime > 0) {
    10         var primaryVel = acc * _delay;
    11         acc = (_primaryForce + _secondaryForce) / _mass;
    12         result += primaryVel * secondaryTime + 0.5 * acc * secondaryTime * secondaryTime;
    13     }
    14     return result;
    15 }

    按照作者的话来说,这真是丑陋的代码啊,我反正抄都抄了很久

    这个是一个物理中的一个神马公式我给忘了

    见图,acc被两次赋值,第一次是为了保存第一次力造成的初始速度,

    第二次保存两个力共同作用造成的加速度,这里我们使用final替换第二次的结果

     1 function getDistanceTravelled(time) {
     2     var result;
     3     var acc = _primaryForce / _mass;
     4     var primaryTime = Math.min(time, _delay);
     5 
     6     result = 0.5 * acc * primaryTime * primaryTime;
     7     var secondaryTime = time - _delay;
     8 
     9     if (secondaryTime > 0) {
    10         var primaryVel = acc * _delay;
    11         var final = (_primaryForce + _secondaryForce) / _mass;
    12         result += primaryVel * secondaryTime + 0.5 * final * secondaryTime * secondaryTime;
    13     }
    14     return result;
    15 }

    然后我们再使用下其它手段试试: ①提炼函数,②以查询取代变量

    PS:我知道了力/质量=重力加速度

    function getDistanceTravelled(time) {
        var result = 0.5 * accelerationOfGravity(_primaryForce, _mass) 
    * primaryTime(time, _delay);
        if (secondaryTime() > 0) {
            result += primaryVel(_primaryForce, _mass) * secondaryTime() 
    + 0.5 * final(_primaryForce, _secondaryForce, _mass) * secondaryTime() * secondaryTime();
        }
        return result;
    }
    
    function accelerationOfGravity(force, mass) {
        return force / mass;
    }
    function primaryTime(time, _delay) {
        return Math.min(time, _delay) * Math.min(time, _delay);
    }
    function secondaryTime() {
        return time - _delay;
    }
    function primaryVel(_primaryForce, _mass) {
        return accelerationOfGravity(_primaryForce, _mass) * _delay;
    }
    function final(_primaryForce, _secondaryForce, _mass) {
        return (_primaryForce + _secondaryForce) / _mass;
    }

    下面是我完成不理解程序情况下胡乱意淫改的,不必在意

    移除参数赋值

    代码对一个参数赋值,那么以一个临时变量取代该参数位置(这对js不知道好使不)

    1 function discount(inputVal, quantity, yearToDate) {
    2     if (inputVal > 50) inputVal -= 2;
    3 }
    4 //修改后
    5 function discount(inputVal, quantity, yearToDate) {
    6     var result = inputVal;
    7     if (inputVal > 50) result -= 2;
    8 }

    这样做的目的主要为了消除按值传递与按引用传递带来的问题,这里我们来深入纠结一番

     1 function discount(inputVal, quantity, yearToDate) {
     2     if (inputVal > 50) inputVal -= 2;
     3 }
     4 //修改后
     5 function discount(inputVal, quantity, yearToDate) {
     6     var result = inputVal;
     7     if (inputVal > 50) result -= 2;
     8 }
     9 
    10 这样做的目的主要为了消除按值传递与按引用传递带来的问题,这里我们来深入纠结一番
    11 var value = 1;
    12 function demoVal(p) {
    13     p++;
    14 }
    15 console.log(value);
    16 demoVal(value);
    17 console.log(value);
    18 
    19 
    20 var obj = {
    21 name: 'yexiaochai',
    22 age: 20
    23 };
    24 function demoRefer(obj) {
    25     obj.age++;
    26 }
    27 console.log(obj);
    28 demoRefer(obj);
    29 console.log(obj);

    所以你懂的,函数内部操作,有时无意就会改变传入参数

    以函数对象取代函数

    我们有一个大型函数,其中对局部变量的使用使你无法采用提炼函数方法,将这个函数放进一个单独对象中,如此一来局部变量就成了对象内的字段

    然后你可以在同一个对象中将这个大型函数分解为多个小型函数 函数分解难度较高,将变量替换为查询可以减小他的难度,如果也不行的话,就将函数对象化吧

    这里来一个相当简化的代码:

     1 function gamma(inputVal, quantity, yearToDate) {
     2     var v1 = (inputVal * quantity) + delta();
     3     var v2 = (inputVal * yearToDate) + 100;
     4 
     5     if (yearToDate - v1 > 100) v2 -= 20;
     6     var v3 = v2 * 7;
     7 
     8     //......
     9     return v3 - 2 * v1;
    10 }

    为了说明这个问题,作者写了一段莫名其妙的代码,我一看,确实莫名其妙......

     1 function delta() {return 10; }
     2 function gamma(inputVal, quantity, yearToDate) {
     3     var v1 = (inputVal * quantity) + delta();
     4     var v2 = (inputVal * yearToDate) + 100;
     5 
     6     if (yearToDate - v1 > 100) v2 -= 20;
     7     var v3 = v2 * 7;
     8 
     9     //......
    10     return v3 - 2 * v1;
    11 }
    12 
    13 var Gamma = function (opts) {
    14     this.inputVal = opts.inputVal;
    15     this.quantity = opts.quantity;
    16     this.yearToDate = opts.yearToDate;
    17     this.v1;
    18     this.v2;
    19     this.v3;
    20 };
    21 Gamma.prototype = {
    22     computer: function () {
    23         this.alter1();
    24         this.alter2();
    25         if (this.yearToDate - this.v1 > 100) this.v2 -= 20;
    26         this.v3 = this.v2 * 7;
    27 
    28         //......
    29         return this.v3 - 2 * this.v1;
    30     },
    31     alter1: function () {
    32         this.v1 = (this.inputVal * this.quantity) + delta();
    33     },
    34     alter2: function () {
    35         this.v2 = (this.inputVal * this.yearToDate) + 100;
    36     }
    37     //....
    38 
    39 };
    40 
    41 //demo
    42 console.log(gamma(5, 6, 7));//865
    43 
    44 var d = new Gamma({
    45 inputVal: 5, 
    46 quantity: 6, 
    47 yearToDate: 7});
    48 console.log(d.computer());//865

    替换算法

    替换算法其实是最难的,在你不熟悉代码业务与逻辑时,你去改的话,兄弟你们就坑吧!!! 来个简单的例子结束今天的任务

     1 function find(data) {
     2     if (data == 1) return '周日';
     3     if (data == 2) return '周一';
     4 
     5     //...
     6     return null;
     7 }
     8 
     9 function find(data) {
    10             
    
    11     return ['周日', '周一', /*......*/][data];
    12 }

    OK,进入任务结束,高高兴兴打游戏了!

  • 相关阅读:
    Sum Root to Leaf Numbers
    Sum Root to Leaf Numbers
    Sort Colors
    Partition List
    Binary Tree Inorder Traversal
    Binary Tree Postorder Traversal
    Remove Duplicates from Sorted List II
    Remove Duplicates from Sorted List
    Search a 2D Matrix
    leetcode221
  • 原文地址:https://www.cnblogs.com/yexiaochai/p/3371121.html
Copyright © 2011-2022 走看看