美文网首页
重构,改善既有代码的设计读后感-至大量嵌套函数

重构,改善既有代码的设计读后感-至大量嵌套函数

作者: 东方欲晓_莫道君行早 | 来源:发表于2020-12-27 16:50 被阅读0次

    该书的第一章遥进行重构的代码(补充了一些中文注释):

    //剧目数据
    var plays =
    {
        "hamlet":{"name":"Hamlet","type":"tragedy"},
        "as-like":{"name":"As You Like It","type":"comedy"},
        "othello":{"name":"othello","type":"tragedy"}
    }
    
    //账单
    var invoices =
    [
        {
            "customer":"BigCo", //客户
            "performances":[  //演出情况
                {
                    "payID":"hamlet",//莎士比亚的哈姆雷特
                    "audience":55 //观众数
                },
                {
                    "payID":"as-like",//莎士比亚的皆大欢喜
                    "audience":35
                },
                {
                    "payID":"othello",//莎士比亚的奥赛罗
                    "audience":40
                }
            ]
        }
    ]
    
    
    function statement ( invoice,plays ) {
        let totalAmount = 0 ;  // 账单总额
        let volumeCredits = 0 ;  //观众量积分,用于获取折扣,提升客户忠诚度
        let result = `Statement for ${invoice.customer}\n`; //用于打印的字符串
    
        //用于格式化数字,优化显示货币。
        //locale 是必传参数(支持不同国家的数字分隔符格式)
        //option 是可选参数是一个对象
        //style,currency 可以设置货币符号或者精确度计算
        //unit 是可选参数,可以增加单位(长度单位等)
        const format = new Intl.NumberFormat("en-US",{ style:"currency",currency:"USD",minimumFractionDigits:2}).format;
        for(let perf of invoice.performances){
            const play = plays[perf.payID];
            let thisAmount = 0 ;
    
            //用于计算总账单
            switch (play.type) {
                case "tragedy":
                    thisAmount = 40000 ;
                    if (perf.audience > 30) {
                        thisAmount += 1000 * (perf.audience - 30);
                    }
                    break;
                case "comedy":
                    thisAmount = 30000 ;
                    if ( perf.audience > 20 ) {
                        thisAmount += 10000 + 500 * (perf.audience - 20);
                    }
                    thisAmount += 300 * perf.audience;
                    break;
                default:
                    throw new Error(`unknown type:${play.type}`);
            }
    
            //计算观众积分 add volume credits
            volumeCredits += Math.max(perf.audience - 30,0); //取两者较大数,防止结果小于0
            //add extra credit for every ten comedy attendees
            if("comedy" === play.type)  volumeCredits += Math.floor(perf.audience / 5);
    
            //print line for this order
            result += ` ${play.name}:${format(thisAmount/100)} (${perf.audience} seats)\n`;
            totalAmount += thisAmount;
        }
        result += `Amount owed is ${format(totalAmount/100)}\n`;
        result += `You earned ${volumeCredits} credits\n`;
        return result;
    }
    

    通过调用函数statement ( invoices[0],plays )便可以打印出结果

    statement.png

    如果你要给程序添加一个特性,但发现代码因缺乏良好的结构而不易于修改,那就先重构那个程序使其比较容易添加该特性,然后再添加该特性。

    重构前,先检查自己是否有一套可靠的测试集。这些测试,必须有自我检验能力。

    本书第一章举了一个例子,将一个函数进行拆分重构。以我不太充分的知识库看来,主要做的就是:
    1.将函数中独立的功能重新写成函数,在顶层函数调用。
    2.对顶层函数与拆分出来的函数进行变量名优化。
    3.将一些临时变量提炼成为函数,通过直接调用函数来获取。
    4.将一些在循环中累加的变量,将其剥离出来,在函数中重新遍历累加,以实现以查询取代临时变量的目的。

    有两个问题:
    1.提炼临时变量,会造成多次调用函数
    2.同一个循环拆成多个同样会造成性能损耗

    书中写道:

    大多数时候,重复一次这样的循环对性能的影响都可以忽略不计。
    当然,’大多数时候‘不等同于‘所有时候’。有时,一些重构手法会显著的影响性能。但即便如此,我也不去管他,继续重构,因为有了一份结构良好的代码,回头调优其性能也会容易得多。如果我在重构时引入了明显的性能消耗,我后面会花时间进行性能调优。进行调优时,可能会回退我早先做的一些重构----但更多时候,因为重构,我可以使用更高效的调优方案。最后我得到的是既整洁又高效的代码。

    但是针对实际的工作环境,在特定时间长度下,能做的工作有限。所以个人觉得,并非所有重构都要按部就班,可以根据实际情况具体分析。如果时间不够,对于一些可能会影响性能的暂时忽略。如果时间允许,则可以尝试,先重构,后调优的路子。---仅个人见解

    编程时,需要遵循营地法则:保证你离开时的代码库一定比来时更健康。

    实际编程中,普通程序员实现可能比较难。因为在一定的时间内,除了要完成任务之外还要重构会耽误文本的开发计划。而且重构并不能保证代码不会出现bug。程序很脆弱,每一次改动都应当有比较完备的测试。
    实际上,可以针对一些方法进行简单重构,过程也不一定时持续的,也不一定是一个人。
    更好的时有领导决定,以任务的形式派发下来,给重构代码的程序员时间。

    重构技术,就是以微小的步伐修改程序。如果你犯下错误,很容易便可发现他。

    实际工作中,可能不需要这样,如果仅仅是 如修改参数变量名这样的事情。当然了,如果步伐更好,出错概率也更小,好处是显而易见的。这是在测试容易的情况下。如果测试过程比较耗时,就需要你自己平衡关系了。

    跟随书中示例代码,大概的见识一下重构的过程。
    第一眼比较容易的看到的时swich语句,这段代码用来计算每一种剧目需要的花费,所以可以考虑将这段抽取成独立的函数。这个过程称为 提炼函数(106).
    抽取的代码 涉及三个临时变量,其中perf,play仅仅是使用,不会修改,可以作为参数传过来,thisAmount是改变的,可以将其作为返回值。同时为了更明显的看出作为返回值的变量,故修改变量名。参数命名也同样需要让其意义更确定。以定冠词开头,意义更明显。当然,如果可以的话,修改临时变量与参数应该分成单独的步骤。结果如下:

    //提炼函数
    function statement ( invoice,plays ) {
        let totalAmount = 0 ;  // 账单总额
        let volumeCredits = 0 ;  //观众量积分,用于获取折扣,提升客户忠诚度
        let result = `Statement for ${invoice.customer}\n`; //用于打印的字符串
    
        const format = new Intl.NumberFormat("en-US",{ style:"currency",currency:"USD",minimumFractionDigits:2}).format;
        for(let aPerformance of invoice.performances){
            const play = plays[aPerformance.payID];
            let thisAmount = amountFor(aPerformance,play) ;
    
            //计算观众积分 add volume credits
            volumeCredits += Math.max(aPerformance.audience - 30,0); //取两者较大数,防止结果小于0
            //add extra credit for every ten comedy attendees
            if("comedy" === play.type)  volumeCredits += Math.floor(aPerformance.audience / 5);
    
            //print line for this order
            result += ` ${play.name}:${format(thisAmount/100)} (${aPerformance.audience} seats)\n`;
            totalAmount += thisAmount;
        }
        result += `Amount owed is ${format(totalAmount/100)}\n`;
        result += `You earned ${volumeCredits} credits\n`;
        return result;
    
        function amountFor(aPerformance,play){
            let result= 0 ;
            //用于计算总账单
            switch (play.type) {
                case "tragedy":
                    result= 40000 ;
                    if (aPerformance.audience > 30) {
                        result+= 1000 * (aPerformance.audience - 30);
                    }
                    break;
                case "comedy":
                    result= 30000 ;
                    if ( aPerformance.audience > 20 ) {
                        result+= 10000 + 500 * (aPerformance.audience - 20);
                    }
                    result+= 300 * aPerformance.audience;
                    break;
                default:
                    throw new Error(`unknown type:${play.type}`);
            }
            return result;
        }
    }
    

    最上层函数看起来明晰了很多。

    以查询取代临时变量(178)
    观察提炼出来的amountFor函数的参数,可以发现第一个参数是从循环中取得,每次都是变化的,但是第二个参数是由performances计算得来,因此没必要将其作为参数引入。

    //内联变量
    function statement ( invoice,plays ) {
        let totalAmount = 0 ;  // 账单总额
        let volumeCredits = 0 ;  //观众量积分,用于获取折扣,提升客户忠诚度
        let result = `Statement for ${invoice.customer}\n`; //用于打印的字符串
    
        const format = new Intl.NumberFormat("en-US",{ style:"currency",currency:"USD",minimumFractionDigits:2}).format;
        for(let aPerformance of invoice.performances){
            let thisAmount = amountFor(aPerformance,playFor(aPerformance)) ;
    
            //计算观众积分 add volume credits
            volumeCredits += Math.max(aPerformance.audience - 30,0); //取两者较大数,防止结果小于0
            //add extra credit for every ten comedy attendees
            if("comedy" === playFor(aPerformance).type)  volumeCredits += Math.floor(aPerformance.audience / 5);
    
            //print line for this order
            result += ` ${playFor(aPerformance).name}:${format(thisAmount/100)} (${aPerformance.audience} seats)\n`;
            totalAmount += thisAmount;
        }
        result += `Amount owed is ${format(totalAmount/100)}\n`;
        result += `You earned ${volumeCredits} credits\n`;
        return result;
    
        function playFor(aperformance) {
            return plays[aperformance.playID];
        }
    
        function amountFor(aPerformance,play){
            let result= 0 ;
            //用于计算总账单
            switch (play.type) {
                case "tragedy":
                    result= 40000 ;
                    if (aPerformance.audience > 30) {
                        result+= 1000 * (aPerformance.audience - 30);
                    }
                    break;
                case "comedy":
                    result= 30000 ;
                    if ( aPerformance.audience > 20 ) {
                        result+= 10000 + 500 * (aPerformance.audience - 20);
                    }
                    result+= 300 * aPerformance.audience;
                    break;
                default:
                    throw new Error(`unknown type:${play.type}`);
            }
            return result;
        }
    }
    

    改变函数声明(124)

    //改变函数声明(124)
    function statement ( invoice,plays ) {
        let totalAmount = 0 ;  // 账单总额
        let volumeCredits = 0 ;  //观众量积分,用于获取折扣,提升客户忠诚度
        let result = `Statement for ${invoice.customer}\n`; //用于打印的字符串
    
        const format = new Intl.NumberFormat("en-US",{ style:"currency",currency:"USD",minimumFractionDigits:2}).format;
        for(let aPerformance of invoice.performances){
            let thisAmount = amountFor(aPerformance) ;
    
            //计算观众积分 add volume credits
            volumeCredits += Math.max(aPerformance.audience - 30,0); //取两者较大数,防止结果小于0
            //add extra credit for every ten comedy attendees
            if("comedy" === playFor(aPerformance).type)  volumeCredits += Math.floor(aPerformance.audience / 5);
    
            //print line for this order
            result += ` ${playFor(aPerformance).name}:${format(thisAmount/100)} (${aPerformance.audience} seats)\n`;
            totalAmount += thisAmount;
        }
        result += `Amount owed is ${format(totalAmount/100)}\n`;
        result += `You earned ${volumeCredits} credits\n`;
        return result;
    
        function playFor(aPerformance) {
            return plays[aPerformance.playID];
        }
    
        function amountFor(aPerformance){
            let result= 0 ;
            //用于计算总账单
            switch (playFor(aPerformance).type) {
                case "tragedy":
                    result= 40000 ;
                    if (aPerformance.audience > 30) {
                        result+= 1000 * (aPerformance.audience - 30);
                    }
                    break;
                case "comedy":
                    result= 30000 ;
                    if ( aPerformance.audience > 20 ) {
                        result+= 10000 + 500 * (aPerformance.audience - 20);
                    }
                    result+= 300 * aPerformance.audience;
                    break;
                default:
                    throw new Error(`unknown type:${playFor(aPerformance).type}`);
            }
            return result;
        }
    }
    

    提炼计算观众积分的逻辑
    将函数变量改成函数声明,同时注意函数名要尽量表意。

    //改变函数声明(124)
    function statement ( invoice,plays ) {
        let totalAmount = 0 ;  // 账单总额
        let volumeCredits = 0 ;  //观众量积分,用于获取折扣,提升客户忠诚度
        let result = `Statement for ${invoice.customer}\n`; //用于打印的字符串
        for(let aPerformance of invoice.performances){
            let thisAmount = amountFor(aPerformance) ;
            //计算观众积分 add volume credits
            volumeCredits += volumeCreditsFor(aPerformance);
            //print line for this order
            result += ` ${playFor(aPerformance).name}:${usd(thisAmount/100)} (${aPerformance.audience} seats)\n`;
            totalAmount += thisAmount;
        }
        result += `Amount owed is ${usd(totalAmount/100)}\n`;
        result += `You earned ${volumeCredits} credits\n`;
        return result;
    
        //这一轮循环增加的量
        function volumeCreditsFor(aPerformance) {
            let result = 0;
            result += Math.max(aPerformance - 30 , 0);
            if ("comedy" == playFor(aPerformance).type) result += Math.floor(aPerformance.audience / 5);
            return result;
        }
        
        function usd(aNumber) {
            return new Intl.NumberFormat("en-US",
                    { style:"currency",currency:"USD",
                        minimumFractionDigits:2}).format(aNumber/100);
        }
    
        function playFor(aPerformance) {
            return plays[aPerformance.playID];
        }
    
        function amountFor(aPerformance){
            let result= 0 ;
            //用于计算总账单
            switch (playFor(aPerformance).type) {
                case "tragedy":
                    result= 40000 ;
                    if (aPerformance.audience > 30) {
                        result+= 1000 * (aPerformance.audience - 30);
                    }
                    break;
                case "comedy":
                    result= 30000 ;
                    if ( aPerformance.audience > 20 ) {
                        result+= 10000 + 500 * (aPerformance.audience - 20);
                    }
                    result+= 300 * aPerformance.audience;
                    break;
                default:
                    throw new Error(`unknown type:${playFor(aPerformance).type}`);
            }
            return result;
        }
    }
    

    移除观众量总积分

    //移除观众量积分总和
    function statement ( invoice,plays ) {
        let totalAmount = 0 ;  // 账单总额
        let result = `Statement for ${invoice.customer}\n`; //用于打印的字符串
        for(let aPerformance of invoice.performances){
            //print line for this order
            result += ` ${playFor(aPerformance).name}:${usd(amountFor(aPerformance)/100)} (${aPerformance.audience} seats)\n`;
            totalAmount += amountFor(aPerformance);
        }
        let volumeCredits = totalVolumeCredits();//计算观众积分 add volume credits
        result += `Amount owed is ${usd(totalAmount/100)}\n`;
        result += `You earned ${volumeCredits} credits\n`;
        return result;
    
        //计算观众积分 add volume credits
        function totalVolumeCredits() {
            let volumeCredits = 0 ;  //观众量积分,用于获取折扣,提升客户忠诚度
            for(let aPerformance of invoice.performances){
                //计算观众积分 add volume credits
                volumeCredits += volumeCreditsFor(aPerformance);
            }
            return volumeCredits;
        }
    
        //这一轮循环增加的量
        function volumeCreditsFor(aPerformance) {
            let result = 0;
            result += Math.max(aPerformance - 30 , 0);
            if ("comedy" == playFor(aPerformance).type) result += Math.floor(aPerformance.audience / 5);
            return result;
        }
    
        function usd(aNumber) {
            return new Intl.NumberFormat("en-US",
                { style:"currency",currency:"USD",
                    minimumFractionDigits:2}).format(aNumber/100);
        }
    
        function playFor(aPerformance) {
            return plays[aPerformance.playID];
        }
    
        function amountFor(aPerformance){
            let result= 0 ;
            //用于计算总账单
            switch (playFor(aPerformance).type) {
                case "tragedy":
                    result= 40000 ;
                    if (aPerformance.audience > 30) {
                        result+= 1000 * (aPerformance.audience - 30);
                    }
                    break;
                case "comedy":
                    result= 30000 ;
                    if ( aPerformance.audience > 20 ) {
                        result+= 10000 + 500 * (aPerformance.audience - 20);
                    }
                    result+= 300 * aPerformance.audience;
                    break;
                default:
                    throw new Error(`unknown type:${playFor(aPerformance).type}`);
            }
            return result;
        }
    }
    

    其中使用了
    拆分循环(227) 分离出累加过程
    移动语句(223) 将累加变量的声明与累加过程集中到一起
    提炼函数(106) 提炼出计算总和的函数
    内联变量(123) 完全移除中间变量

    同样的步骤来移除totalAmount

    //移除观众量积分总和
    function statement ( invoice,plays ) {
        let result = `Statement for ${invoice.customer}\n`; //用于打印的字符串
        for(let aPerformance of invoice.performances){
            //print line for this order
            result += ` ${playFor(aPerformance).name}:${usd(amountFor(aPerformance)/100)} (${aPerformance.audience} seats)\n`;
        }
        result += `Amount owed is ${usd(totalAmount()/100)}\n`;
        result += `You earned ${totalVolumeCredits()} credits\n`;
        return result;
    
        //计算总的账目
        function totalAmount() {
            let totalAmount = 0 ;  // 账单总额
            for(let aPerformance of invoice.performances){
                totalAmount += amountFor(aPerformance);
            }
            return totalAmount;
        }
    
        //计算观众积分 add volume credits
        function totalVolumeCredits() {
            let volumeCredits = 0 ;  //观众量积分,用于获取折扣,提升客户忠诚度
            for(let aPerformance of invoice.performances){
                //计算观众积分 add volume credits
                volumeCredits += volumeCreditsFor(aPerformance);
            }
            return volumeCredits;
        }
    
        //这一轮循环增加的量
        function volumeCreditsFor(aPerformance) {
            let result = 0;
            result += Math.max(aPerformance - 30 , 0);
            if ("comedy" == playFor(aPerformance).type) result += Math.floor(aPerformance.audience / 5);
            return result;
        }
    
        //格式化数字,显示为货币
        function usd(aNumber) {
            return new Intl.NumberFormat("en-US",
                { style:"currency",currency:"USD",
                    minimumFractionDigits:2}).format(aNumber/100);
        }
    
        //获取某一剧目
        function playFor(aPerformance) {
            return plays[aPerformance.playID];
        }
    
        //计算某一剧目需要的账目
        function amountFor(aPerformance){
            let result= 0 ;
            //用于计算总账单
            switch (playFor(aPerformance).type) {
                case "tragedy":
                    result= 40000 ;
                    if (aPerformance.audience > 30) {
                        result+= 1000 * (aPerformance.audience - 30);
                    }
                    break;
                case "comedy":
                    result= 30000 ;
                    if ( aPerformance.audience > 20 ) {
                        result+= 10000 + 500 * (aPerformance.audience - 20);
                    }
                    result+= 300 * aPerformance.audience;
                    break;
                default:
                    throw new Error(`unknown type:${playFor(aPerformance).type}`);
            }
            return result;
        }
    }
    

    将函数折叠后再看,是不是思路比较清晰明了了


    daima.png

    相关文章

      网友评论

          本文标题:重构,改善既有代码的设计读后感-至大量嵌套函数

          本文链接:https://www.haomeiwen.com/subject/pvpcnktx.html