GitHub 漫游指南

博客作者:联系请点击,搬运不易,希望请作者喝咖啡,可以点击联系博客作者

前言

关于作者

黄峰达(Phodal Huang)是一个创客、工程师、咨询师和作家。他毕业于西安文理学院电子信息工程专业,现作为一个咨询师就职于 ThoughtWorks 深圳。长期活跃于开源软件社区 GitHub,目前专注于物联网和前端领域。
作为一个开源软件作者,著有 Growth、Stepping、Lan、Echoesworks 等软件。其中开源学习应用 Growth,广受读者和用户好评,可在 App Store 及各大 Android 应用商店下载。
作为一个技术作者,著有《自己动手设计物联网》(电子工业出版社)、《全栈应用开发:精益实践》(电子工业出版社,正在出版)。并在 GitHub 上开源有《Growth:全栈增长工程师指南》、《GitHub 漫游指南》等七本电子书。
作为技术专家,他为英国 Packt 出版社审阅有物联网书籍《Learning IoT》、《Smart IoT》,前端书籍《Angular 2 Serices》、《Getting started with Angular》等技术书籍。
他热爱编程、写作、设计、旅行、hacking,你可以从他的个人网站:https://www.phodal.com/ 了解到更多的内容。
其它相关信息:
当前为预览版,在使用的过程中遇到任何问题请及时与我联系。阅读过程中的问题,不妨在 GitHub 上提出来:Issues
阅读过程中遇到语法错误、拼写错误、技术错误等等,不妨来个 Pull Request,这样可以帮助到其他阅读这本电子书的童鞋。
我的电子书:
我的微信公众号:作者微信公众号:phodal-weixin
支持作者,可以加入作者的小密圈:小密圈
我的 GitHub 主页上写着加入的时间——Joined on Nov 8, 2010,那时才大一,在那之后的那么长的日子里我都没有登录过。也许是因为我学的不是计算机,到了今天——2015.3.9,我才发现这其实是程序员的社交网站。
过去,曾经有很长的一些时间我试过在 GitHub 上连击,也试着去了解别人是如何用好这个工具的。当然粉丝在 GitHub 上也是很重要的。
在这里,我会试着将我在 GitHub 上学到的东西一一分享出来。

我与 GitHub 的故事

在我大四找工作的时候,试图去寻找一份硬件、物联网相关的工作(PS:专业是电子信息工程)。尽管简历上写得满满的各种经历、经验,然而并没有卵用。跑了几场校园招聘会后,十份简历(PS:事先已经有心里准备)一个也没有投出去——因为学校直接被拒。我对霸面什么的一点兴趣都没有,千里马需要伯乐。后来,我加入了Martin Flower所在的公司,当然这是后话了。
这是一个残酷的世界,在学生时代,如果你长得不帅不高的话,那么多数的附加技能都是白搭(PS:通常富的是看不到这篇文章的)。在工作时期,如果你上家没有名气,那么将会影响你下一份工作的待遇。而,很多东西却可以改变这些,GitHub 就是其中一个。
注册 GitHub 的时候大概是大一的时候,我熟悉的时候已经是大四了,现在已经毕业一年了。在过去的近两年里,我试着以几个维度在 GitHub 上创建项目:
  1. 1.
    快速上手框架来实战,即 demo
  2. 2.
    重构别人的代码
  3. 3.
    创建自己可用的框架
  4. 4.
    快速构建大型应用
  5. 5.
    构建通用的框架

GitHub 与收获

先说说与技能无关的收获吧,毕业设计做的是一个《最小物联网系统》,考虑到我们专业老师没有这方面知识,答辩时会带来问题,尽量往这方面靠拢。当我毕业后,这个项目已经有过百个 Star 了,这样易上手的东西还是比较受欢迎的(PS:不过这种硬件相关的项目通常受限于GitHub上硬件开发工程师比较少的困扰)。
毕业后一个月收到 PACKT 出版社的邮件(PS:他们是在 GitHub 上找到我的),内容是关于 Review 一本物联网书籍,即在《从 Review 到翻译 IT书籍》中提到的《Learning Internet of Things》。作为一个四级没过的“物联网专家”,去审阅一本英文的物联网书籍。。。
当然,后来是审阅完了,书上有我的英文简介。Phodal Huang Introduction
一个月前,收到 MANNING 出版社的邮件(PS:也是在 GitHub 上),关于 Review 一本物联网书籍的目录,并提出建议。
也因此带来了其他更多的东西,当然不是这里的主题。在这里,我们就不讨论各种骚扰邮件,或者中文合作。从没有想象过,我也可以在英语世界有一片小天地。
这些告诉我们,GitHub 上找一个你擅长的主题,那么会有很多人找上你的。

GitHub 与成长

过去写过一篇《如何通过 GitHub 提升自己》的文章,现在只想说三点:
  1. 1.
    测试
  2. 2.
    更多的测试
  3. 3.
    更多的、更多的、更多的测试
