在 重构:改善既有代码的设计 (第2版) 中,js 嵌套函数的使用

1、在学习重构:改善既有代码的设计 (第2版)时,发现函数 statement 在末尾缺少1个 },进而导致代码格式显示上存在问题。如图1

图1

    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 perf of invoice.performances) {
            let thisAmount = amountFor(perf
                , playFor(perf)
            );

            // add volume credits
            volumeCredits += Math.max(perf.audience - 30, 0);
            // add extra credit for every ten comedy attendees
            if ("comedy" === playFor(perf).type) volumeCredits += Math.floor(perf.audience / 5);

            // print line for this order
            result += ` ${playFor(perf).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;

2、一直误以为是代码本身漏写了 },不得不自行补充上。最终的程序实现如下

<script>
    let invoice = {
        "customer": "BigCo",
        "performances": [
            {
                "playID": "hamlet",
                "audience": 55
            },
            {
                "playID": "as-like",
                "audience": 35
            },
            {
                "playID": "othello",
                "audience": 40
            }
        ]
    };

    let plays = {
        "hamlet": {
            "name": "Hamlet",
            "type": "tragedy"
        },
        "as-like": {
            "name": "As You Like It",
            "type": "comedy"
        },
        "othello": {
            "name": "Othello",
            "type": "tragedy"
        }
    };

    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 volumeCreditsFor(aPerformance) {
        let result = 0;
        result += Math.max(aPerformance.audience - 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 totalVolumeCredits() {
        let result = 0;
        for (let perf of invoice.performances) {
            result += volumeCreditsFor(perf);
        }
        return result;
    }

    function totalAmount() {
        let result = 0;
        for (let perf of invoice.performances) {
            result += amountFor(perf);
        }
        return result;
    }

    function statement(invoice, plays) {
        let result = `Statement for ${invoice.customer}\n`;
        for (let perf of invoice.performances) {
            result += ` ${playFor(perf).name}: ${usd(amountFor(perf))} (${perf.audience} seats)\n`;
        }
        result += `Amount owed is ${usd(totalAmount())}\n`;
        result += `You earned ${totalVolumeCredits()} credits\n`;
        return result;
    }

    console.log(statement(invoice, plays));
</script>

3、在 1.5 进展:大量嵌套函数 重构至此,是时候停下来欣赏一下代码的全貌了。发现 函数 statement 的 } 竟然是存在的,只不过由于其中包含了一些嵌套函数,由于并非全貌,因此,并未显示出来。如图2

图2

4、最终决定使用嵌套函数,仍然可以正常运行。如图3

图3

<script>
    let invoice = {
        "customer": "BigCo",
        "performances": [
            {
                "playID": "hamlet",
                "audience": 55
            },
            {
                "playID": "as-like",
                "audience": 35
            },
            {
                "playID": "othello",
                "audience": 40
            }
        ]
    };

    let plays = {
        "hamlet": {
            "name": "Hamlet",
            "type": "tragedy"
        },
        "as-like": {
            "name": "As You Like It",
            "type": "comedy"
        },
        "othello": {
            "name": "Othello",
            "type": "tragedy"
        }
    };

    function statement(invoice, plays) {
        let result = `Statement for ${invoice.customer}\n`;
        for (let perf of invoice.performances) {
            result += ` ${playFor(perf).name}: ${usd(amountFor(perf))} (${perf.audience} seats)\n`;
        }
        result += `Amount owed is ${usd(totalAmount())}\n`;
        result += `You earned ${totalVolumeCredits()} credits\n`;
        return result;

        function totalAmount() {
            let result = 0;
            for (let perf of invoice.performances) {
                result += amountFor(perf);
            }
            return result;
        }

        function totalVolumeCredits() {
            let result = 0;
            for (let perf of invoice.performances) {
                result += volumeCreditsFor(perf);
            }
            return result;
        }

        function usd(aNumber) {
            return new Intl.NumberFormat("en-US",
                {
                    style: "currency", currency: "USD",
                    minimumFractionDigits: 2
                }).format(aNumber / 100);
        }

        function volumeCreditsFor(aPerformance) {
            let result = 0;
            result += Math.max(aPerformance.audience - 30, 0);
            if ("comedy" === playFor(aPerformance).type) result += Math.floor(aPerformance.audience / 5);
            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;
        }
    }

    console.log(statement(invoice, plays));
</script>
永夜