-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinitTopicData.sql
More file actions
520 lines (318 loc) · 77.7 KB
/
initTopicData.sql
File metadata and controls
520 lines (318 loc) · 77.7 KB
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
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
-- 话题若干
INSERT INTO topic (user_id, title, content, theme, create_time, update_time, topic_comment_num, topic_like_num) VALUES (16122697,'Swift 语言的设计错误','<div class="inner">
<p>在『<a href="http://www.yinwang.org/blog-cn/2015/11/21/programming-philosophy">编程的智慧</a>』一文中,我分析和肯定了 Swift 语言的 optional type 设计,但这并不等于 Swift 语言的整体设计是完美没有问题的。其实 Swift 1.0 刚出来的时候,我就发现它的 array 可变性设计存在严重的错误。Swift 2.0 修正了这个问题,然而他们的修正方法却没有击中要害,所以导致了其它的问题。这个错误一直延续到今天。</p>
<p>Swift 1.0 试图利用 var 和 let 的区别来指定 array 成员的可变性,然而其实 var 和 let 只能指定 array reference 的可变性,而不能指定 array 成员的可变性。举个例子,Swift 1.0 试图实现这样的语义:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var shoppingList = ["Eggs", "Milk"]
// 可以对 array 成员赋值
shoppingList[0] = "Salad"
</code></pre></div></div>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>let shoppingList = ["Eggs", "Milk"]
// 不能对 array 成员赋值,报错
shoppingList[0] = "Salad"
</code></pre></div></div>
<p>这是错误的。在 Swift 1.0 里面,array 像其它的 object 一样,是一种“reference type”。为了理解这个问题,你应该清晰地区分 array reference 和 array 成员的区别。在这个例子里,<code class="highlighter-rouge">shoppingList</code> 是一个 array reference,而 <code class="highlighter-rouge">shoppingList[0]</code> 是访问一个 array 成员,这两者有着非常大的不同。</p>
<p>var 和 let 本来是用于指定 <code class="highlighter-rouge">shoppingList</code> 这个 reference 是否可变,也就是决定 <code class="highlighter-rouge">shoppingList</code> 是否可以指向另一个 array 对象。正确的用法应该是这样:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var shoppingList = ["Eggs", "Milk"]
// 可以对 array reference 赋值
shoppingList = ["Salad", "Noodles"]
// 可以对 array 成员赋值
shoppingList[0] = "Salad"
</code></pre></div></div>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>let shoppingList = ["Eggs", "Milk"]
// 不能对 array reference 赋值,报错
shoppingList = ["Salad", "Noodles"]
// let 不能限制对 array 成员赋值,不报错
shoppingList[0] = "Salad"
</code></pre></div></div>
<p>也就是说你可以用 var 和 let 来限制 <code class="highlighter-rouge">shoppingList</code> 这个 reference 的可变性,而不能用来限制 <code class="highlighter-rouge">shoppingList[0]</code> 这样的成员访问的可变性。</p>
<p>var 和 let 一旦被用于指定 array reference 的可变性,就不再能用于指定 array 成员的可变性。实际上 var 和 let 用于局部变量定义的时候,只能指定栈上数据的可变性。如果你理解 reference 是放在栈(stack)上的,而 Swift 1.0 的 array 是放在堆(heap)上的,就会明白array 成员(一种堆数据)可变性,必须用另外的方式来指定,而不能用 var 和 let。</p>
<p>很多古老的语言都已经看清楚了这个问题,它们明确的用两种不同的方式来指定栈和堆数据的可变性。C++ 程序员都知道 <code class="highlighter-rouge">int const *</code> 和 <code class="highlighter-rouge">int * const</code> 的区别。Objective C 程序员都知道 <code class="highlighter-rouge">NSArray</code> 和 <code class="highlighter-rouge">NSMutableArray</code> 的区别。我不知道为什么 Swift 的设计者看不到这个问题,试图用同样的关键字(var 和 let)来指定栈和堆两种不同位置数据的可变性。实际上,不可变数组和可变数组,应该使用两种不同的类型来表示,就像 Objective C 的 <code class="highlighter-rouge">NSArray</code> 和 <code class="highlighter-rouge">NSMutableArray</code> 那样,而不应该使用 var 和 let 来区分。</p>
<p>Swift 2.0 修正了这个问题,然而可惜的是,它的修正方式是错误的。Swift 2.0 做出了一个离谱的改动,它把 array 从 reference type 变成了所谓 value type,也就是说把整个 array 放在栈上,而不是堆上。这貌似解决了以上的问题,由于 array 成了 value type,那么 <code class="highlighter-rouge">shoppingList</code> 就不是 reference,而代表整个 array 本身。所以在 array 是 value type 的情况下,你确实可以用 var 和 let 来决定它的成员是否可变。</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>let shoppingList = ["Eggs", "Milk"]
// 不能对 array 成员赋值,因为 shoppingList 是 value type
// 它表示整个 array 而不是一个指针
// 这个 array 的任何一部分都不可变
shoppingList[0] = "Salad"
</code></pre></div></div>
<p>这看似一个可行的解决方案,然而它却没有击中要害。这是一种削足适履的做法,它带来了另外的问题。把 array 作为 value type,使得每一次对 array 变量的赋值或者参数传递,都必须进行拷贝。你没法让两个变量指向同一个 array,也就是说 array 不再能被共享。比如:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var a = [1, 2, 3]
// a 的内容被拷贝给 b
// a 和 b 是两个不同的 array,有相同的内容
var b = a
</code></pre></div></div>
<p>这违反了程序员对于数组这种大型结构的心理模型,他们不再能清晰方便的对 array 进行思考。由于 array 会被不经意的自动拷贝,很容易犯错误。数组拷贝需要大量时间,就算接收者不修改它也必须拷贝,所以效率上有很大影响。不能共享同一个 array,在里面读写数据,是一个很大的功能缺失。由于这个原因,没有任何其它现代语言(Java,C#,……)把 array 作为 value type。</p>
<p>如果你看透了 value type 的实质,就会发现这整个概念的存在,在具有垃圾回收(GC)的现代语言里,几乎是没有意义的。有些新语言比如 Swift 和 Rust,试图利用 value type 来解决内存管理的效率问题,然而它带来的性能提升其实是微乎其微的,给程序员带来的麻烦和困扰却是有目共睹的。完全使用 reference type 的语言(比如 Java,Scheme,Python),程序员不需要思考 value type 和 reference type 的区别,大大简化和加速了编程的思维过程。Java 不但有非常高效的 GC,还可以利用 escape analysis 自动把某些堆数据放在栈上,程序员不需要思考就可以达到 value type 带来的那么一点点性能提升。相比之下,Swift,Rust 和 C# 的 value type 制造的更多是麻烦,而没有带来实在的性能优势。</p>
<p>Swift 1.0 犯下这种我一眼就看出来的低级错误,你也许从中发现了一个道理:编译器专家并不等于程序语言专家。很多经验老到的程序语言专家一看到 Swift 最初的 array 设计,就知道那是错的。只要团队里有一个语言专家指出了这个问题,就不需要这样反复的修改折腾。为什么 Swift 直到 1.0 发布都没有发现这个问题,到了 2.0 修正却仍然是错的?我猜这是因为 Apple 并没有聘请到合格的程序语言专家来进行 Swift 的设计,或者有合格的人,然而他们的建议却没有被领导采纳。Swift 的首席设计师是 Chris Lattner,也就是 LLVM 的设计者。他是不错的编译器专家,然而在程序语言设计方面,恐怕只能算业余水平。编译器和程序语言,真的是两个非常不同的领域。Apple 的领导们以为好的编译器作者就能设计出好的程序语言,以至于让 Chris Lattner 做了总设计师。</p>
<p>Swift 团队不像 Go 语言团队完全是一知半解的外行,他们在语言方面确实有一定的基础,所以 Swift 在大体上不会有特别严重的问题。然而可以看出来这些人功力还不够深厚,略带年轻人的自负,浮躁,盲目的创新和借鉴精神。有些设计并不是出自自己深入的见解,而只是“借鉴”其它语言的做法,所以可能犯下经验丰富的语言专家根本不会犯的错误。第一次就应该做对的事情,却需要经过多次返工。以至于每出一个新的版本,就出现一些“不兼容改动”,导致老版本语言写出来的代码不再能用。这个趋势在 Swift 3.0 还要继续。由于 Apple 的统治地位,这种情况对于 Swift 语言也许不是世界末日,然而它确实犯了语言设计的大忌。一个好的语言可以缺少一些特性,但它绝不应该加入错误的设计,导致日后出现不兼容的改变。我希望 Apple 能够早日招募到资深一些的语言设计专家,虚心采纳他们的建议。BTW,如果 Apple 支付足够多的费用,我倒可以考虑兼职做他们的语言设计顾问 ;-)</p>
<h3 id="java-有-value-type-吗">Java 有 value type 吗?</h3>
<p>有人看了以上的内容,问我:“你说 Java 只有 reference type,但是根据 Java 的<a href="http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html">官方文档</a>,Java 也有 value type 和 reference type 的区别的。” 由于这个问题相当的有趣,我另外写了一篇<a href="http://www.yinwang.org/blog-cn/2016/06/08/java-value-type">文章</a>来回答这个问题。</p>
</div>','swift语言','2016-06-06 00:03:02','2016-06-06 00:03:02',0,0);
INSERT INTO topic (user_id, title, content, theme, create_time, update_time, topic_comment_num, topic_like_num) VALUES (16122131,'我看自动驾驶技术','<div class="inner">
<p>这段时间,Google的自动车,Tesla的autopilot,经常出现在新闻头条。人们热烈的讨论自动驾驶技术,对这“科幻般”的技术充满了憧憬,好奇,甚至恐惧。Google说:“自动车很安全。人类是糟糕的驾驶员。” 很多人不假思索就接受了这种观点,以为自己不久以后就会被自动车所代替,所以我今天想谈谈对这些“自动车”的看法。</p>
<p>从我的另一篇<a href="http://www.jianshu.com/p/1f6f624d9815">文章</a>,你应该已经看到,Tesla的autopilot其实根本不算是“自动驾驶”,它完全不能和Google的自动车相比。Tesla把这种不成熟的软件推送到用户的车里,为的只是跟Google抢风头,塑造自己的高大形象。看,我们先出了自动车!可是呢,Tesla那东西顶多算一个“adaptive cruise control”,离真正的自动驾驶还很遥远。可惜的是,Tesla为了自己的名声,拿用户的性命当儿戏,还有些人为它叫好。</p>
<p>然而就算是Google的自动车,离能够投入使用,其实还差得很远。我这里说的“很远”,不是像某些人预测的10年,20年,而是至少100年,1000年…… 甚至永远无法实现。这是为什么呢?Google不是声称,每天都要让它的自动车“学习”上百万mile的行驶记录吗?难道学习了如此的“大数据”,不能让这车子变得跟人一样聪明吗?</p>
<p>如果你这么想,那你可能根本不了解人工智能(AI)。需要“学习上百万mile”,并不能说明自动车很聪明。恰恰相反,这说明它们很笨。只需要问自己一个问题:一个人要学会开车,需要开多少里程?普通人从完全不会,到能安全上路,一般只需要12节课,每节课1小时。就算这一个小时你都在高速公路上开,也就80 mile的样子。12个小时就960 mile。也就是说,普通人只需要小于1000 mile的驾驶,就能成为比较可靠的司机。</p>
<p>对比一下Google的自动车,它们每天“分析”和“学习”一百万mile的“虚拟里程”,而且经常在外面采集数据,累计上百万的mile。然而这些自动车,仍然只能在白天,天气好的时候,在道路环境非常简单的Mountain View行驶。Mountain View就是一个小镇子,总共就没几条路,路上几乎没有行人。我从未在时速超过50mph的公路上,或者交通复杂的大城市,见到过Google的自动车。</p>
<p>另外据最近的<a href="http://www.forbes.com/sites/brookecrothers/2016/01/13/google-self-driving-car-failures-total-272-over-one-year-but-improvement-seen">报道</a>,Google的自动车在过去一年时间里,发生了272起需要“人工干预”的错误情况。如果人不及时抢过控制权,不少情况会出现车祸。在如此简单的条件下,还需要如此多的人工干预。如果环境稍微复杂一些,自动车恐怕就完全不知所措了。</p>
<p>这里还有一个“特殊关照”的问题,由于Google的自动车身上有着明显的标志,行人和其它驾驶员看到它,其实都有点提心吊胆的,不敢轻举妄动,怕它犯傻撞了自己,这也变相的降低了自动车的环境复杂度。一旦Google把车身上的标志去掉,大家看不出来谁是自动车,不对它们进行特殊的关照,我行我素,事故率恐怕就上去了。</p>
<p>所以Google的自动车,离能够投入真正的使用,差距还非常远。在这种情况下就妄言“自动车很安全”,“人类是糟糕的驾驶员”,…… 未免也太早了些吧?自动车跟人类差距到底有多远呢?天壤之别。普通人只需要开1000 mile就能学会开车,而这些自动车学习了几百万,几千万,几亿mile,仍然门都没有摸到。这说明自动车跟人类的运动神经,有着根本的区别。</p>
<p>人在运动的时候看见一个物体,他的头脑里会立即闪现与之相应的“概念”,然后很快浮现出这种东西的运动特点,以及相应的对策。相比之下,自动车看到物体,它并不能准确的判断它是什么东西:它是一个车,一个人,一棵树,一个施工路障,一个大坑,还是前面的车掉下来的床垫呢?所以自动车就像一个智障儿童,学了这么久连什么是什么都不知道,却有人指望它们在十年之内能开车穿越美国。</p>
<p>对的,自动车配备了GPS,激光,雷达,…… 它的“感官”接收到很多的数据,有些是人类无法感觉到的。然而自动车的“头脑”(电脑),是没有认知能力的,所以就算收集到了大量的数据,它仍然不知道那东西是什么,它们之间是什么关系。电脑没有这些“常识”,所以它无法为人做出正确的判断。在危急的关头,它很可能会做出危及乘客安全的决定。“认知”是一个根本性的问题,AI领域至今没有解决它,甚至根本没有动手去研究它。</p>
<p>自动车使用的所谓“机器学习”的技术,跟人类的“学习”,完全是两回事。举个例子,一个小孩从来没见过猫,你只需要给她一只猫,告诉他这是“猫咪”。下一次,当她见到不管什么颜色的猫,不管它摆出什么姿势,都知道这是“猫咪”。现在的电脑,认知能力其实比小孩子,甚至其它动物都差很多。你先让电脑分析上百万张猫的照片,各种颜色,各种姿势,各种角度,拿一只猫摆在它的摄像头面前,让它看整整一年…… 最后它仍然不理解猫是什么,不能准确的判断一个东西是否是猫。如果说电脑有智商,那么它的级别就像一个蠕虫,甚至连蠕虫都不如。电脑没有认识和适应环境的能力,所以就算它再用功,“学习”再多的数据,都是白费劲。</p>
<p>很多人听说“人工智能”(AI),或者“机器学习”(machine learning),“深度学习”(deep learning)这类很酷的名词,就想起科幻小说里的智能机器人,就以为科幻就要成为现实。等你真的进入“机器学习”这领域,才发现一堆堆莫名其妙,稀里糊涂的做法,最后其实不怎么管用。这些大口号,包括所谓“深度学习”,其实跟人的思维方式,几乎完全不搭边。所谓“机器学习”,不过是一些普通的统计方法,拟合一些函数参数。吹得神乎其神,倒让统计专业的人士笑话。</p>
<p>人工智能在80年代出现过一次热潮。当时人们乐观的相信,电脑在不久就会拥有人类的智能。日本还号称要动员全国的力量,制造所谓“第五代计算机”,发展智能的编程语言(比如Prolog)。结果最后呢?人们意识到,超越人类(动物)的智能,比他们想象的困难太多太多。浮夸的许诺没能实现,AI领域进入了冬天。最近因为“大数据”,“自动车”和“Internet of Things”等热门话题的出现,“AI热”又死灰复燃。然而当今的AI,其实并没有比80年代的进步很多。人们对于自己的脑子以及感官的工作原理,仍然所知甚少,却盲目的认为那些从统计学偷来的概念,改名换姓叫“机器学习”,就能造出跟自己的头脑媲美的机器。这些人其实大大的低估了自己身体的神奇程度。</p>
<p>视觉和认知能力,是动物(包括人类)特有的,卓越的能力。它们让动物能够准确的感知身边复杂的世界,对此作出适合自己生存的计划。一辆能够穿越整个国家的自动车,它必须适应各种复杂的环境:天气,路况,交通,意外情况…… 所以它需要动物的认知能力。我并不是说机器永远不可能具有这种能力,然而如果你根本不去欣赏,研究和理解这种能力,倒以为所谓“机器学习”就能办到这些事情,张口闭口拿“人类”说事,你又怎么可能用机器实现它呢?我的预测是,直到人类能够完全的理解动物的脑子和感官如何工作,才有可能制造出能够接近人类能力的自动车。</p>
<p>诚然,有少数人开车不小心,甚至酒后驾车,导致了很多的车祸。然而因此就声称“人类是糟糕的驾驶员”,那就是以偏概全了。大部分的人还是遵纪守法,注意安全的。很多人开车几十年,从没出过车祸。另外,我们必须把“态度”和“能力”区分开来看。酒后驾车的人,不是技术不够好,而是态度有问题。电脑当然没有态度问题,然而它的技术确实难以达到人的水平。就算那些酒后驾车的人,他们的能力其实也远远在电脑之上。我无法想象当今的电脑技术,要如何才能超越驾驶技术好的人,以及职业赛车手。</p>
<p>如果你还没明白,也许下面这个图片可以把你拉回到现实世界:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/68562-39e22022670591ee.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/400" alt=""></p>
<p>一个机器,如何能知道旁边的车上正在发生什么,即将可能发生什么样的危险情况呢?它如何知道,需要赶快避开这辆车呢?它不能。一个没有认知能力的机器,是难以应付复杂多变的现实世界的。</p>
<p>现在人们对于自动车技术的关注,热情,盲目乐观和浮夸,感觉跟文化大革命,“大跃进”年代的思维方式类似。只不过现在“毛泽东”换成了Google或者Tesla,“每亩产量十万”换成了“两年之内自动驾驶穿越美国”…… 我觉得与其瞎折腾自动驾驶技术,不如做点脚踏实地,在短期内能够见效,改善人们生活的东西。</p>
</div>','机器学习','2016-02-12 10:03:02','2016-02-12 10:03:02',0,0);
INSERT INTO topic (user_id, title, content, theme, create_time, update_time, topic_comment_num, topic_like_num) VALUES(16122697,'经验和洞察力','<div class="inner">
<p>很多人很在乎“经验”,比如号称自己在某领域有 30 年的经验,会用这样那样的技术。我觉得经验是有价值的,我也有经验,各个领域的都有点。然而我并不把经验放在很重要的位置,因为我拥有大部分人都缺乏而且忽视的一种东西:洞察力(insight)。</p>
<p>每进入一个新的公司,我进入的几乎都是不同的领域。所以最开头的时候,我有可能对那个领域所知甚少。甚至有人觉得我没有经验,所以可以“教育”我。然而每一次他们都没有想到的是,我很快就掌握了他们的经验,并且经过提炼,抛弃其中的垃圾,很快的超越了他们,完成他们根本无法达到的目标。这就是洞察力的威力。</p>
<p>举个亲身例子,很多人都有用线程的经验,可是有多少人知道线程的本质是什么?有多少人在头脑里有一幅画面,显示出多线程程序的各种动态特征?其实很少有人知道。这就是为什么很多人过度的使用线程并发,结果产生各种同步问题,竞争状态(race condition),死锁等现象。某公司的一片多线程代码,号称是“有非常多并发程序经验”的程序员写的。结果没多久我就发现里面其实含有非常微妙的竞争情况,会在非常小的概率随机发作。发现之后没过几天,已经卖出去用了两年多的产品,由于这个竞争情况,终于引发了严重的后果。有那么多并发编程经验的程序员,两年多都没有察觉这个竞争情况,而很少写多线程程序的我,不但发现了这个竞争,而且很快的想出了修复它的办法,这是为什么呢?靠的就是洞察力。我知道线程的本质,而这是经验不会告诉你的。</p>
<p>什么是洞察力?洞察力就是透过现象看到本质的能力。有洞察力的人很容易得到经验,然而有经验的人却不一定有洞察力。再愚钝的人,总是可以通过大量的时间获取经验,然而就算你花再多的时间和精力,也难以得到洞察力。所以洞察力是比经验宝贵很多的东西。很难说清楚如何才能有洞察力,也很少有人会告诉你如何去得到它。当然,我也不会告诉你。</p>
<p>看别人简历,经常会列出各种各样的技术经验,我看一眼就会的东西,也会在上面占个位置。由于这个原因,我把自己 Linkedin 上面曾经列出的“工作经验”全都删掉了。这些东西列在那里,对于我本身的价值,实在是一种贬低。我是一个身上不贴任何标签的,不能被任何头衔所局限的,真正有价值的人。</p>
<p>经验虽然不是最重要的,然而还是有必要的。很多技术你不能完全不碰它,然而一碰就明白了。但如果没有实际的问题,你又会没有动力去接触那些技术。所以我一直在做的一件事情,就是接触各种技术,然后利用洞察力来获得越来越多的经验。回国之后的初期,我打算着手做自己的产品。同时,我想跟国内的各种公司或者个人做这样的交易。我利用洞察力帮助解决他们最棘手的,已有经验无法解决的难题,从而让我获得经验。当然,我不是作为公司的职工,而只是作为独立的顾问。对公司我会象征性的收取一定的费用,换句话,就是作为“职业杀手”。对于个人,他的问题必须对我也有启发意义。对此感兴趣的公司或者个人,可以跟我联系。</p>
</div>','人生经验','2018-04-14 10:13:02','2018-04-14 10:13:02',0,0);
INSERT INTO topic (user_id, title, content, theme, create_time, update_time, topic_comment_num, topic_like_num) VALUES(16122847,'谈程序的正确性','<div class="inner">
<p>不管在学术圈还是在工业界,总有很多人过度的关心所谓“程序的正确性”,有些甚至到了战战兢兢,舍本逐末的地步。下面举几个例子:</p>
<ul>
<li>
<p>很多人把测试(test)看得过于重要。代码八字还没一撇呢,就吵着要怎么怎么严格的测试,防止“将来”有人把代码改错了。这些人到后来往往被测试捆住了手脚,寸步难行。不但代码bug百出,连测试里面也很多bug。</p>
</li>
<li>
<p>有些人对于“使用什么语言”这个问题过度的在乎,仿佛只有用最新最酷,功能最多的语言,他们才能完成一些很基本的任务。这种人一次又一次的视一些新语言为“灵丹妙药”,然后一次又一次的幻灭,最后他们什么有用的代码也没写出来。</p>
</li>
<li>
<p>有些人过度的重视所谓“类型安全”(type safety),经常抱怨手头的语言缺少一些炫酷的类型系统功能,甚至因此说没法写代码了!他们没有看到,即使缺少一些由编译器静态保障的类型安全,代码其实一点问题都没有,而且也许更加简单。</p>
</li>
<li>
<p>有些人走上极端,认为所有的代码都必须使用所谓“形式化方法”(formal methods),用机器定理证明的方式来确保它100%的没有错误。这种人对于证明玩具大小的代码乐此不疲,结果一辈子也没写出过能解决实际问题的代码。</p>
</li>
</ul>
<p>100%可靠的代码,这是多么完美的理想!可是到最后你发现,天天念叨着要“正确性”,“可靠性”的人,几乎总是眼高手低,说的比做的多。自己没写出什么解决实际问题的代码,倒是很喜欢对别人的“代码质量”评头论足。这些人自己的代码往往复杂不堪,喜欢使用各种看似高深的奇技淫巧,用以保证所谓“正确”。他们的代码被很多所谓“测试工具”和“类型系统”捆住手脚,却仍然bug百出。到后来你逐渐发现,对“正确性”的战战兢兢,其实是这些人不解决手头问题的借口。</p>
<h3 id="衡量程序最重要的标准">衡量程序最重要的标准</h3>
<p>这些人其实不明白一个重要的道理:你得先写出程序,才能开始谈它的正确性。看一个程序好不好,最重要的标准,是看它能否有效地解决问题,而不是它是否正确。如果你的程序没有解决问题,或者解决了错误的问题,或者虽然解决问题但却非常难用,那么这程序再怎么正确,再怎么可靠,都不是好的程序。</p>
<p>正确不等于简单,不等于优雅,不等于高效。一个不简单,不优雅,效率低的程序,就算你费尽周折证明了它的正确,它仍然不会很好的工作。这就像你得先有了房子,才能开始要求房子是安全的。想想吧,如果一个没有房子的流浪汉,路过一座没有人住的房子,他会因为这房子“不是100%安全”,而继续在野外风餐露宿吗?写出代码就像有了房子,而代码的正确性,就像房子的安全性。写出可以解决问题的程序,永远是第一位的。而这个程序的正确性,不管它如何的重要,永远是第二位的。对程序的正确性的强调,永远不应该高于写出程序本身。</p>
<p>每当谈起这个问题,我就喜欢打一个比方:如果“黎曼猜想”被王垠证明出来了,它会改名叫“王垠定理”吗?当然不会。它会被叫做“黎曼定理”!这是因为,无论一个人多么聪明多么厉害,就算他能够证明出黎曼猜想,但这个猜想并不是他最先想出来的。如果黎曼没有提出这个猜想,你根本不会想到它,又何谈证明呢?所以我喜欢说,一流的数学家提出猜想,二流的数学家证明别人的猜想。同样的道理,写出解决问题的代码的人,比起那些去证明(测试)他的代码正确性的人,永远是更重要的。因为如果他没写出这段代码,你连要证明(测试)什么都不知道!</p>
<h3 id="如何提高程序的正确性">如何提高程序的正确性</h3>
<p>话说回来,虽然程序的正确性相对于解决问题,处于相对次要的地位,然而它确实是不可忽视的。但这并不等于天天鼓吹要“测试”,要“形式化证明”,就可以提高程序的正确性。</p>
<p>如果你深入研究过程序的逻辑推导就会知道,测试和形式化证明的能力都是非常有限的。测试只能测试到最常用的情况,而无法覆盖所有的情况。别被所谓“测试覆盖”(test coverage)给欺骗了。一行代码被测试覆盖而没有出错,并不等于在那里不会出错。一行代码是否出错,取决于在它运行之前所经过的所有条件。这些条件的数量是组合爆炸关系,基本上没有测试能够覆盖所有这些前提条件。</p>
<p>形式化方法对于非常简单直接的程序是有效的,然而一旦程序稍微大点,形式化方法就寸步难行。你也许没有想到,你可以用非常少的代码,写出<a href="https://en.wikipedia.org/wiki/Collatz_conjecture">Collatz Conjecture</a>这样至今没人证明出来的数学猜想。实际使用中的代码,比这种数学猜想要复杂不知道多少倍。你要用形式化方法去证明所有的代码,基本上等于你永远也没法完成项目。</p>
<p>那么提高程序正确性最有效的方法是什么呢?在我看来,最有效的方法莫过于对代码反复琢磨推敲,让它变得简单,直观,直到你一眼就可以看得出它不可能有问题。</p>
</div>','编程经验','2018-05-14 10:13:02','2018-05-14 10:13:02',0,0);
INSERT INTO topic (user_id, title, content, theme, create_time, update_time, topic_comment_num, topic_like_num) VALUES(16121663,'编辑器与IDE','<div class="inner">
<h3 id="无谓的编辑器战争">无谓的编辑器战争</h3>
<p>很多人都喜欢争论哪个编辑器是最好的。其中最大的争论莫过于 Emacs 与 vi 之争。vi 的支持者喜欢说:“看 vi 打起字来多快,手指完全不离键盘,连方向键都可以不用。”Emacs 的支持者往往对此不屑一顾,说:“打字再快又有什么用。我在 Emacs 里面按一个键,等于你在 vi 里面按几十个键。”</p>
<p>其实还有另外一帮人,这些人喜欢说:“对于 Emacs 与 vi 之争,我的答案是 {jEdit, Geany, TextMate, Sublime…}”这些人厌倦了 Emacs 的无休止的配置和 bug,也厌倦了 vi 的盲目求快和麻烦的模式切换,所以他们选择了另外的更加简单的解决方案。</p>
<h3 id="临时解决方案---ide">临时解决方案 - IDE</h3>
<p>那么我对此的答案是什么呢?在目前的情况下,我对程序编辑的临时答案是:IDE。</p>
<p>写程序的时候,我通常根据语言来选择最能“理解”那种语言的“IDE”(比如 Visual Studio, Eclipse, IntelliJ IDEA 等),而不是一种通用的“文本编辑器”(比如 Emacs, vi, jEdit, …)。这是因为“文本编辑器”这种东西一般都不真正的理解程序语言。很多 Emacs 和 vi 的用户以为用 etags 和 ctags 这样的工具就能让他们“跳转到定义”,然而这些 tags 工具其实只是对程序的“文本”做一些愚蠢的正则表达式匹配。它们根本没有对程序进行 parse,所以其实只是在进行一些“瞎猜”。简单的函数定义它们也许能猜对位置,但是对于有重名的定义,或者局部变量的时候,它们就力不从心了。</p>
<p>很多人对 IDE 有偏见,因为他们认为这些工具让编程变得“傻瓜化”了,他们觉得写程序就是应该“困难”,所以他们眼看着免费的 IDE 也不试一下。有些人写 Java 都用 Emacs 或者 vi,而不是 Eclipse 或者 IntelliJ。可是这些人错了。他们没有意识到 IDE 里面其实蕴含了比普通文本编辑器高级很多的技术。这些 IDE 会对程序文本进行真正的 parse,之后才开始分析里面的结构。它们的“跳转到定义”一般都是很精确的跳转,而不是像文本编辑器那样瞎猜。</p>
<p>这种针对程序语言的操作可以大大提高人们的思维效率,它让程序员的头脑从琐碎的细节里面解脱出来,所以他们能够更加专注于程序本身的语义和算法,这样他们能写出更加优美和可靠的程序。这就是我用 Eclipse 写 Java 程序的时候相对于 Emacs 的感觉。我感觉到自己的“心灵之眼”能够“看见”程序背后所表现的“模型”,而不只是看到程序的文本和细节。所以,我经常发现自己的头脑里面能够同时看到整个程序,而不只是它的一部分。我的代码比很多人的都要短很多也很有很大部分是这个原因,因为我使用的工具可以让我在相同的时间之内,对代码进行比别人多很多次的结构转换,所以我往往能够把程序变成其他人想象不到的样子。</p>
<p>对于 Lisp 和 Scheme,Emacs 可以算是一个 IDE。Emacs 对于 elisp 当然是最友好的了,它的 Slime 模式用来编辑 Common Lisp 也相当不错。然而对于任何其它语言,Emacs 基本上都是门外汉。我大部分时间在 Emacs 里面是在写一些超级短小的 Scheme 代码,我有自己的一个简单的<a href="http://www.yinwang.org/blog-cn/2013/04/11/scheme-setup">配置方案</a>。虽然谈不上是 IDE,Emacs 编辑 Scheme 确实比其它编辑器方便。R. Kent Dybvig 写 Chez Scheme 居然用的是 vi,但是我并不觉得他的编程效率比我高。我的代码很多时候比他的还要干净利落,一部分原因就是因为我使用的 ParEdit mode 能让我非常高效的转换代码的“形状”。</p>
<p>当要写 Java 的时候,我一般都用 Eclipse。最近写 C++ 比较多,C++ 的最好的 IDE 当然是 Visual Studio。可惜的是 VS 没有 Linux 的版本,所以就拿 Eclipse 凑合用着,感觉还比较顺手。个别情况 Eclipse “跳转定义”到一些完全不相关的地方,对于 C++ 的 refactor 实现也很差,除了最简单的一些情况(比如局部变量重命名),其它时候几乎完全不可用。当然 Eclipse 遇到的这些困难,其实都来自于 C++ 语言本身的糟糕设计。</p>
<h3 id="终极解决方案---结构化编辑器">终极解决方案 - 结构化编辑器</h3>
<p>想要设计一个 IDE,可以支持所有的程序语言,这貌似一个不大可能的事情,但是其实没有那么难。有一种叫做“结构化编辑器”的东西,我觉得它可能就是未来编程的终极解决方案。</p>
<p>跟普通的 IDE 不同,这种编辑器可以让你直接编辑程序的 AST 结构,而不是停留于文本。每一个界面上的“操作”,对应的是一个对 AST 结构的转换,而不是对文本字符的“编辑”。这种 AST 的变化,随之引起屏幕上显示的变化,就像是变化后的 AST 被“pretty print”出来一样。这些编辑器能够直接把程序语言保存为结构化的数据(比如 S表达式,XML 或者 JSON),到时候直接通过对 S表达式,XML 或者 JSON 的简单的“解码”,而不需要针对不同的程序语言进行不同的 parse。这样的编辑器,可以很容易的扩展到任何语言,并且提供很多人都想象不到的强大功能。这对于编程工具来说将是一个革命性的变化。</p>
<ul>
<li>
<p>已经有人设计了这样一种编辑器的模型,并且设计的相当不错。你可以参考一下这个<a href="http://blogs.msdn.com/b/kirillosenkov/archive/2009/09/08/first-videos-of-the-structured-editor-prototype.aspx">结构化编辑器</a>,它包含一些 Visual Studio 和 Eclipse 都没有的强大功能,却比它们两者都要更加容易实现。你可以在这个网页上下载这个编辑器模型来试用一下。</p>
</li>
<li>
<p>我之前推荐过的 <a href="http://www.yinwang.org/blog-cn/2012/09/18/texmacs">TeXmacs</a> 其实在本质上就是一个“超豪华”的结构化编辑器。你可能不知道,TeXmacs 不但能排版出 TeX 的效果,而且能够运行 Scheme 代码。</p>
</li>
<li>
<p>IntelliJ IDEA 的制造者 JetBrains 做了一个结构化编辑系统,叫做 <a href="http://www.jetbrains.com/mps">MPS</a>。它是开源软件,并且可以免费下载。</p>
</li>
<li>
<p>另外,Microsoft Word 的创造者 Charles Simonyi 开了一家叫做 <a href="http://www.intentsoft.com/intentional-technology/meta">Intentional Software</a> 的公司,也做类似的软件。</p>
</li>
</ul>
</div>','编程经验','2018-05-20 21:13:02','2018-05-20 21:13:02',0,0);
INSERT INTO topic (user_id, title, content, theme, create_time, update_time, topic_comment_num, topic_like_num) VALUES(16122847,'如何掌握所有的程序语言','<div class="inner">
<p>对的,我这里要讲的不是如何掌握一种程序语言,而是所有的……</p>
<p>很多编程初学者至今还在给我写信请教,问我该学习什么程序语言,怎么学习。由于我知道如何掌握“所有”的程序语言,总是感觉这种该学“一种”什么语言的问题比较低级,所以一直没来得及回复他们 :P 可是逐渐的,我发现原来不只是小白们有这个问题,就连美国大公司的很多资深工程师,其实也没搞明白。</p>
<p>今天我有动力了,想来统一回答一下这个搁置已久的“初级问题”。类似的话题貌似曾经写过,然而现在我想把它重新写一遍。因为在跟很多人交流之后,我对自己头脑中的(未转化为语言的)想法,有了更精准的表达。</p>
<p>如果你存在以下的种种困惑,那么这篇文章也许会对你有所帮助:</p>
<ol>
<li>你是编程初学者,不知道该选择什么程序语言来入门。</li>
<li>你是资深的程序员或者团队领导,对新出现的种种语言感到困惑,不知道该“投资”哪种语言。</li>
<li>你的团队为使用哪种程序语言争论不休,发生各种宗教斗争。</li>
<li>你追逐潮流采用了某种时髦的语言,结果两个月之后发现深陷泥潭,痛苦不堪……</li>
</ol>
<p>虽然我已经不再过问这些世事,然而无可置疑的现实是,程序语言仍然是很重要的话题,这个情况短时间内不会改变。程序员的岗位往往会要求熟悉某些语言,甚至某些奇葩的公司要求你“深入理解 OOP 或者 FP 设计模式”。对于在职的程序员,程序语言至今仍然是可以争得面红耳赤的宗教话题。它的宗教性之强,以至于我在批评和调侃某些语言(比如 Go 语言)的时候,有些人会本能地以为我是另外一种语言(比如 Java)的粉丝。</p>
<p>显然我不可能是任何一种语言的粉丝,我甚至不是 Yin 语言的粉丝 ;) 对于任何从没见过的语言,我都是直接拿起来就用,而不需要经过学习的过程。看了这篇文章,也许你会明白我为什么可以达到这个效果。理解了这里面的东西,每个程序员都应该可以做到这一点。嗯,但愿吧。</p>
<h3 id="重视语言特性而不是语言">重视语言特性,而不是语言</h3>
<p>很多人在乎自己或者别人是否“会”某种语言,对“发明”了某种语言的人倍加崇拜,为各种语言的孰优孰劣争得面红耳赤。这些问题对于我来说都是不存在的。虽然我写文章批评过不少语言的缺陷,在实际工作中我却很少跟人争论这些。如果有其它人在我身边争论,我甚至会戴上耳机,都懒得听他们说什么 ;) 为什么呢?我发现归根结底的原因,是因为我重视的是“语言特性”,而不是整个的“语言”。我能用任何语言写出不错的代码,就算再糟糕的语言也差不了多少。</p>
<p>任何一种“语言”,都是各种“语言特性”的组合。打个比方吧,一个程序语言就像一台电脑。它的牌子可能叫“联想”,或者“IBM”,或者“Dell”,或者“苹果”。那么,你可以说苹果一定比 IBM 好吗?你不能。你得看看它里面装的是什么型号的处理器,有多少个核,主频多少,有多少 L1 cache,L2 cache……,有多少内存和硬盘,显示器分辨率有多大,显卡是什么 GPU,网卡速度,等等各种“配置”。有时候你还得看各个组件之间的兼容性。</p>
<p>这些配置对应到程序语言里面,就是所谓“语言特性”。举一些语言特性的例子:</p>
<ul>
<li>变量定义</li>
<li>算术运算</li>
<li>for 循环语句,while 循环语句</li>
<li>函数定义,函数调用</li>
<li>递归</li>
<li>静态类型系统</li>
<li>类型推导</li>
<li>lambda 函数</li>
<li>面向对象</li>
<li>垃圾回收</li>
<li>指针算术</li>
<li>goto 语句</li>
</ul>
<p>这些语言特性,就像你在选择一台电脑的时候,看它里面是什么配置。选电脑的时候,没有人会说 Dell 一定是最好的,他们只会说这个型号里面装的是 Intel 的 i7 处理器,这个比 i5 的好,DDR3 的内存 比 DDR2 的快这么多,SSD 比磁盘快很多,ATI 的显卡是垃圾…… 如此等等。</p>
<p>程序语言也是一样的道理。对于初学者来说,其实没必要纠结到底要先学哪一种语言,再学哪一种。曾经有人给我发信问这种问题,纠结了好几个星期,结果一个语言都还没开始学。有这纠结的时间,其实都可以把他纠结过的语言全部掌握了。</p>
<p>初学者往往不理解,每一种语言里面必然有一套“通用”的特性。比如变量,函数,整数和浮点数运算,等等。这些是每个通用程序语言里面都必须有的,一个都不能少。你只要通过“某种语言”学会了这些特性,掌握这些特性的根本概念,就能随时把这些知识应用到任何其它语言。你为此投入的时间基本不会浪费。所以初学者纠结要“先学哪种语言”,这种时间花的很不值得,还不如随便挑一个语言,跳进去。</p>
<p>如果你不能用一种语言里面的基本特性写出好的代码,那你换成另外一种语言也无济于事。你会写出一样差的代码。我经常看到有些人 Java 代码写得相当乱,相当糟糕,却骂 Java 不好,雄心勃勃要换用 Go 语言。这些人没有明白,是否能写出好的代码在于人,而不在于语言。如果你的心中没有清晰简单的思维模型,你用任何语言表述出来都是一堆乱麻。如果你 Java 代码写得很糟糕,那么你写 Go 语言代码也会一样糟糕,甚至更差。</p>
<p>很多初学者不了解,一个高明的程序员如果开始用一种新的程序语言,他往往不是去看这个语言的大部头手册或者书籍,而是先有一个需要解决的问题。手头有了问题,他可以用两分钟浏览一下这语言的手册,看看这语言大概长什么样。然后,他直接拿起一段例子代码来开始修改捣鼓,想法把这代码改成自己正想解决的问题。在这个简短的过程中,他很快的掌握了这个语言,并用它表达出心里的想法。</p>
<p>在这个过程中,随着需求的出现,他可能会问这样的问题:</p>
<ul>
<li>这个语言的“变量定义”是什么语法,需要“声明类型”吗,还是可以用“类型推导”?</li>
<li>它的“类型”是什么语法?是否支持“泛型”?泛型的 “variance” 如何表达?</li>
<li>这个语言的“函数”是什么语法,“函数调用”是什么语法,可否使用“缺省参数”?</li>
<li>……</li>
</ul>
<p>注意到了吗?上面每一个引号里面的内容,都是一种语言特性(或者叫概念)。这些概念可以存在于任何的语言里面,虽然语法可能不一样,它们的本质都是一样的。比如,有些语言的参数类型写在变量前面,有些写在后面,有些中间隔了一个冒号,有些没有。</p>
<p>这些实际问题都是随着写实际的代码,解决手头的问题,自然而然带出来的,而不是一开头就抱着语言手册看得仔仔细细。因为掌握了语言特性的人都知道,自己需要的特性,在任何语言里面一定有对应的表达方式。如果没有直接的方式表达,那么一定有某种“绕过方式”。如果有直接的表达方式,那么它只是语法稍微有所不同而已。所以,他是带着问题找特性,就像查字典一样,而不是被淹没于大部头的手册里面,昏昏欲睡一个月才开始写代码。</p>
<p>掌握了通用的语言特性,剩下的就只剩某些语言“特有”的特性了。研究语言的人都知道,要设计出新的,好的,无害的特性,是非常困难的。所以一般说来,一种好的语言,它所特有的新特性,终究不会超过一两种。如果有个语言号称自己有超过 5 种新特性,那你就得小心了,因为它们带来的和可能不是优势,而是灾难!</p>
<p>同样的道理,最好的语言研究者,往往不是某种语言的设计者,而是某种关键语言特性的设计者(或者支持者)。举个例子,著名的计算机科学家 Dijkstra 就是“递归”的强烈支持者。现在的语言里面都有递归,然而你可能不知道,早期的程序语言是不支持递归的。直到 Dijkstra 强烈要求 Algol 60 委员会加入对递归的支持,这个局面才改变了。Tony Hoare 也是语言特性设计者。他设计了几个重要的语言特性,却没有设计过任何语言。另外大家不要忘了,有个语言专家叫王垠,他是早期 union type 的支持者和实现者,也是 checked exception 特性的支持者,他在自己的<a href="http://www.yinwang.org/blog-cn/2017/05/23/kotlin">博文</a>里指出了 checked exception 和 union type 之间的关系 :P</p>
<p>很多人盲目的崇拜语言设计者,只要听到有人设计(或者美其民曰“发明”)了一个语言,就热血沸腾,佩服的五体投地。他们却没有理解,其实所有的程序语言,不过是像 Dell,联想一样的“组装机”。语言特性的设计者,才是像 Intel,AMD,ARM,Qualcomm 那样核心技术的创造者。</p>
<h3 id="合理的入门语言">合理的入门语言</h3>
<p>所以初学者要想事半功倍,就应该从一种“合理”的,没有明显严重问题的语言出发,掌握最关键的语言特性,然后由此把这些概念应用到其它语言。哪些是合理的入门语言呢?我个人觉得这些语言都可以用来入门:</p>
<ul>
<li>Scheme</li>
<li>C</li>
<li>Java</li>
<li>Python</li>
<li>JavaScript</li>
</ul>
<p>那么相比之下,我不推荐用哪些语言入门呢?</p>
<ul>
<li>Shell</li>
<li>PowerShell</li>
<li>AWK</li>
<li>Perl</li>
<li>PHP</li>
<li>Basic</li>
<li>Go</li>
<li>Rust</li>
</ul>
<p>总的说来,你不应该使用所谓“<a href="http://www.yinwang.org/blog-cn/2013/03/29/scripting-language">脚本语言</a>”作为入门语言,特别是那些源于早期 Unix 系统的脚本语言工具。PowerShell 虽然比 Unix 的 Shell 有所进步,然而它仍然没有摆脱脚本语言的根本问题——他们的设计者不知道他们自己在干什么 :P</p>
<p>采用脚本语言学编程,一个很严重的问题就是使得学习者抓不住关键。脚本语言往往把一些系统工具性质的东西(比如正则表达式,Web 概念)加入到语法里面,导致初学者为它们浪费太多时间,却没有理解编程最关键的概念:变量,函数,递归,类型……</p>
<p>不推荐 Go 语言的原因类似,虽然 Go 语言不算脚本语言,然而他的设计者显然不明白自己在干什么。所以使用 Go 语言来学编程,你不能专注于最关键,最好的语言特性。关于 Go 语言的各种毛病,你可以参考这篇<a href="http://www.yinwang.org/blog-cn/2014/04/18/golang">文章</a>。</p>
<p>同样的,我不觉得 Rust 适合作为入门语言。Rust 花了太大精力来夸耀它的“新特性”,而这些新特性不但不是最关键的部分,而且很多是有问题的。初学者过早的关注这些特性,不仅学不会最关键的编程思想,而且可能误入歧途。关于 Rust 的一些问题,你可以参考这篇<a href="http://www.yinwang.org/blog-cn/2016/09/18/rust">文章</a>。</p>
<h3 id="掌握关键语言特性忽略次要特性">掌握关键语言特性,忽略次要特性</h3>
<p>为了达到我之前提到的融会贯通,一通百通的效果,初学者应该专注于语言里面最关键的特性,而不是被次要的特性分心。</p>
<p>举个夸张点的例子。我发现很多编程培训班和野鸡大学的编程入门课,往往一来就教学生如何使用 printf 打印“Hello World!”,进而要他们记忆 printf 的各种“格式字符”的意义,要他们实现各种复杂格式的打印输出,甚至要求打印到文本文件里,然后再读出来……</p>
<p>可是殊不知,这种输出输入操作其实根本不算是语言的一部分,而且对于掌握编程的核心概念来说,都是次要的。有些人的 Java 课程进行了好几个星期,居然还在布置各种 printf 的作业。学生写出几百行的 printf,却不理解变量和函数是什么,甚至连算术语句和循环语句都不知道怎么用!这就是为什么很多初学者感觉编程很难,我连 <code class="highlighter-rouge">%d</code>,<code class="highlighter-rouge">%f</code>,<code class="highlighter-rouge">%.2f</code> 的含义都记不住,还怎么学编程!</p>
<p>然而这些野鸡大学的“教授”头衔是如此的洗脑,以至于被他们教过的学生(比如我女朋友)到我这里请教,居然骂我净教一些没用的东西,学了连 printf 的作业都没法完成 :P 你别跟我讲 for 循环,函数什么的了…… 可不可以等几个月,等我背熟了 printf 的用法再学那些啊?</p>
<p>所以你就发现一旦被差劲的老师教过,这个程序员基本就毁了。就算遇到好的老师,他们也很难纠正过来。</p>
<p>当然这是一个夸张的例子,因为 printf 根本不算是语言特性,但这个例子从同样的角度说明了次要肤浅的语言特性带来的问题。</p>
<p>这里举一些次要语言特性的例子:</p>
<ul>
<li>C 语言的语句块,如果里面只有一条语句,可以不打花括号。</li>
<li>Go 语言的函数参数类型如果一样可以合并在一起写,比如 <code class="highlighter-rouge">func foo(s string, x, y, z int, c bool) { ... }</code></li>
<li>Perl 把正则表达式作为语言的一种特殊语法</li>
<li>JavaScript 语句可以在某些时候省略句尾的分号</li>
<li>Haskell 和 ML 等语言的 <a href="http://www.yinwang.org/blog-cn/2013/04/02/currying">currying</a></li>
</ul>
<h3 id="自己动手实现语言特性">自己动手实现语言特性</h3>
<p>在基本学会了各种语言特性,能用它们来写代码之后,下一步的进阶就是去实现它们。只有实现了各种语言特性,你才能完全地拥有它们,成为它们的主人。否则你就只是它们的使用者,你会被语言的设计者牵着鼻子走。</p>
<p>有个大师说得好,完全理解一种语言最好的方法就是自己动手实现它,也就是自己写一个解释器来实现它的语义。但我觉得这句话应该稍微修改一下:完全理解一种“语言特性”最好的方法就是自己亲自实现它。</p>
<p>注意我在这里把“语言”改为了“语言特性”。你并不需要实现整个语言来达到这个目的,因为我们最终使用的是语言特性。只要你自己实现了一种语言特性,你就能理解这个特性在任何语言里的实现方式和用法。</p>
<p>举个例子,学习 SICP 的时候,大家都会亲自用 Scheme 实现一个面向对象系统。用 Scheme 实现的面向对象系统,跟 Java,C++,Python 之类的语言语法相去甚远,然而它却能帮助你理解任何这些 OOP 语言里面的“面向对象”这一概念,它甚至能帮助你理解各种面向对象实现的差异。</p>
<p>这种效果是你直接学习 OOP 语言得不到的,因为在学习 Java,C++,Python 之类语言的时候,你只是一个用户,而用 Scheme 自己动手实现了 OO 系统之后,你成为了一个创造者。</p>
<p>类似的特性还包括类型推导,类型检查,惰性求值,如此等等。我实现过几乎所有的语言特性,所以任何语言在我的面前,都是可以被任意拆卸组装的玩具,而不再是凌驾于我之上的神圣。</p>
<h3 id="总结">总结</h3>
<p>写了这么多,重要的话重复三遍:语言特性,语言特性,语言特性,语言特性!不管是初学者还是资深程序员,应该专注于语言特性,而不是纠结于整个的“语言品牌”。只有这样才能达到融会贯通,拿起任何语言几乎立即就会用,并且写出高质量的代码。</p>
<p>(如果你觉得这篇文章有所帮助,可以<a href="http://www.yinwang.org/blog-cn/2016/04/13/pay-blog">付款</a>购买,价格随意。)</p>
</div>','编程经验','2018-05-19 21:13:02','2018-05-19 21:13:02',0,0);
INSERT INTO topic (user_id, title, content, theme, create_time, update_time, topic_comment_num, topic_like_num) VALUES(16121663,'谈语法','<div class="inner">
<p><img src="http://www.yinwang.org/images/lisp_cycles.png" width="95%"></p>
<p>使用和研究过这么多程序语言之后,我觉得几乎不包含多余功能的语言,只有一个:Scheme。所以我觉得它是学习程序设计最好的入手点和进阶工具。当然 Scheme 也有少数的问题,而且缺少一些我想要的功能,但这些都瑕不掩瑜。在用了很多其它的语言之后,我觉得 Scheme 真的是非常优美的语言。</p>
<p>要想指出 Scheme 所有的优点,并且跟其它语言比较,恐怕要写一本书才讲的清楚。所以在这篇文章里,我只提其中一个最简单,却又几乎被所有人忽视的方面:语法。</p>
<p>其它的 Lisp “方言”也有跟 Scheme 类似的语法(都是基于“S表达式”),所以在这篇(仅限这篇)文章里我所指出的“Scheme 的优点”,其实也可以作用于其它的 Lisp 方言。从现在开始,“Scheme”和“Lisp”这两个词基本上含义相同。</p>
<p>我觉得 Scheme (Lisp) 的基于“S表达式”(S-expression)的语法,是世界上最完美的设计。其实我希望它能更简单一点,但是在现存的语言中,我没有找到第二种能与它比美。也许在读过这篇文章之后,你会发现这种语法设计的合理性,已经接近理论允许的最大值。</p>
<p>为什么我喜欢这样一个“全是括号,前缀表达式”的语言呢?这是出于对语言结构本质的考虑。其实,我觉得语法是完全不应该存在的东西。即使存在,也应该非常的简单。因为语法其实只是对语言的本质结构,“抽象语法树”(abstract syntax tree,AST),的一种编码。一个良好的编码,应该极度简单,不引起歧义,而且应该容易解码。在程序语言里,这个“解码”的过程叫做“语法分析”(parse)。</p>
<p>为什么我们却又需要语法呢?因为受到现有工具(操作系统,文本编辑器)的限制,到目前为止,几乎所有语言的程序都是用字符串的形式存放在文件里的。为了让字符串能够表示“树”这种结构,人们才给程序语言设计了“语法”这种东西。但是人们喜欢耍小聪明,在有了基本的语法之后,他们开始在这上面大做文章,使得简单的问题变得复杂。</p>
<p>Lisp (Scheme 的前身)是世界上第二老的程序语言。最老的是 Fortran。Fortran 的程序,最早的时候都是用打孔机打在卡片上的,所以它其实是几乎没有语法可言的。</p>
<p><img src="http://www.yinwang.org/images/punch-card.gif" alt="Fortran"></p>
<p>显然,这样写程序很痛苦。但是它却比现代的很多语言有一个优点:它没有歧义,没有复杂的 parse 过程。</p>
<p>在 Lisp 诞生的时候,它的设计者们一下子没能想出一种好的语法,所以他们决定干脆先用括号把这语法树的结构全都括起来,一个不漏。等想到更好的语法再换。</p>
<p>自己想一下,如果要表达一颗“树”,最简单的编码方式是什么?就是用括号把每个节点的“数据”和“子节点”都括起来放在一起。Lisp 的设计者们就是这样想的。他们把这种完全用括号括起来的表达式,叫做“S表达式”(S 代表 “symbolic”)。这貌似很“粗糙”的设计,甚至根本谈不上“设计”。奇怪的是,在用过一段时间之后,他们发现自己已经爱上了这个东西,再也不想设计更加复杂的语法。于是S表达式就沿用至今。</p>
<p>在使用过 Scheme,Haskell,ML,和常见的 Java,C,C++,Python,Perl,…… 之后,我也惊讶的发现, Scheme 的语法,不但是最简单,而且是最好看的一个。这不是我情人眼里出西施,而是有一定理论依据的。</p>
<p>首先,把所有的结构都用括号括起来,轻松地避免了别的语言里面可能发生的“歧义”。程序员不再需要记忆任何“运算符优先级”。</p>
<p>其次,把“操作符”全都放在表达式的最前面,使得基本算术操作和函数调用,在语法上发生完美的统一,而且使得程序员可以使用几乎任何符号作为函数名。</p>
<p>在其他的语言里,函数调用看起来像这个样子:<code class="highlighter-rouge">f(1)</code>,而算术操作看起来是这样:<code class="highlighter-rouge">1+2</code>。在 Lisp 里面,函数调用看起来是这样<code class="highlighter-rouge">(f 1)</code>,而算术操作看起来也是这样<code class="highlighter-rouge">(+ 1 2)</code>。你发现有什么共同点吗?那就是 <code class="highlighter-rouge">f</code> 和 <code class="highlighter-rouge">+</code> 在位置上的对应。实际上,加法在本质也是一个函数。这样做的好处,不但是突出了加法的这一本质,而且它让人可以用跟定义函数一模一样的方式,来定义“运算符”!这比起 C++ 的“运算符重载”强大很多,却又极其简单。</p>
<p>关于“前缀表达式”与“中缀表达式”,我有一个很独到的见解:我觉得“中缀表达式”其实是一种过时的,来源于传统数学的历史遗留产物。几百年以来,人们都在用 <code class="highlighter-rouge">x+y</code> 这样的符号来表示加法。之所以这样写,而不是 <code class="highlighter-rouge">(+ x y)</code>,是因为在没有计算机以前,数学公式都得写在纸上,写 <code class="highlighter-rouge">x+y</code> 显然比 <code class="highlighter-rouge">(+ x y)</code> 方便简洁。但是,中缀表达式却是容易出现歧义的。如果你有多个操作符,比如 <code class="highlighter-rouge">1+2*3</code>。那么它表示的是 <code class="highlighter-rouge">(+ 1 (* 2 3))</code> 呢,还是 <code class="highlighter-rouge">(* (+ 1 2) 3)</code>?所以才出现了“运算符优先级”这种东西。看见没有,S表达式已经在这里显示出它没有歧义的优点。你不需要知道 <code class="highlighter-rouge">+</code> 和 <code class="highlighter-rouge">*</code> 的优先级,就能明白 <code class="highlighter-rouge">(+ 1 (* 2 3))</code> 和 <code class="highlighter-rouge">(* (+ 1 2) 3)</code> 的区别。第一个先乘后加,而第二个先加后乘。</p>
<p>对于四则运算,这些优先级还算简单。可是一旦有了更多的操作,就容易出现混淆。这就是为什么数学(以及逻辑学)的书籍难以看懂。 实际上,那些看似复杂的公式,符号,不过是在表示一些程序里的“数据结构”,“对象”以及“函数”。大部分读数学书的时间,其实是浪费在琢磨这些公式:它们到底要表达的什么样一个“数据结构”或者“操作”!这个“琢磨”的过程,其实就是程序语言里所谓的“语法分析”(parse)。</p>
<p>这种问题在微积分里面就更加明显。微积分难学,很大部分原因,就是因为微积分的那些传统的运算符,其实不是很好的设计。如果你想了解更好的设计,可以参考一下 Mathematica 的公式设计。试试在 Mathematica 里面输入“单行”的微积分运算(而不使用它传统的“2D语法”)。</p>
<p>其实 Lisp 已经可以轻松地表示这种公式,比如对 <code class="highlighter-rouge">x^2</code> 进行微分,可以表示成</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> (D ‘(^ x 2) ‘x)
</code></pre></div></div>
<p>看到了吗?微分不过是一个用于处理符号的函数 <code class="highlighter-rouge">D</code>,输入一个表达式和另一个符号,输出一个新的表达式。</p>
<p>同样的公式,传统的数学符号是这个样子:</p>
<p><img src="http://www.yinwang.org/images/deriv-math.png"></p>
<p>这是什么玩意啊?<code class="highlighter-rouge">d</code> 除以 <code class="highlighter-rouge">dx</code>,然后乘以 <code class="highlighter-rouge">x</code> 的平方?</p>
<p>在 Lisp 里,你其实可以比较轻松地实现符号微分的计算。SICP里貌似有一节就是教你写个符号微分程序。做微积分这种无聊的事情,就是应该交给电脑去做。总之,这从一方面显示了,Lisp 的语法其实超越了传统的数学。</p>
<p>其实我一直都在想,如果把数学看成是一种程序语言,它也许就是世界上语法最糟糕的语言。数学里的“变量”,几乎总是没有明确定义的作用域(scope)。也就是说他们只有“全局变量”。上一段话的 x,跟下一段话的 x,经常指的不是同一个东西。所以训练有素的数学家,总是避免使用同一个符号来表示两种不同的东西。很快他们就发现所有的拉丁字母都用光了,于是乎开始用希腊字母。大写的,小写的,粗体的,斜体的,花体的,…… 而其实,他们只不过是想实现 C++ 里的 “namespace”。</p>
<p>可惜的是,很多程序语言的设计者没能摆脱数学的思想束缚,对数学和逻辑有盲目崇拜的倾向。所以他们继续在新的语言里使用中缀表达法。Haskell,ML,Coq,Agda,这些“超高级”的语言设计,其实都中了这个圈套。在 Coq 和 Agda 里面,你不但可以使用中缀表达式,还可以定义所谓的 “mixfix” 表达式。这样其实是把简单的问题复杂化。想让自己看起来像“数学”,很神秘的样子,其实是学会了数学的糟粕,自讨苦吃。</p>
<p>另外,由于 Lisp 的表达能力和灵活性比其他语言要大很多,所以类似 C 或者 Pascal 那样的语法其实不能满足 Lisp 的需要。在 Lisp 里,你可以写 (+ 10 (if test 1 2)) 这样的代码,然而如果你使用 C 那样的无括号语法,就会发现没法很有效的嵌入里面的那个条件语句而不出现歧义。这就是为什么 C 必须使用 test? 1 : 2 这样的语法来表示 Lisp 的 if 能表示的东西。然而即使如此,你仍然会经常被迫加上一对括号,结果让程序非常难看,最后的效果其实还不如用 Lisp 的语法。在 C 这样的语言里,由于结构上有很多限制,所以才觉得那样的语法还可以。可是一旦加入 Lisp 的那些表达能力强的结构,就发现越来越难看。JavaScript(node.js)就是对此最好的一个证据。</p>
<p>最后,从美学的角度上讲,S表达式是很美观的设计。所有的符号都用括号括起来,这形成一种“流线型”的轮廓。而且由于可以自由的换行排版,你可以轻松地对齐相关的部分。在 Haskell 里,你经常会发现一些很蹩脚,很难看的地方。这是因为中缀表达式的“操作符”,经常不能对在一起。比如,如果你有像这样一个 case 表达式:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>case x
Short _ -> 1
VeryLooooooooooooooooooooooooog _ -> 2
</code></pre></div></div>
<p>为了美观,很多 Haskell 程序员喜欢把那两个箭头对齐。结果就成了这样:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>case x
Short _ -> 1
VeryLooooooooooooooooooooooooog _ -> 2
</code></pre></div></div>
<p>作为一个菜鸟级摄影师,你不觉得第一行中间太“空”了一点吗?</p>
<p>再来看看S表达式如何表达这东西:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(case x
(-> (Short _) 1)
(-> (VeryLooooooooooooooooooooooooog _) 2))
</code></pre></div></div>
<p>发现“操作符总在最前”的好处了吗?不但容易看清楚,而且容易对齐,而且没有多余的间隙。</p>
<p>其实我们还可以更进一步。因为箭头的两边全都用括号括起来了,所以其实我们并不需要那两个箭头就能区分“左”和“右”。所以我们可以把它简化为:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(case x
((Short _) 1)
((VeryLooooooooooooooooooooooooog _) 2))
</code></pre></div></div>
<p>最后我们发现,这个表达式“进化”成了 Lisp 的 case 表达式。</p>
<p>Lisp 的很多其它的设计,比如“垃圾回收”,后来被很多现代语言(比如 Java)所借鉴。可是人们遗漏了一个很重要的东西:Lisp 的语法,其实才是世界上最好的语法。</p>
</div>','编程经验','2018-05-30 23:13:02','2018-05-30 23:13:02',0,0);
INSERT INTO topic (user_id, title, content, theme, create_time, update_time, topic_comment_num, topic_like_num) VALUES(16122620,'谈 Linux,Windows 和 Mac','<div class="inner">
<p>这段时间受到很多人的来信。他们看了我很早以前写的推崇 Linux 的文章,想知道如何“抛弃 Windows,学习 Linux”。天知道他们在哪里找到那么老的文章,真是好事不出门…… 我觉得我有责任消除我以前的文章对人的误导,洗清我这个“Linux 狂热分子”的恶名。我觉得我已经写过一些澄清的文章了,可是怎么还是有人来信问 Linux 的问题。也许因为感觉到“舆论压力”,我把文章都删了。</p>
<p>简言之,我想对那些觉得 Linux 永远也学不会的“菜鸟”们说:</p>
<ol>
<li>
<p>Linux 和 Unix 里面包含了一些非常糟糕的设计。不要被 Unix 的教条主义者吓倒。学不会有些东西很多时候不是你的错,而是 Linux 的错,是“Unix 思想” 的错。不要浪费时间去学习太多工具的用法,钻研稀奇古怪的命令行。那些貌似难的,复杂的东西,特别要小心分析。</p>
</li>
<li>
<p>Windows 避免了 Unix,Linux 和 Mac OS X 的很多问题。微软是值得尊敬的公司,是真正在乎程序开发工具的公司。我收回曾经对微软的鄙视态度。请菜鸟们吸收 Windows 设计里面好的东西。另外 Visual Studio 是非常好的工具,会带来编程效率的大幅度提升。请不要歧视 IDE。要正视 Emacs,VIM 等文本编辑器的局限性。当然,这些正面评价不等于说你应该为微软工作。就像我喜欢 iPhone,但是却不一定想给 Apple 工作一样。</p>
</li>
<li>
<p>学习操作系统最好的办法是学会(真正的)程序设计思想,而不是去“学习”各种古怪的工具。所有操作系统,数据库,Internet,以至于 WEB 的设计思想(和缺陷),几乎都能用程序语言的思想简单的解释。</p>
</li>
</ol>
<p>先说说我现在对 Linux 和相关工具(比如 TeX)的看法吧。我每天上班都用 Linux,可是回家才不想用它呢。上班的时候,我基本上只是尽我所能的改善它,让它不要给我惹麻烦。Unix 有许许多多的设计错误,却被当成了教条,传给了一代又一代的程序员,恶性循环。Unix 的 shell,命令,配置方式,图形界面,都是相当糟糕的。每一个新版本的 Ubuntu 都会在图形界面的设计上出现新的错误,让你感觉历史怎么会倒退。其实这只是表面现象。Linux 所用的图形界面(X Window)在本质上几乎是没救的。我不想在这里细说 Unix 的缺点,在它出现的早期,已经有人写了一本书,名叫 Unix Hater’s Handbook,里面专门有一章叫做 The X-Windows Disaster。它分析后指出,X Window 貌似高明的 client-server 设计,其实并不像说的那么好。</p>
<p>这本书汇集了 Unix 出现的年代,很多人对它的咒骂。有趣的是,这本书有一个“反序言”,是 Unix 的创造者之一 Dennis Ritchie 写的。我曾经以为这些骂 Unix 的人都是一些菜鸟。他们肯定是智商太低,或者被 Windows 洗脑了,不能理解 Unix 的高明设计才在那里骂街。现在理解了程序语言的设计原理之后,才发现他们说的那些话里面居然大部分是实话!其实他们里面有些人在当年就是世界顶尖的编程高手,自己写过操作系统和编译器,功底不亚于 Unix 的创造者。在当年他们就已经使用过设计更加合理的系统,比如 Multics,Lisp Machine 等。</p>
<p>可惜的是,在现在的操作系统书籍里面,Multics 往往只是被用来衬托 Unix 的“简单”和伟大。Unix 的书籍喜欢在第一章讲述这样的历史:“Multics 由于设计过于复杂,试图包罗万象,而且价格昂贵,最后失败了。” 可是 Multics 失败了吗?Multics,Oberon,IBM System/38, Lisp Machine,…… 在几十年前就拥有了 Linux 现在都还没有的好东西。Unix 里面的东西,什么虚拟内存,文件系统,…… 基本上都是从 Multics 学来的。Multics 的机器,一直到 2000 年都还在运行。Unix 不但“窜改”了历史教科书,而且似乎永远不吸取教训,到现在还没有实现那些早期系统早就有的好东西。Unix 的设计几乎完全没有一致性和原则。各种工具程序功能重复,冗余,没法有效地交换数据。可是最后 Unix 靠着自己的“廉价”,“宗教”和“哲学”,战胜了别的系统在设计上的先进,统治了程序员的世界。</p>
<p>如果你想知道这些“失败的”操作系统里面有哪些我们现在都还没有的先进技术,可以参考这篇文章:Oberon - The Overlooked Jewel。它介绍的是 Niklaus Wirth(也就是 Pascal 语言的设计者)的 Oberon 操作系统。</p>
<p>胜者为王,可是 Unix 其实是一个暴君,它不允许你批评它的错误。它利用其它程序员的舆论压力,让每一个系统设计上的错误,都被说成是用户自己的失误。你不敢说一个工具设计有毛病,因为如果别人听到了,就会以为你自己不够聪明,说你“人笨怪刀钝”。这就像是“皇帝的新装”里的人们,明明知道皇帝没穿衣服,还要说“这衣服这漂亮”!总而言之,“对用户友好”这个概念,在 Unix 的世界里是被歧视,被曲解的。Unix 的狂热分子很多都带有一种变态的“精英主义”。他们以用难用的工具为豪,鄙视那些使用“对用户友好”的工具的人。</p>
<p>我曾经强烈的推崇 FVWM,TeX 等工具,可是现在擦亮眼睛看来,它们给用户的界面,其实也是非常糟糕的设计,跟 Unix 一脉相承。他们把程序设计的许多没必要的细节和自己的设计失误,无情的暴露给用户。让用户感觉有那么多东西要记,仿佛永远也没法掌握它。实话说吧,当年我把 TeXbook 看了两遍,做完了所有的习题(包括最难的“double bend”习题)。几个月之后,几乎全部忘记干净。为什么呢?因为 TeX 的语言是非常糟糕的设计,它没有遵循程序语言设计的基本原则。</p>
<p>这里有一个鲜为人知的小故事。TeX 之所以有一个“扩展语言”,是 Scheme 的发明者 Guy Steele 的建议。那年夏天,Steele 在 Stanford 实习。他听说 Knuth 在设计一个排版系统,就强烈建议他使用一种扩展语言。后来 Knuth 采纳了他的建议。不幸的是 Steele 几个月后就离开了,没能帮助 Knuth 完成语言的设计。Knuth 老爹显然有我所说的那种“精英主义”,他咋总是设计一些难用的东西,写一些难懂的书?</p>
<p>一个好的工具,应该只有少数几条需要记忆的规则,就像象棋一样。而这些源于 Unix 的工具却像是“魔鬼棋”或者“三国杀”,有太多的,无聊的,人造的规则。有些人鄙视图形界面,鄙视 IDE,鄙视含有垃圾回收的语言(比如 Java),鄙视一切“容易”的东西。他们却不知道,把自己沉浸在别人设计的繁复的规则中,是始终无法成为大师的。就像一个人,他有能力学会各种“魔鬼棋”的规则,却始终无法达到象棋大师的高度。所以,容易的东西不一定是坏的,而困难的东西也不一定是好的。学习计算机(或者任何其它工具),应该“只选对的,不选难的”。记忆一堆的命令,乌七八糟的工具用法,最后脑子里什么也不会留下。学习“原理性”的东西,才是永远不会过时的。</p>
<p>Windows 技术设计上的很多细节,也许在早期是同样糟糕的。但是它却向着更加结构化,更加简单的方向发展。Windows 的技术从 OLE,COM,发展到 .NET,再加上 Visual Studio 这样高效的编程工具,这些带来了程序员和用户效率的大幅度提高,避免了 Unix 和 C 语言的很多不必存在的问题。Windows 程序从很早的时候就能比较方便的交换数据。比如,OLE 让你可以把 Excel 表格嵌入到 Word 文档里面。不得不指出,这些是非常好的想法,是超越“Unix 哲学”的。相反,由于受到“Unix 哲学”的误导,Unix 的程序间交换数据一直以来都是用字符串,而且格式得不到统一,以至于很多程序连拷贝粘贴都没法正确进行。Windows 的“配置”,全都记录在一个中央数据库(注册表)里面,这样程序的配置得到大大的简化。虽然在 Win95 的年代,注册表貌似老是惹麻烦,但现在基本上没有什么问题了。相反,Unix 的配置,全都记录在各种稀奇古怪的配置文件里面,分布在系统的各个地方。你搞不清楚哪个配置文件记录了你想要的信息。每个配置文件连语法都不一样!这就是为什么用 Unix 的公司总是需要一个“系统管理员”,因为软件工程师们才懒得记这些麻烦的东西。</p>
<p>再来比较一下 Windows 和 Mac 吧。我认识一个 Adobe 的高级设计师。他告诉我说,当年他们把 Photoshop 移植到 Intel 构架的 Mac,花了两年时间。只不过换了个处理器,移植个应用程序就花了两年时间,为什么呢?因为 Xcode 比起 Visual Studio 真是差太多了。而 Mac OS X 的一些设计原因,让他们的移植很痛苦。不过他很自豪的说,当年很多人等了两年也没有买 Intel 构架的 Mac,就是因为他们在等待 Photoshop。最后他直言不讳的说,微软其实才是真正在乎程序员工具的公司。相比之下,Apple 虽然对用户显得友好,但是对程序员的界面却差很多。Apple 尚且如此,Linux 对程序员就更差了。可是有啥办法呢,有些人就是受虐狂。自己痛过之后,还想让别人也痛苦。就像当年的我。</p>
<p>我当然不是人云亦云。微软在程序语言上的造诣和投入,我看得很清楚。我只是通过别人的经历,来验证我已经早已存在的看法。所以一再宣扬别的系统都是向自己学习的 Apple 受到这样的评价,我也一点不惊讶。Mac OS X 毕竟是从 Unix 改造而来的,还没有到脱胎换骨的地步。我有一个 Macbook Air,一个 iPhone 5,和一个退役的,装着 Windows 7 的 T60。我不得不承认,虽然我很喜欢 Macbook 和 iPhone 的硬件,但我发现 Windows 在软件上的很多设计其实更加合理。</p>
<p>我为什么当年会鄙视微软?这很简单。我就是跟着一群人瞎起哄而已!他们说 Linux 能拯救我们,给我们自由。他们说微软是邪恶的公司…… 到现在我身边还有人无缘无故的鄙视微软,却不知道理由。可是 Unix 是谁制造的呢?是 AT&T。微软和 AT&T 哪个更邪恶呢?我不知道。但是你应该了解一下 Unix 的历史。AT&T 当年发现 Unix 有利可图,找多少人打了多少年官司?说微软搞垄断,其实 AT&T 早就搞过垄断了,还被拆散成了好几个公司。想想世界上还有哪一家公司,独立自主的设计出这从底至上全套家什:程序语言,编译器,IDE,操作系统,数据库,办公软件,游戏机,手机…… 我不得不承认,微软是值得尊敬的公司。</p>
<p>公司还不都一样,都是以利益为本的。我们程序员就不要被他们利用,作为利益斗争的炮灰啦。见到什么好就用什么,就学什么。自己学到的东西,又不属于那些垄断企业。我们都有自由的头脑。</p>
<p>当然我不是在这里打击 Linux 和 Mac 而鼓吹 Windows。这些系统的纷争基本上已经不关我什么事。我只是想告诉新人们,去除头脑里的宗教,偏激,仇恨和鄙视。每次仇恨一个东西,你就失去了向它学习的机会。</p>
<p>后记:“对用户友好”是一个值得研究,却又研究得非常不够的东西。很多 UI 的设计者,把东西设计的很漂亮,但是却不方便,不顺手。如果你想了解我认为怎样的设计才是“对用户友好的”,可以参考这篇博客《<a href="http://www.yinwang.org/blog-cn/2012/05/18/user-friendliness">什么是“对用户友好</a>”》</p>
</div>','Windows/Linux/Mac','2018-05-31 09:13:02','2018-05-31 09:13:02',0,0);