“代码千万行,注释第一行;编程不规范,同事两行泪”;"道路千万条,安全第一条。代码不规范,亲人两行泪。"在技术圈广为盛传,可见代码不规范让程序员们是多么的头痛。
如何让你的代码整洁而优雅?如何写出让同事膜拜的漂亮代码?
今天栈长给大家介绍的这本书教你有条不紊的改善代码,也许你有所耳闻,它就是《重构:改善既有代码的设计(第2版)(精装版)》。
又月底了,栈长免费送 20 本给大家,看大家运气了,见文末送书说明。
01
何谓重构
重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
重构与性能优化有很多相似之处:两者都需要修改代码,并且两者都不会改变程序的整体功能。两者的差别在于其目的:重构是为了让代码“更容易理解,更易于修改”。这可能使程序运行得更快,也可能使程序运行得更慢。在性能优化时,我只关心让程序运行得更快,最终得到的代码有可能更难理解和维护,对此我有心理准备。
02
两顶帽子
Kent Beck提出了“两顶帽子”的比喻。使用重构技术开发软件时,我把自己的时间分配给两种截然不同的行为:添加新功能和重构。
添加新功能时,我不应该修改既有代码,只管添加新功能。通过添加测试并让测试正常运行,我可以衡量自己的工作进度。
重构时我就不能再添加功能,只管调整代码的结构。此时我不应该添加任何测试(除非发现有先前遗漏的东西),只在绝对必要(用以处理接口变化)时才修改测试。
03
为何重构
我不想把重构说成是包治百病的万灵丹,它绝对不是所谓的“银弹”。不过它的确很有价值,尽管它不是一颗“银弹”,却可以算是一把“银钳子”,可以帮你始终良好地控制自己的代码。重构是一个工具,它可以(并且应该)用于以下几个目的。
04
何时重构
在我编程的每个小时,我都会做重构。有几种方式可以把重构融入我的工作过程里。
05
重构的挑战
每当有人大力推荐一种技术、工具或者架构时,我总是会观察这东西会遇到哪些挑战,毕竟生活中很少有晴空万里的好事。你需要了解一件事背后的权衡取舍,才能决定何时何地应用它。
我认为重构是一种很有价值的技术,大多数团队都应该更多地重构,但它也不是完全没有挑战的。有必要充分了解重构会遇到的挑战,这样才能做出有效应对。
06
重构,第一个示例
在本书第1版中,我使用的示例程序是为影片出租店的顾客打印一张详单。放到今天,很多人可能要问了:“影片出租店是什么?”为了避免过多回答这个问题,我翻新了一下示例,将其包装成一个仍有古典韵味又尚未消亡的现代示例。
设想有一个戏剧演出团,演员们经常要去各种场合表演戏剧。通常客户(customer)会指定几出剧目,而剧团则根据观众(audience)人数及剧目类型来向客户收费。该团目前出演两种戏剧:悲剧(tragedy)和喜剧(comedy)。给客户发出账单时,剧团还会根据到场观众的数量给出“观众量积分”(volume credit)优惠,下次客户再请剧团表演时可以使用积分获得折扣—你可以把它看作一种提升客户忠诚度的方式。
该剧团将剧目的数据存储在一个简单的JSON文件中。
1{
2 "hamlet": {"name": "Hamlet", "type": "tragedy"},
3 "as-like": {"name": "As You Like It", "type": "comedy"},
4 "othello": {"name": "Othello", "type": "tragedy"}
5}
他们开出的账单也存储在一个JSON文件里。
1[
2 {
3 "customer": "BigCo",
4 "performances": [
5 {
6 "playID": "hamlet",
7 "audience": 55
8 },
9 {
10 "playID": "as-like",
11 "audience": 35
12 },
13 {
14 "playID": "othello",
15 "audience": 40
16 }
17 ]
18 }
19]
下面这个简单的函数用于打印账单详情。
1function statement (invoice, plays) {
2 let totalAmount = 0;
3 let volumeCredits = 0;
4 let result = `Statement for ${invoice.customer}\n`;
5 const format = new Intl.NumberFormat("en-US",
6 { style: "currency", currency: "USD",
7 minimumFractionDigits: 2 }).format;
8 for (let perf of invoice.performances) {
9 const play = plays[perf.playID];
10 let thisAmount = 0;
11 switch (play.type) {
12 case "tragedy":
13 thisAmount = 40000;
14 if (perf.audience > 30) {
15 thisAmount += 1000 * (perf.audience - 30);
16 }
17 break;
18 case "comedy":
19 thisAmount = 30000;
20 if (perf.audience > 20) {
21 thisAmount += 10000 + 500 * (perf.audience - 20);
22 }
23 thisAmount += 300 * perf.audience;
24 break;
25 default:
26 throw new Error(`unknown type: ${play.type}`);
27 }
28 // add volume credits
29 volumeCredits += Math.max(perf.audience - 30, 0);
30 // add extra credit for every ten comedy attendees
31 if ("comedy" === play.type) volumeCredits += Math.floor(perf.audience / 5);
32 // print line for this order
33 result += ` ${play.name}: ${format(thisAmount/100)} (${perf.audience} seats)\n`;
34 totalAmount += thisAmount;
35 }
36 result += `Amount owed is ${format(totalAmount/100)}\n`;
37 result += `You earned ${volumeCredits} credits\n`;
38 return result;
39}
用上面的数据文件(invoices.json
和plays.json
)作为测试输入,运行这段代码,会得到如下输出:
1Statement for BigCo
2 Hamlet: $650.00 (55 seats)
3 As You Like It: $580.00 (35 seats)
4 Othello: $500.00 (40 seats)
5Amount owed is $1,730.00
6You earned 47 credits
更多重构手法推荐大家有时间看下这本书,写出更漂亮的代码, 《重构:改善既有代码的设计(第2版)》 作者:马丁·福勒(Martin Fowler)