• 重构,改善既有代码的设计读后感-拆分计算阶段与格式化阶段


    假如,需要增加一个功能:目前仅仅有文本详单,需要增加一个HTML详单。如果仅仅是重构到当前步骤,还需要将函数拷贝到另一个函数中。虽然,条例也算清晰,但是如果我们实现的更好,能将所有需要的数据放到一个数据结构,HTML详单可以调用一个函数获取所有数据,在进行HTML编码,效果会更好吧。

    拆分阶段(154)
    要实现服用由多种方法,本书推荐的技术是拆分阶段。本例中第一阶段为产生数据,第二阶段为渲染数据。要开始拆分阶段,应该先对第二阶段的代码应用提炼函数。在这个例子中,这部分代码就是打印详单的代码,即statement函数的全部内容(七行代码)。要把他们与所有嵌套函数一起抽象到一个新的顶层函数中。然后创建一个对象,作为两个阶段间传递的中转数据结构。

    function statement(invoice,plays) {
        const statementData = {};
        return renderPlainText(statementData,invoice,plays);//第二阶段
    }
    
    //拆分阶段 render 呈现    plaintext纯文本
    function renderPlainText (data, invoice,plays ) {
        let result = `Statement for ${invoice.customer}
    `; //用于打印的字符串
        for(let aPerformance of invoice.performances){
            //print line for this order
            result += ` ${playFor(aPerformance).name}:${usd(amountFor(aPerformance)/100)} (${aPerformance.audience} seats)
    `;
        }
        result += `Amount owed is ${usd(totalAmount()/100)}
    `;
        result += `You earned ${totalVolumeCredits()} credits
    `;
        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;
        }
    }
    

    现在检查一下renderPlaintext其他参数,我希望将参数都挪到中转参数中,让renderPlainText只操作data传过来的数据。
    那么就先invoice的两个属性到data

    function statement(invoice,plays) {
        const statementData = {};
        statementData.customer = invoice.customer;
        statementData.performances = invoice.performances;
        return renderPlainText(statementData,plays);
    }
    
    //拆分阶段 render 呈现    plaintext纯文本
    function renderPlainText (data,plays ) {
        let result = `Statement for ${data.customer}
    `; //用于打印的字符串
        for(let aPerformance of data.performances){
            //print line for this order
            result += ` ${playFor(aPerformance).name}:${usd(amountFor(aPerformance)/100)} (${aPerformance.audience} seats)
    `;
        }
        result += `Amount owed is ${usd(totalAmount()/100)}
    `;
        result += `You earned ${totalVolumeCredits()} credits
    `;
        return result;
    
        //计算总的账目
        function totalAmount() {
            let totalAmount = 0 ;  // 账单总额
            for(let aPerformance of data.performances){
                totalAmount += amountFor(aPerformance);
            }
            return totalAmount;
        }
    
        //计算观众积分 add volume credits
        function totalVolumeCredits() {
            let volumeCredits = 0 ;  //观众量积分,用于获取折扣,提升客户忠诚度
            for(let aPerformance of data.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;
        }
    }
    

    另外,如果希望“剧目名称”数据也从中转数据得来,就需要使用play中的数据填充aPerformance对象。
    同样的手法处理amountFor
    接下来就搬移观众量积分,然后将两个计算总数的搬移到statement函数中,同时将usd移动到顶层,以便于 其他渲染方式调用,结果如下:

    function statement(invoice,plays) {
        const statementData = {};
        statementData.customer = invoice.customer;
        //这里是一个知识点,类似与Java的新循环
        statementData.performances = invoice.performances.map(enrichPerformance);
        statementData.totalAmount = totalAmount(statementData);
        statementData.totalVolumeCredits = totalVolumeCredits(statementData);
        return renderPlainText(statementData,plays);
    
        function enrichPerformance(aPerformance) {
            const result = Object.assign({},aPerformance);
            result.play = playFor(result);
            result.amount = amountFor(result);
            result.volumeCredits = volumeCreditsFor(result);
            return result;
        }
    
        //获取某一剧目
        function playFor(aPerformance) {
            return plays[aPerformance.playID];
        }
    
        //计算某一剧目需要的账目
        function amountFor(aPerformance){
            let result= 0 ;
            //用于计算总账单
            switch (aPerformance.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:${aPerformance.play.type}`);
            }
            return result;
        }
    
        //这一轮循环增加的量
        function volumeCreditsFor(aPerformance) {
            let result = 0;
            result += Math.max(aPerformance - 30 , 0);
            if ("comedy" == aPerformance.play.type) result += Math.floor(aPerformance.audience / 5);
            return result;
        }
    
        //计算总的账目
        function totalAmount(data) {
            let totalAmount = 0 ;  // 账单总额
            for(let aPerformance of data.performances){
                totalAmount += aPerformance.amount;
            }
            return totalAmount;
        }
    
        //计算观众积分 add volume credits
        function totalVolumeCredits(data) {
            let volumeCredits = 0 ;  //观众量积分,用于获取折扣,提升客户忠诚度
            for(let aPerformance of data.performances){
                //计算观众积分 add volume credits
                volumeCredits += aPerformance.volumeCredits;
            }
            return volumeCredits;
        }
    }
    
    //拆分阶段 render 呈现    plaintext纯文本
    function renderPlainText (data,plays ) {
        let result = `Statement for ${data.customer}
    `; //用于打印的字符串
        for(let aPerformance of data.performances){
            //print line for this order
            result += ` ${aPerformance.play.name}:${usd(aPerformance.amount/100)} (${aPerformance.audience} seats)
    `;
        }
        result += `Amount owed is ${usd(data.totalAmount)}
    `;
        result += `You earned ${data.totalVolumeCredits} credits
    `;
        return result;
    }
    
    //提到顶层,以供其他函数使用
    //格式化数字,显示为货币
    function usd(aNumber) {
        return new Intl.NumberFormat("en-US",
            { style:"currency",currency:"USD",
                minimumFractionDigits:2}).format(aNumber/100);
    }
    

    以管道取代循环(231)
    接下来以管道取代循环(231),就可以将第一阶段的代码提取到独立的函数中了。同时,再去实现html版本就非常容易了,也很好的实现了代码的复用。

    function statement() {
        return renderPlainText(createStatementData(invoices,plays))
    }
    
    function createStatementData(invoice,plays) {
        const statementData = {};
        statementData.customer = invoice.customer;
        //这里是一个知识点,类似与Java的新循环
        statementData.performances = invoice.performances.map(enrichPerformance);
        statementData.totalAmount = totalAmount(statementData);
        statementData.totalVolumeCredits = totalVolumeCredits(statementData);
        return statementData;
    
        function enrichPerformance(aPerformance) {
            const result = Object.assign({},aPerformance);
            result.play = playFor(result);
            result.amount = amountFor(result);
            result.volumeCredits = volumeCreditsFor(result);
            return result;
        }
    
        //获取某一剧目
        function playFor(aPerformance) {
            return plays[aPerformance.playID];
        }
    
        //计算某一剧目需要的账目
        function amountFor(aPerformance){
            let result= 0 ;
            //用于计算总账单
            switch (aPerformance.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:${aPerformance.play.type}`);
            }
            return result;
        }
    
        //这一轮循环增加的量
        function volumeCreditsFor(aPerformance) {
            let result = 0;
            result += Math.max(aPerformance - 30 , 0);
            if ("comedy" == aPerformance.play.type) result += Math.floor(aPerformance.audience / 5);
            return result;
        }
    
        //计算总的账目
        function totalAmount(data) {
            return data.performances.reduce((total,p) =>total+p.amount,0);
        }
    
        //计算观众积分 add volume credits
        function totalVolumeCredits(data) {
            return data.performances.reduce((total,p) =>total+p.volumeCredits,0)
        }
    }
    
    //拆分阶段 render 呈现    plaintext纯文本
    function renderPlainText (data,plays ) {
        let result = `Statement for ${data.customer}
    `; //用于打印的字符串
        for(let aPerformance of data.performances){
            //print line for this order
            result += ` ${aPerformance.play.name}:${usd(aPerformance.amount/100)} (${aPerformance.audience} seats)
    `;
        }
        result += `Amount owed is ${usd(data.totalAmount)}
    `;
        result += `You earned ${data.totalVolumeCredits} credits
    `;
        return result;
    }
    
    //提到顶层,以供其他函数使用
    //格式化数字,显示为货币
    function usd(aNumber) {
        return new Intl.NumberFormat("en-US",
            { style:"currency",currency:"USD",
                minimumFractionDigits:2}).format(aNumber/100);
    }
    
    function htmlStatement(invoices,plays) {
        return rederHtml(createStatementData(invoices,plays));
    }
    
    function rederHtml(data) {
        //...
    }
    

    同时,生成中转参数的函数可以提取到单独的文件。
    也许,有的代码还能够优化,但是我们经常需要在重构与添加新特性之间寻找平衡。当我们面临选择时,应当尽可能的遵循营地法则:保证你离开时的代码库一定比来时更健康。

    欢迎大家留言,以便于后面的人更快解决问题!另外亦欢迎大家可以关注我的微信公众号,方便利用零碎时间互相交流。共勉!

  • 相关阅读:
    渗透利器-kali工具 (第六章-1) 密码破解
    渗透利器-kali工具 (第五章-6) Metasploit后门生成模块
    渗透利器-kali工具 (第五章-5) Metasploit漏洞利用模块二
    渗透利器-kali工具 (第五章-4) Metasploit漏洞利用模块一
    渗透利器-kali工具 (第五章-3) Metasploit密码爆破模块
    渗透利器-kali工具 (第五章-2) Metasploit扫描漏洞模块
    渗透利器-kali工具 (第五章-1) Metasploit框架介绍与基本命令
    关于计算机网络的性能指标你需要知道这些
    写给大忙人看的计算机网络参考模型
    PHP基础编程之鬼斧神工的正则表达式-正则表达式基本语法+简单实例
  • 原文地址:https://www.cnblogs.com/caozz/p/refact1.html
Copyright © 2020-2023  润新知