没有测试的项目是很扯淡的,除非你的项目只有一个函数,然后那个函数返回Hello,World
如果你的项目代码有上千行,如果你能保证测试覆盖率可以达到95%以的话,那么我想你的项目不会有太复杂的函数。假使有这样的函数,那么它也是被测试覆盖住的。
如果你在用心做这个项目,那么你看到代码写得不好也会试着改进,即重构。当有了一些,你的技能会不断提升。你开始会试着接触更多的东西,如 stub,如 mock,如 fakeserver。
有一天,你会发现你离不开测试。
然后就会相信:那些没有写测试的项目都是在耍流氓

为什么你应该深入 GitHub

上面我们说的都是我们可以收获到的东西,我们开始尝试就意味着我们知道它可能给我们带来好处。上面已经提到很多可以提升自己的例子了,这里再说说其他的。

方便工作

我们可以从中获取到不同的知识、内容、信息。每个人都可以从别人的代码中学习,当我们需要构建一个库的时候,我们可以在上面寻找不同的库和代码来实现我们的功能。如当我在实现一个库的时候,我会在 GitHub 上找到相应的组件:
  • Promise 支持
  • Class 类(PS:没有一个好的类使用的方式)
  • Template 一个简单的模板引擎
  • Router 用来控制页面的路由
  • Ajax 基本的 Ajax Get/Post 请求

获得一份工作

越来越多的人因为 GitHub 获得工作,因为他们的做的东西正好符合一些公司的要求。那么,这些公司在寻找代码的时候,就会试着邀请他们。
因而,在 GitHub 寻找合适的候选人,已经是一种趋势。

扩大交际

如果我们想创造出更好、强大地框架时,那么认识更多的人可能会带来更多的帮助。有时候会同上面那一点一样的效果

创建开源项目

人们出于不同的目的来创建开源项目,可不论目的是什么,过程都是一样的。
  1. 1.
    首先,我们需要为我们的项目取一个名字。
  2. 2.
    然后,为我们的开源项目选择一个合适的 LICENSE
  3. 3.
    然后再去创建项目

取一个好的名字

取名字,从来就不是一件容易的事。
因此,我就长话短说,一般就是取一个有意义的名字,当然没有意义也没有任何问题。
通常而言,如果自己计划有一系列的开源项目,那么我们可以保持一定的命名规则。

挑选好 LICENSE

在二十世纪而七十年代末和八十年代初,为了防止自己的软件被竞争对手所使用,大多数厂家停止分发其软件源代码,并开始使用版权和限制性软件许可证,来限制或者禁止软件源代码的复制或再分配。随后,Richard Matthew Stallman(Richard Matthew Stallman)发起了自由软件运动,他开创了 Copyleft 的概念:使用版权法的原则来保护使用、修改和分发自由软件的权利,并且是描述这些术语的自由软件许可证的主要作者。最为人所称道的是GPL(被z广泛使用的自由软件协议)。1
(PS:关于自由软件及 RMS 的更多信息、历史,可以阅读《若为自由故:自由软件之父 - 理查德 斯托曼传》)
随后,便诞生了开源软件的概念,开源的要求比自由软件宽松一些2。迄今发布的自由软件源代码都是开源软件,而并非所有的开源软件都是自由软件。这是因为不同的许可(协议)赋予用户不同的权利,如 GPL 协议强制要求开源修改过源码的代码,而宽松一点的 MIT 则不会有这种要求。
如下是不同开源许可证的市场占有率及使用情况。License 使用情况
又比如,在我们看到的一些外版书籍上,如果拥有代码。那么作者一般就会在前言或者类似的位置里,指明书中代码的版权所属。如:
也许你需要在自己的程序或文档中用到本书的代码,但除非大篇幅地使用,否则不必与我们联系取得授权。例如,用本书中的几段代码编写程序无需请求许可,blabla。
于是,选择一个合理的 LICENSE,就变成了一个有趣的话题。为此,笔者做了一个如何进行开源协议选型的流程图:
简单地来说,这些 License 之间是一些权利的区别,如当你把代码放置到公有领域,就意味着任何人可以修改,并且不需要标明出注;可如果你想要别人标明出处及作者,你就需要 MIT 协议;而你希望别人闭源的话,那么你就需要 MPL 协议等等。
那么,下面让我们简单地介绍一下不同的几个协议。

公有领域

WTFPL(Do What The Fuck You Want To Public License,中文译名:你他妈的想干嘛就干嘛公共许可证)是一种不太常用的、极度放任的自由软件许可证。它的条款基本等同于贡献到公有领域。3
这就意味着,对于拿到这些代码的其他人,他们想怎么修改就可以怎么修改。

GPL

由于 GPL 的传染性,便意味着,他人引用我们的代码时,其所写的代码也需要使用 GPL 开源。即:GPL 是有 “传染性” 的 “病毒” ,因为 GPL 条款规定演绎作品也必须是 GPL 的。
而如果我们只针对的是,他人可以使用库,而不开源,则可以用 LGPL。但是修改库则不适用。

MIT

因此,一般而言,我使用的是 MIT 协议。至少我保留了一个署名权,即你可以修改我的代码,但是在 LICENSE 里必须加上我的名字。
选用 MIT 特别有意思,特别是在最近几年里,发生过:
等等。这告诫了我们,如果你不想要有这种经历,那么就不要用 MIT 了。

Creative Commons

