├── .github ├── FUNDING.yml └── workflows │ └── static.yml ├── .gitignore ├── CNAME ├── Makefile ├── README.md ├── build ├── author.html ├── head.html ├── metadata.xml ├── share.html ├── stats.html └── title.txt ├── chapters ├── 01-introduction.md ├── 01-start-project.md ├── 02-github-fundamentals.md ├── 03-build-github-project.md ├── 04-commit-message.md ├── 05-create-project-documents.md ├── 06-refactor-project.md ├── 07-tdd-with-autotest.md ├── 08-github-marketing.md ├── 09-maintain-project.md ├── 10-git-tools.md ├── 11-analytics.md ├── 12-find-github-project.md ├── 13-read-code.md ├── 14-streak-your-github.md ├── 15-milestone.md ├── 16-find-in-github.md ├── 18-get-star.md ├── 19-joke.md ├── 999-faq.md ├── README.md ├── _sidebar.md └── generate.py ├── epub.css ├── github-roam.epub ├── github-roam.md ├── github-roam.mobi ├── github-roam.pdf ├── github-roam.rtf ├── img ├── 10000.png ├── 2014-01-01.png ├── 2014.png ├── 2015.png ├── 2016.png ├── 365-streak.jpg ├── after-add.png ├── alipay.png ├── api-examples.png ├── before-add.png ├── clone-flask.png ├── comparison.png ├── cover.jpg ├── echoesworks.png ├── eclipse-cla.png ├── elasticsearch_ionit_map.jpg ├── favicon.ico ├── feb-results.png ├── feel-free-to.png ├── flask-0.1.png ├── flask-init.png ├── flask.png ├── for-stars-make-money.png ├── for-stars.png ├── git-diff-screenshot.png ├── github-200-days.png ├── github-365.jpg ├── github-500.jpg ├── github-desktop.jpg ├── github-intro.png ├── github-new-project-checklist.png ├── github-no-open.jpg ├── github-roam-create.jpg ├── github-star-history.png ├── github-trending-example.png ├── github_traffic.png ├── go-mqtt.png ├── google-cla.png ├── google-new-project-checklist.png ├── gource.jpg ├── growth-ebook-example.png ├── growth.png ├── growth_traffic.png ├── grwoth-old.png ├── huovd.png ├── it-works-cms.png ├── lan-example.png ├── lan-iot.jpg ├── lan.png ├── lettuce.png ├── licenses.png ├── linux-history.png ├── lodash-code-example.png ├── longest-streak.png ├── main-events.png ├── mole.png ├── mopass-weibo.png ├── nginx_pig.jpg ├── permissive-vs-copylift-license-2.jpg ├── phodal-intro.jpg ├── phodal-results.png ├── problem.jpg ├── project-init.jpg ├── pycharm-diff.jpg ├── python-social-auth-example.png ├── qingxu.jpg ├── qrcode.jpg ├── react-features-example.png ├── react-intro.png ├── readme-example.png ├── redux-examples.png ├── replace.jpg ├── repo-status.png ├── resume.png ├── rpc-example.png ├── sherlock.png ├── skillmap.png ├── skilltree.jpg ├── smtwtfs.png ├── solr.png ├── sourcetree.jpg ├── wechat-pay.png ├── wechat.jpg └── xiaomiquan.jpg ├── index.html ├── listings-setup.tex ├── template.tex └── website_old ├── index.html └── style.css /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://book.douban.com/subject/33477112/ 13 | -------------------------------------------------------------------------------- /.github/workflows/static.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["gh-pages"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Single deploy job since we're just deploying 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | - name: Setup Pages 35 | uses: actions/configure-pages@v5 36 | - name: Upload artifact 37 | uses: actions/upload-pages-artifact@v3 38 | with: 39 | # Upload entire repository 40 | path: '.' 41 | - name: Deploy to GitHub Pages 42 | id: deployment 43 | uses: actions/deploy-pages@v4 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | github.phodal.com 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include_dir=build 2 | source=chapters/*.md 3 | title='GitHub 漫游指南' 4 | filename='github-roam' 5 | 6 | 7 | all: html epub rtf pdf mobi 8 | 9 | markdown: 10 | awk 'FNR==1{print ""}{print}' $(source) > $(filename).md 11 | 12 | html: markdown 13 | pandoc -s $(filename).md -t html5 -o index.html -c style.css \ 14 | --metadata pagetitle=$(title) \ 15 | --include-in-header $(include_dir)/head.html \ 16 | --include-before-body $(include_dir)/author.html \ 17 | --include-before-body $(include_dir)/share.html \ 18 | --include-after-body $(include_dir)/stats.html \ 19 | --title-prefix $(title) \ 20 | --toc-depth=3 \ 21 | --toc 22 | 23 | epub: markdown 24 | pandoc -s $(filename).md -t epub -o $(filename).epub \ 25 | --epub-metadata $(include_dir)/metadata.xml \ 26 | --epub-cover-image img/cover.jpg \ 27 | --title-prefix $(title) \ 28 | --toc 29 | 30 | rtf: markdown 31 | pandoc -s $(filename).md -o $(filename).rtf \ 32 | --title-prefix $(title) 33 | 34 | pdf: markdown 35 | # OS X: http://www.tug.org/mactex/ 36 | # Then find its path: find /usr/ -name "pdflatex" 37 | # Then symlink it: ln -s /path/to/pdflatex /usr/local/bin 38 | pandoc -s $(filename).md -o $(filename).pdf \ 39 | --title-prefix $(title) \ 40 | --listings -H listings-setup.tex \ 41 | --template=template.tex \ 42 | --toc 43 | 44 | mobi: epub 45 | # Symlink bin: ln -s /path/to/kindlegen /usr/local/bin 46 | kindlegen $(filename).epub 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub 漫游指南 2 | 3 | > 漫游,即随意游玩~。 4 | 5 | 在线阅读:[GitHub 漫游指南](http://github.phodal.com/),下载:[pdf](https://github.com/phodal/github-roam/raw/gh-pages/github-roam.pdf)、[mobi](https://github.com/phodal/github-roam/raw/gh-pages/github-roam.mobi)、[epub](https://github.com/phodal/github-roam/raw/gh-pages/github-roam.epub) 6 | 7 | 2014 年,写了《[一步步搭建物联网系统](https://github.com/phodal/designiot)》(电子书)。 8 | 9 | 2015.3.9 号,想着写个《[GitHub 漫游指南](http://github.phodal.com/)》,于是在最开始的地方写着: 10 | 11 | > 我的 GitHub 主页上写着加入的时间——``Joined on Nov 8, 2010``,那时才大一。在那之后的日子里,也许是因为我学的不是计算机的关系,并没有熟练使用它。到了今天——``2015.3.9``,我发现 GitHub 是程序员的社交网站。 12 | 13 | 但是过了很久都没有动静,今天是 2015.10.24,我想是时候完成这个目标了。 14 | 15 | ## License 16 | 17 | [![Phodal's Book](http://brand.phodal.com/shields/book-small.svg)](https://www.phodal.com/) 18 | 19 | © 2015~2019 [Phodal Huang](https://www.phodal.com). This code is distributed under the Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 License. See `LICENSE` in this directory. 20 | 21 | [![待我代码编成,娶你为妻可好](http://brand.phodal.com/slogan/slogan.svg)](http://www.xuntayizhan.com/person/ji-ke-ai-qing-zhi-er-shi-dai-wo-dai-ma-bian-cheng-qu-ni-wei-qi-ke-hao-wan/) 22 | -------------------------------------------------------------------------------- /build/author.html: -------------------------------------------------------------------------------- 1 |

