forked from phodal/repractise
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
2101 lines (2008 loc) · 185 KB
/
index.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
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
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="generator" content="pandoc">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>RePractise - </title>
<style type="text/css">code{white-space: pre;}</style>
<style type="text/css">
div.sourceCode { overflow-x: auto; }
table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode {
margin: 0; padding: 0; vertical-align: baseline; border: none; }
table.sourceCode { width: 100%; line-height: 100%; }
td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; }
td.sourceCode { padding-left: 5px; }
code > span.kw { color: #007020; font-weight: bold; } /* Keyword */
code > span.dt { color: #902000; } /* DataType */
code > span.dv { color: #40a070; } /* DecVal */
code > span.bn { color: #40a070; } /* BaseN */
code > span.fl { color: #40a070; } /* Float */
code > span.ch { color: #4070a0; } /* Char */
code > span.st { color: #4070a0; } /* String */
code > span.co { color: #60a0b0; font-style: italic; } /* Comment */
code > span.ot { color: #007020; } /* Other */
code > span.al { color: #ff0000; font-weight: bold; } /* Alert */
code > span.fu { color: #06287e; } /* Function */
code > span.er { color: #ff0000; font-weight: bold; } /* Error */
code > span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
code > span.cn { color: #880000; } /* Constant */
code > span.sc { color: #4070a0; } /* SpecialChar */
code > span.vs { color: #4070a0; } /* VerbatimString */
code > span.ss { color: #bb6688; } /* SpecialString */
code > span.im { } /* Import */
code > span.va { color: #19177c; } /* Variable */
code > span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code > span.op { color: #666666; } /* Operator */
code > span.bu { } /* BuiltIn */
code > span.ex { } /* Extension */
code > span.pp { color: #bc7a00; } /* Preprocessor */
code > span.at { color: #7d9029; } /* Attribute */
code > span.do { color: #ba2121; font-style: italic; } /* Documentation */
code > span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code > span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
</style>
<link rel="stylesheet" href="style.css">
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
<meta name="viewport" content="width=device-width">
</head>
<body>
<p>
<h1>RePractise</h1>
<h3>By Phodal Huang(<a href="http://www.phodal.com">Geek's Life</a>)</h3>
</p>
<div>
<iframe src="http://ghbtns.com/github-btn.html?user=phodal&repo=repractise&type=watch&count=true"
allowtransparency="true" frameborder="0" scrolling="0" width="110px" height="20px"></iframe>
<div>
<nav id="TOC">
<ul>
<li><a href="#引言">引言</a><ul>
<li><a href="#re-practise">Re-Practise</a></li>
<li><a href="#技术与业务">技术与业务</a></li>
<li><a href="#资讯爆炸">资讯爆炸</a></li>
<li><a href="#lost">Lost</a></li>
</ul></li>
<li><a href="#前端篇-前端演进史">前端篇: 前端演进史</a><ul>
<li><a href="#什么是前端">什么是前端?</a></li>
<li><a href="#前端演进史">前端演进史</a><ul>
<li><a href="#数据-模板-样式混合">数据-模板-样式混合</a></li>
<li><a href="#model-view-controller">Model-View-Controller</a></li>
<li><a href="#从桌面版到移动版">从桌面版到移动版</a></li>
<li><a href="#app与过渡期api">APP与过渡期API</a></li>
<li><a href="#过渡期spa">过渡期SPA</a></li>
<li><a href="#hybird与viewmodel">Hybird与ViewModel</a></li>
<li><a href="#一次构建跨平台运行">一次构建,跨平台运行</a></li>
</ul></li>
<li><a href="#repractise">RePractise</a></li>
</ul></li>
<li><a href="#后台与服务篇">后台与服务篇</a><ul>
<li><a href="#restful与服务化">RESTful与服务化</a><ul>
<li><a href="#设计restful-api">设计RESTful API</a></li>
<li><a href="#资源">资源</a></li>
</ul></li>
<li><a href="#微服务">微服务</a><ul>
<li><a href="#微内核">微内核</a></li>
</ul></li>
<li><a href="#混合服务">混合服务</a></li>
</ul></li>
<li><a href="#易读">易读</a><ul>
<li><a href="#简介">简介</a><ul>
<li><a href="#编程经验">编程经验</a></li>
<li><a href="#代码整洁">代码整洁</a></li>
<li><a href="#别人的代码很烂">别人的代码很烂?</a></li>
</ul></li>
<li><a href="#变量名">变量名</a></li>
<li><a href="#函数名">函数名</a></li>
<li><a href="#小函数">小函数</a></li>
<li><a href="#测试">测试</a></li>
</ul></li>
<li><a href="#重构篇">重构篇</a><ul>
<li><a href="#网站重构">网站重构</a><ul>
<li><a href="#网站重构目的">网站重构目的</a></li>
</ul></li>
<li><a href="#代码重构">代码重构</a></li>
<li><a href="#使用工具重构">使用工具重构</a></li>
<li><a href="#借助工具重构">借助工具重构</a><ul>
<li><a href="#code-climate">Code Climate</a></li>
</ul></li>
<li><a href="#测试驱动开发">测试驱动开发</a><ul>
<li><a href="#一次测试驱动开发的故事">一次测试驱动开发的故事</a></li>
<li><a href="#说说测试驱动开发">说说测试驱动开发</a></li>
<li><a href="#思考">思考</a></li>
</ul></li>
</ul></li>
<li><a href="#架构篇-cms的重构与演进">架构篇: CMS的重构与演进</a><ul>
<li><a href="#动态cms">动态CMS</a><ul>
<li><a href="#cms简介">CMS简介</a></li>
<li><a href="#cms架构与django">CMS架构与Django</a></li>
<li><a href="#编辑-发布分离">编辑-发布分离</a></li>
<li><a href="#基于github的编辑-发布-开发分离">基于Github的编辑-发布-开发分离</a></li>
<li><a href="#repractise-1">Repractise</a></li>
</ul></li>
<li><a href="#构建基于git为数据中心的cms">构建基于Git为数据中心的CMS</a><ul>
<li><a href="#用户场景">用户场景</a></li>
</ul></li>
<li><a href="#code-生成静态页面">Code: 生成静态页面</a></li>
<li><a href="#builder-构建生成工具">Builder: 构建生成工具</a></li>
<li><a href="#contentjson格式">Content:JSON格式</a><ul>
<li><a href="#从schema到数据库">从Schema到数据库</a></li>
<li><a href="#git作为nosql数据库">git作为NoSQL数据库</a></li>
</ul></li>
<li><a href="#一键发布编辑器">一键发布:编辑器</a></li>
<li><a href="#移动应用">移动应用</a></li>
<li><a href="#小结">小结</a><ul>
<li><a href="#其他-1">其他</a></li>
</ul></li>
</ul></li>
<li><a href="#无栈篇架构设计">无栈篇:架构设计</a><ul>
<li><a href="#博客与技术驱动">博客与技术驱动</a><ul>
<li><a href="#技术组成">技术组成</a></li>
</ul></li>
<li><a href="#lan与架构设计">Lan与架构设计</a><ul>
<li><a href="#物联网层级结构">物联网层级结构</a></li>
<li><a href="#分层架构">分层架构</a></li>
<li><a href="#六边形架构">六边形架构</a></li>
</ul></li>
</ul></li>
<li><a href="#消息队列">消息队列</a><ul>
<li><a href="#jms">JMS</a></li>
<li><a href="#mq">MQ</a></li>
</ul></li>
<li><a href="#模式篇设计与架构">模式篇:设计与架构</a><ul>
<li><a href="#观察者模式">观察者模式</a><ul>
<li><a href="#ruby观察者模式">Ruby观察者模式</a></li>
<li><a href="#pubsub">PUB/SUB</a></li>
</ul></li>
<li><a href="#模板方法">模板方法</a><ul>
<li><a href="#从基本的app说起">从基本的App说起</a></li>
<li><a href="#template-method">Template Method</a></li>
<li><a href="#template-method实战">Template Method实战</a></li>
</ul></li>
<li><a href="#pipe-and-filters">Pipe and Filters</a><ul>
<li><a href="#unix-shell">Unix Shell</a></li>
<li><a href="#pipe-and-filter模式">Pipe and Filter模式</a></li>
<li><a href="#fluent-api">Fluent API</a></li>
<li><a href="#dsl-表达式生成器">DSL 表达式生成器</a></li>
<li><a href="#pipe-and-filter模式实战">Pipe and Filter模式实战</a></li>
</ul></li>
</ul></li>
<li><a href="#数据模型与领域">数据模型与领域</a><ul>
<li><a href="#数据">数据</a><ul>
<li><a href="#数据库">数据库</a></li>
<li><a href="#数据模型">数据模型</a></li>
</ul></li>
<li><a href="#领域">领域</a><ul>
<li><a href="#ddd">DDD</a></li>
<li><a href="#dsl">DSL</a></li>
<li><a href="#dsl示例">DSL示例</a></li>
</ul></li>
</ul></li>
</ul>
</nav>
<h1 id="引言">引言</h1>
<p>回到一年前的今天(2014.09.29),一边在准备着去沙漠之旅,一边在准备国庆后的印度培训。</p>
<p>当时我还在用我的Lumia 920,上面没有各式各样的软件,除了我最需要的地图、相机。所以,我需要为我的手机写一个应用,用于在地图上显示图片信息及照片。</p>
<p>今天Github已经可以支持geojson了,于是你可以看到我在之前生成的geojson在地图上的效果<a href="https://github.com/phodal-archive/onmap/blob/master/gps.geojson">gps.geojson</a>。</p>
<h2 id="re-practise">Re-Practise</h2>
<p>在过去的近一年时期里,花费了很多时间在提高代码质量与构建架构知识。试着学习某一方面的架构知识,应用到某个熟悉领域。</p>
<ol type="1">
<li><p>所谓的一万小时天才理论一直在说明练习的重要性,你需要不断地去练习。但是并不是说你练习了一万小时之后就可以让你成为一个专家,而练习是必须的。</p></li>
<li><p>让我想起了在大学时代学的PID算法,虽然我没有掌握好控制领域的相关理论及算法,但是我对各种调节还算有点印象。简单地来说,我们需要不断调整自己的方向。</p></li>
</ol>
<p>现在还存在的那些互联网公司或者说开源项目,我们会发现两个不算有趣的规律:</p>
<ol type="1">
<li>一个一直在运行的软件。</li>
<li>尝试了几个产品,最后找到了一个合适的方向。</li>
</ol>
<p>我发现我属于不断尝试地类型。一直想构建一个开源软件,但是似乎一直没有找对合理的用户?但是,我们会发现上述地两者都在不断地retry,不断地retry归根于那些人在不断的repractise。与之成为反例的便是:</p>
<ol type="1">
<li>一个成功发布几次的软件,但是最后失败了</li>
<li>尝试了不同的几个产品,但是失败了</li>
</ol>
<p>所谓的失败,就是你离开人世了。所以,在我们还活着的时候,我们总会有机会去尝试。在那之前,我们都是在不断地re-practise。</p>
<p>这让我想到了Linux,这算是一个不错地软件,从一开始就存活到了现在。但是有多少开源软件就没有这么幸运,时间在淘汰越来越多的过去想法。人们创造事物的能力也越来越强,但是那只是因为创造变得越来越简单。</p>
<p>在我们看到的那些走上人生巅峰的CEO,还都在不断地re-practise。</p>
<h2 id="技术与业务">技术与业务</h2>
<p>于是,我又再次回到了这样一个现实的问题。技术可以不断地练习,不断地调整方向。但是技术地成本在不断地降低,代码的长度在不断地降低。整个技术的门槛越来越低,新出现的技术总会让新生代的程序员获利。但是不可避免地,业务地复杂度并没有因此而降低。这就是一个复杂的话题,难道业务真的很复杂吗?</p>
<p>人们总会提及写好CSS很难,但是写好Java就是一件容易的事。因为每天我们都在用Java、JavaScript去写代码,但是我们并没有花费时间去学。</p>
<p>因为我们一直将我们的时候花费的所谓的业务上,我们可以不断地将一些重复的代码抽象成一个库。但是我们并没有花费过多的时间去整理我们的业务,作为程序员,我们切换工作很容易只是因为相同的技术栈。作为一些营销人员,他们从一个领域到一个新的领域,不需要过多的学习,因为本身是相通的。</p>
<p>技术本身是如此,业务本身也是如此。</p>
<p>从技术到技术-领域是一条难走通的路?</p>
<h2 id="资讯爆炸">资讯爆炸</h2>
<p>回顾到近几年出现的各种资讯程序——开发者头条、极客头条、掘金、博乐头条等等,他们帮助我们的是丰富我们的信息,而不是简化我们的信息。</p>
<p>作为一个开发人员,过去我们并不需要关注那么多的内容。如果我们没有关注那么多的点,那么我们就可以集中于我们的想法里。实现上,我们需要的是一个更智能的时代。</p>
<p>业务本身是一种重复,技术本身也是重复的。只是在某个特定的时刻,一个好的技术可以帮助我们更好地Re-Practise。如推荐算法本身依赖于人为对信息进行分类,但是我们需要去区分大量地信息。而人本身的经历是足够有险的,这时候就需要机器来帮我们做很多事。</p>
<p>今天我在用MX5,但是发现不及Lumia 1020来得安静。功能越强大的同时,意味着我在上面花费的时间会更多。事情有好的一面总会有不好的一面,不好的一面也就意味着有机会寻找好的一面。</p>
<p>我们需要摒弃一些东西,以重新纠正我们的方向。于是,我需要再次回到Lumia 1020上。</p>
<h2 id="lost">Lost</h2>
<blockquote>
<p>一开始就输在起跑线上</p>
</blockquote>
<p>这是一个很有意思的话题,尽管试图将本章中从书中删除,但是我还是忍了下来。如果你学得比别人晚,在很长的一段时间里(可能直到进棺材)输给别人是必然的——落后就要挨打。就好像我等毕业于一所二本垫底的学校里,如果在过去我一直保持着和别人(各种重点)一样的学习速度,那么我只能一直是Loser。</p>
<p>需要注意的是,对你来说考上二本很难,并不是因为你比别人笨。教育资源分配不均的问题,在某种程度上导致了新的阶级制度的出现。如我的首页说的那样: THE ONLY FAIR IS NOT FAIR——唯一公平的是它是不公平的。我们可以做的还有很多——CREATE & SHARE。真正的不幸是,因为营养不良导致的教育问题。如果你还有机会正常地思想,那说明这个世界对你还是公平的。</p>
<h1 id="前端篇-前端演进史">前端篇: 前端演进史</h1>
<p>细细整理了过去接触过的那些前端技术,发现前端演进是段特别有意思的历史。人们总是在过去就做出未来需要的框架,而现在流行的是过去的过去发明过的。如,响应式设计不得不提到的一个缺点是:<strong>他只是将原本在模板层做的事,放到了样式(CSS)层来完成</strong>。</p>
<p>复杂度同力一样不会消失,也不会凭空产生,它总是从一个物体转移到另一个物体或一种形式转为另一种形式。</p>
<p>如果六、七年前的移动网络速度和今天一样快,那么直接上的技术就是响应式设计,APP、SPA就不会流行得这么快。尽管我们可以预见未来这些领域会变得更好,但是更需要的是改变现状。改变现状的同时也需要预见未来的需求。</p>
<h3 id="什么是前端">什么是前端?</h3>
<p>维基百科是这样说的:前端Front-end和后端back-end是描述进程开始和结束的通用词汇。前端作用于采集输入信息,后端进行处理。计算机程序的界面样式,视觉呈现属于前端。</p>
<p>这种说法给人一种很模糊的感觉,但是他说得又很对,它负责视觉展示。在MVC结构或者MVP中,负责视觉显示的部分只有View层,而今天大多数所谓的View层已经超越了View层。前端是一个很神奇的概念,但是而今的前端已经发生了很大的变化。</p>
<p>你引入了Backbone、Angluar,你的架构变成了MVP、MVVM。尽管发生了一些架构上的变化,但是项目的开发并没有因此而发生变化。这其中涉及到了一些职责的问题,如果某一个层级中有太多的职责,那么它是不是加重了一些人的负担?</p>
<h2 id="前端演进史">前端演进史</h2>
<p>过去一直想整理一篇文章来说说前端发展的历史,但是想着这些历史已经被人们所熟知。后来发现并非如此,大抵是幸存者偏见——关注到的都知道这些历史。</p>
<h3 id="数据-模板-样式混合">数据-模板-样式混合</h3>
<p>在有限的前端经验里,我还是经历了那段用Table来作样式的年代。大学期间曾经有偿帮一些公司或者个人开发、维护一些CMS,而Table是当时帮某个网站更新样式接触到的——ASP.Net(maybe)。当时,我们启动这个CMS用的是一个名为<code>aspweb.exe</code>的程序。于是,在我的移动硬盘里找到了下面的代码。</p>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><TABLE</span><span class="ot"> cellSpacing=</span><span class="st">0</span><span class="ot"> cellPadding=</span><span class="st">0</span><span class="ot"> width=</span><span class="st">910</span><span class="ot"> align=</span><span class="st">center</span><span class="ot"> border=</span><span class="st">0</span><span class="kw">></span>
<span class="kw"><TBODY></span>
<span class="kw"><TR></span>
<span class="kw"><TD</span><span class="ot"> vAlign=</span><span class="st">top</span><span class="ot"> width=</span><span class="st">188</span><span class="kw">><TABLE</span><span class="ot"> cellSpacing=</span><span class="st">0</span><span class="ot"> cellPadding=</span><span class="st">0</span><span class="ot"> width=</span><span class="st">184</span><span class="ot"> align=</span><span class="st">center</span><span class="ot"> border=</span><span class="st">0</span><span class="kw">></span>
<span class="kw"><TBODY></span>
<span class="kw"><TR></span>
<span class="kw"><TD><IMG</span><span class="ot"> src=</span><span class="st">"Images/xxx.gif"</span><span class="ot"> width=</span><span class="st">184</span><span class="kw">></TD></TR></span>
<span class="kw"><TR></span>
<span class="kw"><TD></span>
<span class="kw"><TABLE</span><span class="ot"> cellSpacing=</span><span class="st">0</span><span class="ot"> cellPadding=</span><span class="st">0</span><span class="ot"> width=</span><span class="st">184</span><span class="ot"> align=</span><span class="st">center</span>
<span class="ot"> background=</span><span class="st">Images/xxx.gif</span><span class="ot"> border=</span><span class="st">0</span><span class="kw">></span></code></pre></div>
<p>虽然,我也已经在HEAD里找到了现代的雏形——DIV + CSS,然而这仍然是一个Table的年代。</p>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><LINK</span><span class="ot"> href=</span><span class="st">"img/xxx.css"</span><span class="ot"> type=</span><span class="st">text/css</span><span class="ot"> rel=</span><span class="st">stylesheet</span><span class="kw">></span></code></pre></div>
<p><strong>人们一直在说前端很难,问题是你学过么???</strong></p>
<p><strong>人们一直在说前端很难,问题是你学过么???</strong></p>
<p><strong>人们一直在说前端很难,问题是你学过么???</strong></p>
<p>也许,你也一直在说CSS不好写,但是CSS真的不好写么?人们总在说JS很难用,但是你学过么?只在需要的时候才去学,那肯定很难。<strong>你不曾花时间去学习一门语言,但是却能直接写出可以work的代码,说明他们容易上手</strong>。如果你看过一些有经验的Ruby、Scala、Emacs Lisp开发者写出来的代码,我想会得到相同的结论。有一些语言可以让写程序的人Happy,但是看的人可能就不Happy了。做事的方法不止一种,但是不是所有的人都要用那种方法去做。</p>
<p>过去的那些程序员都是<strong>真正的全栈程序员</strong>,这些程序员不仅仅做了前端的活,还做了数据库的工作。</p>
<div class="sourceCode"><pre class="sourceCode sql"><code class="sourceCode sql"><span class="kw">Set</span> rs = Server.CreateObject(<span class="ot">"ADODB.Recordset"</span>)
sql = <span class="ot">"select id,title,username,email,qq,adddate,content,Re_content,home,face,sex from Fl_Book where ispassed=1 order by id desc"</span>
rs.open sql, Conn, <span class="dv">1</span>, <span class="dv">1</span>
fl.SqlQueryNum = fl.SqlQueryNum + <span class="dv">1</span></code></pre></div>
<p>在这个ASP文件里,它从数据库里查找出了数据,然后Render出HTML。如果可以看到历史版本,那么我想我会看到有一个作者将style=“”的代码一个个放到css文件中。</p>
<p>在这里的代码里也免不了有动态生成JavaScript代码的方法:</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript">show_other <span class="op">=</span> <span class="st">"<SCRIPT language=javascript>"</span>
show_other <span class="op">=</span> show_other <span class="op">&</span> <span class="st">"function checkform()"</span>
show_other <span class="op">=</span> show_other <span class="op">&</span> <span class="st">"{"</span>
show_other <span class="op">=</span> show_other <span class="op">&</span> <span class="st">"if (document.add.title.value=='')"</span>
show_other <span class="op">=</span> show_other <span class="op">&</span> <span class="st">"{"</span></code></pre></div>
<p>请尽情嘲笑,然后再看一段代码:</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="im">import</span> React <span class="im">from</span> <span class="st">"react"</span><span class="op">;</span>
<span class="im">import</span> <span class="op">{</span> getData <span class="op">}</span> <span class="im">from</span> <span class="st">"../../common/request"</span><span class="op">;</span>
<span class="im">import</span> styles <span class="im">from</span> <span class="st">"./style.css"</span><span class="op">;</span>
<span class="im">export</span> <span class="im">default</span> <span class="kw">class</span> HomePage <span class="kw">extends</span> <span class="va">React</span>.<span class="at">Component</span> <span class="op">{</span>
<span class="at">componentWillMount</span>() <span class="op">{</span>
<span class="va">console</span>.<span class="at">log</span>(<span class="st">"[HomePage] will mount with server response: "</span><span class="op">,</span> <span class="kw">this</span>.<span class="va">props</span>.<span class="va">data</span>.<span class="at">home</span>)<span class="op">;</span>
<span class="op">}</span>
<span class="at">render</span>() <span class="op">{</span>
<span class="kw">let</span> <span class="op">{</span> title <span class="op">}</span> <span class="op">=</span> <span class="kw">this</span>.<span class="va">props</span>.<span class="va">data</span>.<span class="at">home</span><span class="op">;</span>
<span class="cf">return</span> (
<span class="op"><</span>div className<span class="op">={</span><span class="va">styles</span>.<span class="at">content</span><span class="op">}></span>
<span class="op"><</span>h1<span class="op">>{</span>title<span class="op">}<</span><span class="ss">/h1></span>
<span class="ss"> <p className={styles.welcomeText}>Thanks for joining!</p</span><span class="op">></span>
<span class="op"><</span><span class="ss">/div></span>
<span class="ss"> </span><span class="sc">)</span><span class="ss">;</span>
<span class="ss"> }</span>
<span class="ss"> static fetchData = function</span><span class="sc">(</span><span class="ss">params</span><span class="sc">)</span><span class="ss"> {</span>
<span class="ss"> return getData</span><span class="sc">(</span><span class="ss">"/home</span><span class="st">");</span>
<span class="op">}</span>
<span class="op">}</span></code></pre></div>
<p>10年前和10年后的代码,似乎没有太多的变化。有所不同的是数据层已经被独立出去了,如果你的component也混合了数据层,即直接查询数据库而不是调用数据层接口,那么你就需要好好思考下这个问题。你只是在追随潮流,还是在改变。用一个View层更换一个View层,用一个Router换一个Router的意义在哪?</p>
<h3 id="model-view-controller">Model-View-Controller</h3>
<p>人们在不断地反思这其中复杂的过程,整理了一些好的架构模式,其中不得不提到的是我司Martin Folwer的《企业应用架构模式》。该书中文译版出版的时候是2004年,那时对于系统的分层是</p>
<table>
<thead>
<tr class="header">
<th style="text-align: left;">层次</th>
<th style="text-align: left;">职责</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;">表现层</td>
<td style="text-align: left;">提供服务、显示信息、用户请求、HTTP请求和命令行调用。</td>
</tr>
<tr class="even">
<td style="text-align: left;">领域层</td>
<td style="text-align: left;">逻辑处理,系统中真正的核心。</td>
</tr>
<tr class="odd">
<td style="text-align: left;">数据层</td>
<td style="text-align: left;">与数据库、消息系统、事物管理器和其他软件包通讯。</td>
</tr>
</tbody>
</table>
<p>化身于当时最流行的Spring,就是MVC。人们有了iBatis这样的数据持久层框架,即ORM,对象关系映射。于是,你的package就会有这样的几个文件夹:</p>
<pre><code>|____mappers
|____model
|____service
|____utils
|____controller</code></pre>
<p>在mappers这一层,我们所做的莫过于如下所示的数据库相关查询:</p>
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="fu">@Insert</span>(
<span class="st">"INSERT INTO users(username, password, enabled) "</span> +
<span class="st">"VALUES (#{userName}, #{passwordHash}, #{enabled})"</span>
)
<span class="fu">@Options</span>(keyProperty = <span class="st">"id"</span>, keyColumn = <span class="st">"id"</span>, useGeneratedKeys = <span class="kw">true</span>)
<span class="dt">void</span> <span class="fu">insert</span>(User user);</code></pre></div>
<p>model文件夹和mappers文件夹都是数据层的一部分,只是两者间的职责不同,如:</p>
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">public</span> String <span class="fu">getUserName</span>() {
<span class="kw">return</span> userName;
}
<span class="kw">public</span> <span class="dt">void</span> <span class="fu">setUserName</span>(String userName) {
<span class="kw">this</span>.<span class="fu">userName</span> = userName;
}</code></pre></div>
<p>而他们最后都需要在Controller,又或者称为ModelAndView中处理:</p>
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="fu">@RequestMapping</span>(value = {<span class="st">"/disableUser"</span>}, method = RequestMethod.<span class="fu">POST</span>)
<span class="kw">public</span> ModelAndView <span class="fu">processUserDisable</span>(HttpServletRequest request, ModelMap model) {
String userName = request.<span class="fu">getParameter</span>(<span class="st">"userName"</span>);
User user = userService.<span class="fu">getByUsername</span>(userName);
userService.<span class="fu">disable</span>(user);
Map<String,User> map = <span class="kw">new</span> HashMap<String,User>();
Map <User,String> usersWithRoles= userService.<span class="fu">getAllUsersWithRole</span>();
model.<span class="fu">put</span>(<span class="st">"usersWithRoles"</span>,usersWithRoles);
<span class="kw">return</span> <span class="kw">new</span> <span class="fu">ModelAndView</span>(<span class="st">"redirect:users"</span>,map);
}</code></pre></div>
<p>在多数时候,Controller不应该直接与数据层的一部分,而将业务逻辑放在Controller层又是一种错误,这时就有了Service层,如下图:</p>
<figure>
<img src="http://repractise.phodal.com/img/frontend/service-mvc.png" alt="Service MVC" /><figcaption>Service MVC</figcaption>
</figure>
<p>然而对于Domain相关的Service应该放在哪一层,总会有不同的意见:</p>
<figure>
<img src="http://repractise.phodal.com/img/frontend/mvcplayer.gif" alt="MVC Player" /><figcaption>MVC Player</figcaption>
</figure>
<figure>
<img src="http://repractise.phodal.com/img/frontend/ms-mvc.png" alt="MS MVC" /><figcaption>MS MVC</figcaption>
</figure>
<p>Domain(业务)是一个相当复杂的层级,这里是业务的核心。一个合理的Controller只应该做自己应该做的事,它不应该处理业务相关的代码:</p>
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">if</span> (isNewnameEmpty == <span class="kw">false</span> && newuser == <span class="kw">null</span>){
user.<span class="fu">setUserName</span>(newUsername);
List<Post> myPosts = postService.<span class="fu">findMainPostByAuthorNameSortedByCreateTime</span>(principal.<span class="fu">getName</span>());
<span class="kw">for</span> (<span class="dt">int</span> k = <span class="dv">0</span>;k < myPosts.<span class="fu">size</span>();k++){
Post post = myPosts.<span class="fu">get</span>(k);
post.<span class="fu">setAuthorName</span>(newUsername);
postService.<span class="fu">save</span>(post);
}
userService.<span class="fu">update</span>(user);
Authentication oldAuthentication = SecurityContextHolder.<span class="fu">getContext</span>().<span class="fu">getAuthentication</span>();
Authentication authentication = <span class="kw">null</span>;
<span class="kw">if</span>(oldAuthentication == <span class="kw">null</span>){
authentication = <span class="kw">new</span> <span class="fu">UsernamePasswordAuthenticationToken</span>(newUsername,user.<span class="fu">getPasswordHash</span>());
}<span class="kw">else</span>{
authentication = <span class="kw">new</span> <span class="fu">UsernamePasswordAuthenticationToken</span>(newUsername,user.<span class="fu">getPasswordHash</span>(),oldAuthentication.<span class="fu">getAuthorities</span>());
}
SecurityContextHolder.<span class="fu">getContext</span>().<span class="fu">setAuthentication</span>(authentication);
map.<span class="fu">clear</span>();
map.<span class="fu">put</span>(<span class="st">"user"</span>,user);
model.<span class="fu">addAttribute</span>(<span class="st">"myPosts"</span>, myPosts);
model.<span class="fu">addAttribute</span>(<span class="st">"namesuccess"</span>, <span class="st">"User Profile updated successfully"</span>);
<span class="kw">return</span> <span class="kw">new</span> <span class="fu">ModelAndView</span>(<span class="st">"user/profile"</span>, map);
}</code></pre></div>
<p>我们在Controller层应该做的事是:</p>
<ol type="1">
<li>处理请求的参数</li>
<li>渲染和重定向</li>
<li>选择Model和Service</li>
<li>处理Session和Cookies</li>
</ol>
<p>业务是善变的,昨天我们可能还在和对手竞争谁先推出新功能,但是今天可能已经合并了。我们很难预见业务变化,但是我们应该能预见Controller是不容易变化的。在一些设计里面,这种模式就是Command模式。</p>
<p>View层是一直在变化的层级,人们的品味一直在更新,有时甚至可能因为竞争对手而产生变化。在已经取得一定市场的情况下,Model-Service-Controller通常都不太会变动,甚至不敢变动。企业意识到创新的两面性,要么带来死亡,要么占领更大的市场。但是对手通常都比你想象中的更聪明一些,所以这时<strong>开创新的业务是一个更好的选择</strong>。</p>
<p>高速发展期的企业和发展初期的企业相比,更需要前端开发人员。在用户基数不够、业务待定的情形中,View只要可用并美观就行了,这时可能就会有大量的业务代码放在View层:</p>
<div class="sourceCode"><pre class="sourceCode jsp"><code class="sourceCode jsp"><span class="kw"><c:choose></span>
<span class="kw"><c:when</span><span class="ot"> test</span>=<span class="dt">"</span>${ hasError }<span class="dt">"</span><span class="kw">></span>
<p<span class="ot"> class</span>=<span class="dt">"prompt-error"</span>>
${errors.username} ${errors.password}
</p>
<span class="kw"></c:when></span>
<span class="kw"><c:otherwise></span>
<p<span class="ot"> class</span>=<span class="dt">"prompt"</span>>
Woohoo, User <span<span class="ot"> class</span>=<span class="dt">"username"</span>>${user.userName}</span> has been created successfully!
</p>
<span class="kw"></c:otherwise></span>
<span class="kw"></c:choose></span> </code></pre></div>
<p>不同的情形下,人们都会对此有所争议,但只要符合当前的业务便是最好的选择。作为一个前端开发人员,在过去我需要修改JSP、PHP文件,这期间我需要去了解这些Template:</p>
<div class="sourceCode"><pre class="sourceCode php"><code class="sourceCode php">{<span class="kw">foreach</span> <span class="kw">$lists</span> <span class="kw">as</span> <span class="kw">$v</span>}
<li itemprop=<span class="st">"breadcrumb"</span>><span{<span class="kw">if</span><span class="ot">(</span>newest<span class="ot">(</span><span class="kw">$v</span><span class="ot">[</span><span class="st">'addtime'</span><span class="ot">],</span><span class="dv">24</span><span class="ot">))</span>} style=<span class="st">"color:red"</span>{/<span class="kw">if</span>}><span class="ot">[</span>{fun <span class="fu">date</span><span class="ot">(</span><span class="st">'Y-m-d'</span><span class="ot">,</span><span class="kw">$v</span><span class="ot">[</span><span class="st">'addtime'</span><span class="ot">])</span>}<span class="ot">]</span></span><a href=<span class="st">"</span><span class="kw">{$v['url']}</span><span class="st">"</span> style=<span class="st">"</span><span class="kw">{$v['style']}</span><span class="st">"</span> target=<span class="st">"_blank"</span>>{<span class="kw">$v</span><span class="ot">[</span><span class="st">'title'</span><span class="ot">]</span>}</a></li>
{/<span class="kw">foreach</span>}</code></pre></div>
<p>有时像Django这一类,自称为Model-Template-View的框架,更容易让人理解其意图:</p>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html">{% for blog_post in blog_posts.object_list %}
{% block blog_post_list_post_title %}
<span class="kw"><section</span><span class="ot"> class=</span><span class="st">"section--center mdl-grid mdl-grid--no-spacing mdl-shadow--2dp mdl-cell--11-col blog-list"</span><span class="kw">></span>
{% editable blog_post.title %}
<span class="kw"><div</span><span class="ot"> class=</span><span class="st">"mdl-card__title mdl-card--border mdl-card--expand"</span><span class="kw">></span>
<span class="kw"><h2</span><span class="ot"> class=</span><span class="st">"mdl-card__title-text"</span><span class="kw">></span>
<span class="kw"><a</span><span class="ot"> href=</span><span class="st">"{{ blog_post.get_absolute_url }}"</span><span class="ot"> itemprop=</span><span class="st">"headline"</span><span class="kw">></span>{{ blog_post.title }} › <span class="kw"></a></span>
<span class="kw"></h2></span>
<span class="kw"></div></span>
{% endeditable %}
{% endblock %}</code></pre></div>
<p>作为一个前端人员,我们真正在接触的是View层和Template层,但是MVC并没有说明这些。</p>
<h3 id="从桌面版到移动版">从桌面版到移动版</h3>
<p>Wap出现了,并带来了更多的挑战。随后,分辨率从1024x768变成了176×208,开发人员不得不面临这些挑战。当时所需要做的仅仅是修改View层,而View层随着iPhone的出现又发生了变化。</p>
<figure>
<img src="http://repractise.phodal.com/img/frontend/wap.gif" alt="WAP 网站" /><figcaption>WAP 网站</figcaption>
</figure>
<p>这是一个短暂的历史,PO还需要为手机用户制作一个怎样的网站?于是他们把桌面版的网站搬了过去变成了移动版。由于网络的原因,每次都需要重新加载页面,这带来了不佳的用户体验。</p>
<p>幸运的是,人们很快意识到了这个问题,于是就有了SPA。<strong>如果当时的移动网络速度可以更快的话,我想很多SPA框架就不存在了</strong>。</p>
<p>先说说jQuery Mobile,在那之前,先让我们来看看两个不同版本的代码,下面是一个手机版本的blog详情页:</p>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><ul</span><span class="ot"> data-role=</span><span class="st">"listview"</span><span class="ot"> data-inset=</span><span class="st">"true"</span><span class="ot"> data-splittheme=</span><span class="st">"a"</span><span class="kw">></span>
{% for blog_post in blog_posts.object_list %}
<span class="kw"><li></span>
{% editable blog_post.title blog_post.publish_date %}
<span class="kw"><h2</span><span class="ot"> class=</span><span class="st">"blog-post-title"</span><span class="kw">><a</span><span class="ot"> href=</span><span class="st">"{% url "</span><span class="er">blog_post_detail"</span><span class="ot"> blog_post.slug</span> <span class="er">%}"</span><span class="kw">></span>{{ blog_post.title }}<span class="kw"></a></h2></span>
<span class="kw"><em</span><span class="ot"> class=</span><span class="st">"since"</span><span class="kw">></span>{% blocktrans with sometime=blog_post.publish_date|timesince %}{{ sometime }} ago{% endblocktrans %}<span class="kw"></em></span>
{% endeditable %}
<span class="kw"></li></span>
{% endfor %}
<span class="kw"></ul></span></code></pre></div>
<p>而下面是桌面版本的片段:</p>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html">{% for blog_post in blog_posts.object_list %}
{% block blog_post_list_post_title %}
{% editable blog_post.title %}
<span class="kw"><h2></span>
<span class="kw"><a</span><span class="ot"> href=</span><span class="st">"{{ blog_post.get_absolute_url }}"</span><span class="kw">></span>{{ blog_post.title }}<span class="kw"></a></span>
<span class="kw"></h2></span>
{% endeditable %}
{% endblock %}
{% block blog_post_list_post_metainfo %}
{% editable blog_post.publish_date %}
<span class="kw"><h6</span><span class="ot"> class=</span><span class="st">"post-meta"</span><span class="kw">></span>
{% trans "Posted by" %}:
{% with blog_post.user as author %}
<span class="kw"><a</span><span class="ot"> href=</span><span class="st">"{% url "</span><span class="er">blog_post_list_author"</span><span class="ot"> author</span> <span class="er">%}"</span><span class="kw">></span>{{ author.get_full_name|default:author.username }}<span class="kw"></a></span>
{% endwith %}
{% with blog_post.categories.all as categories %}
{% if categories %}
{% trans "in" %}
{% for category in categories %}
<span class="kw"><a</span><span class="ot"> href=</span><span class="st">"{% url "</span><span class="er">blog_post_list_category"</span><span class="ot"> category.slug</span> <span class="er">%}"</span><span class="kw">></span>{{ category }}<span class="kw"></a></span>{% if not forloop.last %}, {% endif %}
{% endfor %}
{% endif %}
{% endwith %}
{% blocktrans with sometime=blog_post.publish_date|timesince %}{{ sometime }} ago{% endblocktrans %}
<span class="kw"></h6></span>
{% endeditable %}
{% endblock %}</code></pre></div>
<p>人们所做的只是<strong>重载View层</strong>。这也是一个有效的SEO策略,上面这些代码是我博客过去的代码。对于桌面版和移动版都是不同的模板和不同的JS、CSS。</p>
<figure>
<img src="http://repractise.phodal.com/img/frontend/mobile-web.png" alt="移动版网页" /><figcaption>移动版网页</figcaption>
</figure>
<p>在这一时期,桌面版和移动版的代码可能在同一个代码库中。他们使用相同的代码,调用相同的逻辑,只是View层不同了。但是,每次改动我们都要维护两份代码。</p>
<p>随后,人们发现了一种更友好的移动版应用——APP。</p>
<h3 id="app与过渡期api">APP与过渡期API</h3>
<p>这是一个艰难的时刻,过去我们的很多API都是在原来的代码库中构建的,即桌面版和移动版一起。我们已经在这个代码库中开发了越来越多的功能,系统开发变得臃肿。如《Linux/Unix设计思想》中所说,这是一个伟大的系统,但是它臃肿而又缓慢。</p>
<p>我们是选择重新开发一个结合第一和第二系统的最佳特性的第三个系统,还是继续臃肿下去。我想你已经有答案了。随后我们就有了APP API,构建出了博客的APP。</p>
<figure>
<img src="http://repractise.phodal.com/img/frontend/mobile-app.jpg" alt="应用" /><figcaption>应用</figcaption>
</figure>
<p>最开始,人们越来越喜欢用APP,因为与移动版网页相比,其响应速度更快,而且更流畅。对于服务器来说,也是一件好事,因为请求变少了。</p>
<p>但是并非所有的人都会下载APP——<strong>有时只想看看上面有没有需要的东西</strong>。对于刚需不强的应用,人们并不会下载,只会访问网站。</p>
<p>有了APP API之后,我们可以向网页提供API,我们就开始设想要有一个好好的移动版。</p>
<h3 id="过渡期spa">过渡期SPA</h3>
<p>Backbone诞生于2010年,和响应式设计出现在同一个年代里,但他们似乎在同一个时代里火了起来。如果CSS3早点流行开来,似乎就没有Backbone啥事了。不过移动网络还是限制了响应式的流行,只是在今天这些都有所变化。</p>
<p>我们用Ajax向后台请求API,然后Mustache Render出来。因为JavaScript在模块化上的缺陷,所以我们就用Require.JS来进行模块化。</p>
<p>下面的代码就是我在尝试对我的博客进行SPA设计时的代码:</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="at">define</span>([
<span class="st">'zepto'</span><span class="op">,</span>
<span class="st">'underscore'</span><span class="op">,</span>
<span class="st">'mustache'</span><span class="op">,</span>
<span class="st">'js/ProductsView'</span><span class="op">,</span>
<span class="st">'json!/configure.json'</span><span class="op">,</span>
<span class="st">'text!/templates/blog_details.html'</span><span class="op">,</span>
<span class="st">'js/renderBlog'</span>
]<span class="op">,</span><span class="kw">function</span>($<span class="op">,</span> _<span class="op">,</span> Mustache<span class="op">,</span> ProductsView<span class="op">,</span> configure<span class="op">,</span> blogDetailsTemplate<span class="op">,</span> GetBlog)<span class="op">{</span>
<span class="kw">var</span> BlogDetailsView <span class="op">=</span> <span class="va">Backbone</span>.<span class="va">View</span>.<span class="at">extend</span> (<span class="op">{</span>
<span class="dt">el</span><span class="op">:</span> <span class="at">$</span>(<span class="st">"#content"</span>)<span class="op">,</span>
<span class="dt">initialize</span><span class="op">:</span> <span class="kw">function</span> () <span class="op">{</span>
<span class="kw">this</span>.<span class="at">params</span> <span class="op">=</span> <span class="st">'#content'</span><span class="op">;</span>
<span class="op">},</span>
<span class="dt">getBlog</span><span class="op">:</span> <span class="kw">function</span>(slug) <span class="op">{</span>
<span class="kw">var</span> getblog <span class="op">=</span> <span class="kw">new</span> <span class="at">GetBlog</span>(<span class="kw">this</span>.<span class="at">params</span><span class="op">,</span> configure[<span class="st">'blogPostUrl'</span>] <span class="op">+</span> slug<span class="op">,</span> blogDetailsTemplate)<span class="op">;</span>
<span class="va">getblog</span>.<span class="at">renderBlog</span>()<span class="op">;</span>
<span class="op">}</span>
<span class="op">}</span>)<span class="op">;</span>
<span class="cf">return</span> BlogDetailsView<span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span></code></pre></div>
<p>从API获取数据,结合Template来Render出Page。但是这无法改变我们需要Client Side Render和Server Side Render的两种Render方式,除非我们可以像淘宝一样不需要考虑SEO——因为它不那么依靠搜索引擎带来流量。</p>
<p>这时,我们还是基于类MVC模式。只是数据的获取方式变成了Ajax,我们就犯了一个错误——将大量的业务逻辑放在前端。这时候我们已经不能再从View层直接访问Model层,从安全的角度来说有点危险。</p>
<p>如果你的View层还可以直接访问Model层,那么说明你的架构还是MVC模式。之前我在Github上构建一个Side Project的时候直接用View层访问了Model层,由于Model层是一个ElasticSearch的搜索引擎,它提供了JSON API,这使得我要在View层处理数据——即业务逻辑。将上述的JSON API放入Controller,尽管会加重这一层的复杂度,但是业务逻辑就不再放置于View层。</p>
<p>如果你在你的View层和Model层总有一层接口,那么你采用的就是MVP模式——MVC模式的衍生(PS:为了区别别的事情,总会有人取个表意的名称)。</p>
<p>一夜之前,我们又回到了过去。我们离开了JSP,将View层变成了Template与Controller。而原有的Services层并不是只承担其原来的责任,这些Services开始向ViewModel改变。</p>
<p>一些团队便将Services抽成多个Services,美其名为微服务。传统架构下的API从下图</p>
<figure>
<img src="http://repractise.phodal.com/img/frontend/api-gateway.png" alt="API Gateway" /><figcaption>API Gateway</figcaption>
</figure>
<p>变成了直接调用的微服务:</p>
<figure>
<img src="http://repractise.phodal.com/img/frontend/microservices.png" alt="Micro Services" /><figcaption>Micro Services</figcaption>
</figure>
<p>对于后台开发者来说,这是一件大快人心的大好事,但是对于应用端/前端来说并非如此。调用的服务变多了,在应用程序端进行功能测试变得更复杂,需要Mock的API变多了。</p>
<h3 id="hybird与viewmodel">Hybird与ViewModel</h3>
<p>这时候遇到问题的不仅仅只在前端,而在App端,小的团队已经无法承受开发成本。人们更多的注意力放到了Hybird应用上。Hybird应用解决了一些小团队在开发初期遇到的问题,这部分应用便交给了前端开发者。</p>
<p>前端开发人员先熟悉了单纯的JS + CSS + HTML,又熟悉了Router + PageView + API的结构,现在他们又需要做手机APP。这时候只好用熟悉的jQuer Mobile + Cordova。</p>
<p>随后,人们先从Cordova + jQuery Mobile,变成了Cordova + Angular的 Ionic。在那之前,一些团队可能已经用Angular代换了Backbone。他们需要更好的交互,需要data binding。</p>
<p>接着,我们可以直接将我们的Angular代码从前端移到APP,比如下面这种博客APP的代码:</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"> .<span class="at">controller</span>(<span class="st">'BlogCtrl'</span><span class="op">,</span> <span class="kw">function</span> ($scope<span class="op">,</span> Blog) <span class="op">{</span>
<span class="va">$scope</span>.<span class="at">blogs</span> <span class="op">=</span> <span class="kw">null</span><span class="op">;</span>
<span class="va">$scope</span>.<span class="at">blogOffset</span> <span class="op">=</span> <span class="dv">0</span><span class="op">;</span>
<span class="co">//</span>
<span class="va">$scope</span>.<span class="at">doRefresh</span> <span class="op">=</span> <span class="kw">function</span> () <span class="op">{</span>
<span class="va">Blog</span>.<span class="at">async</span>(<span class="st">'https://www.phodal.com/api/v1/app/?format=json'</span>).<span class="at">then</span>(<span class="kw">function</span> (results) <span class="op">{</span>
<span class="va">$scope</span>.<span class="at">blogs</span> <span class="op">=</span> <span class="va">results</span>.<span class="at">objects</span><span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span>
<span class="va">$scope</span>.<span class="at">$broadcast</span>(<span class="st">'scroll.refreshComplete'</span>)<span class="op">;</span>
<span class="va">$scope</span>.<span class="at">$apply</span>()
<span class="op">};</span>
<span class="va">Blog</span>.<span class="at">async</span>(<span class="st">'https://www.phodal.com/api/v1/app/?format=json'</span>).<span class="at">then</span>(<span class="kw">function</span> (results) <span class="op">{</span>
<span class="va">$scope</span>.<span class="at">blogs</span> <span class="op">=</span> <span class="va">results</span>.<span class="at">objects</span><span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span>
<span class="va">$scope</span>.<span class="at">loadMore</span> <span class="op">=</span> <span class="kw">function</span>() <span class="op">{</span>
<span class="va">$scope</span>.<span class="at">blogOffset</span> <span class="op">=</span> <span class="va">$scope</span>.<span class="at">blogOffset</span> <span class="op">+</span> <span class="dv">1</span><span class="op">;</span>
<span class="va">Blog</span>.<span class="at">async</span>(<span class="st">'https://www.phodal.com/api/v1/app/?limit=10&offset='</span><span class="op">+</span> <span class="va">$scope</span>.<span class="at">blogOffset</span> <span class="op">*</span> <span class="dv">20</span> <span class="op">+</span> <span class="st">'&format=json'</span>).<span class="at">then</span>(<span class="kw">function</span> (results) <span class="op">{</span>
<span class="va">Array</span>.<span class="va">prototype</span>.<span class="va">push</span>.<span class="at">apply</span>(<span class="va">$scope</span>.<span class="at">blogs</span><span class="op">,</span> <span class="va">results</span>.<span class="at">objects</span>)<span class="op">;</span>
<span class="va">$scope</span>.<span class="at">$broadcast</span>(<span class="st">'scroll.infiniteScrollComplete'</span>)<span class="op">;</span>
<span class="op">}</span>)
<span class="op">};</span>
<span class="op">}</span>)</code></pre></div>
<p>结果<strong>时间轴又错了</strong>,人们总是<strong>超前一个时期做错了一个在未来是正确的决定</strong>。人们遇到了网页版的用户授权问题,于是发明了JWT——Json Web Token。</p>
<p>然而,由于WebView在一些早期的Android手机上出现了性能问题,人们开始考虑替换方案。接着出现了两个不同的解决方案:</p>
<ol type="1">
<li>React Native</li>
<li>新的WebView——Crosswalk</li>
</ol>
<p>开发人员开始欢呼React Native这样的框架。但是,他们并没有预见到<strong>人们正在厌恶APP</strong>,APP在我们的迭代里更新着,可能是一星期,可能是两星期,又或者是一个月。谁说APP内自更新不是一件坏事,但是APP的提醒无时无刻不在干扰着人们的生活,噪声越来越多。<strong>不要和用户争夺他们手机的使用权</strong></p>
<h3 id="一次构建跨平台运行">一次构建,跨平台运行</h3>
<p>在我们需要学习C语言的时候,GCC就有了这样的跨平台编译。</p>
<p>在我们开发桌面应用的时候,QT有就这样的跨平台能力。</p>
<p>在我们构建Web应用的时候,Java有这样的跨平台能力。</p>
<p>在我们需要开发跨平台应用的时候,Cordova有这样的跨平台能力。</p>
<p>现在,React这样的跨平台框架又出现了,而响应式设计也是跨平台式的设计。</p>
<p>响应式设计不得不提到的一个缺点是:<strong>他只是将原本在模板层做的事,放到了样式(CSS)层</strong>。你还是在针对着不同的设备进行设计,两种没有什么多大的不同。复杂度不会消失,也不会凭空产生,它只会从一个物体转移到另一个物体或一种形式转为另一种形式。</p>
<p>React,将一小部分复杂度交由人来消化,将另外一部分交给了React自己来消化。在用Spring MVC之前,也许我们还在用CGI编程,而Spring降低了这部分复杂度,但是这和React一样降低的只是新手的复杂度。在我们不能以某种语言的方式写某相关的代码时,这会带来诸多麻烦。</p>
<h2 id="repractise">RePractise</h2>
<p>如果你是一只辛勤的蜜蜂,那么我想你应该都玩过上面那些技术。你是在练习前端的技术,还是在RePractise?如果你不花点时间整理一下过去,顺便预测一下未来,那么你就是在白搭。</p>
<p>前端的演进在这一年特别快,Ruby On Rails也在一个合适的年代里出现,在那个年代里也流行得特别快。RoR开发效率高的优势已然不再突显,语法灵活性的副作用就是运行效率降低,同时后期维护难——每个人元编程了自己。</p>
<p>如果不能把Controller、Model Mapper变成ViewModel,又或者是Micro Services来解耦,那么ES6 + React只是在现在带来更高的开发效率。而所谓的高效率,只是相比较而意淫出来的,因为他只是一层View层。将Model和Controller再加回View层,以后再拆分出来?</p>
<p>现有的结构只是将View层做了View层应该做的事。</p>
<p>首先,你应该考虑的是一种可以让View层解耦于Domain或者Service层。今天,桌面、平板、手机并不是唯一用户设备,虽然你可能在明年统一了这三个平台,现在新的设备的出现又将设备分成两种类型——桌面版和手机版。一开始桌面版和手机版是不同的版本,后来你又需要合并这两个设备。</p>
<p>其次,你可以考虑用混合Micro Services优势的Monolithic Service来分解业务。如果可以举一个成功的例子,那么就是Linux,一个混合内核的“Service”。</p>
<p>最后,Keep Learning。我们总需要在适当的时候做出改变,尽管我们觉得一个Web应用代码库中含桌面版和移动版代码会很不错,但是在那个时候需要做出改变。</p>
<p>对于复杂的应用来说,其架构肯定不是只有纯MVP或者纯MVVM这么简单的。如果一个应用混合了MVVM、MVP和MVC,那么他也变成了MVC——因为他直接访问了Model层。但是如果细分来看,只有访问了Model层的那一部分才是MVC模式。</p>
<p>模式,是人们对于某个解决方案的描述。在一段代码中可能有各种各样的设计模式,更何况是架构。</p>
<h1 id="后台与服务篇">后台与服务篇</h1>
<p>尽管在最初我也想去写一篇文章来说说后台的发展史,后来想了想还是让我们把它划分成不同的几部分。以便于我们可以更好的说说这些内容,不过相信这是一个好的开始。</p>
<h2 id="restful与服务化">RESTful与服务化</h2>
<h3 id="设计restful-api">设计RESTful API</h3>
<blockquote>
<p>REST从资源的角度来观察整个网络,分布在各处的资源由URI确定,而客户端的应用通过URI来获取资源的表征。获得这些表征致使这些应用程序转变了其状态。随着不断获取资源的表征,客户端应用不断地在转变着其状态,所谓表征状态转移。</p>
</blockquote>
<p>因为我们需要的是一个Machine到Machine沟通的平台,需要设计一个API。而设计一个API来说,RESTful是很不错的一种选择,也是主流的选择。而设计一个RESTful服务,的首要步骤便是设计资源模型。</p>
<h3 id="资源">资源</h3>
<p>互联网上的一切信息都可以看作是一种资源。</p>
<table>
<thead>
<tr class="header">
<th style="text-align: left;">HTTP Method</th>
<th style="text-align: left;">Operation Performed</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;">GET</td>
<td style="text-align: left;">Get a resource (Read a resource)</td>
</tr>
<tr class="even">
<td style="text-align: left;">POST</td>
<td style="text-align: left;">Create a resource</td>
</tr>
<tr class="odd">
<td style="text-align: left;">PUT</td>
<td style="text-align: left;">Update a resource</td>
</tr>
<tr class="even">
<td style="text-align: left;">DELETE</td>
<td style="text-align: left;">Delete Resource</td>
</tr>
</tbody>
</table>
<p>设计RESTful API是一个有意思的话题。下面是一些常用的RESTful设计原则:</p>
<ul>
<li>组件间交互的可伸缩性</li>
<li>接口的通用性</li>
<li>组件的独立部署</li>
<li>通过中间组件来减少延迟、实施安全策略和封装已有系统</li>
</ul>
<p>判断是否是 RESTful的约束条件</p>
<ul>
<li>客户端-服务器分离</li>
<li>无状态</li>
<li>可缓存</li>
<li>多层系统</li>
<li>统一接口</li>
<li>随需代码(可选)</li>
</ul>
<h2 id="微服务">微服务</h2>
<h3 id="微内核">微内核</h3>
<p>这只是由微服务与传统架构之间对比而引发的一个思考,让我引一些资料来当参考吧.</p>
<blockquote>
<p>单内核:也称为宏内核。将内核从整体上作为一个大过程实现,并同时运行在一个单独的地址空间。所有的内核服务都在一个地址空间运行,相互之间直接调用函数,简单高效。微内核:功能被划分成独立的过程,过程间通过IPC进行通信。模块化程度高,一个服务失效不会影响另外一个服务。Linux是一个单内核结构,同时又吸收了微内核的优点:模块化设计,支持动态装载内核模块。Linux还避免了微内核设计上的缺陷,让一切都运行在内核态,直接调用函数,无需消息传递。</p>
</blockquote>
<p>对就的微内核便是:</p>
<blockquote>
<p>微内核――在微内核中,大部分内核都作为单独的进程在特权状态下运行,他们通过消息传递进行通讯。在典型情况下,每个概念模块都有一个进程。因此,假如在设计中有一个系统调用模块,那么就必然有一个相应的进程来接收系统调用,并和能够执行系统调用的其他进程(或模块)通讯以完成所需任务。</p>
</blockquote>
<p>如果读过《操作系统原理》及其相关书籍的人应该很了解这些,对就的我们就可以一目了然地解决我们当前是的微服务的问题。</p>
<p>文章的来源是James Lewis与Martin Fowler写的<a href="http://martinfowler.com/articles/microservices.html">Microservices</a>。对就于上面的</p>
<ul>
<li>monolithic kernel</li>
<li>microkernel</li>
</ul>
<p>与文中的</p>
<ul>
<li>monolithic services</li>
<li>microservices</li>
</ul>
<p>我们还是将其翻译成<code>微服务</code>与<code>宏服务</code>。</p>
<p>引起原文中对于微服务的解释:</p>
<blockquote>
<p>简短地说,微服务架构风格是一种使用一套小服务来开发单个应用的方式途径,每个服务运行在自己的进程中,通过轻量的通讯机制联系,经常是基于HTTP资源API,这些服务基于业务能力构建,能够通过自动化部署方式独立部署,这些服务自己有一些小型集中化管理,可以是使用不同的编程语言编写,正如不同的数据存储技术一样。</p>
</blockquote>
<p>原文是:</p>
<blockquote>
<p>In short, the microservice architectural style [1] is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare mininum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.</p>
</blockquote>
<p>而关于微服务的提出是早在2011年的5月份</p>
<blockquote>
<p>The term “microservice” was discussed at a workshop of software architects near Venice in May, 2011 to describe what the participants saw as a common architectural style that many of them had been recently exploring.</p>
</blockquote>
<p>简单地与微内核作一些对比。微内核,<strong>微内核部分经常只但是是个消息转发站</strong>,而微服务从某种意义上也是如此,他们都有着下面的优点。</p>
<ul>
<li>有助于实现模块间的隔离</li>
<li>在不影响系统其他部分的情况下,用更高效的实现代替现有文档系统模块的工作将会更加容易。</li>
</ul>
<p>对于微服务来说</p>
<ul>
<li>每个服务本身都是很简单的</li>
<li>对于每个服务,我们可以选择最好和最合适的工具来开发</li>
<li>系统本质上是松耦合的</li>
<li>不同的团队可以工作在不同的服务中</li>
<li>可以持续发布,而其他部分还是稳定的</li>
</ul>
<p>从某种意义上来说微服务更适合于大型企业架构,而不是一般的应用,对于一般的应用来说他们的都在同一台主机上。无力于支付更多的系统开销,于是如<strong>微服务不是免费的午餐</strong>一文所说</p>
<ul>
<li>微服务带来很多的开销操作</li>
<li>大量的DevOps技能要求</li>
<li>隐式接口</li>
<li>重复努力</li>
<li>分布式系统的复杂性</li>
<li>异步性是困难的!</li>
<li>可测试性挑战</li>
</ul>
<p>因而不得不再后面补充一些所知的额外的东西。</p>
<p>针对于同样的话题,开始了解其中的一些问题。当敏捷的思想贯穿于开发过程时,我们不得不面对持续集成与发布这样的问题。我们确实可以在不同的服务下工作,然而当我们需要修改API时,就对我们的集成带来很多的问题。我们需要同时修改两个API!我们也需要同时部署他们!</p>
<h2 id="混合服务">混合服务</h2>
<p>参考</p>
<p><a href="http://highscalability.com/blog/2014/4/8/microservices-not-a-free-lunch.html">Microservices - Not A Free Lunch!</a></p>
<p><a href="http://martinfowler.com/articles/microservices.html">Microservices</a></p>
<h1 id="易读">易读</h1>
<h2 id="简介">简介</h2>
<h3 id="编程经验">编程经验</h3>
<blockquote>
<p>只要我有更多时间,我就会写一封更短的信给你。</p>
</blockquote>
<p>从小学算起我的编程年限应该也有十几年了吧,笑~~。只是我过去的多年编程经验对于我现在的工作来说,是多年的无关经验(详见《REWORK》——多年的无关经验)。</p>
<p>高中的时候学习了点游戏编程,也因此学了点C++的皮毛,除了学会面向对象,其他都忘光了。随后在学习Linux内核,当时代码里就各种struct。比起之前学过的Logo和QBASIC简直是特别大的进步,然当时觉得struct与面向对象两者间没啥太大区别。在那个年少的时候,便天真的以为程序语言间的区别不是很大。</p>
<p>大学的时候主要营业范围是各种硬件,也没有发现写出好的代码是特别重要的一件事。也试了试Lisp,尝试过设计模式,然后失败了,GoF写DP的时候一定花了特别长的时间,所以这本书很短。期间出于生活压力(没有钱买硬件),便开始兼职各种Web前端开发。</p>
<p>在有了所谓的GNU/Linux系统编译经验、写过各种杂七杂八的硬件代码,如Ada、汇编,要保证代码工作是一件很简单的事,从某个项目中引入部分代码,再从某个Demo中引入更多的代码,东拼西凑一下就能工作了。</p>
<p>多年的无关经验只让我写出能工作的代码——在别人看来就是很烂的代码。于是,虽然有着看上去很长的编程经验,但是却比不上实习的时候6个月学到的东西。</p>
<p>只是因为,我们不知道: 我们不知道。</p>
<h3 id="代码整洁">代码整洁</h3>
<p>过去,我有过在不同的场合吐槽别人的代码写得烂。而我写的仅仅是比别人好一点而已——而不是好很多。</p>
<p>然而这是一件很难的事,人们对于同一件事物未来的考虑都是不一样的。同样的代码在相同的情景下,不同的人会有不同的设计模式。同样的代码在不同的情景下,同样的人会有不同的设计模式。在这里,我们没有办法讨论设计模式,也不需要讨论。</p>
<p>我们所需要做的是,确保我们的代码易读、易测试,看上去这样就够了,然而这也是挺复杂的一件事:</p>
<ol type="1">
<li>确保我们的变量名、函数名是易读的</li>
<li>没有复杂的逻辑判断</li>
<li>没有多层嵌套</li>
<li>减少复杂函数的出现</li>
</ol>
<p>然后,你要去测试它。这样你就知道需要什么,实际上要做到这些也不是一些难事。</p>
<p>只是首先,我们要知道我们要自己需要这些。</p>
<h3 id="别人的代码很烂">别人的代码很烂?</h3>
<p>什么是很烂的代码? 应该会有几种境界吧。</p>
<ol type="1">
<li>不能工作,不能读懂</li>
<li>不能工作,能读懂</li>
<li>能工作,很难读懂</li>
<li>能工作,能读懂,但是没有意图</li>
<li>能工作,能理解意图,但是读不懂</li>
</ol>
<p>如果我们能读懂,能理解意图,那么我们还说他烂,可能是因为他并不整洁。这就回到了上面的问题,模式是一种因人而异的东西。</p>
<p>我们在做Code Review的时候,总会尝试问对方说: “这样做的意图是”。</p>
<p>对于代码来说也是如此,如果我们能理解意图的话,那么我们要理解代码相对也比较容易。如果对方是没有意图,那么代码是没救的。</p>
<h2 id="变量名">变量名</h2>
<h2 id="函数名">函数名</h2>
<h2 id="小函数">小函数</h2>
<h2 id="测试">测试</h2>
<h1 id="重构篇">重构篇</h1>
<p>什么是重构?</p>
<blockquote>
<p>重构,一言以蔽之,就是在不改变外部行为的前提下,有条不紊地改善代码。</p>
</blockquote>
<p>相似的</p>
<blockquote>
<p>代码重构(英语:Code refactoring)指对软件代码做任何更动以增加可读性或者简化结构而不影响输出结果。</p>
</blockquote>
<h2 id="网站重构">网站重构</h2>
<p>与上述相似的是:在不改变外部行为的前提下,简化结构、添加可读性,而在网站前端保持一致的行为。也就是说是在不改变UI的情况下,对网站进行优化,在扩展的同时保持一致的UI。</p>
<p>过去人们所说的<code>网站重构</code></p>
<blockquote>
<p>把“未采用CSS,大量使用HTML进行定位、布局,或者虽然已经采用CSS,但是未遵循HTML结构化标准的站点”变成“让标记回归标记的原本意义。通过在HTML文档中使用结构化的标记以及用CSS控制页面表现,使页面的实际内容与它们呈现的格式相分离的站点。”的过程就是网站重构(Website Reconstruction)</p>
</blockquote>
<p>依照我做过的一些案例,对于传统的网站来说重构通常是</p>
<ul>
<li>表格(table)布局改为DIV+CSS</li>
<li>使网站前端兼容于现代浏览器(针对于不合规范的CSS、如对IE6有效的)</li>
<li>对于移动平台的优化</li>
<li>针对于SEO进行优化</li>
</ul>
<p>过去的网站重构就是“DIV+CSS”,想法固然极度局限。但也不是另一部分的人认为是“XHTML+CSS”,因为“XHTML+CSS”只是页面重构。</p>
<p>而真正的网站重构</p>
<blockquote>
<p>应包含结构、行为、表现三层次的分离以及优化,行内分工优化,以及以技术与数据、人文为主导的交互优化等。</p>
</blockquote>
<p>深层次的网站重构应该考虑的方面</p>
<ul>
<li>减少代码间的耦合</li>
<li>让代码保持弹性</li>
<li>严格按规范编写代码</li>
<li>设计可扩展的API</li>
<li>代替旧有的框架、语言(如VB)</li>
<li>增强用户体验</li>
</ul>
<p>通常来说对于速度的优化也包含在重构中</p>
<ul>
<li>压缩JS、CSS、image等前端资源(通常是由服务器来解决)</li>
<li>程序的性能优化(如数据读写)</li>
<li>采用CDN来加速资源加载</li>
<li>对于JS DOM的优化</li>
<li>HTTP服务器的文件缓存</li>
</ul>
<p>可以应用的的方面</p>
<ul>
<li><a href="http://www.phodal.com/blog/nginx-with-ngx-pagespeed-module-improve-website-cache/">使用Ngx_pagespeed优化前端</a></li>
<li>解耦复杂的模块</li>
<li>对缓存进行优化</li>
<li>针对于内容创建或预留API</li>
<li>需要添加新API,如(weChat等的支持)</li>
<li>用新的语言、框架代码旧的框架(如VB.NET,C#.NET)</li>
</ul>
<h3 id="网站重构目的">网站重构目的</h3>
<p>希望自己的网站</p>
<ul>
<li>成本变得更低</li>
<li>运行得更好</li>
<li>访问者更多</li>
<li>维护愈加简单</li>
<li>功能更强</li>
</ul>
<h2 id="代码重构">代码重构</h2>
<p>在经历了一年多的工作之后,我平时的主要工作就是修Bug。刚开始的时候觉得无聊,后来才发现修Bug需要更好的技术。有时候你可能要面对着一坨一坨的代码,有时候你可能要花几天的时间去阅读代码。而,你重写那几十代码可能只会花上你不到一天的时间。但是如果你没办法理解当时为什么这么做,你的修改只会带来更多的bug。修Bug,更多的是维护代码。还是前人总结的那句话对:</p>
<blockquote>
<p>写代码容易,读代码难。</p>
</blockquote>
<h2 id="使用工具重构">使用工具重构</h2>
<h2 id="借助工具重构">借助工具重构</h2>
<ul>
<li>当你写了一大堆代码,你没有意识到里面有一大堆重复。</li>
<li>当你写了一大堆测试,却不知道覆盖率有多少。</li>
</ul>
<p>这就是个问题了,于是偶然间看到了一个叫code climate的网站。</p>
<h3 id="code-climate">Code Climate</h3>
<blockquote>
<p>Code Climate consolidates the results from a suite of static analysis tools into a single, real-time report, giving your team the information it needs to identify hotspots, evaluate new approaches, and improve code quality.</p>
</blockquote>
<p>Code Climate整合一组静态分析工具的结果到一个单一的,实时的报告,让您的团队需要识别热点,探讨新的方法,提高代码质量的信息。</p>
<p>简单地来说:</p>
<ul>
<li>对我们的代码评分</li>
<li>找出代码中的坏味道</li>
</ul>
<p>于是,我们先来了个例子</p>
<table>
<thead>
<tr class="header">
<th style="text-align: left;">Rating</th>
<th style="text-align: left;">Name</th>
<th style="text-align: left;">Complexity</th>
<th style="text-align: left;">Duplication</th>
<th style="text-align: left;">Churn</th>
<th style="text-align: left;">C/M</th>
<th style="text-align: left;">Coverage</th>
<th style="text-align: left;">Smells</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;">A</td>
<td style="text-align: left;">lib/coap/coap_request_handler.js</td>
<td style="text-align: left;">24</td>
<td style="text-align: left;">0</td>
<td style="text-align: left;">6</td>
<td style="text-align: left;">2.6</td>
<td style="text-align: left;">46.4%</td>
<td style="text-align: left;">0</td>
</tr>
<tr class="even">
<td style="text-align: left;">A</td>
<td style="text-align: left;">lib/coap/coap_result_helper.js</td>
<td style="text-align: left;">14</td>
<td style="text-align: left;">0</td>
<td style="text-align: left;">2</td>
<td style="text-align: left;">3.4</td>
<td style="text-align: left;">80.0%</td>
<td style="text-align: left;">0</td>
</tr>
<tr class="odd">
<td style="text-align: left;">A</td>
<td style="text-align: left;">lib/coap/coap_server.js</td>
<td style="text-align: left;">16</td>
<td style="text-align: left;">0</td>
<td style="text-align: left;">5</td>
<td style="text-align: left;">5.2</td>
<td style="text-align: left;">44.0%</td>
<td style="text-align: left;">0</td>
</tr>
<tr class="even">
<td style="text-align: left;">A</td>
<td style="text-align: left;">lib/database/db_factory.js</td>
<td style="text-align: left;">8</td>
<td style="text-align: left;">0</td>
<td style="text-align: left;">3</td>
<td style="text-align: left;">3.8</td>
<td style="text-align: left;">92.3%</td>
<td style="text-align: left;">0</td>
</tr>
<tr class="odd">
<td style="text-align: left;">A</td>
<td style="text-align: left;">lib/database/iot_db.js</td>
<td style="text-align: left;">7</td>
<td style="text-align: left;">0</td>
<td style="text-align: left;">6</td>
<td style="text-align: left;">1.0</td>
<td style="text-align: left;">58.8%</td>
<td style="text-align: left;">0</td>
</tr>
<tr class="even">
<td style="text-align: left;">A</td>
<td style="text-align: left;">lib/database/mongodb_helper.js</td>
<td style="text-align: left;">63</td>
<td style="text-align: left;">0</td>
<td style="text-align: left;">11</td>
<td style="text-align: left;">4.5</td>
<td style="text-align: left;">35.0%</td>
<td style="text-align: left;">0</td>
</tr>
<tr class="odd">
<td style="text-align: left;">C</td>
<td style="text-align: left;">lib/database/sqlite_helper.js</td>
<td style="text-align: left;">32</td>
<td style="text-align: left;">86</td>
<td style="text-align: left;">10</td>
<td style="text-align: left;">4.5</td>
<td style="text-align: left;">35.0%</td>
<td style="text-align: left;">2</td>
</tr>
<tr class="even">
<td style="text-align: left;">B</td>
<td style="text-align: left;">lib/rest/rest_helper.js</td>
<td style="text-align: left;">19</td>
<td style="text-align: left;">62</td>
<td style="text-align: left;">3</td>
<td style="text-align: left;">4.7</td>
<td style="text-align: left;">37.5%</td>
<td style="text-align: left;">2</td>
</tr>
<tr class="odd">
<td style="text-align: left;">A</td>
<td style="text-align: left;">lib/rest/rest_server.js</td>
<td style="text-align: left;">17</td>
<td style="text-align: left;">0</td>
<td style="text-align: left;">2</td>
<td style="text-align: left;">8.6</td>
<td style="text-align: left;">88.9%</td>
<td style="text-align: left;">0</td>
</tr>
<tr class="even">
<td style="text-align: left;">A</td>
<td style="text-align: left;">lib/url_handler.js</td>
<td style="text-align: left;">9</td>
<td style="text-align: left;">0</td>
<td style="text-align: left;">5</td>
<td style="text-align: left;">2.2</td>
<td style="text-align: left;">94.1%</td>
<td style="text-align: left;">0</td>
</tr>
</tbody>
</table>
<p>分享得到的最后的结果是:</p>
<figure>
<img src="http://repractise.phodal.com/img/refactor/coverage.png" alt="Coverage" /><figcaption>Coverage</figcaption>
</figure>
<h4 id="代码的坏味道">代码的坏味道</h4>
<p>于是我们就打开<code>lib/database/sqlite_helper.js</code>,因为其中有两个坏味道</p>
<blockquote>
<p>Similar code found in two :expression_statement nodes (mass = 86)</p>
</blockquote>
<p>在代码的 <code>lib/database/sqlite_helper.js:58…61 < ></code></p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">deleteData</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
<span class="st">'use strict'</span><span class="op">;</span>
<span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">"DELETE FROM "</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">" where "</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getKeyFromURL</span>(url) <span class="op">+</span> <span class="st">"="</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getValueFromURL</span>(url)<span class="op">;</span>
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">basic</span>(sql_command<span class="op">,</span> callback)<span class="op">;</span></code></pre></div>
<p>lib/database/sqlite_helper.js:64…67 < ></p>
<p>与</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">getData</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
<span class="st">'use strict'</span><span class="op">;</span>
<span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">"SELECT * FROM "</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">" where "</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getKeyFromURL</span>(url) <span class="op">+</span> <span class="st">"="</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getValueFromURL</span>(url)<span class="op">;</span>
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">basic</span>(sql_command<span class="op">,</span> callback)<span class="op">;</span></code></pre></div>
<p>只是这是之前修改过的重复。。</p>
<p>原来的代码是这样的</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">postData</span> <span class="op">=</span> <span class="kw">function</span> (block<span class="op">,</span> callback) <span class="op">{</span>
<span class="st">'use strict'</span><span class="op">;</span>
<span class="kw">var</span> db <span class="op">=</span> <span class="kw">new</span> <span class="va">sqlite3</span>.<span class="at">Database</span>(<span class="va">config</span>.<span class="at">db_name</span>)<span class="op">;</span>
<span class="kw">var</span> str <span class="op">=</span> <span class="kw">this</span>.<span class="at">parseData</span>(<span class="va">config</span>.<span class="at">keys</span>)<span class="op">;</span>
<span class="kw">var</span> string <span class="op">=</span> <span class="kw">this</span>.<span class="at">parseData</span>(block)<span class="op">;</span>
<span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">"insert or replace into "</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">" ("</span> <span class="op">+</span> str <span class="op">+</span> <span class="st">") VALUES ("</span> <span class="op">+</span> string <span class="op">+</span> <span class="st">");"</span><span class="op">;</span>
<span class="va">db</span>.<span class="at">all</span>(sql_command<span class="op">,</span> <span class="kw">function</span> (err) <span class="op">{</span>
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">errorHandler</span>(err)<span class="op">;</span>
<span class="va">db</span>.<span class="at">close</span>()<span class="op">;</span>
<span class="at">callback</span>()<span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span>
<span class="op">};</span>
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">deleteData</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
<span class="st">'use strict'</span><span class="op">;</span>
<span class="kw">var</span> db <span class="op">=</span> <span class="kw">new</span> <span class="va">sqlite3</span>.<span class="at">Database</span>(<span class="va">config</span>.<span class="at">db_name</span>)<span class="op">;</span>
<span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">"DELETE FROM "</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">" where "</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getKeyFromURL</span>(url) <span class="op">+</span> <span class="st">"="</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getValueFromURL</span>(url)<span class="op">;</span>
<span class="va">db</span>.<span class="at">all</span>(sql_command<span class="op">,</span> <span class="kw">function</span> (err) <span class="op">{</span>
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">errorHandler</span>(err)<span class="op">;</span>
<span class="va">db</span>.<span class="at">close</span>()<span class="op">;</span>
<span class="at">callback</span>()<span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span>
<span class="op">};</span>
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">getData</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
<span class="st">'use strict'</span><span class="op">;</span>
<span class="kw">var</span> db <span class="op">=</span> <span class="kw">new</span> <span class="va">sqlite3</span>.<span class="at">Database</span>(<span class="va">config</span>.<span class="at">db_name</span>)<span class="op">;</span>
<span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">"SELECT * FROM "</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">" where "</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getKeyFromURL</span>(url) <span class="op">+</span> <span class="st">"="</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getValueFromURL</span>(url)<span class="op">;</span>
<span class="va">db</span>.<span class="at">all</span>(sql_command<span class="op">,</span> <span class="kw">function</span> (err<span class="op">,</span> rows) <span class="op">{</span>
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">errorHandler</span>(err)<span class="op">;</span>
<span class="va">db</span>.<span class="at">close</span>()<span class="op">;</span>
<span class="at">callback</span>(<span class="va">JSON</span>.<span class="at">stringify</span>(rows))<span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span>
<span class="op">};</span></code></pre></div>
<p>说的也是大量的重复,重构完的代码</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">basic</span> <span class="op">=</span> <span class="kw">function</span>(sql<span class="op">,</span> db_callback)<span class="op">{</span>