是的,当我写 Markdown 的时候,考虑到未来会以纸质书的形式出现,便会使用 CC-BY-NC-ND 协议:
  • CC -> Creative Commons
  • BY -> 署名(英语:Attribution,by)
  • NC -> 非商业性使用(英语:NonCommercial)
  • ND -> 禁止演绎(英语:NoDerivs)。
即,任何人可以使用我写的电子书来自由复制、散布、展示及演出,但是不得用于商业用途(作者本人可以)。它可以随意地放在他的博客上,他的各个文章里。但是必须标明出自,并且不得改变、转变或更改本作品。
如果你不介意的话,你可以使用公有领域(Public Domain)。可是这样一来,万一有一天,别人直接拿你的作品出书,你就骂爹了。

Git 基本知识与 GitHub 使用

Git

从一般开发者的角度来看,Git 有以下功能:
  1. 1.
    从服务器上克隆数据库(包括代码和版本信息)到单机上。
  2. 2.
    在自己的机器上创建分支,修改代码。
  3. 3.
    在单机上自己创建的分支上提交代码。
  4. 4.
    在单机上合并分支。
  5. 5.
    新建一个分支,把服务器上最新版的代码 fetch 下来,然后跟自己的主分支合并。
  6. 6.
    生成补丁(patch),把补丁发送给主开发者。
  7. 7.
    看主开发者的反馈,如果主开发者发现两个一般开发者之间有冲突(他们之间可以合作解决的冲突),就会要求他们先解决冲突,然后再由其中一个人提交。如果主开发者可以自己解决,或者没有冲突,就通过。
  8. 8.
    一般开发者之间解决冲突的方法,开发者之间可以使用 pull 命令解决冲突,解决完冲突之后再向主开发者提交补丁。
从主开发者的角度(假设主开发者不用开发代码)看,Git 有以下功能:
  1. 1.
    查看邮件或者通过其它方式查看一般开发者的提交状态。
  2. 2.
    打上补丁,解决冲突(可以自己解决,也可以要求开发者之间解决以后再重新提交,如果是开源项目,还要决定哪些补丁有用,哪些不用)。
  3. 3.
    向公共服务器提交结果,然后通知所有开发人员。

Git 初入

如果是第一次使用 Git,你需要设置署名和邮箱:
$ git config --global user.name "用户名"
$ git config --global user.email "电子邮箱"
将代码仓库 clone 到本地,其实就是将代码复制到你的机器里,并交由 Git 来管理:
$ git clone [email protected]:someone/symfony-docs-chs.git
你可以修改复制到本地的代码了(symfony-docs-chs 项目里都是 rst 格式的文档)。当你觉得完成了一定的工作量,想做个阶段性的提交:
向这个本地的代码仓库添加当前目录的所有改动:
$ git add .
或者只是添加某个文件:
$ git add -p
我们可以输入
$git status
来看现在的状态,如下图是添加之前的:Before add
下面是添加之后 的After add
可以看到状态的变化是从黄色到绿色,即 unstage 到 add。

GitHub

Wiki 百科上是这么说的
GitHub 是一个共享虚拟主机服务,用于存放使用Git版本控制的软件代码和内容项目。它由GitHub公司(曾称Logical Awesome)的开发者Chris Wanstrath、PJ Hyett和Tom Preston-Werner 使用Ruby on Rails编写而成。
当然让我们看看官方的介绍:
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.
它还是什么?
  • 网站
  • 免费博客
  • 管理配置文件
  • 收集资料
  • 简历
  • 管理代码片段
  • 托管编程环境
  • 写作
等等。看上去像是大餐,但是你还需要了解点什么?

版本管理与软件部署

jQuery[^jQuery] 在发布版本2.1.3,一共有 152 个 commit。我们可以看到如下的提交信息:
  • Ajax: Always use script injection in globalEval … bbdfbb4
  • Effects: Reintroduce use of requestAnimationFrame … 72119e0
  • Effects: Improve raf logic … 708764f
  • Build: Move test to appropriate module fbdbb6f
  • Build: Update commitplease dev dependency

GitHub 与 Git

Git是一个分布式的版本控制系统,最初由Linus Torvalds编写,用作Linux内核代码的管理。在推出后,Git在其它项目中也取得了很大成功,尤其是在Ruby社区中。目前,包括Rubinius、Merb和Bitcoin在内的很多知名项目都使用了Git。Git同样可以被诸如Capistrano和Vlad the Deployer这样的部署工具所使用。
GitHub可以托管各种git库,并提供一个web界面,但与其它像 SourceForge或Google Code这样的服务不同,GitHub的独特卖点在于从另外一个项目进行分支的简易性。为一个项目贡献代码非常简单:首先点击项目站点的“fork”的按钮,然后将代码检出并将修改加入到刚才分出的代码库中,最后通过内建的“pull request”机制向项目负责人申请代码合并。已经有人将GitHub称为代码玩家的MySpace。

在 GitHub 创建项目

接着,我们试试在上面创建一个项目:GitHub Roam
就会有下面的提醒:GitHub Roam
它提供多种方式的创建方法:
…or create a new repository on the command line
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
…or push an existing repository from the command line
git remote add origin [email protected]:phodal/github-roam.git
git push -u origin master
如果你完成了上面的步骤之后,那么我想你想知道你需要怎样的项目。

