forked from tkchu/Game-Programming-Patterns-CN
-
Notifications
You must be signed in to change notification settings - Fork 0
/
architecture-performance-and-games.html
332 lines (329 loc) · 24.1 KB
/
architecture-performance-and-games.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
<title>Architecture, Performance, and Games · Introduction · Game Programming Patterns</title>
<!-- Tell mobile browsers we're optimized for them and they don't need to crop
the viewport. -->
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="stylesheet" type="text/css" href="style.css" />
<link href="http://fonts.googleapis.com/css?family=Merriweather:400,400italic,700,700italic|Source+Code+Pro|Source+Sans+Pro:200,300,400,600,400italic,600italic|Rock+Salt" rel="stylesheet" type="text/css">
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-42804721-1', 'gameprogrammingpatterns.com');
ga('send', 'pageview');
</script>
<script src="jquery-1.10.1.min.js"></script>
<script src="script.js"></script>
</head>
<body id="top">
<div class="page sidebar">
<div class="content">
<nav class="top">
<span class="prev">← <a href="introduction.html">Previous Chapter</a></span>
<span class="next"><a href="design-patterns-revisited.html">Next Chapter</a> →</span>
<span class="toc">≡ <a href="/">The Book</a></span>
</nav>
<h1>Architecture, Performance, and Games</h1>
<h1 class="book"><a href="/">Game Programming Patterns</a><span class="section"><a href="introduction.html">Introduction</a></span></h1>
<p>在我们一头扎入模式之前,我想先讲一些对软件架构和其在游戏上的应用的理解,
也许能帮你更好的理解这本书的其余部分。
至少,在你被卷入一场关于软件架构有多么糟糕(或多么优秀)的<span name="ammo">辩论</span>时,
这可以给你一些武器支援。</p>
<aside name="ammo">
<p>注意我没有建议你在战斗中选哪一边。就像任何军火贩子一样,我愿意向作战双方出售武器。</p>
</aside>
<h2><a href="#什么是软件架构?" name="什么是软件架构?">什么是软件架构?</a></h2>
<p><span name="won't">如果</span>你把本书从头到尾读一遍,
你不会找到在3D图形背后的线性代数或者游戏物理背后的微积分。
这书不会告诉你如何用α-β修剪你的AI树,也不会告诉你如何在音频中模拟房间中的混响。</p>
<aside name="won't">
<p>Wow,这段给这本书打了一个糟糕的广告啊。</p>
</aside>
<p>相反,这本书告诉你在这些<em>之间</em>的事情。
与其说这本书是关于如何写代码。不如说是关于如何<em>组织</em>代码的。
每个程序都有<em>一定</em>组织,
哪怕是“将所有东西都塞到<code>main()</code>中然后看看发生了什么”,
所以我认为讲讲什么造成了<em>好的</em>组织是很有意思的。我们如何区分好架构和坏架构呢?</p>
<p>我思考这个问题五年了。当然,像你一样,我有关于好设计的直觉。我们都被<span name="suffered">糟糕的</span>代码折磨的不轻,唯一能做的事情就是删掉它们,结束它们的痛苦。</p>
<aside name="suffered">
<p>承认吧,我们中大多数都该对它们中的一些<em>负责</em>。</p>
</aside>
<p>只有很少的幸运者有相反的经验,
有机会在好好设计的代码库上工作。
那种代码库看上去是间豪华酒店,里面的门房随时准备满足你心血来潮的需求。
这两者之间的区别是什么呢?</p>
<h3><a href="#什么是*好的*软件架构?" name="什么是*好的*软件架构?">什么是<em>好的</em>软件架构?</a></h3>
<p>对于我来说,好的设计意味着当我改了点什么,
整个程序就好像正等着这种改动。
我可以加入几个函数调用完成任务,同时丝毫不改变代码平静表面下的脉动。</p>
<p>这听起来很棒,只是完全不可行。“把代码写到改变不会影响其平静表面就好。”成了。</p>
<p>让我们通俗些。
第一个关键点是<em>架构是有关于变化的</em>。
总有人改动代码库。如果没人碰代码——无论是因为代码至善至美,还是糟糕透顶以至于没人会为了修改它而玷污自己的文本编辑器——那么它的架构设计就无关紧要。
评价架构设计就是评价它应对变化有多么轻松。
没有了变化,它就是一个永远不会离开起跑线的运动员。</p>
<h3><a href="#你如何处理变化?" name="你如何处理变化?">你如何处理变化?</a></h3>
<p>在你改变代码去添加新特性,去修复漏洞,或者随便什么需要你使用编辑器的时候,
你需要理解现在的代码在做些什么。当然,你不需要理解整个程序,
但你需要将所有相关的东西<span name="ocr">装进</span>你的灵长类大脑。</p>
<aside name="ocr">
<p>有点诡异,这字面上是一个OCR过程。</p>
</aside>
<p>我们通常无视了这步,但这往往是编程中最耗时的部分。
如果你认为将数据从磁盘上分页到RAM上很慢,
那么试着通过一对神经纤维将数据分页到猴子的脑子里。</p>
<p>一旦把所有正确的上下文都记到了你的湿件里,
你想了一会,然后找到了解决方案。
这可能会有来回打转的时候,但通常比较简单。
一旦你理解了问题和需要改动的代码,实际的编码工作有时候是微不足道的。</p>
<p>你用肥手指在键盘上敲打一阵子,直到屏幕上显示着正确颜色的光芒,
然后就算搞定了,对吧?还没呢!
在你为之写<span name="tests">测试</span>并发送其到代码评审之前,你通常有些清理工作要做。</p>
<aside name="tests">
<p>我是不是说了“测试”?噢,是的,我说了。为有些游戏代码写单元测试很难,但代码库的一大部分是完全可以测试的。</p>
<p>我不会在这里发表演说,但是我建议你,如果还没有做自动测试,请考虑一下。
除了手动验证以外你就没别的事要做了吗?</p>
</aside>
<p>你将一些代码加入了你的游戏,但你不想下一个人被你留下来的小问题绊倒。
除非改动很小,否则就还需要一些工作去微调你的新代码,使之无缝对接到程序的其他部分。如果你做对了,那么下一个见到代码的人甚至无法说出哪些代码是新加入的。</p>
<p>简而言之,编程的流程图看起来是这样的:</p>
<p><img src="images/architecture-cycle.png" alt="Get problem → Learn code → Code solution → Clean up → and back around to the beginning." /></p>
<p><span name="life-cycle"></span></p>
<aside name="life-cycle">
<p>无法脱离循环令人震惊,我现在发现了。</p>
</aside>
<h3><a href="#解耦怎么帮了忙?" name="解耦怎么帮了忙?">解耦怎么帮了忙?</a></h3>
<p>虽然并不明显,但我认为很多软件架构都是关于学习阶段。
将代码载入到神经元太过缓慢,找些策略减少载入的总量是很值得做的事。
这本书有整整一个章节是关于<a href="decoupling-patterns.html"><em>解耦</em>模式</a>,
还有很多<em>设计模式</em>是关于同样的主题。</p>
<p>你可以用多种方式定义“解耦”,但我认为如果有两块代码是耦合的,
那就意味着你无法只理解其中的一个。
如果你<em>解</em>耦了他俩,你就可以独自的理解某一块。
这当然很好,因为只有一块与你的问题相关,
你只需要将<em>这一块</em>加载到你的猴脑中而不需要加载另外一块。</p>
<p>对于我来说,下面是软件架构的关键目标:
<em>最小化你在处理前需要进入大脑的知识</em>。</p>
<p>当然,也可以从后期阶段来看。
另一种解耦的定义是当一块代码有<em>变化</em>时,没必要修改另外的代码。
我们肯定需要修改<em>一些东西</em>,但耦合程度越小,变化会波及的范围就越小。</p>
<h2><a href="#以什么代价?" name="以什么代价?">以什么代价?</a></h2>
<p>听起来很棒,对吧?解耦任何东西,然后你能像风一样编码。
每个变化都只修改一两个特定的方法,你在代码库的水面上跳舞,只留下倒影。</p>
<p>这就是人们对抽象,模块化,设计模式和软件架构兴奋的原因。
在有好架构的程序上工作是很好的体验,每个人都希望能更有效率的工作。
好架构能造成生产力上<em>巨大的</em>不同。很难再夸大它那强力的影响。</p>
<p>但是,就像生活中的任何事物一样,没有免费的午餐。好的设计需要汗水和纪律。
每一次做出改动或是实现特性,你都需要将它优雅的集成到程序的其他部分。
你需要花费大量的努力去<span name="maintain">管理</span>代码,
在开发过程中面对数千次变化仍然<em>保持</em>它的管理结构。</p>
<aside name="maintain">
<p>这个的第二部分——管理你的设计——需要特别关注。我看到无数的程序优雅的开始,然后结束在程序员一遍又一遍添加的“微小的黑魔法”中。</p>
<p>就像园艺,增加新植物是不够的。你还需要除草和修剪。</p>
</aside>
<p>You have to think about which parts of the program should be decoupled and
introduce abstractions at those points. Likewise, you have to determine where
extensibility should be engineered in so future changes are easier to make.</p>
<p>你得考虑你的程序的哪部分需要解耦,然后再引入抽象。
同样,你需要决定哪些部分要设计得有扩展性来方便未来的变化。</p>
<p>人们对这点变得狂热。
他们设想未来的开发者(或者只是他们的未来的自己)进入代码库,
并发现它极为开放,
功能强大,只需扩展。他们想象至尊引擎应所求。</p>
<p>但是,这是开始棘手的部分。
每当你添加了一层抽象或者支持扩展的部分,你是<em>赌</em>你以后需要灵活性。
添加代码和复杂性到游戏中,这都需要时间来开发,调试和维护。</p>
<p>That effort pays off if you guess right and end up touching that code later. But
<span name="yagni">predicting</span> the future is <em>hard</em>, and when that
modularity doesn’t end up being helpful, it quickly becomes actively harmful.
After all, it is more code you have to deal with.</p>
<p>如果你猜对了,后来接触了代码,那么功夫不负有心人。
但<span name="yagni">预测</span>未来<em>很难</em>,如果模块化最终没有帮助,那它就有伤害。
毕竟,你得处理更多的代码。</p>
<aside name="yagni">
<p>有些人喜欢简写为术语“YAGNI”——<a href="http://en.wikipedia.org/wiki/You_aren't_gonna_need_it">You aren’t gonna need it(你不需要那个)</a>——来对抗预测将来需求的强烈欲望。</p>
</aside>
<p>当人们过分关注这点时,你会得到失去控制的代码库。
接口和抽象无处不在。插件系统,抽象基类,虚方法,还有各种各样的扩展点。</p>
<p>回溯所有的脚手架去找真正做事的代码就要消耗无尽的时间。
当你需要做出改变,当然,有可能某个接口能帮上忙,但能不能找到就只能祝你好运了。
理论上,解耦意味着在扩展代码之前需要了解的代码更少,
但抽象层本身就会填满你的心灵暂存磁盘。</p>
<p>Codebases like this are what turn people <em>against</em> software architecture, and
design patterns in particular. It’s easy to get so wrapped up in the code itself
that you lose sight of the fact that you’re trying to ship a <em>game</em>. The siren
song of extensibility sucks in countless developers who spend years working on
an “engine” without ever figuring out what it’s an engine <em>for</em>.</p>
<p>像这样的代码库让人<em>反对</em>软件架构,特别是设计模式。
人们很容易沉浸在代码中而忽略你要发布<em>游戏</em>的这点。
无数的开发者听着加强可扩展性的警报,花费多年时间制作“引擎”,
却没有搞清楚做引擎是<em>为了什么</em>。</p>
<h2><a href="#性能和速度" name="性能和速度">性能和速度</a></h2>
<p>软件架构和抽象有时会被批评,尤其是在游戏开发中: 它伤害了游戏的性能。
许多让代码更灵活的模式依靠虚拟调度、 接口、 指针、 消息,和<span name="templates">其他机制</span>,
他们都会消耗运行时成本。</p>
<aside name="templates">
<p>一个有趣的反面例子是在C++中的模板。模板编程有时可以给你抽象接口而无需运行时开销。</p>
<p>这是灵活性的两极。但你写代码调用类中的具体方法时,你在<em>写作</em>时修改类——你硬编码了调用的是哪个类。但通过虚方法或接口时,直到<em>运行</em>时你才知道调用的类。这更加灵活但增加了运行时开销。</p>
<p>模板编程是在两者这件。你在<em>编译时</em>初始化模板,决定调用哪些类。</p>
</aside>
<p>还有一个原因。很多软件架构的目的是使程序更加灵活。
这让改变它需要较少的努力。编码时对程序有更少的假设。
您可以使用接口,让您的代码可与<em>任何</em>实现它的类交互,而不仅仅是<em>现在</em>写的类。
今天。您可以使用<a href="observer.html" class="gof-pattern">观察者</a>和<a href="event-queue.html" class="pattern">消息</a>让游戏的两部分交流,
而以后它可以很容易地扩展为三个或四个部分相互交流。</p>
<p>但性能与假设相关。实践优化基于确定的限制。
永远不会超过256种敌人吗?好,我们就可以将ID编码为为一个字节。
在一个具体类型中只调用一个方法吗?好,我们可以做静态调度或内联。
所有的实体都是同一类?太好了,我们可以做一个 <a href="data-locality.html" class="pattern">连续数组</a>存储他们。</p>
<p>但这并不意味着灵活性是坏的!它可以让我们快速改进我们的游戏,
<em>开发</em>速度是获取有趣开发经验的绝对重要因素。
没有人,哪怕是Will Wright,能在纸面上构建一个平衡的游戏。这需要迭代和实验。</p>
<p>越快尝试想法看看效果如何,你就越能尝试更多的东西,就越有可能找到有价值的东西。
就算找到正确的机制之后,你也需要足够的时间调整。
一个微小的不平衡可能破坏整个游戏的乐趣。</p>
<p>这里没有简单方案。
让你的程序更加灵活,你能在损失一点点性能的前提下更快的做出原型。
同样的,优化代码会让它不那么灵活。</p>
<p>就我个人经验而言,让有趣的游戏变快比让快速的游戏变有趣简单得多。
一种折中的办法是保持代码灵活直到设计定下来,再抽出抽象层来提高性能。</p>
<h2><a href="#糟糕代码的优势" name="糟糕代码的优势">糟糕代码的优势</a></h2>
<p>这就来到了下一观点:不同的代码风格各有千秋。
这本书的大部分是关于保持干净可控的代码,所以我坚持应该用<em>正确</em>方式写代码,但糟糕的代码也有一定的优势。</p>
<p>编写良好架构的代码需要仔细的思考,这会转为时间上的代价。
在项目的整个周期中<em>保持</em>良好的架构需要花费大量的努力。
你需要像露营者处理营地一样小心处理代码库:总是保持其优于你刚开始的时候。</p>
<p>当你要在一个项目上花费很久时间的话,这很好。
但,就像我早先提到的,游戏设计需要很多实验和探索。
特别是在早期,写一些你<em>知道</em>要扔掉的代码是很普遍的事情。</p>
<p>如果你只想试试游戏的某些主意是不是正确的,
良好的设计意味着你在屏幕上看到和获取反馈之前要消耗很长时间。
如果最后证明这个点子不对,那么当你删除代码的时候,那些花在让代码更优雅的时间就完全浪费了。</p>
<p>Prototyping — slapping together code that’s just barely functional enough to
answer a design question — is a perfectly legitimate programming practice.
There is a very large caveat, though. If you write throwaway code, you <em>must</em>
ensure you’re able to throw it away. I’ve seen bad managers play this game time
and time again:</p>
<p>原型——一坨勉强拼凑在一起,只能回答设计问题简单代码——是一个完全合理的编程习惯。
虽然当你写一次性代码的时候,<em>必须</em>保证你可以扔掉它。
我见过很多次糟糕的经理人在玩这种把戏:</p>
<blockquote>
<p>老板:“嗨,我们有些想试试的点子。只要原型,不需要做的很好。你能多快搞定?”</p>
<p>开发者:“额,如果我删掉这些部分,不测试,不写文档,允许很多的漏洞,那么几天后我能给你一个临时的代码文件。”</p>
<p>老板:“太好了。”</p>
</blockquote>
<p><em>几天后</em></p>
<blockquote>
<p>老板:“嘿,原型很棒,你能花上几个小时清理一下然后变为成品吗?”</p>
</blockquote>
<p>你得让人们清楚,<span name="throwaway">可抛弃</span>的代码即使看上去能工作,也不能被<em>维护</em>,<em>必须</em>重写。
如果<em>有可能</em>要维护这段代码,你就得防御性好好编写它。</p>
<aside name="throwaway">
<p>一个保证你的原型代码不会变成真正用的代码的技巧是使用一种和游戏不同的语言。这样,你在实际应用于游戏中之前必须重写。</p>
</aside>
<h2><a href="#保持平衡" name="保持平衡">保持平衡</a></h2>
<p>有些因素在相互角力:</p>
<ol>
<li>为了在项目的整个生命周期保持其可读性,<span name="speed">我们</span>需要好架构。</li>
<li>我们需要更好的运行时性能。</li>
<li>我们需要让现在的特性更快的实现。</li>
</ol>
<aside name="speed">
<p>有趣的是,这些都是速度:我们长期开发的速度,游戏运行的速度,和我们短期开发的速度。</p>
</aside>
<p>这些目标至少是部分对立的。
好的架构长期来看提高了生产力,
也意味着维护每个变化都需要更多努力让代码保持整洁。</p>
<p>草就的代码很少是<em>运行时</em>最快的。
相反,提升性能需要很多的编程时间。
一旦完成,它会污染代码库:高度优化的代码不灵活,很难改动。</p>
<p>总有今日事今日毕的压力。但是如果尽可能快地实现特性,
代码库就会充满黑魔法,漏洞和混乱,阻碍未来的产出。</p>
<p>没有简单的解决方案,只有权衡。
从我收到的邮件看,这伤了很多人的心,特别是那些只是想做个游戏的人。
它似乎是在恐吓,“没有正确的答案,只有不同的口味的错误。”</p>
<p>But, to me, this is exciting! Look at any field that people dedicate careers to
mastering, and in the center you will always find a set of intertwined
constraints. After all, if there was an easy answer, everyone would just do
that. A field you can master in a week is ultimately boring. You don’t hear of
someone’s distinguished career in <span name="ditch">ditch digging</span>.</p>
<p>但,对我来说,这让人兴奋!看看任何人们从事的领域,
你总能发现某些相互抵触的限制。无论如何,如果有简单的答案,每个人都会那么做。
一周就能掌握的领域是很无聊的。你从来没有听说过有人讨论<span name="ditch">挖坑事业</span>。</p>
<aside name="ditch">
<p>游戏你会;我没有深究这个类比。
我知道的是,可能有挖坑热爱着,挖坑规范,以及一整套亚文化。
我算什么人,能在此大放厥词?</p>
</aside>
<p>对我来说,这和游戏有很多相似之处。
国际象棋之类的游戏永远不能被掌握,因为每一个棋子都很完美的与其他棋子相平衡。
这意味你可以花费一生探索可选的广阔策略。糟糕的游戏就像井字棋,你玩了一遍又一遍,直到你厌烦了就退出。</p>
<h2><a href="#简单" name="简单">简单</a></h2>
<p>最近,我感觉如果有什么能简化这些限制,那就是<em>简单</em>。
在我现在的代码中,我努力去写最简单,最直接的解决方案。
你读过这种代码后,完全理解了它在做什么,想不出其他完成的方法。</p>
<p>I aim to get the data structures and algorithms right (in about that order) and
then go from there. I find if I can keep things simple, there’s less code
overall. That means less code to load into my head in order to change it.</p>
<p>我的目的是正确获得数据结构和算法(大致是这样的先后),然后在从那里开始。
我发现如果能让事物变得简单,就有更少的代码,
就意味着改动时有更少的代码载入脑海。</p>
<p>它通常跑的很快,因为没什么开销,也没什么代码执行。
(虽然大部分时候事实并非如此。你可以在一小段代码里加入大量的循环和递归。)</p>
<p>但是,注意我并没有说<span name="simple">简单的代码</span>需要更少的时间<em>编写</em>。
你会这么觉得是因为最终获得了更少的代码,但是好的解决方案不是往代码中注水,而是<em>蒸干</em>代码。</p>
<aside name="simple">
<p>Blaise Pascal有句著名的信件结尾,“我没时间写的更短。”</p>
<p>另一句名言来自Antoine de Saint-Exupery:“完美是可达到的,不是没有东西可以添加的时候,而是没有东西可以删除的时候。”</p>
<p>言归正传,我发现每次我重写本章,它就更短。有些章节比他们刚完成时短了20%。</p>
</aside>
<p>我们很少遇到优雅表达的问题。取而代之的是一堆用况。
你想要X在Z情况下做Y,在A情况下做W,诸如此类。换言之,一长列不同行为。</p>
<p>最不消耗心血的解决方法就是为每段用况编写一段代码。
如果你看看新手程序员,他们经常这么干:
他们为每个手头的问题编写逻辑循环。</p>
<p>但这一点也不优雅,那种风格的代码遇到一点点没想到的输入就会崩溃。
当我们想象优雅的代码时,我们想的是<em>通用</em>的那一个:
只需要很少的逻辑就可以覆盖整个用况。</p>
<p>找到这样的方法有点像模式识别或者解决谜题。
需要努力去识别散乱的用例下隐藏的规律。
完成的时候你会感觉好得不能再好。</p>
<h2><a href="#就快完了" name="就快完了">就快完了</a></h2>
<p>几乎每个人都会跳过介绍章节,所以祝贺你看到这里。
我没有太多东西回报你的耐心,但我还有一些建议给你,希望对你有用:</p>
<ul>
<li>
<p>抽象和解耦让扩展代码更快更容易,但除非确信需要灵活性,否则不要做。</p>
</li>
<li>
<p>在你的整个开发周期中<span name="think">考虑</span>并为性能设计,但是尽可能推迟那些底层的,基本事实的优化,那会锁死你的代码。</p>
</li>
</ul>
<aside name="think">
<p>相信我,在发布前两个月<em>不是</em>你开始思考“游戏运行只有1FPS”的唠叨小问题的时候。</p>
</aside>
<ul>
<li>
<p>快速的探索你游戏的设计空间,但不要跑的太快,在身后留下一团乱麻。毕竟,你总得回来处理他们。</p>
</li>
<li>
<p>如果你打算抛弃这段代码,就不要尝试将其写完美。摇滚明星将旅店房间弄得一团糟,因为他们知道明天会有人来打扫干净。</p>
</li>
<li>
<p>但最重要的是,<strong>如果你想要做出让人享受的东西,那就享受做它的过程。</strong></p>
</li>
</ul>
<nav>
<span class="prev">← <a href="introduction.html">Previous Chapter</a></span>
<span class="next"><a href="design-patterns-revisited.html">Next Chapter</a> →</span>
<span class="toc">≡ <a href="/">The Book</a></span>
</nav>
</div>
</div>
<footer>© 2009-2015 Robert Nystrom</footer>
</body>
</html>