forked from phodal/github
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
2527 lines (2412 loc) · 192 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>GitHub 漫游指南 – </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>GitHub 漫游指南</h1>
<p>项目首页: <a href="https://github.com/phodal/github-roam">GitHub 漫游指南</a></p>
<p>By <a href="https://www.phodal.com">Phodal Huang</a>(微博、知乎、GitHub、SegmentFault: @<a href="http://weibo.com/phodal">phodal</a>)
</p>
<p>微信公众号</p>
<img src="./img/qrcode.jpg" alt=""/>
</p>
<div style="width:800px">
<iframe src="http://ghbtns.com/github-btn.html?user=phodal&repo=github-roam&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="#我与github的故事">我与GitHub的故事</a><ul>
<li><a href="#github与收获">GitHub与收获</a></li>
<li><a href="#github与成长">GitHub与成长</a></li>
</ul></li>
<li><a href="#为什么你应该深入github">为什么你应该深入GitHub</a><ul>
<li><a href="#方便工作">方便工作</a></li>
<li><a href="#获得一份工作">获得一份工作</a></li>
<li><a href="#扩大交际">扩大交际</a></li>
</ul></li>
</ul></li>
<li><a href="#git基本知识与github使用">Git基本知识与GitHub使用</a><ul>
<li><a href="#git">Git</a><ul>
<li><a href="#git初入">Git初入</a></li>
</ul></li>
<li><a href="#github">GitHub</a><ul>
<li><a href="#版本管理与软件部署">版本管理与软件部署</a></li>
<li><a href="#github与git">GitHub与Git</a></li>
<li><a href="#在github创建项目">在GitHub创建项目</a></li>
</ul></li>
<li><a href="#github流行项目分析">GitHub流行项目分析</a></li>
<li><a href="#pull-request">Pull Request</a><ul>
<li><a href="#我的第一个pr">我的第一个PR</a></li>
<li><a href="#cla">CLA</a></li>
</ul></li>
</ul></li>
<li><a href="#构建github项目">构建GitHub项目</a><ul>
<li><a href="#如何用好github">如何用好GitHub</a><ul>
<li><a href="#敏捷软件开发">敏捷软件开发</a></li>
<li><a href="#测试">测试</a></li>
<li><a href="#ci">CI</a></li>
<li><a href="#代码质量">代码质量</a></li>
</ul></li>
<li><a href="#模块分离与测试">模块分离与测试</a><ul>
<li><a href="#代码模块化">代码模块化</a></li>
<li><a href="#自动化测试">自动化测试</a></li>
<li><a href="#jshint">Jshint</a></li>
<li><a href="#mocha">Mocha</a></li>
<li><a href="#测试示例">测试示例</a></li>
</ul></li>
<li><a href="#代码质量与重构">代码质量与重构</a><ul>
<li><a href="#code-climate">Code Climate</a></li>
<li><a href="#代码的坏味道">代码的坏味道</a></li>
</ul></li>
</ul></li>
<li><a href="#创建项目文档">创建项目文档</a><ul>
<li><a href="#readme">README</a></li>
<li><a href="#在线文档">在线文档</a></li>
<li><a href="#可用示例">可用示例</a></li>
</ul></li>
<li><a href="#测试-1">测试</a><ul>
<li><a href="#tdd">TDD</a><ul>
<li><a href="#一次测试驱动开发">一次测试驱动开发</a></li>
<li><a href="#说说tdd">说说TDD</a></li>
<li><a href="#tdd思考">TDD思考</a></li>
</ul></li>
<li><a href="#功能测试">功能测试</a><ul>
<li><a href="#轻量级网站测试twill">轻量级网站测试TWill</a></li>
<li><a href="#twill-登陆测试">Twill 登陆测试</a></li>
<li><a href="#twill-测试脚本">Twill 测试脚本</a></li>
</ul></li>
<li><a href="#fake-server">Fake Server</a></li>
</ul></li>
<li><a href="#重构">重构</a><ul>
<li><a href="#为什么重构">为什么重构?</a></li>
<li><a href="#重构umarkdown">重构uMarkdown</a><ul>
<li><a href="#代码说明">代码说明</a></li>
</ul></li>
<li><a href="#intellij-idea重构">Intellij Idea重构</a><ul>
<li><a href="#rename">Rename</a></li>
<li><a href="#extract-method">Extract Method</a></li>
<li><a href="#inline-method">Inline Method</a></li>
<li><a href="#pull-members-up">Pull Members Up</a></li>
<li><a href="#重构之以查询取代临时变量">重构之以查询取代临时变量</a></li>
</ul></li>
</ul></li>
<li><a href="#如何在github寻找灵感fork">如何在GitHub“寻找灵感(fork)”</a><ul>
<li><a href="#lettuce构建过程">Lettuce构建过程</a><ul>
<li><a href="#需求">需求</a></li>
<li><a href="#计划">计划</a></li>
<li><a href="#实现第一个需求">实现第一个需求</a></li>
<li><a href="#实现第二个需求">实现第二个需求</a></li>
</ul></li>
</ul></li>
<li><a href="#github用户分析">GitHub用户分析</a><ul>
<li><a href="#生成图表">生成图表</a><ul>
<li><a href="#数据解析">数据解析</a></li>
<li><a href="#matplotlib">Matplotlib</a></li>
</ul></li>
<li><a href="#每周分析">每周分析</a><ul>
<li><a href="#python-github-每周情况分析">python github 每周情况分析</a></li>
<li><a href="#python-数据分析">Python 数据分析</a></li>
<li><a href="#python-matplotlib图表">Python Matplotlib图表</a></li>
</ul></li>
<li><a href="#存储到数据库中">存储到数据库中</a><ul>
<li><a href="#sqlite3">SQLite3</a></li>
<li><a href="#数据导入">数据导入</a></li>
<li><a href="#redis">Redis</a></li>
</ul></li>
<li><a href="#邻近算法与相似用户">邻近算法与相似用户</a></li>
</ul></li>
<li><a href="#github连击">GitHub连击</a><ul>
<li><a href="#天">100天</a><ul>
<li><a href="#天的提升">40天的提升</a></li>
<li><a href="#天的挑战">100天的挑战</a></li>
<li><a href="#天的希冀">140天的希冀</a></li>
</ul></li>
<li><a href="#天的showcase">200天的Showcase</a><ul>
<li><a href="#一些项目简述">一些项目简述</a></li>
<li><a href="#google-map-solr-polygon-搜索">google map solr polygon 搜索</a></li>
<li><a href="#技能树">技能树</a></li>
</ul></li>
<li><a href="#天-1">365天</a><ul>
<li><a href="#编程的基础能力">编程的基础能力</a></li>
<li><a href="#技术与框架设计">技术与框架设计</a></li>
<li><a href="#领域与练习">领域与练习</a></li>
<li><a href="#其他-1">其他</a></li>
</ul></li>
<li><a href="#天-2">500天</a><ul>
<li><a href="#天与10000小时">500天与10000小时</a></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="#天里">365*2-7天里</a><ul>
<li><a href="#编码的练习">编码的练习</a></li>
<li><a href="#see-you-again">See you Again</a></li>
</ul></li>
</ul></li>
</ul>
</nav>
<h1 id="前言">前言</h1>
<p>我的GitHub主页上写着加入的时间——<code>Joined on Nov 8, 2010</code>,那时才大一,在那之后的那长日子里我都没有过到。也许是因为我学的不是计算机,到了今天——<code>2015.3.9</code>,我也发现这其实是程序员的社交网站。</p>
<p>过去,曾经有很长的一些时间我试过在GitHub上连击,也试着去了解别人是如何用好这个工具的。当然粉丝在GitHub上也是很重要的。</p>
<p>在这里,我会试着将我在GitHub上学到的东西一一分享出来。</p>
<h2 id="我与github的故事">我与GitHub的故事</h2>
<p>在我大四找工作的时候,试图去寻找一份硬件、物联网相关的工作(ps: 专业是电子信息工程)。尽管简历上写得满满的各种经历、经验,然而并没有卵用。跑了几场校园招聘会后,十份简历(ps: 事先已经有心里准备)一个也没有投出去——因为学校直接被拒。我对霸面什么的一点兴趣都没有,千里马需要伯乐。后来,我加入了Martin Flower所在的公司,当然这是后话了。</p>
<p>这是一个残酷的世界,在学生时代,如果你长得不帅不高的话,那么多数的附加技能都是白搭(ps: 通常富的是看不到这篇文章的)。在工作时期,如果你上家没有名气,那么将会影响你下一份工作的待遇。而,很多东西却会改变这些,GitHub就是其中一个。</p>
<p>注册GitHub的时候大概是大一的时候,我熟悉的时候已经是大四了,现在已经毕业一年了。在过去的近两年里,我试着以几个维度在GitHub上创建项目:</p>
<ol type="1">
<li>快速上手框架来实战,即demo</li>
<li>重构别人的代码</li>
<li>创建自己可用的框架</li>
<li>快速构建大型应用</li>
<li>构建通用的框架</li>
</ol>
<h3 id="github与收获">GitHub与收获</h3>
<p>先说说<strong>与技能无关的收获</strong>吧,毕业设计做的是一个《<a href="https://github.com/phodal/iot">最小物联网系统</a>》,考虑到我们专业老师没有这方面知识,答辩时会带来问题,尽量往这方面靠拢。当我毕业后,这个项目已经有过百个star了,这样易上手的东西还是比较受欢迎的(ps: 不过这种硬件相关的项目通常受限于GitHub上硬件开发工程师比较少的困扰)。</p>
<p>毕业后一个月收到PACKT出版社的邮件(ps: 他们是在github上找到我的),内容是关于Review一本<a href="iot">物联网</a>书籍,即在《<a href="http://www.phodal.com/blog/review-it-books-with-translate-book/">从Review到翻译IT书籍</a>》中提到的《Learning Internet of Things》。作为一个四级没过的“物联网专家”,去审阅一本英文的物联网书籍。。。</p>
<p>当然,后来是审阅完了,书上有我的英文简介。</p>
<figure>
<img src="./img/phodal-intro.jpg" alt="Phodal Huang Introduction" /><figcaption>Phodal Huang Introduction</figcaption>
</figure>
<p>一个月前,收到MANNING出版社的邮件(ps: 也是在github上),关于Review一本<a href="iot">物联网</a>书籍的目录,并提出建议。</p>
<p>也因此带来了其他更多的东西,当然不是这里的主题。在这里,我们就不讨论各种骚扰邮件,或者中文合作。从没有想象过,我也可以在英语世界有一片小天地。</p>
<p>这些告诉我们,GitHub上找一个你擅长的主题,那么会有很多人找上你的。</p>
<h3 id="github与成长">GitHub与成长</h3>
<p>过去写过一篇《<a href="http://www.phodal.com/blog/use-github-grow-self/">如何通过github提升自己</a>》的文章,现在只想说三点:</p>
<ol type="1">
<li>测试</li>
<li>更多的测试</li>
<li>更多的、更多的、更多的测试</li>
</ol>
<p>没有测试的项目是很扯淡的,除非你的项目只有一个函数,然后那个函数返回<code>Hello,World</code>。</p>
<p>如果你的项目代码有上千行,如果你能保证测试覆盖率可以达到95%以的话,那么我想你的项目不会有太复杂的函数。假使有这样的函数,那么他也是被测试覆盖住的。</p>
<p>如果你在用心做这个项目,那么你看到代码写得不好也会试着改进,即重构。当有了一些,你的技能会不断提升。你开始会试着接触更多的东西,如stub,如mock,如fakeserver。</p>
<p>有一天,你会发现你离不开测试。</p>
<p>然后就会相信: <strong>那些没有写测试的项目都是在耍流氓</strong></p>
<h2 id="为什么你应该深入github">为什么你应该深入GitHub</h2>
<p>上面我们说的都是我们可以收获到的东西,我们开始尝试就意味着我们知道它可能给我们带来好处。上面已经提到很多可以提升自己的例子了,这里再说说其他的。</p>
<h3 id="方便工作">方便工作</h3>
<p>我们可以从中获取到不同的知识、内容、信息。每个人都可以从别人的代码中学习,当我们需要构建一个库的时候我们可以在上面寻找不同的库和代码来实现我们的功能。如当我在实现一个库的时候,我会在GitHub上到相应的组件:</p>
<ul>
<li>Promise 支持</li>
<li>Class类(ps:没有一个好的类使用的方式)</li>
<li>Template 一个简单的模板引擎</li>
<li>Router 用来控制页面的路由</li>
<li>Ajax 基本的Ajax Get/Post请求</li>
</ul>
<h3 id="获得一份工作">获得一份工作</h3>
<p>越来越多的人因为GitHub获得工作,因为他们的做的东西正好符合一些公司的要求。那么,这些公司在寻找代码的时候,就会试着邀请他们。</p>
<p>因而,在GitHub寻找合适的候选人,已经是一种趋势。</p>
<h3 id="扩大交际">扩大交际</h3>
<p>如果我们想创造出更好、强大地框架时,那么认识更多的人可能会带来更多的帮助。有时候会同上面那一点一样的效果</p>
<hr />
<h1 id="git基本知识与github使用">Git基本知识与GitHub使用</h1>
<h2 id="git">Git</h2>
<p>从一般开发者的角度来看,git有以下功能:</p>
<ol type="1">
<li>从服务器上克隆数据库(包括代码和版本信息)到单机上。</li>
<li>在自己的机器上创建分支,修改代码。</li>
<li>在单机上自己创建的分支上提交代码。</li>
<li>在单机上合并分支。</li>
<li>新建一个分支,把服务器上最新版的代码fetch下来,然后跟自己的主分支合并。</li>
<li>生成补丁(patch),把补丁发送给主开发者。</li>
<li>看主开发者的反馈,如果主开发者发现两个一般开发者之间有冲突(他们之间可以合作解决的冲突),就会要求他们先解决冲突,然后再由其中一个人提交。如果主开发者可以自己解决,或者没有冲突,就通过。</li>
<li>一般开发者之间解决冲突的方法,开发者之间可以使用pull 命令解决冲突,解决完冲突之后再向主开发者提交补丁。</li>
</ol>
<p>从主开发者的角度(假设主开发者不用开发代码)看,git有以下功能:</p>
<ol type="1">
<li>查看邮件或者通过其它方式查看一般开发者的提交状态。</li>
<li>打上补丁,解决冲突(可以自己解决,也可以要求开发者之间解决以后再重新提交,如果是开源项目,还要决定哪些补丁有用,哪些不用)。</li>
<li>向公共服务器提交结果,然后通知所有开发人员。</li>
</ol>
<h3 id="git初入">Git初入</h3>
<p>如果是第一次使用Git,你需要设置署名和邮箱:</p>
<pre><code>$ git config --global user.name "用户名"
$ git config --global user.email "电子邮箱"</code></pre>
<p>将代码仓库clone到本地,其实就是将代码复制到你的机器里,并交由Git来管理:</p>
<pre><code>$ git clone [email protected]:someone/symfony-docs-chs.git</code></pre>
<p>你可以修改复制到本地的代码了(symfony-docs-chs项目里都是rst格式的文档)。当你觉得完成了一定的工作量,想做个阶段性的提交:</p>
<p>向这个本地的代码仓库添加当前目录的所有改动:</p>
<pre><code>$ git add .</code></pre>
<p>或者只是添加某个文件:</p>
<pre><code>$ git add -p</code></pre>
<p>我们可以输入</p>
<pre><code>$git status</code></pre>
<p>来看现在的状态,如下图是添加之前的:</p>
<figure>
<img src="./img/before-add.png" alt="Before add" /><figcaption>Before add</figcaption>
</figure>
<p>下面是添加之后 的</p>
<figure>
<img src="./img/after-add.png" alt="After add" /><figcaption>After add</figcaption>
</figure>
<p>可以看到状态的变化是从黄色到绿色,即unstage到add。</p>
<h2 id="github">GitHub</h2>
<p>Wiki百科上是这么说的</p>
<blockquote>
<p>GitHub 是一个共享虚拟主机服务,用于存放使用Git版本控制的软件代码和内容项目。它由GitHub公司(曾称Logical Awesome)的开发者Chris Wanstrath、PJ Hyett和Tom Preston-Werner 使用Ruby on Rails编写而成。</p>
</blockquote>
<p>当然让我们看看官方的介绍:</p>
<blockquote>
<p>GitHub is the best place to share code with friends, co-workers, classmates, and complete strangers. Over eight million people use GitHub to build amazing things together.</p>
</blockquote>
<p>它还是什么?</p>
<ul>
<li>网站</li>
<li>免费博客</li>
<li>管理配置文件</li>
<li>收集资料</li>
<li>简历</li>
<li>管理代码片段</li>
<li>托管编程环境</li>
<li>写作</li>
</ul>
<p>等等。看上去像是大餐,但是你还需要了解点什么?</p>
<h3 id="版本管理与软件部署">版本管理与软件部署</h3>
<p>jQuery[^jQuery]在发布版本<code>2.1.3</code>,一共有152个commit。我们可以看到如下的提交信息:</p>
<ul>
<li>Ajax: Always use script injection in globalEval … bbdfbb4</li>
<li>Effects: Reintroduce use of requestAnimationFrame … 72119e0</li>
<li>Effects: Improve raf logic … 708764f</li>
<li>Build: Move test to appropriate module fbdbb6f</li>
<li>Build: Update commitplease dev dependency</li>
<li>…</li>
</ul>
<h3 id="github与git">GitHub与Git</h3>
<blockquote>
<p>Git是一个分布式的版本控制系统,最初由Linus Torvalds编写,用作Linux内核代码的管理。在推出后,Git在其它项目中也取得了很大成功,尤其是在Ruby社区中。目前,包括Rubinius、Merb和Bitcoin在内的很多知名项目都使用了Git。Git同样可以被诸如Capistrano和Vlad the Deployer这样的部署工具所使用。</p>
</blockquote>
<blockquote>
<p>GitHub可以托管各种git库,并提供一个web界面,但与其它像 SourceForge或Google Code这样的服务不同,GitHub的独特卖点在于从另外一个项目进行分支的简易性。为一个项目贡献代码非常简单:首先点击项目站点的“fork”的按钮,然后将代码检出并将修改加入到刚才分出的代码库中,最后通过内建的“pull request”机制向项目负责人申请代码合并。已经有人将GitHub称为代码玩家的MySpace。</p>
</blockquote>
<h3 id="在github创建项目">在GitHub创建项目</h3>
<p>接着,我们试试在上面创建一个项目:</p>
<figure>
<img src="./img/github-roam-create.jpg" alt="GitHub Roam" /><figcaption>GitHub Roam</figcaption>
</figure>
<p>就会有下面的提醒:</p>
<figure>
<img src="./img/project-init.jpg" alt="GitHub Roam" /><figcaption>GitHub Roam</figcaption>
</figure>
<p>它提供多种方式的创建方法:</p>
<blockquote>
<p>…or create a new repository on the command line</p>
</blockquote>
<pre><code>echo "# github-roam" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin [email protected]:phodal/github-roam.git
git push -u origin master</code></pre>
<blockquote>
<p>…or push an existing repository from the command line</p>
</blockquote>
<pre><code>git remote add origin [email protected]:phodal/github-roam.git
git push -u origin master</code></pre>
<p>如果你完成了上面的步骤之后,那么我想你想知道你需要怎样的项目。</p>
<h2 id="github流行项目分析">GitHub流行项目分析</h2>
<p>之前曾经分析过一些GitHub的用户行为,现在我们先来说说GitHub上的Star吧。(截止: 2015年3月9日23时。)</p>
<table style="width:56%;">
<colgroup>
<col style="width: 8%" />
<col style="width: 15%" />
<col style="width: 15%" />
<col style="width: 9%" />
<col style="width: 6%" />
</colgroup>
<thead>
<tr class="header">
<th>用户</th>
<th>项目名</th>
<th>Language</th>
<th>Star</th>
<th>Url</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>twbs</td>
<td>Bootstrap</td>
<td>CSS</td>
<td>78490</td>
<td><a href="https://github.com/twbs/bootstrap" class="uri">https://github.com/twbs/bootstrap</a></td>
</tr>
<tr class="even">
<td>vhf</td>
<td>free-programming books</td>
<td>-</td>
<td>37240</td>
<td><a href="https://github.com/vhf/free-programming-books" class="uri">https://github.com/vhf/free-programming-books</a></td>
</tr>
<tr class="odd">
<td>angular</td>
<td>angular.js</td>
<td>JavaScript</td>
<td>36,061</td>
<td><a href="https://github.com/angular/angular.js" class="uri">https://github.com/angular/angular.js</a></td>
</tr>
<tr class="even">
<td>mbostock</td>
<td>d3</td>
<td>JavaScript</td>
<td>35,257</td>
<td><a href="https://github.com/mbostock/d3" class="uri">https://github.com/mbostock/d3</a></td>
</tr>
<tr class="odd">
<td>joyent</td>
<td>node</td>
<td>JavaScript</td>
<td>35,077</td>
<td><a href="https://github.com/joyent/node" class="uri">https://github.com/joyent/node</a></td>
</tr>
</tbody>
</table>
<p>上面列出来的是前5的,看看大于1万个stars的项目的分布,一共有82个:</p>
<table>
<thead>
<tr class="header">
<th>语言</th>
<th>项目数</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>JavaScript</td>
<td>37</td>
</tr>
<tr class="even">
<td>Ruby</td>
<td>6</td>
</tr>
<tr class="odd">
<td>CSS</td>
<td>6</td>
</tr>
<tr class="even">
<td>Python</td>
<td>4</td>
</tr>
<tr class="odd">
<td>HTML</td>
<td>3</td>
</tr>
<tr class="even">
<td>C++</td>
<td>3</td>
</tr>
<tr class="odd">
<td>VimL</td>
<td>2</td>
</tr>
<tr class="even">
<td>Shell</td>
<td>2</td>
</tr>
<tr class="odd">
<td>Go</td>
<td>2</td>
</tr>
<tr class="even">
<td>C</td>
<td>2</td>
</tr>
</tbody>
</table>
<p>类型分布:</p>
<ul>
<li>库和框架: 如<code>jQuery</code></li>
<li>系统: 如<code>Linux</code>、<code>hhvm</code>、<code>docker</code></li>
<li>配置集: 如<code>dotfiles</code></li>
<li>辅助工具: 如<code>oh-my-zsh</code></li>
<li>工具: 如<code>Homewbrew</code>和<code>Bower</code></li>
<li>资料收集: 如<code>free programming books</code>,<code>You-Dont-Know-JS</code>,<code>Font-Awesome</code></li>
<li>其他:简历如<code>Resume</code></li>
</ul>
<h2 id="pull-request">Pull Request</h2>
<p>除了创建项目之外,我们也可以创建Pull Request来做贡献。</p>
<h3 id="我的第一个pr">我的第一个PR</h3>
<p>我的第一个PR是给一个小的Node的CoAP相关的库的Pull Request。原因比较简单,是因为它的README.md写错了,导致我无法办法进行下一步。</p>
<pre><code> const dgram = require('dgram')
- , coapPacket = require('coap-packet')
+ , package = require('coap-packet')</code></pre>
<p>很简单,却又很有用的步骤,另外一个也是:</p>
<pre><code> else
cat << END
$0: error: module ngx_pagespeed requires the pagespeed optimization library.
-Look in obj/autoconf.err for more details.
+Look in objs/autoconf.err for more details.
END
exit 1
fi</code></pre>
<h3 id="cla">CLA</h3>
<p>CLA即Contributor License Agreement,在为一些大的组织、机构提交Pull Request的时候,可能需要签署这个协议。他们会在你的Pull Request里问你,只有你到他们的网站去注册并同意协议才会接受你的PR。</p>
<p>以下是我为Google提交的一个PR</p>
<figure>
<img src="./img/google-cla.png" alt="Google CLA" /><figcaption>Google CLA</figcaption>
</figure>
<p>以及Eclipse的一个PR</p>
<figure>
<img src="./img/eclipse-cla.png" alt="Eclipse CLA" /><figcaption>Eclipse CLA</figcaption>
</figure>
<p>他们都要求我签署CLA。</p>
<hr>
<h1 id="构建github项目">构建GitHub项目</h1>
<h2 id="如何用好github">如何用好GitHub</h2>
<p>如何用好GitHub,并实践一些敏捷软件开发是一个很有意思的事情.我们可以在上面做很多事情,从测试到CI,再到自动部署.</p>
<h3 id="敏捷软件开发">敏捷软件开发</h3>
<p>显然我是在扯淡,这和敏捷软件开发没有什么关系。不过我也不知道瀑布流是怎样的。说说我所知道的一个项目的组成吧:</p>
<ul>
<li>看板式管理应用程序(如trello,简单地说就是管理软件功能)</li>
<li>CI(持续集成)</li>
<li>测试覆盖率</li>
<li>代码质量(code smell)</li>
</ul>
<p>对于一个不是远程的团队(如只有一个人的项目) 来说,Trello、Jenkin、Jira不是必需的:</p>
<blockquote>
<p>你存在,我深深的脑海里</p>
</blockquote>
<p>当只有一个人的时候,你只需要明确知道自己想要什么就够了。我们还需要的是CI、测试,以来提升代码的质量。</p>
<h3 id="测试">测试</h3>
<p>通常我们都会找Document,如果没有的话,你会找什么?看源代码,还是看测试?</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="at">it</span>(<span class="st">"specifying response when you need it"</span><span class="op">,</span> <span class="kw">function</span> (done) <span class="op">{</span>
<span class="kw">var</span> doneFn <span class="op">=</span> <span class="va">jasmine</span>.<span class="at">createSpy</span>(<span class="st">"success"</span>)<span class="op">;</span>
<span class="va">lettuce</span>.<span class="at">get</span>(<span class="st">'/some/cool/url'</span><span class="op">,</span> <span class="kw">function</span> (result) <span class="op">{</span>
<span class="at">expect</span>(result).<span class="at">toEqual</span>(<span class="st">"awesome response"</span>)<span class="op">;</span>
<span class="at">done</span>()<span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span>
<span class="at">expect</span>(<span class="va">jasmine</span>.<span class="va">Ajax</span>.<span class="va">requests</span>.<span class="at">mostRecent</span>().<span class="at">url</span>).<span class="at">toBe</span>(<span class="st">'/some/cool/url'</span>)<span class="op">;</span>
<span class="at">expect</span>(doneFn).<span class="va">not</span>.<span class="at">toHaveBeenCalled</span>()<span class="op">;</span>
<span class="va">jasmine</span>.<span class="va">Ajax</span>.<span class="va">requests</span>.<span class="at">mostRecent</span>().<span class="at">respondWith</span>(<span class="op">{</span>
<span class="st">"status"</span><span class="op">:</span> <span class="dv">200</span><span class="op">,</span>
<span class="st">"contentType"</span><span class="op">:</span> <span class="st">'text/plain'</span><span class="op">,</span>
<span class="st">"responseText"</span><span class="op">:</span> <span class="st">'awesome response'</span>
<span class="op">}</span>)<span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span></code></pre></div>
<p>代码来源: <a href="https://github.com/phodal/lettuce" class="uri">https://github.com/phodal/lettuce</a></p>
<p>上面的测试用例,清清楚楚地写明了用法,虽然写得有点扯。</p>
<p>等等,测试是用来干什么的。那么,先说说我为什么会想去写测试吧:</p>
<ul>
<li>我不希望每次做完一个个新功能的时候,再手动地去测试一个个功能。(自动化测试)</li>
<li>我不希望在重构的时候发现破坏了原来的功能,而我还一无所知。</li>
<li>我不敢push代码,因为我没有把握。</li>
</ul>
<p>虽然,我不是TDD的死忠,测试的目的是保证功能正常,TDD没法让我们写出质量更高的代码。但是有时TDD是不错的,可以让我们写出逻辑更简单地代码。</p>
<p>也许你已经知道了<code>Selenium</code>、<code>Jasmine</code>、<code>Cucumber</code>等等的框架,看到过类似于下面的测试</p>
<pre><code> Ajax
✓ specifying response when you need it
✓ specifying html when you need it
✓ should be post to some where
Class
✓ respects instanceof
✓ inherits methods (also super)
✓ extend methods
Effect
✓ should be able fadein elements
✓ should be able fadeout elements</code></pre>
<p>代码来源: <a href="https://github.com/phodal/lettuce" class="uri">https://github.com/phodal/lettuce</a></p>
<p>看上去似乎每个测试都很小,不过补完每一个测试之后我们就得到了测试覆盖率</p>
<table style="width:68%;">
<colgroup>
<col style="width: 8%" />
<col style="width: 18%" />
<col style="width: 15%" />
<col style="width: 16%" />
<col style="width: 9%" />
</colgroup>
<thead>
<tr class="header">
<th>File</th>
<th>Statements</th>
<th>Branches</th>
<th>Functions</th>
<th>Lines</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>lettuce.js</td>
<td>98.58% (209 / 212)</td>
<td>82.98%(78 / 94)</td>
<td>100.00% (54 / 54)</td>
<td>98.58% (209 / 212)</td>
</tr>
</tbody>
</table>
<p>本地测试都通过了,于是我们添加了<code>Travis-CI</code>来跑我们的测试</p>
<h3 id="ci">CI</h3>
<p>虽然node.js不算是一门语言,但是因为我们用的node,下面的是一个简单的<code>.travis.yml</code>示例:</p>
<div class="sourceCode"><pre class="sourceCode yml"><code class="sourceCode yaml"><span class="fu">language:</span> node_js
<span class="fu">node_js:</span>
<span class="kw">-</span> <span class="st">"0.10"</span>
<span class="fu">notifications:</span>
<span class="fu">email:</span> false
<span class="fu">before_install:</span> npm install -g grunt-cli
<span class="fu">install:</span> npm install
<span class="fu">after_success:</span> CODECLIMATE_REPO_TOKEN=321480822fc37deb0de70a11931b4cb6a2a3cc411680e8f4569936ac8ffbb0ab codeclimate < coverage/lcov.info</code></pre></div>
<p>代码来源: <a href="https://github.com/phodal/lettuce" class="uri">https://github.com/phodal/lettuce</a></p>
<p>我们把这些集成到<code>README.md</code>之后,就有了之前那张图。</p>
<p>CI对于一个开发者在不同城市开发同一项目上来说是很重要的,这意味着当你添加的部分功能有测试覆盖的时候,项目代码会更加强壮。</p>
<h3 id="代码质量">代码质量</h3>
<p>像<code>jslint</code>这类的工具,只能保证代码在语法上是正确的,但是不能保证你写了一堆bad smell的代码。</p>
<ul>
<li>重复代码</li>
<li>过长的函数</li>
<li>等等</li>
</ul>
<p><code>Code Climate</code>是一个与github集成的工具,我们不仅仅可以看到测试覆盖率,还有代码质量。</p>
<p>先看看上面的ajax类:</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">Lettuce</span>.<span class="at">get</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
<span class="va">Lettuce</span>.<span class="at">send</span>(url<span class="op">,</span> <span class="st">'GET'</span><span class="op">,</span> callback)<span class="op">;</span>
<span class="op">};</span>
<span class="va">Lettuce</span>.<span class="at">send</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> method<span class="op">,</span> callback<span class="op">,</span> data) <span class="op">{</span>
data <span class="op">=</span> data <span class="op">||</span> <span class="kw">null</span><span class="op">;</span>
<span class="kw">var</span> request <span class="op">=</span> <span class="kw">new</span> <span class="at">XMLHttpRequest</span>()<span class="op">;</span>
<span class="cf">if</span> (callback <span class="kw">instanceof</span> Function) <span class="op">{</span>
<span class="va">request</span>.<span class="at">onreadystatechange</span> <span class="op">=</span> <span class="kw">function</span> () <span class="op">{</span>
<span class="cf">if</span> (<span class="va">request</span>.<span class="at">readyState</span> <span class="op">===</span> <span class="dv">4</span> <span class="op">&&</span> (<span class="va">request</span>.<span class="at">status</span> <span class="op">===</span> <span class="dv">200</span> <span class="op">||</span> <span class="va">request</span>.<span class="at">status</span> <span class="op">===</span> <span class="dv">0</span>)) <span class="op">{</span>
<span class="at">callback</span>(<span class="va">request</span>.<span class="at">responseText</span>)<span class="op">;</span>
<span class="op">}</span>
<span class="op">};</span>
<span class="op">}</span>
<span class="va">request</span>.<span class="at">open</span>(method<span class="op">,</span> url<span class="op">,</span> <span class="kw">true</span>)<span class="op">;</span>
<span class="cf">if</span> (data <span class="kw">instanceof</span> Object) <span class="op">{</span>
data <span class="op">=</span> <span class="va">JSON</span>.<span class="at">stringify</span>(data)<span class="op">;</span>
<span class="va">request</span>.<span class="at">setRequestHeader</span>(<span class="st">'Content-Type'</span><span class="op">,</span> <span class="st">'application/json'</span>)<span class="op">;</span>
<span class="op">}</span>
<span class="va">request</span>.<span class="at">setRequestHeader</span>(<span class="st">'X-Requested-With'</span><span class="op">,</span> <span class="st">'XMLHttpRequest'</span>)<span class="op">;</span>
<span class="va">request</span>.<span class="at">send</span>(data)<span class="op">;</span>
<span class="op">};</span></code></pre></div>
<p>代码来源: <a href="https://github.com/phodal/lettuce" class="uri">https://github.com/phodal/lettuce</a></p>
<p>在<a href="https://codeclimate.com/github/phodal/lettuce/src/ajax.js">Code Climate</a>在出现了一堆问题</p>
<ul>
<li>Missing “use strict” statement. (Line 2)</li>
<li>Missing “use strict” statement. (Line 14)</li>
<li>‘Lettuce’ is not defined. (Line 5)</li>
</ul>
<p>而这些都是小问题啦,有时可能会有</p>
<ul>
<li>Similar code found in two :expression_statement nodes (mass = 86)</li>
</ul>
<p>这就意味着我们可以对上面的代码进行重构,他们是重复的代码。</p>
<h2 id="模块分离与测试">模块分离与测试</h2>
<p>在之前说到</p>
<blockquote>
<p>奋斗了近半个月后,将fork的代码读懂、重构、升级版本、调整,添加新功能、添加测试、添加CI、添加分享之后,终于almost finish。</p>
</blockquote>
<p>今天就来说说是怎样做的。</p>
<p>以之前造的<a href="https://github.com/phodal/lettuce">Lettuce</a>为例,里面有:</p>
<ul>
<li>代码质量(Code Climate)</li>
<li>CI状态(Travis CI)</li>
<li>测试覆盖率(96%)</li>
<li>自动化测试(npm test)</li>
<li>文档</li>
</ul>
<p>按照<a href="https://github.com/phodal/awesome-developer">Web Developer路线图</a>来说,我们还需要有:</p>
<ul>
<li>版本管理</li>
<li>自动部署</li>
</ul>
<p>等等。</p>
<h3 id="代码模块化">代码模块化</h3>
<p>在SkillTree的源码里,大致分为三部分:</p>
<ul>
<li>namespace函数: 顾名思义</li>
<li>Calculator也就是TalentTree,主要负责解析、生成url,头像,依赖等等</li>
<li>Skill 主要是tips部分。</li>
</ul>
<p>而这一些都在一个js里,对于一个库来说,是一件好事,但是对于一个项目来说,并非如此。</p>
<p>依赖的库有</p>
<ul>
<li>jQuery</li>
<li>Knockout</li>
</ul>
<p>好在Knockout可以用Require.js进行管理,于是,使用了<code>Require.js</code>进行管理:</p>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><script</span><span class="ot"> type=</span><span class="st">"text/javascript"</span><span class="ot"> data-main=</span><span class="st">"app/scripts/main.js"</span><span class="ot"> src=</span><span class="st">"app/lib/require.js"</span><span class="kw">></script></span></code></pre></div>
<p><code>main.js</code>配置如下:</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">require</span>.<span class="at">config</span>(<span class="op">{</span>
<span class="dt">baseUrl</span><span class="op">:</span> <span class="st">'app'</span><span class="op">,</span>
<span class="dt">paths</span><span class="op">:{</span>
<span class="dt">jquery</span><span class="op">:</span> <span class="st">'lib/jquery'</span><span class="op">,</span>
<span class="dt">json</span><span class="op">:</span> <span class="st">'lib/json'</span><span class="op">,</span>
<span class="dt">text</span><span class="op">:</span> <span class="st">'lib/text'</span>
<span class="op">}</span>
<span class="op">}</span>)<span class="op">;</span>
<span class="at">require</span>([<span class="st">'scripts/ko-bindings'</span>])<span class="op">;</span>
<span class="at">require</span>([<span class="st">'lib/knockout'</span><span class="op">,</span> <span class="st">'scripts/TalentTree'</span><span class="op">,</span> <span class="st">'json!data/web.json'</span>]<span class="op">,</span> <span class="kw">function</span>(ko<span class="op">,</span> TalentTree<span class="op">,</span> TalentData) <span class="op">{</span>
<span class="st">'use strict'</span><span class="op">;</span>
<span class="kw">var</span> vm <span class="op">=</span> <span class="kw">new</span> <span class="at">TalentTree</span>(TalentData)<span class="op">;</span>
<span class="va">ko</span>.<span class="at">applyBindings</span>(vm)<span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span></code></pre></div>
<p>text、json插件主要是用于处理web.json,即用json来处理技能,于是不同的类到了不同的js文件。</p>
<pre><code>.
|____Book.js
|____Doc.js
|____ko-bindings.js
|____Link.js
|____main.js
|____Skill.js
|____TalentTree.js
|____Utils.js</code></pre>
<p>加上了后来的推荐阅读书籍等等。而Book和Link都是继承自Doc。</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="at">define</span>([<span class="st">'scripts/Doc'</span>]<span class="op">,</span> <span class="kw">function</span>(Doc) <span class="op">{</span>
<span class="st">'use strict'</span><span class="op">;</span>
<span class="kw">function</span> <span class="at">Book</span>(_e) <span class="op">{</span>
<span class="va">Doc</span>.<span class="at">apply</span>(<span class="kw">this</span><span class="op">,</span> arguments)<span class="op">;</span>
<span class="op">}</span>
<span class="va">Book</span>.<span class="at">prototype</span> <span class="op">=</span> <span class="kw">new</span> <span class="at">Doc</span>()<span class="op">;</span>
<span class="cf">return</span> Book<span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span> </code></pre></div>
<p>而这里便是后面对其进行重构的内容。Doc类则是Skillock中类的一个缩影</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="at">define</span>([]<span class="op">,</span> <span class="kw">function</span>() <span class="op">{</span>
<span class="st">'use strict'</span><span class="op">;</span>
<span class="kw">var</span> Doc <span class="op">=</span> <span class="kw">function</span> (_e) <span class="op">{</span>
<span class="kw">var</span> e <span class="op">=</span> _e <span class="op">||</span> <span class="op">{};</span>
<span class="kw">var</span> self <span class="op">=</span> <span class="kw">this</span><span class="op">;</span>
<span class="va">self</span>.<span class="at">label</span> <span class="op">=</span> <span class="va">e</span>.<span class="at">label</span> <span class="op">||</span> (<span class="va">e</span>.<span class="at">url</span> <span class="op">||</span> <span class="st">'Learn more'</span>)<span class="op">;</span>
<span class="va">self</span>.<span class="at">url</span> <span class="op">=</span> <span class="va">e</span>.<span class="at">url</span> <span class="op">||</span> <span class="st">'javascript:void(0)'</span><span class="op">;</span>
<span class="op">};</span>
<span class="cf">return</span> Doc<span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span></code></pre></div>
<p>或者说这是一个AMD的Class应该有的样子。考虑到this的隐性绑定,作者用了self=this来避免这个问题。最后Return了这个对象,我们在调用的就需要new一个。大部分在代码中返回的都是对象,除了在Utils类里面返回的是函数:</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="cf">return</span> <span class="op">{</span>
<span class="dt">getSkillsByHash</span><span class="op">:</span> getSkillsByHash<span class="op">,</span>
<span class="dt">getSkillById</span><span class="op">:</span> getSkillById<span class="op">,</span>
<span class="dt">prettyJoin</span><span class="op">:</span> prettyJoin
<span class="op">};</span></code></pre></div>
<p>当然函数也是一个对象。</p>
<h3 id="自动化测试">自动化测试</h3>
<p>一直习惯用Travis CI,于是也继续用Travis Ci,<code>.travis.yml</code>配置如下所示:</p>
<div class="sourceCode"><pre class="sourceCode yml"><code class="sourceCode yaml"><span class="fu">language:</span> node_js
<span class="fu">node_js:</span>
<span class="kw">-</span> <span class="st">"0.10"</span>
<span class="fu">notifications:</span>
<span class="fu">email:</span> false
<span class="fu">branches:</span>
<span class="fu">only:</span>
<span class="kw">-</span> gh-pages</code></pre></div>
<p>使用gh-pages的原因是,我们一push代码的时候,就可以自动测试、部署等等,好处一堆堆的。</p>
<p>接着我们需要在<code>package.json</code>里面添加脚本</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="st">"scripts"</span><span class="op">:</span> <span class="op">{</span>
<span class="st">"test"</span><span class="op">:</span> <span class="st">"mocha"</span>
<span class="op">}</span></code></pre></div>
<p>这样当我们push代码的时候便会自动跑所有的测试。因为mocha的主要配置是用<code>mocha.opts</code>,所以我们还需要配置一下<code>mocha.opts</code></p>
<pre><code>--reporter spec
--ui bdd
--growl
--colors
test/spec </code></pre>
<p>最后的<code>test/spec</code>是指定测试的目录。</p>
<h3 id="jshint">Jshint</h3>
<blockquote>
<p>JSLint定义了一组编码约定,这比ECMA定义的语言更为严格。这些编码约定汲取了多年来的丰富编码经验,并以一条年代久远的编程原则 作为宗旨:能做并不意味着应该做。JSLint会对它认为有的编码实践加标志,另外还会指出哪些是明显的错误,从而促使你养成好的 JavaScript编码习惯。</p>
</blockquote>
<p>当我们的js写得不合理的时候,这时测试就无法通过:</p>
<pre><code>line 5 col 25 A constructor name should start with an uppercase letter.
line 21 col 62 Strings must use singlequote.</code></pre>
<p>这是一种驱动写出更规范js的方法。</p>
<h3 id="mocha">Mocha</h3>
<blockquote>
<p>Mocha 是一个优秀的JS测试框架,支持TDD/BDD,结合 should.js/expect/chai/better-assert,能轻松构建各种风格的测试用例。</p>
</blockquote>
<p>最后的效果如下所示:</p>
<pre><code>Book,Link
Book Test
✓ should return book label & url
Link Test
✓ should return link label & url</code></pre>
<h3 id="测试示例">测试示例</h3>
<p>简单地看一下Book的测试:</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="co">/* global describe, it */</span>
<span class="kw">var</span> requirejs <span class="op">=</span> <span class="at">require</span>(<span class="st">"requirejs"</span>)<span class="op">;</span>
<span class="kw">var</span> assert <span class="op">=</span> <span class="at">require</span>(<span class="st">"assert"</span>)<span class="op">;</span>
<span class="kw">var</span> should <span class="op">=</span> <span class="at">require</span>(<span class="st">"should"</span>)<span class="op">;</span>
<span class="va">requirejs</span>.<span class="at">config</span>(<span class="op">{</span>
<span class="dt">baseUrl</span><span class="op">:</span> <span class="st">'app/'</span><span class="op">,</span>
<span class="dt">nodeRequire</span><span class="op">:</span> require
<span class="op">}</span>)<span class="op">;</span>
<span class="at">describe</span>(<span class="st">'Book,Link'</span><span class="op">,</span> <span class="kw">function</span> () <span class="op">{</span>
<span class="kw">var</span> Book<span class="op">,</span> Link<span class="op">;</span>
<span class="at">before</span>(<span class="kw">function</span> (done) <span class="op">{</span>
<span class="at">requirejs</span>([<span class="st">'scripts/Book'</span>、]<span class="op">,</span> <span class="kw">function</span> (Book_Class) <span class="op">{</span>
Book <span class="op">=</span> Book_Class<span class="op">;</span>
<span class="at">done</span>()<span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span>
<span class="at">describe</span>(<span class="st">'Book Test'</span><span class="op">,</span> <span class="kw">function</span> () <span class="op">{</span>
<span class="at">it</span>(<span class="st">'should return book label & url'</span><span class="op">,</span> <span class="kw">function</span> () <span class="op">{</span>
<span class="kw">var</span> book_name <span class="op">=</span> <span class="st">'Head First HTML与CSS'</span><span class="op">;</span>
<span class="kw">var</span> url <span class="op">=</span> <span class="st">'http://www.phodal.com'</span><span class="op">;</span>
<span class="kw">var</span> books <span class="op">=</span> <span class="op">{</span>
<span class="dt">label</span><span class="op">:</span> book_name<span class="op">,</span>
<span class="dt">url</span><span class="op">:</span> url
<span class="op">};</span>
<span class="kw">var</span> _book <span class="op">=</span> <span class="kw">new</span> <span class="at">Book</span>(books)<span class="op">;</span>
<span class="va">_book</span>.<span class="va">label</span>.<span class="va">should</span>.<span class="at">equal</span>(book_name)<span class="op">;</span>
<span class="va">_book</span>.<span class="va">url</span>.<span class="va">should</span>.<span class="at">equal</span>(url)<span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span></code></pre></div>
<p>因为我们用<code>require.js</code>来管理浏览器端,在后台写测试来测试的时候,我们也需要用他来管理我们的依赖,这也就是为什么这个测试这么长的原因,多数情况下一个测试类似于这样子的。(用Jasmine似乎会是一个更好的主意,但是用习惯Jasmine了)</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="at">describe</span>(<span class="st">'Book Test'</span><span class="op">,</span> <span class="kw">function</span> () <span class="op">{</span>
<span class="at">it</span>(<span class="st">'should return book label & url'</span><span class="op">,</span> <span class="kw">function</span> () <span class="op">{</span>
<span class="kw">var</span> book_name <span class="op">=</span> <span class="st">'Head First HTML与CSS'</span><span class="op">;</span>
<span class="kw">var</span> url <span class="op">=</span> <span class="st">'http://www.phodal.com'</span><span class="op">;</span>
<span class="kw">var</span> books <span class="op">=</span> <span class="op">{</span>
<span class="dt">label</span><span class="op">:</span> book_name<span class="op">,</span>
<span class="dt">url</span><span class="op">:</span> url
<span class="op">};</span>
<span class="kw">var</span> _book <span class="op">=</span> <span class="kw">new</span> <span class="at">Book</span>(books)<span class="op">;</span>
<span class="va">_book</span>.<span class="va">label</span>.<span class="va">should</span>.<span class="at">equal</span>(book_name)<span class="op">;</span>
<span class="va">_book</span>.<span class="va">url</span>.<span class="va">should</span>.<span class="at">equal</span>(url)<span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span></code></pre></div>
<p>最后的断言,也算是测试的核心,保证测试是有用的。</p>
<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>Rating</th>
<th>Name</th>
<th>Complexity</th>
<th>Duplication</th>
<th>Churn</th>
<th>C/M</th>
<th>Coverage</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>A</td>
<td>lib/coap/coap_request_handler.js</td>
<td>24</td>
<td>0</td>
<td>6</td>
<td>2.6</td>
<td>46.4%</td>
</tr>
<tr class="even">
<td>A</td>
<td>lib/coap/coap_result_helper.js</td>
<td>14</td>
<td>0</td>
<td>2</td>
<td>3.4</td>
<td>80.0%</td>
</tr>
<tr class="odd">
<td>A</td>
<td>lib/coap/coap_server.js</td>
<td>16</td>
<td>0</td>
<td>5</td>
<td>5.2</td>
<td>44.0%</td>
</tr>
<tr class="even">
<td>A</td>
<td>lib/database/db_factory.js</td>
<td>8</td>
<td>0</td>
<td>3</td>
<td>3.8</td>
<td>92.3%</td>
</tr>
<tr class="odd">
<td>A</td>
<td>lib/database/iot_db.js</td>
<td>7</td>
<td>0</td>
<td>6</td>
<td>1.0</td>
<td>58.8%</td>
</tr>
<tr class="even">
<td>A</td>
<td>lib/database/mongodb_helper.js</td>
<td>63</td>
<td>0</td>
<td>11</td>
<td>4.5</td>
<td>35.0%</td>
</tr>
<tr class="odd">
<td>C</td>
<td>lib/database/sqlite_helper.js</td>
<td>32</td>
<td>86</td>
<td>10</td>
<td>4.5</td>
<td>35.0%</td>
</tr>
<tr class="even">
<td>B</td>
<td>lib/rest/rest_helper.js</td>
<td>19</td>
<td>62</td>
<td>3</td>
<td>4.7</td>
<td>37.5%</td>
</tr>
<tr class="odd">
<td>A</td>
<td>lib/rest/rest_server.js</td>
<td>17</td>
<td>0</td>
<td>2</td>
<td>8.6</td>
<td>88.9%</td>
</tr>
<tr class="even">
<td>A</td>
<td>lib/url_handler.js</td>
<td>9</td>
<td>0</td>
<td>5</td>
<td>2.2</td>
<td>94.1%</td>
</tr>
</tbody>
</table>
<p>分享得到的最后的结果是:</p>
<p>[Coverage][1]</p>
<h3 id="代码的坏味道">代码的坏味道</h3>
<p>于是我们就打开<code>lib/database/sqlite_helper.js</code>,因为其中有两个坏味道</p>
<p>Similar code found in two :expression_statement nodes (mass = 86)</p>
<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>