GitHub 流行项目分析

之前曾经分析过一些 GitHub 的用户行为,现在我们先来说说 GitHub 上的 Star 吧。(截止:2015年3月9日23时。)
用户
项目名
Language
Star
Url
twbs
Bootstrap
CSS
78490
vhf
free-programming books
-
37240
angular
angular.js
JavaScript
36,061
mbostock
d3
JavaScript
35,257
joyent
node
JavaScript
35,077
上面列出来的是前5的,看看大于 1 万个 Stars 的项目的分布,一共有 82 个:
语言
项目数
JavaScript
37
Ruby
6
CSS
6
Python
4
HTML
3
C++
3
VimL
2
Shell
2
Go
2
C
2
类型分布:
  • 库和框架:如jQuery
  • 系统:如Linuxhhvmdocker
  • 配置集:如dotfiles
  • 辅助工具:如oh-my-zsh
  • 工具:如HomewbrewBower
  • 资料收集:如free programming booksYou-Dont-Know-JSFont-Awesome
  • 其他:简历如Resume

Pull Request

除了创建项目之外,我们也可以创建 Pull Request 来做贡献。

我的第一个 PR

我的第一个 PR 是给一个小的 Node 的 CoAP 相关的库的 Pull Request。原因比较简单,是因为它的 README.md 写错了,导致我无法进行下一步。
const dgram = require('dgram')
- , coapPacket = require('coap-packet')
+ , package = require('coap-packet')
很简单,却又很有用的步骤,另外一个也是:
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

CLA

CLA 即 Contributor License Agreement,在为一些大的组织、机构提交 Pull Request 的时候,可能需要签署这个协议。他们会在你的 Pull Request 里问你,只有你到他们的网站去注册并同意协议才会接受你的 PR。
以下是我为 Google 提交的一个 PRGoogle CLA
以及 Eclipse 的一个 PREclipse CLA
他们都要求我签署 CLA。

构建 GitHub 项目

如何用好 GitHub

如何用好 GitHub,并实践一些敏捷软件开发是一个很有意思的事情.我们可以在上面做很多事情,从测试到 CI,再到自动部署.

敏捷软件开发

显然我是在扯淡,这和敏捷软件开发没有什么关系。不过我也不知道瀑布流是怎样的。说说我所知道的一个项目的组成吧:
  • 看板式管理应用程序(如 trello,简单地说就是管理软件功能)
  • CI(持续集成)
  • 测试覆盖率
  • 代码质量(code smell)
对于一个不是远程的团队(如只有一个人的项目)来说,Trello、Jenkin、Jira不是必需的:
你存在,我深深的脑海里
当只有一个人的时候,你只需要明确知道自己想要什么就够了。我们还需要的是 CI、测试,以来提升代码的质量。

测试

通常我们都会找 Document,如果没有的话,你会找什么?看源代码,还是看测试?
it("specifying response when you need it", function (done) {
var doneFn = jasmine.createSpy("success");
lettuce.get('/some/cool/url', function (result) {
expect(result).toEqual("awesome response");
done();
});
expect(jasmine.Ajax.requests.mostRecent().url).toBe('/some/cool/url');
expect(doneFn).not.toHaveBeenCalled();
jasmine.Ajax.requests.mostRecent().respondWith({
"status": 200,
"contentType": 'text/plain',
"responseText": 'awesome response'
});
});
上面的测试用例,清清楚楚地写明了用法,虽然写得有点扯。
等等,测试是用来干什么的。那么,先说说我为什么会想去写测试吧:
  • 我不希望每次做完一个个新功能的时候,再手动地去测试一个个功能。(自动化测试)
  • 我不希望在重构的时候发现破坏了原来的功能,而我还一无所知。
  • 我不敢push代码,因为我没有把握。
虽然,我不是 TDD 的死忠,测试的目的是保证功能正常,TDD 没法让我们写出质量更高的代码。但是有时TDD是不错的,可以让我们写出逻辑更简单地代码。
也许你已经知道了SeleniumJasmineCucumber等等的框架,看到过类似于下面的测试
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
看上去似乎每个测试都很小,不过补完每一个测试之后我们就得到了测试覆盖率
File
Statements
Branches
Functions
Lines
lettuce.js
98.58% (209 / 212)
82.98%(78 / 94)
100.00% (54 / 54)
98.58% (209 / 212)
本地测试都通过了,于是我们添加了Travis-CI来跑我们的测试

CI

虽然 node.js 不算是一门语言,但是因为我们用的 node,下面的是一个简单的 .travis.yml 示例:
language: node_js
node_js:
- "0.10"
notifications:
email: false
before_install: npm install -g grunt-cli
install: npm install
after_success: CODECLIMATE_REPO_TOKEN=321480822fc37deb0de70a11931b4cb6a2a3cc411680e8f4569936ac8ffbb0ab codeclimate < coverage/lcov.info
我们把这些集成到 README.md 之后,就有了之前那张图。
CI对于一个开发者在不同城市开发同一项目上来说是很重要的,这意味着当你添加的部分功能有测试覆盖的时候,项目代码会更加强壮。

代码质量

