链接: https://pan.baidu.com/s/1b3cTGmABb66zeCV-5DY-LQ 提取码: 6a9n
里面存在的个人观点,我都会用斜体来表示。
重要的结论,我会用粗体表示。
序言
开篇讲了一个故事,说一个顾问建议一个团队去重构,重构使得代码变得更美了,但重构花费了时间,并没有新增加功能,项目经理其实很不满意,这个项目也在6个月后失败了。有一句古老的工程谚语:“如果它还可以运行,就别去动它”。
那么重构是什么呢?
重构的定义是,在代码写好之后改进它的设计。
第一章
下面介绍了一个戏剧演出团的程序
首先是数据的格式:
plays.json,剧目数据
{ "hamlet": {"name": "Hamlet", "type": "tragedy"}, "as-like": {"name": "As You Like It", "type": "comedy"}, "othello": {"name": "Othello", "type": "tragedy"} }
invoices.json 账单数据
[ { "customer": "BigCo", "performances": [ { "playID": "hamlet", "audience": 55 }, { "playID": "as-like", "audience": 35 }, { "playID": "othello", "audience": 40 } ] } ]
然后有了一个程序来输出这个账单的情况
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) { const play = plays[perf.playID]; 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); // 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 for BigCo Hamlet: $650.00 (55 seats) As You Like It: $580.00 (35 seats) Othello: $500.00 (40 seats) Amount owed is $1,730.00 You earned 47 credits
1.2 评价这个程序
其实这个代码很小,也非常简单,就是一个算账的程序。
如果你发现加一个特性因为代码缺乏良好结构而导致比较难加这个特性的时候,你应该先重构这个代码,然后再加特性
(个人评价:我觉得这句话是句废话,如果我觉得这个代码很好加特性的时候,我当然就直接把这个特性加进去了;但是如果我加不进去,我当然就只能重构了呀。)
这时候作者开始提需求了:
需求一:假设现在想要输出html的格式怎么办?
-
部分程序员会采用if else 来判断输出为html还是输出为log,这样会使得代码变得非常麻烦。
-
部分程序员会采用复制一遍,然后变成两个函数来输出。当然这个做法显然不可行,未来假设要改的时候,就要必须修改两个地方。
需求二:假设演员有更多的表演类型了怎么办?不同的表演类型肯定有不同的计费方式。
这里作者强调了一句话:如果代码现在能正常工作,且不会被修改,那就别重构;除非有人想看你的代码,且别人看不懂,你可以改进一下。
(我觉得主要还是看收益吧,假设原工程总量为V,重构实际上花费了t的时间,增加了未来的效率dx。那么只要V/(x+dx)+t < V/x 就可以开始重构。当然里面的问题是t和dx根本不好评估。)
1.3 重构的第一步
重构的第一步是良好的测试。
(但国内的公司其实并不在乎这个,特别是自动化测试,往往log看上去对了就ok了,可能是习惯问题?)
1.4 分解statement函数
中间那一大坨的计算函数,实际上是可以抽离出来的,作者也进行了抽离。
function amountFor(perf, play) { 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}`); } return 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 perf of invoice.performances) { const play = plays[perf.playID]; let thisAmount = amountFor(perf, play); // add volume credits volumeCredits += Math.max(perf.audience - 30, 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;
注意做完每一次小改动后,都应该过一次测试。
重构技术应该以微小的步伐进行修改程序,如果你犯错,这样会很容易发现问题。
然后提炼函数是一个可以用ide自动完成的重构。真的吗??idea居然真的可以自动抽离函数
然后作者不停的进行重构,这里重构过程略。
好代码的定义应该是 人们能够很简单的修改它
对于我而言,我觉得他的重构部分还是比较繁琐。比如我就从没用过多态