2 |

GitHub 漫游指南

3 |

项目首页: GitHub 漫游指南

4 |

By Phodal Huang(微博、知乎、GitHub、SegmentFault: @phodal) 5 |

6 | 7 |

我的其他电子书:

8 | 18 | 19 |

微信公众号

20 |

21 |

22 | 当前为预览版,在使用的过程中遇到任何遇到请及时与我联系。阅读过程中问题,不烦在GitHub上提出来: 23 | Issues 24 |

25 |

26 | 阅读过程中遇到语法错误、拼写错误、技术错误等等,不烦来个Pull Request,这样可以帮助到其他阅读这本电子书的童鞋。 27 |

-------------------------------------------------------------------------------- /build/head.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /build/metadata.xml: -------------------------------------------------------------------------------- 1 | GitHub 漫游指南 2 | Phodal Huang 3 | Creative Commons Attribution Non-Commercial Share Alike 3.0 4 | zh-CN -------------------------------------------------------------------------------- /build/share.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 5 |
-------------------------------------------------------------------------------- /build/stats.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/github/f990d38dcfd0f995059407fe2d9594459e9b8848/build/stats.html -------------------------------------------------------------------------------- /build/title.txt: -------------------------------------------------------------------------------- 1 | % GitHub 漫游指南 2 | % Phodal Huang -------------------------------------------------------------------------------- /chapters/01-introduction.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | ## Github 4 | 5 | Wiki百科上是这么说的 6 | 7 | > GitHub 是一个共享虚拟主机服务,用于存放使用Git版本控制的软件代码和内容项目。它由GitHub公司(曾称Logical Awesome)的开发者Chris Wanstrath、PJ Hyett和Tom Preston-Werner 8 | 使用Ruby on Rails编写而成。 9 | 10 | 当然让我们看看官方的介绍: 11 | 12 | > 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. 13 | 14 | 15 | 它还是什么? 16 | 17 | - 网站 18 | - 免费博客 19 | - 管理配置文件 20 | - 收集资料 21 | - 简历 22 | - 管理代码片段 23 | - 托管编程环境 24 | - 写作 25 | 26 | 等等。看上去像是大餐,但是你还需要了解点什么? 27 | 28 | ### 版本管理与软件部署 29 | 30 | jQuery[^jQuery]在发布版本``2.1.3``,一共有152个commit。我们可以看到如下的提交信息: 31 | 32 | - Ajax: Always use script injection in globalEval … bbdfbb4 33 | - Effects: Reintroduce use of requestAnimationFrame … 72119e0 34 | - Effects: Improve raf logic … 708764f 35 | - Build: Move test to appropriate module fbdbb6f 36 | - Build: Update commitplease dev dependency 37 | - ... 38 | 39 | ### Github与Git 40 | 41 | > Git是一个分布式的版本控制系统,最初由Linus Torvalds编写,用作Linux内核代码的管理。在推出后,Git在其它项目中也取得了很大成功,尤其是在Ruby社区中。目前,包括Rubinius、Merb和Bitcoin在内的很多知名项目都使用了Git。Git同样可以被诸如Capistrano和Vlad the Deployer这样的部署工具所使用。 42 | 43 | > GitHub可以托管各种git库,并提供一个web界面,但与其它像 SourceForge或Google Code这样的服务不同,GitHub的独特卖点在于从另外一个项目进行分支的简易性。为一个项目贡献代码非常简单:首先点击项目站点的“fork”的按钮,然后将代码检出并将修改加入到刚才分出的代码库中,最后通过内建的“pull request”机制向项目负责人申请代码合并。已经有人将GitHub称为代码玩家的MySpace。 44 | 45 | [^jQuery]: jQuery是一套跨浏览器的JavaScript库,简化HTML与JavaScript之间的操作。 46 | 47 | ## 用好Github 48 | 49 | 如何用好Github,并实践一些敏捷软件开发是一个很有意思的事情.我们可以在上面做很多事情,从测试到CI,再到自动部署. 50 | 51 | ### 敏捷软件开发 52 | 53 | 显然我是在扯淡,这和敏捷软件开发没有什么关系。不过我也不知道瀑布流是怎样的。说说我所知道的一个项目的组成吧: 54 | 55 | - 看板式管理应用程序(如trello,简单地说就是管理软件功能) 56 | - CI(持续集成) 57 | - 测试覆盖率 58 | - 代码质量(code smell) 59 | 60 | 对于一个不是远程的团队(如只有一个人的项目) 来说,Trello、Jenkin、Jira不是必需的: 61 | 62 | > 你存在,我深深的脑海里 63 | 64 | 当只有一个人的时候,你只需要明确知道自己想要什么就够了。我们还需要的是CI、测试,以来提升代码的质量。 65 | 66 | ### 测试 67 | 68 | 通常我们都会找Document,如果没有的话,你会找什么?看源代码,还是看测试? 69 | 70 | ```javascript 71 | it("specifying response when you need it", function (done) { 72 | var doneFn = jasmine.createSpy("success"); 73 | 74 | lettuce.get('/some/cool/url', function (result) { 75 | expect(result).toEqual("awesome response"); 76 | done(); 77 | }); 78 | 79 | expect(jasmine.Ajax.requests.mostRecent().url).toBe('/some/cool/url'); 80 | expect(doneFn).not.toHaveBeenCalled(); 81 | 82 | jasmine.Ajax.requests.mostRecent().respondWith({ 83 | "status": 200, 84 | "contentType": 'text/plain', 85 | "responseText": 'awesome response' 86 | }); 87 | }); 88 | ``` 89 | 90 | 代码来源: [https://github.com/phodal/lettuce](https://github.com/phodal/lettuce) 91 | 92 | 上面的测试用例,清清楚楚地写明了用法,虽然写得有点扯。 93 | 94 | 等等,测试是用来干什么的。那么,先说说我为什么会想去写测试吧: 95 | 96 | - 我不希望每次做完一个个新功能的时候,再手动地去测试一个个功能。(自动化测试) 97 | - 我不希望在重构的时候发现破坏了原来的功能,而我还一无所知。 98 | - 我不敢push代码,因为我没有把握。 99 | 100 | 虽然,我不是TDD的死忠,测试的目的是保证功能正常,TDD没法让我们写出质量更高的代码。但是有时TDD是不错的,可以让我们写出逻辑更简单地代码。 101 | 102 | 也许你已经知道了``Selenium``、``Jasmine``、``Cucumber``等等的框架,看到过类似于下面的测试 103 | 104 | ``` 105 | Ajax 106 | ✓ specifying response when you need it 107 | ✓ specifying html when you need it 108 | ✓ should be post to some where 109 | Class 110 | ✓ respects instanceof 111 | ✓ inherits methods (also super) 112 | ✓ extend methods 113 | Effect 114 | ✓ should be able fadein elements 115 | ✓ should be able fadeout elements 116 | ``` 117 | 118 | 代码来源: [https://github.com/phodal/lettuce](https://github.com/phodal/lettuce) 119 | 120 | 看上去似乎每个测试都很小,不过补完每一个测试之后我们就得到了测试覆盖率 121 | 122 | File | Statements | Branches | Functions | Lines 123 | -----|------------|----------|-----------|------ 124 | lettuce.js | 98.58% (209 / 212)| 82.98%(78 / 94) | 100.00% (54 / 54) | 98.58% (209 / 212) 125 | 126 | 本地测试都通过了,于是我们添加了``Travis-CI``来跑我们的测试 127 | 128 | ### CI 129 | 130 | 虽然node.js不算是一门语言,但是因为我们用的node,下面的是一个简单的``.travis.yml``示例: 131 | 132 | ```yml 133 | language: node_js 134 | node_js: 135 | - "0.10" 136 | 137 | notifications: 138 | email: false 139 | 140 | before_install: npm install -g grunt-cli 141 | install: npm install 142 | after_success: CODECLIMATE_REPO_TOKEN=321480822fc37deb0de70a11931b4cb6a2a3cc411680e8f4569936ac8ffbb0ab codeclimate < coverage/lcov.info 143 | ``` 144 | 145 | 代码来源: [https://github.com/phodal/lettuce](https://github.com/phodal/lettuce) 146 | 147 | 我们把这些集成到``README.md``之后,就有了之前那张图。 148 | 149 | CI对于一个开发者在不同城市开发同一项目上来说是很重要的,这意味着当你添加的部分功能有测试覆盖的时候,项目代码会更加强壮。 150 | 151 | ### 代码质量 152 | 153 | 像``jslint``这类的工具,只能保证代码在语法上是正确的,但是不能保证你没有写一堆bad smell的代码。 154 | 155 | - 重复代码 156 | - 过长的函数 157 | - 等等 158 | 159 | ``Code Climate``是一个与github集成的工具,我们不仅仅可以看到测试覆盖率,还有代码质量。 160 | 161 | 先看看上面的ajax类: 162 | 163 | ```javascript 164 | Lettuce.get = function (url, callback) { 165 | Lettuce.send(url, 'GET', callback); 166 | }; 167 | 168 | Lettuce.send = function (url, method, callback, data) { 169 | data = data || null; 170 | var request = new XMLHttpRequest(); 171 | if (callback instanceof Function) { 172 | request.onreadystatechange = function () { 173 | if (request.readyState === 4 && (request.status === 200 || request.status === 0)) { 174 | callback(request.responseText); 175 | } 176 | }; 177 | } 178 | request.open(method, url, true); 179 | if (data instanceof Object) { 180 | data = JSON.stringify(data); 181 | request.setRequestHeader('Content-Type', 'application/json'); 182 | } 183 | request.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 184 | request.send(data); 185 | }; 186 | ``` 187 | 188 | 代码来源: [https://github.com/phodal/lettuce](https://github.com/phodal/lettuce) 189 | 190 | 在[Code Climate](https://codeclimate.com/github/phodal/lettuce/src/ajax.js)在出现了一堆问题 191 | 192 | - Missing "use strict" statement. (Line 2) 193 | - Missing "use strict" statement. (Line 14) 194 | - 'Lettuce' is not defined. (Line 5) 195 | 196 | 而这些都是小问题啦,有时可能会有 197 | 198 | - Similar code found in two :expression_statement nodes (mass = 86) 199 | 200 | 这就意味着我们可以对上面的代码进行重构,他们是重复的代码。 201 | 202 | ### 重构 203 | 204 | 不想在这里说太多关于``重构``的东西,可以参考Martin Flower的《重构》一书去多了解一些重构的细节。 205 | 206 | 这时想说的是,只有代码被测试覆盖住了,那么才能保证重构的过程没有出错。 207 | -------------------------------------------------------------------------------- /chapters/01-start-project.md: -------------------------------------------------------------------------------- 1 | 创建开源项目 2 | === 3 | 4 | 人们出于不同的目的来创建开源项目,可不论目的是什么,过程都是一样的。 5 | 6 | 1. 首先,我们需要为我们的项目取一个名字。 7 | 2. 然后,为我们的开源项目选择一个合适的 LICENSE 8 | 3. 然后再去创建项目 9 | 10 | 取一个好的名字 11 | --- 12 | 13 | 取名字,从来就不是一件容易的事。 14 | 15 | 因此,我就长话短说,一般就是取一个有意义的名字,当然没有意义也没有任何问题。 16 | 17 | 通常而言,如果自己计划有一系列的开源项目,那么我们可以保持一定的命名规则。 18 | 19 | 挑选好 LICENSE 20 | --- 21 | 22 | > 在二十世纪而七十年代末和八十年代初,为了防止自己的软件被竞争对手所使用,大多数厂家停止分发其软件源代码,并开始使用版权和限制性软件许可证,来限制或者禁止软件源代码的复制或再分配。随后,Richard Matthew Stallman(Richard Matthew Stallman)发起了自由软件运动,他开创了 Copyleft 的概念:使用版权法的原则来保护使用、修改和分发自由软件的权利,并且是描述这些术语的自由软件许可证的主要作者。最为人所称道的是GPL(被广泛使用的自由软件协议)。[^rms] 23 | 24 | (PS:关于自由软件及 RMS 的更多信息、历史,可以阅读《若为自由故:自由软件之父 - 理查德 斯托曼传》) 25 | 26 | [^rms]: https://zh.wikipedia.org/wiki/%E7%90%86%E6%9F%A5%E5%BE%B7%C2%B7%E6%96%AF%E6%89%98%E6%9B%BC 27 | 28 | 随后,便诞生了开源软件的概念,开源的要求比自由软件宽松一些[^gnu_gpl]。迄今发布的自由软件源代码都是开源软件,而并非所有的开源软件都是自由软件。这是因为不同的许可(协议)赋予用户不同的权利,如 GPL 协议强制要求开源修改过源码的代码,而宽松一点的 MIT 则不会有这种要求。 29 | 30 | [^gnu_gpl]: https://www.gnu.org/philosophy/open-source-misses-the-point.zh-cn.html 31 | 32 | 如下是不同开源许可证的市场占有率及使用情况。 33 | 34 | ![License 使用情况](../img/permissive-vs-copylift-license-2.jpg) 35 | 36 | 又比如,在我们看到的一些外版书籍上,如果拥有代码。那么作者一般就会在前言或者类似的位置里,指明书中代码的版权所属。如: 37 | 38 | > 也许你需要在自己的程序或文档中用到本书的代码,但除非大篇幅地使用,否则不必与我们联系取得授权。例如,用本书中的几段代码编写程序无需请求许可,blabla。 39 | 40 | 于是,选择一个合理的 LICENSE,就变成了一个有趣的话题。为此,笔者做了一个如何进行开源协议选型的流程图: 41 | 42 | [![如何选择 License](../img/licenses.png)](https://github.com/phodal/licenses) 43 | 44 | 简单地来说,这些 License 之间是一些权利的区别,如当你把代码放置到公有领域,就意味着任何人可以修改,并且不需要标明出注;可如果你想要别人标明出处及作者,你就需要 MIT 协议;而你希望别人闭源的话,那么你就需要 MPL 协议等等。 45 | 46 | 那么,下面让我们简单地介绍一下不同的几个协议。 47 | 48 | ### 公有领域 49 | 50 | > WTFPL(Do What The Fuck You Want To Public License,中文译名:你他妈的想干嘛就干嘛公共许可证)是一种不太常用的、极度放任的自由软件许可证。它的条款基本等同于贡献到公有领域。[^wtfpl] 51 | 52 | [^wtfpl]: https://zh.wikipedia.org/wiki/WTFPL 53 | 54 | 这就意味着,对于拿到这些代码的其他人,他们想怎么修改就可以怎么修改。 55 | 56 | ### GPL 57 | 58 | 由于 GPL 的传染性,便意味着,他人引用我们的代码时,其所写的代码也需要使用 GPL 开源。即:GPL 是有 “传染性” 的 “病毒” ,因为 GPL 条款规定演绎作品也必须是 GPL 的。 59 | 60 | 而如果我们只针对的是,他人可以使用库,而不开源,则可以用 LGPL。但是修改库则不适用。 61 | 62 | ### MIT 63 | 64 | 因此,一般而言,我使用的是 MIT 协议。至少我保留了一个署名权,即你可以修改我的代码,但是在 LICENSE 里必须加上我的名字。 65 | 66 | 选用 MIT 特别有意思,特别是在最近几年里,发生过: 67 | 68 | - [iView “抄袭” Element UI 事件](https://zhuanlan.zhihu.com/p/25739512) 69 | - [AndroidTVLauncher “抄袭” 事件](https://github.com/JackyAndroid/AndroidTVLauncher/issues/22) 70 | 71 | 等等。这告诫了我们,如果你不想要有这种经历,那么就不要用 MIT 了。 72 | 73 | ### Creative Commons 74 | 75 | 是的,当我写 Markdown 的时候,考虑到未来会以纸质书的形式出现,便会使用 CC-BY-NC-ND 协议: 76 | 77 | - CC -> Creative Commons 78 | - BY -> 署名(英语:Attribution,by) 79 | - NC -> 非商业性使用(英语:NonCommercial) 80 | - ND -> 禁止演绎(英语:NoDerivs)。 81 | 82 | 即,任何人可以使用我写的电子书来自由复制、散布、展示及演出,但是不得用于商业用途(作者本人可以)。它可以随意地放在他的博客上,他的各个文章里。但是必须标明出自,并且不得改变、转变或更改本作品。 83 | 84 | 如果你不介意的话,你可以使用公有领域(Public Domain)。可是这样一来,万一有一天,别人直接拿你的作品出书,你就骂爹了。 85 | 86 | -------------------------------------------------------------------------------- /chapters/02-github-fundamentals.md: -------------------------------------------------------------------------------- 1 | # Git 基本知识与 GitHub 使用 2 | 3 | ## Git 4 | 5 | 从一般开发者的角度来看,Git 有以下功能: 6 | 7 | 1. 从服务器上克隆数据库(包括代码和版本信息)到单机上。 8 | 2. 在自己的机器上创建分支,修改代码。 9 | 3. 在单机上自己创建的分支上提交代码。 10 | 4. 在单机上合并分支。 11 | 5. 新建一个分支,把服务器上最新版的代码 fetch 下来,然后跟自己的主分支合并。 12 | 6. 生成补丁(patch),把补丁发送给主开发者。 13 | 7. 看主开发者的反馈,如果主开发者发现两个一般开发者之间有冲突(他们之间可以合作解决的冲突),就会要求他们先解决冲突,然后再由其中一个人提交。如果主开发者可以自己解决,或者没有冲突,就通过。 14 | 8. 一般开发者之间解决冲突的方法,开发者之间可以使用 pull 命令解决冲突,解决完冲突之后再向主开发者提交补丁。 15 | 16 | 从主开发者的角度(假设主开发者不用开发代码)看,Git 有以下功能: 17 | 18 | 1. 查看邮件或者通过其它方式查看一般开发者的提交状态。 19 | 2. 打上补丁,解决冲突(可以自己解决,也可以要求开发者之间解决以后再重新提交,如果是开源项目,还要决定哪些补丁有用,哪些不用)。 20 | 3. 向公共服务器提交结果,然后通知所有开发人员。 21 | 22 | ### Git 初入 23 | 24 | 如果是第一次使用 Git,你需要设置署名和邮箱: 25 | 26 | ``` 27 | $ git config --global user.name "用户名" 28 | $ git config --global user.email "电子邮箱" 29 | ``` 30 | 31 | 将代码仓库 clone 到本地,其实就是将代码复制到你的机器里,并交由 Git 来管理: 32 | 33 | ``` 34 | $ git clone git@github.com:someone/symfony-docs-chs.git 35 | ``` 36 | 37 | 你可以修改复制到本地的代码了(symfony-docs-chs 项目里都是 rst 格式的文档)。当你觉得完成了一定的工作量,想做个阶段性的提交: 38 | 39 | 向这个本地的代码仓库添加当前目录的所有改动: 40 | 41 | ``` 42 | $ git add . 43 | ``` 44 | 45 | 或者只是添加某个文件: 46 | 47 | ``` 48 | $ git add -p 49 | ```` 50 | 51 | 我们可以输入 52 | 53 | ``` 54 | $git status 55 | ``` 56 | 57 | 来看现在的状态,如下图是添加之前的: 58 | 59 | ![Before add](../img/before-add.png) 60 | 61 | 下面是添加之后 的 62 | 63 | ![After add](../img/after-add.png) 64 | 65 | 可以看到状态的变化是从黄色到绿色,即 unstage 到 add。 66 | 67 | 68 | ## GitHub 69 | 70 | Wiki 百科上是这么说的 71 | 72 | > GitHub 是一个共享虚拟主机服务,用于存放使用Git版本控制的软件代码和内容项目。它由GitHub公司(曾称Logical Awesome)的开发者Chris Wanstrath、PJ Hyett和Tom Preston-Werner 73 | 使用Ruby on Rails编写而成。 74 | 75 | 当然让我们看看官方的介绍: 76 | 77 | > 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. 78 | 79 | 80 | 它还是什么? 81 | 82 | - 网站 83 | - 免费博客 84 | - 管理配置文件 85 | - 收集资料 86 | - 简历 87 | - 管理代码片段 88 | - 托管编程环境 89 | - 写作 90 | 91 | 等等。看上去像是大餐,但是你还需要了解点什么? 92 | 93 | ### 版本管理与软件部署 94 | 95 | jQuery[^jQuery] 在发布版本``2.1.3``,一共有 152 个 commit。我们可以看到如下的提交信息: 96 | 97 | - Ajax: Always use script injection in globalEval … bbdfbb4 98 | - Effects: Reintroduce use of requestAnimationFrame … 72119e0 99 | - Effects: Improve raf logic … 708764f 100 | - Build: Move test to appropriate module fbdbb6f 101 | - Build: Update commitplease dev dependency 102 | - ... 103 | 104 | ### GitHub 与 Git 105 | 106 | > Git是一个分布式的版本控制系统,最初由Linus Torvalds编写,用作Linux内核代码的管理。在推出后,Git在其它项目中也取得了很大成功,尤其是在Ruby社区中。目前,包括Rubinius、Merb和Bitcoin在内的很多知名项目都使用了Git。Git同样可以被诸如Capistrano和Vlad the Deployer这样的部署工具所使用。 107 | 108 | > GitHub可以托管各种git库,并提供一个web界面,但与其它像 SourceForge或Google Code这样的服务不同,GitHub的独特卖点在于从另外一个项目进行分支的简易性。为一个项目贡献代码非常简单:首先点击项目站点的“fork”的按钮,然后将代码检出并将修改加入到刚才分出的代码库中,最后通过内建的“pull request”机制向项目负责人申请代码合并。已经有人将GitHub称为代码玩家的MySpace。 109 | 110 | ### 在 GitHub 创建项目 111 | 112 | 接着,我们试试在上面创建一个项目: 113 | 114 | ![GitHub Roam](../img/github-roam-create.jpg) 115 | 116 | 就会有下面的提醒: 117 | 118 | ![GitHub Roam](../img/project-init.jpg) 119 | 120 | 它提供多种方式的创建方法: 121 | 122 | > …or create a new repository on the command line 123 | 124 | ``` 125 | echo "# github-roam" >> README.md 126 | git init 127 | git add README.md 128 | git commit -m "first commit" 129 | git remote add origin git@github.com:phodal/github-roam.git 130 | git push -u origin master 131 | ``` 132 | 133 | > …or push an existing repository from the command line 134 | 135 | ``` 136 | git remote add origin git@github.com:phodal/github-roam.git 137 | git push -u origin master 138 | ``` 139 | 140 | 如果你完成了上面的步骤之后,那么我想你想知道你需要怎样的项目。 141 | 142 | ## GitHub 流行项目分析 143 | 144 | 之前曾经分析过一些 GitHub 的用户行为,现在我们先来说说 GitHub 上的 Star 吧。(截止:2015年3月9日23时。) 145 | 146 | 用户 | 项目名 | Language | Star | Url 147 | -----|---------- |----------|------|---- 148 | twbs | Bootstrap | CSS | 78490 | [https://github.com/twbs/bootstrap](https://github.com/twbs/bootstrap) 149 | vhf |free-programming books | - | 37240 | [https://github.com/vhf/free-programming-books](https://github.com/vhf/free-programming-books) 150 | angular | angular.js | JavaScript | 36,061 | [https://github.com/angular/angular.js](https://github.com/angular/angular.js) 151 | mbostock | d3 | JavaScript | 35,257 | [https://github.com/mbostock/d3](https://github.com/mbostock/d3) 152 | joyent | node | JavaScript | 35,077 | [https://github.com/joyent/node](https://github.com/joyent/node) 153 | 154 | 上面列出来的是前5的,看看大于 1 万个 Stars 的项目的分布,一共有 82 个: 155 | 156 | 语言 | 项目数 157 | -----|----- 158 | JavaScript | 37 159 | Ruby | 6 160 | CSS | 6 161 | Python | 4 162 | HTML | 3 163 | C++ | 3 164 | VimL | 2 165 | Shell | 2 166 | Go | 2 167 | C | 2 168 | 169 | 类型分布: 170 | 171 | 172 | - 库和框架:如``jQuery`` 173 | - 系统:如``Linux``、``hhvm``、``docker`` 174 | - 配置集:如``dotfiles`` 175 | - 辅助工具:如``oh-my-zsh`` 176 | - 工具:如``Homewbrew``和``Bower`` 177 | - 资料收集:如``free programming books``,``You-Dont-Know-JS``,``Font-Awesome`` 178 | - 其他:简历如``Resume`` 179 | 180 | ## Pull Request 181 | 182 | 除了创建项目之外,我们也可以创建 Pull Request 来做贡献。 183 | 184 | ### 我的第一个 PR 185 | 186 | 我的第一个 PR 是给一个小的 Node 的 CoAP 相关的库的 Pull Request。原因比较简单,是因为它的 README.md 写错了,导致我无法进行下一步。 187 | 188 | const dgram = require('dgram') 189 | - , coapPacket = require('coap-packet') 190 | + , package = require('coap-packet') 191 | 192 | 很简单,却又很有用的步骤,另外一个也是: 193 | 194 | ``` 195 | else 196 | cat << END 197 | $0: error: module ngx_pagespeed requires the pagespeed optimization library. 198 | -Look in obj/autoconf.err for more details. 199 | +Look in objs/autoconf.err for more details. 200 | END 201 | exit 1 202 | fi 203 | ``` 204 | 205 | ### CLA 206 | 207 | CLA 即 Contributor License Agreement,在为一些大的组织、机构提交 Pull Request 的时候,可能需要签署这个协议。他们会在你的 Pull Request 里问你,只有你到他们的网站去注册并同意协议才会接受你的 PR。 208 | 209 | 以下是我为 Google 提交的一个 PR 210 | 211 | ![Google CLA](../img/google-cla.png) 212 | 213 | 以及 Eclipse 的一个 PR 214 | 215 | ![Eclipse CLA](../img/eclipse-cla.png) 216 | 217 | 他们都要求我签署 CLA。 218 | -------------------------------------------------------------------------------- /chapters/03-build-github-project.md: -------------------------------------------------------------------------------- 1 | # 构建 GitHub 项目 2 | 3 | ## 如何用好 GitHub 4 | 5 | 如何用好 GitHub,并实践一些敏捷软件开发是一个很有意思的事情.我们可以在上面做很多事情,从测试到 CI,再到自动部署. 6 | 7 | ### 敏捷软件开发 8 | 9 | 显然我是在扯淡,这和敏捷软件开发没有什么关系。不过我也不知道瀑布流是怎样的。说说我所知道的一个项目的组成吧: 10 | 11 | - 看板式管理应用程序(如 trello,简单地说就是管理软件功能) 12 | - CI(持续集成) 13 | - 测试覆盖率 14 | - 代码质量(code smell) 15 | 16 | 对于一个不是远程的团队(如只有一个人的项目)来说,Trello、Jenkin、Jira不是必需的: 17 | 18 | > 你存在,我深深的脑海里 19 | 20 | 当只有一个人的时候,你只需要明确知道自己想要什么就够了。我们还需要的是 CI、测试,以来提升代码的质量。 21 | 22 | ### 测试 23 | 24 | 通常我们都会找 Document,如果没有的话,你会找什么?看源代码,还是看测试? 25 | 26 | ```javascript 27 | it("specifying response when you need it", function (done) { 28 | var doneFn = jasmine.createSpy("success"); 29 | 30 | lettuce.get('/some/cool/url', function (result) { 31 | expect(result).toEqual("awesome response"); 32 | done(); 33 | }); 34 | 35 | expect(jasmine.Ajax.requests.mostRecent().url).toBe('/some/cool/url'); 36 | expect(doneFn).not.toHaveBeenCalled(); 37 | 38 | jasmine.Ajax.requests.mostRecent().respondWith({ 39 | "status": 200, 40 | "contentType": 'text/plain', 41 | "responseText": 'awesome response' 42 | }); 43 | }); 44 | ``` 45 | 46 | 代码来源:[https://github.com/phodal/lettuce](https://github.com/phodal/lettuce) 47 | 48 | 上面的测试用例,清清楚楚地写明了用法,虽然写得有点扯。 49 | 50 | 等等,测试是用来干什么的。那么,先说说我为什么会想去写测试吧: 51 | 52 | - 我不希望每次做完一个个新功能的时候,再手动地去测试一个个功能。(自动化测试) 53 | - 我不希望在重构的时候发现破坏了原来的功能,而我还一无所知。 54 | - 我不敢push代码,因为我没有把握。 55 | 56 | 虽然,我不是 TDD 的死忠,测试的目的是保证功能正常,TDD 没法让我们写出质量更高的代码。但是有时TDD是不错的,可以让我们写出逻辑更简单地代码。 57 | 58 | 也许你已经知道了``Selenium``、``Jasmine``、``Cucumber``等等的框架,看到过类似于下面的测试 59 | 60 | ``` 61 | Ajax 62 | ✓ specifying response when you need it 63 | ✓ specifying html when you need it 64 | ✓ should be post to some where 65 | Class 66 | ✓ respects instanceof 67 | ✓ inherits methods (also super) 68 | ✓ extend methods 69 | Effect 70 | ✓ should be able fadein elements 71 | ✓ should be able fadeout elements 72 | ``` 73 | 74 | 代码来源:[https://github.com/phodal/lettuce](https://github.com/phodal/lettuce) 75 | 76 | 看上去似乎每个测试都很小,不过补完每一个测试之后我们就得到了测试覆盖率 77 | 78 | File | Statements | Branches | Functions | Lines 79 | -----|------------|----------|-----------|------ 80 | lettuce.js | 98.58% (209 / 212)| 82.98%(78 / 94) | 100.00% (54 / 54) | 98.58% (209 / 212) 81 | 82 | 本地测试都通过了,于是我们添加了``Travis-CI``来跑我们的测试 83 | 84 | ### CI 85 | 86 | 虽然 node.js 不算是一门语言,但是因为我们用的 node,下面的是一个简单的 ``.travis.yml`` 示例: 87 | 88 | ```yml 89 | language: node_js 90 | node_js: 91 | - "0.10" 92 | 93 | notifications: 94 | email: false 95 | 96 | before_install: npm install -g grunt-cli 97 | install: npm install 98 | after_success: CODECLIMATE_REPO_TOKEN=321480822fc37deb0de70a11931b4cb6a2a3cc411680e8f4569936ac8ffbb0ab codeclimate < coverage/lcov.info 99 | ``` 100 | 101 | 代码来源:[https://github.com/phodal/lettuce](https://github.com/phodal/lettuce) 102 | 103 | 我们把这些集成到 ``README.md`` 之后,就有了之前那张图。 104 | 105 | CI对于一个开发者在不同城市开发同一项目上来说是很重要的,这意味着当你添加的部分功能有测试覆盖的时候,项目代码会更加强壮。 106 | 107 | ### 代码质量 108 | 109 | 像 ``jslint`` 这类的工具,只能保证代码在语法上是正确的,但是不能保证你写了一堆 bad smell 的代码。 110 | 111 | - 重复代码 112 | - 过长的函数 113 | - 等等 114 | 115 | ``Code Climate`` 是一个与 GitHub 集成的工具,我们不仅仅可以看到测试覆盖率,还有代码质量。 116 | 117 | 先看看上面的 ajax 类: 118 | 119 | ```javascript 120 | Lettuce.get = function (url, callback) { 121 | Lettuce.send(url, 'GET', callback); 122 | }; 123 | 124 | Lettuce.send = function (url, method, callback, data) { 125 | data = data || null; 126 | var request = new XMLHttpRequest(); 127 | if (callback instanceof Function) { 128 | request.onreadystatechange = function () { 129 | if (request.readyState === 4 && (request.status === 200 || request.status === 0)) { 130 | callback(request.responseText); 131 | } 132 | }; 133 | } 134 | request.open(method, url, true); 135 | if (data instanceof Object) { 136 | data = JSON.stringify(data); 137 | request.setRequestHeader('Content-Type', 'application/json'); 138 | } 139 | request.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 140 | request.send(data); 141 | }; 142 | ``` 143 | 144 | 代码来源:[https://github.com/phodal/lettuce](https://github.com/phodal/lettuce) 145 | 146 | 在 [Code Climate](https://codeclimate.com/github/phodal/lettuce/src/ajax.js) 在出现了一堆问题 147 | 148 | - Missing "use strict" statement. (Line 2) 149 | - Missing "use strict" statement. (Line 14) 150 | - 'Lettuce' is not defined. (Line 5) 151 | 152 | 而这些都是小问题啦,有时可能会有 153 | 154 | - Similar code found in two :expression_statement nodes (mass = 86) 155 | 156 | 这就意味着我们可以对上面的代码进行重构,他们是重复的代码。 157 | 158 | ## 模块分离与测试 159 | 160 | 在之前说到 161 | 162 | > 奋斗了近半个月后,将 fork 的代码读懂、重构、升级版本、调整,添加新功能、添加测试、添加 CI、添加分享之后,终于 almost finish。 163 | 164 | 今天就来说说是怎样做的。 165 | 166 | 以之前造的 [Lettuce](https://github.com/phodal/lettuce) 为例,里面有: 167 | 168 | - 代码质量(Code Climate) 169 | - CI状态(Travis CI) 170 | - 测试覆盖率(96%) 171 | - 自动化测试(npm test) 172 | - 文档 173 | 174 | 按照 [Web Developer 路线图](https://github.com/phodal/awesome-developer)来说,我们还需要有: 175 | 176 | - 版本管理 177 | - 自动部署 178 | 179 | 等等。 180 | 181 | ### 代码模块化 182 | 183 | 在 SkillTree 的源码里,大致分为三部分: 184 | 185 | - namespace 函数:顾名思义 186 | - Calculator 也就是 TalentTree,主要负责解析、生成 url,头像,依赖等等 187 | - Skill 主要是 tips 部分。 188 | 189 | 而这一些都在一个 JS 里,对于一个库来说,是一件好事,但是对于一个项目来说,并非如此。 190 | 191 | 依赖的库有 192 | 193 | - jQuery 194 | - Knockout 195 | 196 | 好在 Knockout 可以用 Require.js 进行管理,于是,使用了 ``Require.js`` 进行管理: 197 | 198 | ```html 199 | 200 | ``` 201 | 202 | ``main.js`` 配置如下: 203 | 204 | ```javascript 205 | require.config({ 206 | baseUrl: 'app', 207 | paths:{ 208 | jquery: 'lib/jquery', 209 | json: 'lib/json', 210 | text: 'lib/text' 211 | } 212 | }); 213 | 214 | require(['scripts/ko-bindings']); 215 | 216 | require(['lib/knockout', 'scripts/TalentTree', 'json!data/web.json'], function(ko, TalentTree, TalentData) { 217 | 'use strict'; 218 | var vm = new TalentTree(TalentData); 219 | ko.applyBindings(vm); 220 | }); 221 | ``` 222 | 223 | text、JSON 插件主要是用于处理 web.json,即用 JSON 来处理技能,于是不同的类到了不同的 JS 文件。 224 | 225 | . 226 | |____Book.js 227 | |____Doc.js 228 | |____ko-bindings.js 229 | |____Link.js 230 | |____main.js 231 | |____Skill.js 232 | |____TalentTree.js 233 | |____Utils.js 234 | 235 | 加上了后来的推荐阅读书籍等等。而 Book 和 Link 都是继承自 Doc。 236 | 237 | ```javascript 238 | define(['scripts/Doc'], function(Doc) { 239 | 'use strict'; 240 | function Book(_e) { 241 | Doc.apply(this, arguments); 242 | } 243 | Book.prototype = new Doc(); 244 | 245 | return Book; 246 | }); 247 | ``` 248 | 249 | 而这里便是后面对其进行重构的内容。Doc 类则是 Skillock 中类的一个缩影 250 | 251 | ```javascript 252 | define([], function() { 253 | 'use strict'; 254 | var Doc = function (_e) { 255 | var e = _e || {}; 256 | var self = this; 257 | 258 | self.label = e.label || (e.url || 'Learn more'); 259 | self.url = e.url || 'javascript:void(0)'; 260 | }; 261 | 262 | return Doc; 263 | }); 264 | ``` 265 | 266 | 或者说这是一个 AMD 的 Class 应该有的样子。考虑到 this 的隐性绑定,作者用了self=this 来避免这个问题。最后 Return 了这个对象,我们在调用的就需要 new 一个。大部分在代码中返回的都是对象,除了在 Utils 类里面返回的是函数: 267 | 268 | ```javascript 269 | return { 270 | getSkillsByHash: getSkillsByHash, 271 | getSkillById: getSkillById, 272 | prettyJoin: prettyJoin 273 | }; 274 | ``` 275 | 276 | 当然函数也是一个对象。 277 | 278 | ### 自动化测试 279 | 280 | 一直习惯用 Travis CI,于是也继续用 Travis Ci,``.travis.yml`` 配置如下所示: 281 | 282 | ```yml 283 | language: node_js 284 | node_js: 285 | - "0.10" 286 | 287 | notifications: 288 | email: false 289 | 290 | branches: 291 | only: 292 | - gh-pages 293 | ``` 294 | 295 | 使用 gh-pages 的原因是,我们一 push 代码的时候,就可以自动测试、部署等等,好处一堆堆的。 296 | 297 | 接着我们需要在 ``package.json`` 里面添加脚本 298 | 299 | ```javascript 300 | "scripts": { 301 | "test": "mocha" 302 | } 303 | ``` 304 | 305 | 这样当我们 push 代码的时候便会自动跑所有的测试。因为 mocha 的主要配置是用 ``mocha.opts``,所以我们还需要配置一下 ``mocha.opts`` 306 | 307 | --reporter spec 308 | --ui bdd 309 | --growl 310 | --colors 311 | test/spec 312 | 313 | 最后的 ``test/spec`` 是指定测试的目录。 314 | 315 | ### JSLint 316 | 317 | > JSLint定义了一组编码约定,这比ECMA定义的语言更为严格。这些编码约定汲取了多年来的丰富编码经验,并以一条年代久远的编程原则 作为宗旨:能做并不意味着应该做。JSLint会对它认为有的编码实践加标志,另外还会指出哪些是明显的错误,从而促使你养成好的 JavaScript编码习惯。 318 | 319 | 当我们的 JS 写得不合理的时候,这时测试就无法通过: 320 | 321 | line 5 col 25 A constructor name should start with an uppercase letter. 322 | line 21 col 62 Strings must use singlequote. 323 | 324 | 这是一种驱动写出更规范 JS 的方法。 325 | 326 | 327 | ### Mocha 328 | 329 | > Mocha 是一个优秀的JS测试框架,支持TDD/BDD,结合 should.js/expect/chai/better-assert,能轻松构建各种风格的测试用例。 330 | 331 | 最后的效果如下所示: 332 | 333 | Book,Link 334 | Book Test 335 | ✓ should return book label & url 336 | Link Test 337 | ✓ should return link label & url 338 | 339 | ### 测试示例 340 | 341 | 简单地看一下 Book 的测试: 342 | 343 | ```javascript 344 | /* global describe, it */ 345 | 346 | var requirejs = require("requirejs"); 347 | var assert = require("assert"); 348 | var should = require("should"); 349 | requirejs.config({ 350 | baseUrl: 'app/', 351 | nodeRequire: require 352 | }); 353 | 354 | describe('Book,Link', function () { 355 | var Book, Link; 356 | before(function (done) { 357 | requirejs(['scripts/Book'、], function (Book_Class) { 358 | Book = Book_Class; 359 | done(); 360 | }); 361 | }); 362 | 363 | describe('Book Test', function () { 364 | it('should return book label & url', function () { 365 | var book_name = 'Head First HTML与CSS'; 366 | var url = 'http://www.phodal.com'; 367 | var books = { 368 | label: book_name, 369 | url: url 370 | }; 371 | 372 | var _book = new Book(books); 373 | _book.label.should.equal(book_name); 374 | _book.url.should.equal(url); 375 | }); 376 | }); 377 | }); 378 | ``` 379 | 380 | 因为我们用 ``require.js`` 来管理浏览器端,在后台写测试来测试的时候,我们也需要用他来管理我们的依赖,这也就是为什么这个测试这么长的原因,多数情况下一个测试类似于这样子的。(用 Jasmine 似乎会是一个更好的主意,但是用习惯 Jasmine 了) 381 | 382 | ```javascript 383 | describe('Book Test', function () { 384 | it('should return book label & url', function () { 385 | var book_name = 'Head First HTML与CSS'; 386 | var url = 'http://www.phodal.com'; 387 | var books = { 388 | label: book_name, 389 | url: url 390 | }; 391 | 392 | var _book = new Book(books); 393 | _book.label.should.equal(book_name); 394 | _book.url.should.equal(url); 395 | }); 396 | }); 397 | ``` 398 | 399 | 最后的断言,也算是测试的核心,保证测试是有用的。 400 | 401 | ## 代码质量与重构 402 | 403 | - 当你写了一大堆代码,你没有意识到里面有一大堆重复。 404 | - 当你写了一大堆测试,却不知道覆盖率有多少。 405 | 406 | 这就是个问题了,于是偶然间看到了一个叫 code climate 的网站。 407 | 408 | ### Code Climate 409 | 410 | > 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. 411 | 412 | Code Climate 整合一组静态分析工具的结果到一个单一的,实时的报告,让您的团队需要识别热点,探讨新的方法,提高代码质量的信息。 413 | 414 | 简单地来说: 415 | 416 | - 对我们的代码评分 417 | - 找出代码中的坏味道 418 | 419 | 于是,我们先来了个例子 420 | 421 | | Rating | Name | Complexity | Duplication | Churn | C/M | Coverage | Smells | 422 | | ------ | -------------------------------- | ---------- | ----------- | ----- | ---- | -------- | ------ | 423 | | A | lib/coap/coap_request_handler.js | 24 | 0 | 6 | 2.6 | 46.4% | 0 | 424 | | A | lib/coap/coap_result_helper.js | 14 | 0 | 2 | 3.4 | 80.0% | 0 | 425 | | A | lib/coap/coap_server.js | 16 | 0 | 5 | 5.2 | 44.0% | 0 | 426 | | A | lib/database/db_factory.js | 8 | 0 | 3 | 3.8 | 92.3% | 0 | 427 | | A | lib/database/iot_db.js | 7 | 0 | 6 | 1.0 | 58.8% | 0 | 428 | | A | lib/database/mongodb_helper.js | 63 | 0 | 11 | 4.5 | 35.0% | 0 | 429 | | C | lib/database/sqlite_helper.js | 32 | 86 | 10 | 4.5 | 35.0% | 2 | 430 | | B | lib/rest/rest_helper.js | 19 | 62 | 3 | 4.7 | 37.5% | 2 | 431 | | A | lib/rest/rest_server.js | 17 | 0 | 2 | 8.6 | 88.9% | 0 | 432 | 433 | 分享得到的最后的结果是: 434 | 435 | ![Coverage][1] 436 | 437 | ### 代码的坏味道 438 | 439 | 于是我们就打开 ``lib/database/sqlite_helper.js``,因为其中有两个坏味道 440 | 441 | Similar code found in two :expression_statement nodes (mass = 86) 442 | 443 | 在代码的 ``lib/database/sqlite_helper.js:58…61 < >`` 444 | 445 | ```javascript 446 | SQLiteHelper.prototype.deleteData = function (url, callback) { 447 | 'use strict'; 448 | var sql_command = "DELETE FROM " + config.table_name + " where " + URLHandler.getKeyFromURL(url) + "=" + URLHandler.getValueFromURL(url); 449 | SQLiteHelper.prototype.basic(sql_command, callback); 450 | ``` 451 | 452 | lib/database/sqlite_helper.js:64…67 < > 453 | 454 | 与 455 | 456 | ```javascript 457 | SQLiteHelper.prototype.getData = function (url, callback) { 458 | 'use strict'; 459 | var sql_command = "SELECT * FROM " + config.table_name + " where " + URLHandler.getKeyFromURL(url) + "=" + URLHandler.getValueFromURL(url); 460 | SQLiteHelper.prototype.basic(sql_command, callback); 461 | ``` 462 | 463 | 只是这是之前修改过的重复。。 464 | 465 | 原来的代码是这样的 466 | 467 | ```javascript 468 | SQLiteHelper.prototype.postData = function (block, callback) { 469 | 'use strict'; 470 | var db = new sqlite3.Database(config.db_name); 471 | var str = this.parseData(config.keys); 472 | var string = this.parseData(block); 473 | 474 | var sql_command = "insert or replace into " + config.table_name + " (" + str + ") VALUES (" + string + ");"; 475 | db.all(sql_command, function (err) { 476 | SQLiteHelper.prototype.errorHandler(err); 477 | db.close(); 478 | callback(); 479 | }); 480 | }; 481 | 482 | SQLiteHelper.prototype.deleteData = function (url, callback) { 483 | 'use strict'; 484 | var db = new sqlite3.Database(config.db_name); 485 | var sql_command = "DELETE FROM " + config.table_name + " where " + URLHandler.getKeyFromURL(url) + "=" + URLHandler.getValueFromURL(url); 486 | db.all(sql_command, function (err) { 487 | SQLiteHelper.prototype.errorHandler(err); 488 | db.close(); 489 | callback(); 490 | }); 491 | }; 492 | 493 | SQLiteHelper.prototype.getData = function (url, callback) { 494 | 'use strict'; 495 | var db = new sqlite3.Database(config.db_name); 496 | var sql_command = "SELECT * FROM " + config.table_name + " where " + URLHandler.getKeyFromURL(url) + "=" + URLHandler.getValueFromURL(url); 497 | db.all(sql_command, function (err, rows) { 498 | SQLiteHelper.prototype.errorHandler(err); 499 | db.close(); 500 | callback(JSON.stringify(rows)); 501 | }); 502 | }; 503 | ``` 504 | 说的也是大量的重复,重构完的代码 505 | 506 | ```javascript 507 | SQLiteHelper.prototype.basic = function(sql, db_callback){ 508 | 'use strict'; 509 | var db = new sqlite3.Database(config.db_name); 510 | db.all(sql, function (err, rows) { 511 | SQLiteHelper.prototype.errorHandler(err); 512 | db.close(); 513 | db_callback(JSON.stringify(rows)); 514 | }); 515 | 516 | }; 517 | 518 | SQLiteHelper.prototype.postData = function (block, callback) { 519 | 'use strict'; 520 | var str = this.parseData(config.keys); 521 | var string = this.parseData(block); 522 | 523 | var sql_command = "insert or replace into " + config.table_name + " (" + str + ") VALUES (" + string + ");"; 524 | SQLiteHelper.prototype.basic(sql_command, callback); 525 | }; 526 | 527 | SQLiteHelper.prototype.deleteData = function (url, callback) { 528 | 'use strict'; 529 | var sql_command = "DELETE FROM " + config.table_name + " where " + URLHandler.getKeyFromURL(url) + "=" + URLHandler.getValueFromURL(url); 530 | SQLiteHelper.prototype.basic(sql_command, callback); 531 | }; 532 | 533 | SQLiteHelper.prototype.getData = function (url, callback) { 534 | 'use strict'; 535 | var sql_command = "SELECT * FROM " + config.table_name + " where " + URLHandler.getKeyFromURL(url) + "=" + URLHandler.getValueFromURL(url); 536 | SQLiteHelper.prototype.basic(sql_command, callback); 537 | }; 538 | ``` 539 | 540 | 重构完后的代码比原来还长,这似乎是个问题~~ 541 | -------------------------------------------------------------------------------- /chapters/04-commit-message.md: -------------------------------------------------------------------------------- 1 | Git 提交信息及几种不同的规范 2 | === 3 | 4 | > 受 Growth 3.0 开发的影响,最近更新文章的频率会有所降低。今天,让我们来谈谈一个好的 Git、SVN 提交信息是怎样规范出来的。 5 | 6 | 在团队协作中,使用版本管理工具 Git、SVN 几乎都是这个行业的标准。当我们提交代码的时候,需要编写提交信息(commit message)。 7 | 8 | 而提交信息的主要用途是:**告诉这个项目的人,这次代码提交里做了些什么**。如,我更新了 React Native Elements 的版本,那么它就可以是:``[T] upgrade react native elements``。对应的我修改的代码就是:``package.json`` 和 ``yarn.lock`` 中的文件。一般来说,建议**小步提交**,即按自己的 Tasking 步骤来的提交,每一小步都有对应的提交信息。这样做的主要目的是:**防止一次修改中,修改过多的文件,导致后期修改、维护、撤销等等困难**。 9 | 10 | 而对于不同的团队来说,都会遵循一定的规范,本文主要会介绍以下几种写法: 11 | 12 | - 工作写法 13 | - 常规写法 14 | - 开源库写法 15 | 16 | 那么,先从我习惯的做法说起。 17 | 18 | 工作写法 19 | --- 20 | 21 | 在我的第一个项目里,我们使用 Jira 作为看板工具,Bamboo 作为持续集成服务器,并采用结对编程的方式进行。 22 | 23 | 在 Jira 里每一个功能卡都有对应的卡号,而 Bamboo 支持使用 Jira 的任务卡号关联的功能。即在持续构建服务器上示例对应的任务卡号,即相应的提交人。 24 | 25 | 因此,这个时候我们的规范稍微有一些特别: 26 | 27 | ``` 28 | [任务卡号] xx & xx: do something 29 | ``` 30 | 31 | 比如:``[PHODAL-0001] ladohp & phodal: update documents``,解释如下: 32 | 33 | - ``PHODAL-0001``,业务的任务卡号,它可以帮我们找到某个业务修改的原因,即点出相应 bug 的来源 34 | - ``ladohp & phodal`` ,结对编程的两个人的名字,后者(phodal)一般是写代码的人,出于礼貌就放在后面了。由于 Git 的提交人只显示一个,所以写上两个的名字。当提交的人不在时,就可以问另外一个人修改的原因。 35 | - ``update documents``,我们做了什么事情 36 | 37 | 缺点:而对于采用看板的团队来说,并不存在任务卡号这种东西,因此就需要一种额外的作法。 38 | 39 | 常规写法 40 | --- 41 | 42 | 对于我来说,我则习惯这种的写法: 43 | 44 | ``` 45 | [任务分类] 主要修改组件(可选):修改内容 46 | ``` 47 | 48 | 示例 1,``[T] tabs: add icons`` 。其中的 ``T`` 表示这是一个技术卡,``tabs`` 表示修改的是 Tabs,``add icons`` 则表示添加了图标。 49 | 50 | 示例 2,``[SkillTree] detail: add link data``。其中的 ``SkillTree`` 表示修改的是技能树 Tab 下的内容,``detail`` 则表示修改的是详情页,``add link data`` 则表示是添加了技能的数据 51 | 52 | 这样做的主要原因是,它可以轻松也帮我 **filter 出相应业务的内容**。 53 | 54 | 缺点:要这样做需要团队达到一致,因此付出一些额外的成本。 55 | 56 | 开源应用、开源库写法 57 | --- 58 | 59 | 与我们日常工作稍有不同的是:工作中的 Release 计划一般都是事先安排好的,不需要一些 CHANGELOG 什么的。而开源应用、开源库需要有对应的 CHANGELOG,则添加了什么功能、修改了什么等等。毕竟有很多东西是由社区来维护的。 60 | 61 | 因此,这里以做得比较好的开源项目 Angular 为例展示。Angular 团队建议采用以下的形式: 62 | 63 | ``` 64 | (): 65 | 66 | 67 | 68 |