jslint 这类的工具,只能保证代码在语法上是正确的,但是不能保证你写了一堆 bad smell 的代码。
  • 重复代码
  • 过长的函数
  • 等等
Code Climate 是一个与 GitHub 集成的工具,我们不仅仅可以看到测试覆盖率,还有代码质量。
先看看上面的 ajax 类:
Lettuce.get = function (url, callback) {
Lettuce.send(url, 'GET', callback);
};
Lettuce.send = function (url, method, callback, data) {
data = data || null;
var request = new XMLHttpRequest();
if (callback instanceof Function) {
request.onreadystatechange = function () {
if (request.readyState === 4 && (request.status === 200 || request.status === 0)) {
callback(request.responseText);
}
};
}
request.open(method, url, true);
if (data instanceof Object) {
data = JSON.stringify(data);
request.setRequestHeader('Content-Type', 'application/json');
}
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
request.send(data);
};
Code Climate 在出现了一堆问题
  • Missing “use strict” statement. (Line 2)
  • Missing “use strict” statement. (Line 14)
  • ‘Lettuce’ is not defined. (Line 5)
而这些都是小问题啦,有时可能会有
  • Similar code found in two :expression_statement nodes (mass = 86)
这就意味着我们可以对上面的代码进行重构,他们是重复的代码。

模块分离与测试

在之前说到
奋斗了近半个月后,将 fork 的代码读懂、重构、升级版本、调整,添加新功能、添加测试、添加 CI、添加分享之后,终于 almost finish。
今天就来说说是怎样做的。
以之前造的 Lettuce 为例,里面有:
  • 代码质量(Code Climate)
  • CI状态(Travis CI)
  • 测试覆盖率(96%)
  • 自动化测试(npm test)
  • 文档
按照 Web Developer 路线图来说,我们还需要有:
  • 版本管理
  • 自动部署
等等。

代码模块化

在 SkillTree 的源码里,大致分为三部分:
  • namespace 函数:顾名思义
  • Calculator 也就是 TalentTree,主要负责解析、生成 url,头像,依赖等等
  • Skill 主要是 tips 部分。
而这一些都在一个 JS 里,对于一个库来说,是一件好事,但是对于一个项目来说,并非如此。
依赖的库有
  • jQuery
  • Knockout
好在 Knockout 可以用 Require.js 进行管理,于是,使用了 Require.js 进行管理:
<script type="text/javascript" data-main="app/scripts/main.js" src="app/lib/require.js"></script>
main.js 配置如下:
require.config({
baseUrl: 'app',
paths:{
jquery: 'lib/jquery',
json: 'lib/json',
text: 'lib/text'
}
});
require(['scripts/ko-bindings']);
require(['lib/knockout', 'scripts/TalentTree', 'json!data/web.json'], function(ko, TalentTree, TalentData) {
'use strict';
var vm = new TalentTree(TalentData);
ko.applyBindings(vm);
});
text、JSON 插件主要是用于处理 web.json,即用 JSON 来处理技能,于是不同的类到了不同的 JS 文件。
.
|____Book.js
|____Doc.js
|____ko-bindings.js
|____Link.js
|____main.js
|____Skill.js
|____TalentTree.js
|____Utils.js
加上了后来的推荐阅读书籍等等。而 Book 和 Link 都是继承自 Doc。
define(['scripts/Doc'], function(Doc) {
'use strict';
function Book(_e) {
Doc.apply(this, arguments);
}
Book.prototype = new Doc();
return Book;
});
而这里便是后面对其进行重构的内容。Doc 类则是 Skillock 中类的一个缩影
define([], function() {
'use strict';
var Doc = function (_e) {
var e = _e || {};
var self = this;
self.label = e.label || (e.url || 'Learn more');
self.url = e.url || 'javascript:void(0)';
};
return Doc;
});
或者说这是一个 AMD 的 Class 应该有的样子。考虑到 this 的隐性绑定,作者用了self=this 来避免这个问题。最后 Return 了这个对象,我们在调用的就需要 new 一个。大部分在代码中返回的都是对象,除了在 Utils 类里面返回的是函数:
return {
getSkillsByHash: getSkillsByHash,
getSkillById: getSkillById,
prettyJoin: prettyJoin
};
当然函数也是一个对象。

自动化测试

一直习惯用 Travis CI,于是也继续用 Travis Ci,.travis.yml 配置如下所示:
language: node_js
node_js:
- "0.10"
notifications:
email: false
branches:
only:
- gh-pages
使用 gh-pages 的原因是,我们一 push 代码的时候,就可以自动测试、部署等等,好处一堆堆的。
接着我们需要在 package.json 里面添加脚本
"scripts": {
"test": "mocha"
}
这样当我们 push 代码的时候便会自动跑所有的测试。因为 mocha 的主要配置是用 mocha.opts,所以我们还需要配置一下 mocha.opts
--reporter spec
--ui bdd
--growl
--colors
test/spec
最后的 test/spec 是指定测试的目录。

Jshint

JSLint定义了一组编码约定,这比ECMA定义的语言更为严格。这些编码约定汲取了多年来的丰富编码经验,并以一条年代久远的编程原则 作为宗旨:能做并不意味着应该做。JSLint会对它认为有的编码实践加标志,另外还会指出哪些是明显的错误,从而促使你养成好的 JavaScript编码习惯。
当我们的 JS 写得不合理的时候,这时测试就无法通过:
line 5 col 25 A constructor name should start with an uppercase letter.
line 21 col 62 Strings must use singlequote.
这是一种驱动写出更规范 JS 的方法。

Mocha

Mocha 是一个优秀的JS测试框架,支持TDD/BDD,结合 should.js/expect/chai/better-assert,能轻松构建各种风格的测试用例。
最后的效果如下所示:
Book,Link
Book Test
✓ should return book label & url
Link Test
✓ should return link label & url

测试示例

简单地看一下 Book 的测试:
/* global describe, it */
var requirejs = require("requirejs");
var assert = require("assert");
var should = require("should");
requirejs.config({
baseUrl: 'app/',
nodeRequire: require
});
describe('Book,Link', function () {
var Book, Link;
before(function (done) {
requirejs(['scripts/Book'、], function (Book_Class) {
Book = Book_Class;
done();
});
});
describe('Book Test', function () {
it('should return book label & url', function () {
var book_name = 'Head First HTML与CSS';
var url = 'http://www.phodal.com';
var books = {
label: book_name,
url: url
};
var _book = new Book(books);
_book.label.should.equal(book_name);
_book.url.should.equal(url);
});
});
});
因为我们用 require.js 来管理浏览器端,在后台写测试来测试的时候,我们也需要用他来管理我们的依赖,这也就是为什么这个测试这么长的原因,多数情况下一个测试类似于这样子的。(用 Jasmine 似乎会是一个更好的主意,但是用习惯 Jasmine 了)
describe('Book Test', function () {
it('should return book label & url', function () {
var book_name = 'Head First HTML与CSS';
var url = 'http://www.phodal.com';
var books = {
label: book_name,
url: url
};
var _book = new Book(books);
_book.label.should.equal(book_name);
_book.url.should.equal(url);
});
});
最后的断言,也算是测试的核心,保证测试是有用的。

代码质量与重构

  • 当你写了一大堆代码,你没有意识到里面有一大堆重复。
  • 当你写了一大堆测试,却不知道覆盖率有多少。
这就是个问题了,于是偶然间看到了一个叫 code climate 的网站。

Code Climate

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.
Code Climate 整合一组静态分析工具的结果到一个单一的,实时的报告,让您的团队需要识别热点,探讨新的方法,提高代码质量的信息。
简单地来说:
  • 对我们的代码评分
  • 找出代码中的坏味道
于是,我们先来了个例子
Rating
Name
Complexity
Duplication
Churn
C/M
Coverage
A
lib/coap/coap_request_handler.js
24
0
6
2.6
46.4%
A
lib/coap/coap_result_helper.js
14
0
2
3.4
80.0%
A
lib/coap/coap_server.js
16
0
5
5.2
44.0%
A
lib/database/db_factory.js
8
0
3
3.8
92.3%
A
lib/database/iot_db.js
7
0
6
1.0
58.8%
A
lib/database/mongodb_helper.js
63
0
11
4.5
35.0%
C
lib/database/sqlite_helper.js
32
86
10
4.5
35.0%
B
lib/rest/rest_helper.js
19
62
3
4.7
37.5%
A
lib/rest/rest_server.js
17
0
2
8.6
88.9%
A
lib/url_handler.js
9
0
5
2.2
94.1%
分享得到的最后的结果是:
[Coverage][1]

代码的坏味道

于是我们就打开 lib/database/sqlite_helper.js,因为其中有两个坏味道
Similar code found in two :expression_statement nodes (mass = 86)
在代码的 lib/database/sqlite_helper.js:58…61 < >
SQLiteHelper.prototype.deleteData = function (url, callback) {
'use strict';
var sql_command = "DELETE FROM " + config.table_name + " where " + URLHandler.getKeyFromURL(url) + "=" + URLHandler.getValueFromURL(url);
SQLiteHelper.prototype.basic(sql_command, callback);
lib/database/sqlite_helper.js:64…67 < >
SQLiteHelper.prototype.getData = function (url, callback) {
'use strict';
var sql_command = "SELECT * FROM " + config.table_name + " where " + URLHandler.getKeyFromURL(url) + "=" + URLHandler.getValueFromURL(url);
SQLiteHelper.prototype.basic(sql_command, callback);
只是这是之前修改过的重复。。
原来的代码是这样的
SQLiteHelper.prototype.postData = function (block, callback) {
'use strict';
var db = new sqlite3.Database(config.db_name);
var str = this.parseData(config.keys);
var string = this.parseData(block);
var sql_command = "insert or replace into " + config.table_name + " (" + str + ") VALUES (" + string + ");";
db.all(sql_command, function (err) {
SQLiteHelper.prototype.errorHandler(err);
db.close();
callback();
});
};
SQLiteHelper.prototype.deleteData = function (url, callback) {
'use strict';
var db = new sqlite3.Database(config.db_name);
var sql_command = "DELETE FROM " + config.table_name + " where " + URLHandler.getKeyFromURL(url) + "=" + URLHandler.getValueFromURL(url);
db.all(sql_command, function (err) {
SQLiteHelper.prototype.errorHandler(err);
db.close();
callback();
});
};
SQLiteHelper.prototype.getData = function (url, callback) {
'use strict';
var db = new sqlite3.Database(config.db_name);
var sql_command = "SELECT * FROM " + config.table_name + " where " + URLHandler.getKeyFromURL(url) + "=" + URLHandler.getValueFromURL(url);
db.all(sql_command, function (err, rows) {
SQLiteHelper.prototype.errorHandler(err);
db.close();
callback(JSON.stringify(rows));
});
};
说的也是大量的重复,重构完的代码
SQLiteHelper.prototype.basic = function(sql, db_callback){
'use strict';
var db = new sqlite3.Database(config.db_name);
db.all(sql, function (err, rows) {
SQLiteHelper.prototype.errorHandler(err);
db.close();
db_callback(JSON.stringify(rows));
});
};
SQLiteHelper.prototype.postData = function (block, callback) {
'use strict';
var str = this.parseData(config.keys);
var string = this.parseData(block);
var sql_command = "insert or replace into " + config.table_name + " (" + str + ") VALUES (" + string + ");";
SQLiteHelper.prototype.basic(sql_command, callback);
};
SQLiteHelper.prototype.deleteData = function (url, callback) {
'use strict';
var sql_command = "DELETE FROM " + config.table_name + " where " + URLHandler.getKeyFromURL(url) + "=" + URLHandler.getValueFromURL(url);
SQLiteHelper.prototype.basic(sql_command, callback);
};
SQLiteHelper.prototype.getData = function (url, callback) {
'use strict';
var sql_command = "SELECT * FROM " + config.table_name + " where " + URLHandler.getKeyFromURL(url) + "=" + URLHandler.getValueFromURL(url);
SQLiteHelper.prototype.basic(sql_command, callback);
};
重构完后的代码比原来还长,这似乎是个问题~~

Git 提交信息及几种不同的规范

受 Growth 3.0 开发的影响,最近更新文章的频率会有所降低。今天,让我们来谈谈一个好的 Git、SVN 提交信息是怎样规范出来的。
在团队协作中,使用版本管理工具 Git、SVN 几乎都是这个行业的标准。当我们提交代码的时候,需要编写提交信息(commit message)。
而提交信息的主要用途是:告诉这个项目的人,这次代码提交里做了些什么。如,我更新了 React Native Elements 的版本,那么它就可以是:[T] upgrade react native elements。对应的我修改的代码就是:package.jsonyarn.lock 中的文件。一般来说,建议小步提交,即按自己的 Tasking 步骤来的提交,每一小步都有对应的提交信息。这样做的主要目的是:防止一次修改中,修改过多的文件,导致后期修改、维护、撤销等等困难
而对于不同的团队来说,都会遵循一定的规范,本文主要会介绍以下几种写法:
  • 工作写法
  • 常规写法
  • 开源库写法
那么,先从我习惯的做法说起。

工作写法

在我的第一个项目里,我们使用 Jira 作为看板工具,Bamboo 作为持续集成服务器,并采用结对编程的方式进行。
在 Jira 里每一个功能卡都有对应的卡号,而 Bamboo 支持使用 Jira 的任务卡号关联的功能。即在持续构建服务器上示例对应的任务卡号,即相应的提交人。
因此,这个时候我们的规范稍微有一些特别:
[任务卡号] xx & xx: do something
比如:[PHODAL-0001] ladohp & phodal: update documents,解释如下:
  • PHODAL-0001,业务的任务卡号,它可以帮我们找到某个业务修改的原因,即点出相应 bug 的来源
  • ladohp & phodal ,结对编程的两个人的名字,后者(phodal)一般是写代码的人,出于礼貌就放在后面了。由于 Git 的提交人只显示一个,所以写上两个的名字。当提交的人不在时,就可以问另外一个人修改的原因。
  • update documents,我们做了什么事情
缺点:而对于采用看板的团队来说,并不存在任务卡号这种东西,因此就需要一种额外的作法。

常规写法

对于我来说,我则习惯这种的写法:
[任务分类] 主要修改组件(可选):修改内容
示例 1,[T] tabs: add icons 。其中的 T 表示这是一个技术卡,tabs 表示修改的是 Tabs,add icons 则表示添加了图标。
示例 2,[SkillTree] detail: add link data。其中的 SkillTree 表示修改的是技能树 Tab 下的内容,detail 则表示修改的是详情页,add link data 则表示是添加了技能的数据
这样做的主要原因是,它可以轻松也帮我 filter 出相应业务的内容
缺点:要这样做需要团队达到一致,因此付出一些额外的成本。

开源应用、开源库写法

与我们日常工作稍有不同的是:工作中的 Release 计划一般都是事先安排好的,不需要一些 CHANGELOG 什么的。而开源应用、开源库需要有对应的 CHANELOG,则添加了什么功能、修改了什么等等。毕竟有很多东西是由社区来维护的。
因此,这里以做得比较好的开源项目 Angular 为例展示。Angular 团队建议采用以下的形式:
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
诸如:docs(changelog): update change log to beta.5 中:
  • docs 则对应修改的类型
  • changelog 则是影响的范围
  • subject 则是对应做的事件
对应的类型有:
  • build:影响构建系统或外部依赖关系的更改(示例范围:gulp,broccoli,npm)
  • ci:更改我们的持续集成文件和脚本(示例范围:Travis,Circle,BrowserStack,SauceLabs)
  • docs:仅文档更改
  • feat:一个新功能
  • fix:修复错误
  • perf:改进性能的代码更改
  • refactor:代码更改,既不修复错误也不添加功能
  • style:不影响代码含义的变化(空白,格式化,缺少分号等)
  • test:添加缺失测试或更正现有测试
同时还对应了 20+ 的 Scope,可以说这种提交比上面的提交更有挑战。
(以上的 10 个类型,感谢 Google Translate 提供的快速翻译支持)
而这样做的优点是,它可以轻松地生成一个 CHANGELOG。与此同时还有一个名为 Conventional Commits 的规范,建议采用相似的形式。

创建项目文档

我们需要为我们的项目创建一个文档,通常我们可以将核心代码以外的东西都称为文档:
  1. 1.
    README
  2. 2.
    文档
  3. 3.
    示例
  4. 4.
    测试
通常这个会在项目的最上方会有一个项目的简介,如下图所示:GitHub Project Introduction

README

README 通常会显示在 GitHub 项目的下面,如下图所示:GitHub README
通常一个好的 README 会让你立马对项目产生兴趣。
如下面的内容是 React 项目的简介:React README
下面的内容写清楚了他们的用途:
  • Just the UI: Lots of people use React as the V in MVC. Since React makes no assumptions about the rest of your technology stack, it’s easy to try it out on a small feature in an existing project.
  • Virtual DOM: React abstracts away the DOM from you, giving a simpler programming model and better performance. React can also render on the server using Node, and it can power native apps using React Native.
  • Data flow: React implements one-way reactive data flow which reduces boilerplate and is easier to reason about than traditional data binding.
通常在这个 README 里,还会有:
  • 针对人群
  • 安装指南
  • 示例
  • 运行的平台
  • 如何参与贡献
  • 协议

官方首页与在线文档

很多开源项目都会有自己的网站,并在上面有一个文档,而有的则会放在https://readthedocs.org/
Read the Docs 托管文档,让文档可以被全文搜索和更易查找。您可以导入您使用任何常用的版本控制系统管理的文档,包括 Mercurial、Git、Subversion 和 Bazaar。 我们支持 webhooks,因此可以在您提交代码时自动构建文档。并且同样也支持版本功能,因此您可以构建来自您代码仓库中某个标签或分支的文档。查看完整的功能列表 。
在一个开源项目中,良好和专业的文档是相当重要的,有时他可能会比软件还会重要。因为如果一个开源项目好用的话,多数人可能不会去查看软件的代码。这就意味着,多数时候他在和你的文档打交道。文档一般会有:API 文档、 配置文档、帮助文档、用户手册、教程等等
写文档的软件有很多,如 Markdown、Doxygen、Docbook 等等。

可用示例

一个简单上手的示例非常重要,特别是通常我们是在为着某个目的而去使用一个开源项目的是时候,我们希望能马上使用到我们的项目中。
你希望看到的是,你打开浏览器,输入下面的代码,然后 It Works
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
React.render(
<HelloMessage name="John" />,
document.getElementById('container')
);
而不是需要繁琐的步骤才能进行下一步。

改善 GitHub 项目代码质量:重构

或许你应该知道了,重构是怎样的,你也知道重构能带来什么。在我刚开始学重构和设计模式的时候,我需要去找一些好的示例,以便于我更好的学习。有时候不得不创造一些更好的场景,来实现这些功能。
有一天,我发现当我需要我一次又一次地重复讲述某些内容,于是我就计划着把这些应该掌握的技能放到 GitHub 上,也就有了 Artisan Stack 计划。
每个程序员都不可避免地是一个 Coder,一个没有掌握好技能的 Coder,算不上是手工艺人,但是手工艺人,需要有创造性的方法。

为什么重构?

为了更好的代码。
在经历了一年多的工作之后,我平时的主要工作就是修 Bug。刚开始的时候觉得无聊,后来才发现修 Bug 需要更好的技术。有时候你可能要面对着一坨一坨的代码,有时候你可能要花几天的时间去阅读代码。而你重写那几十行代码可能只会花上你不到一天的时间。但是如果你没办法理解当时为什么这么做,你的修改只会带来更多的 Bug。修 Bug,更多的是维护代码。还是前人总结的那句话对:
写代码容易,读代码难。
假设我们写这些代码只要半天,而别人读起来要一天。为什么不试着用一天的时候去写这些代码,让别人花半天或者更少的时间来理解。
如果你的代码已经上线,虽然是一坨坨的。但是不要轻易尝试没有测试的重构
从前端开始的原因在于,写得一坨坨且最不容易测试的代码都在前端。
让我们来看看我们的第一个训练,相当有挑战性。

重构 uMarkdown

代码及 setup 请见 GitHub:js-refactor

代码说明

uMarkdown 是一个用于将 Markdown 转化为HTML的库。代码看上去就像一个很典型的过程代码: