黄峰达(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/ 了解到更多的内容。
其它相关信息:
- 微博:http://weibo.com/phodal
- GitHub:https://github.com/phodal
- 知乎:https://www.zhihu.com/people/phodal
- SegmentFault:https://segmentfault.com/u/phodal
当前为预览版,在使用的过程中遇到任何问题请及时与我联系。阅读过程中的问题,不妨在 GitHub 上提出来:Issues
阅读过程中遇到语法错误、拼写错误、技术错误等等,不妨来个 Pull Request,这样可以帮助到其他阅读这本电子书的童鞋。
我的电子书:
- 《GitHub 漫游指南》
- 《我的职业是前端工程师》
- 《Serverless 架构应用开发指南》
- 《Growth:全栈增长工程师指南》
- 《Phodal's Idea 实战指南》
- 《一步步搭建物联网系统》
- 《RePractise》
- 《Growth:全栈增长工程师实战》
我的微信公众号:
我的 GitHub 主页上写着加入的时间——Joined on Nov 8, 2010
,那时才大一,在那之后的那么长的日子里我都没有登录过。也许是因为我学的不是计算机,到了今天——2015.3.9
,我才发现这其实是程序员的社交网站。
过去,曾经有很长的一些时间我试过在 GitHub 上连击,也试着去了解别人是如何用好这个工具的。当然粉丝在 GitHub 上也是很重要的。
在这里,我会试着将我在 GitHub 上学到的东西一一分享出来。
在我大四找工作的时候,试图去寻找一份硬件、物联网相关的工作(PS:专业是电子信息工程)。尽管简历上写得满满的各种经历、经验,然而并没有卵用。跑了几场校园招聘会后,十份简历(PS:事先已经有心里准备)一个也没有投出去——因为学校直接被拒。我对霸面什么的一点兴趣都没有,千里马需要伯乐。后来,我加入了Martin Flower所在的公司,当然这是后话了。
这是一个残酷的世界,在学生时代,如果你长得不帅不高的话,那么多数的附加技能都是白搭(PS:通常富的是看不到这篇文章的)。在工作时期,如果你上家没有名气,那么将会影响你下一份工作的待遇。而,很多东西却可以改变这些,GitHub 就是其中一个。
注册 GitHub 的时候大概是大一的时候,我熟悉的时候已经是大四了,现在已经毕业一年了。在过去的近两年里,我试着以几个维度在 GitHub 上创建项目:
- 快速上手框架来实战,即 demo
- 重构别人的代码
- 创建自己可用的框架
- 快速构建大型应用
- 构建通用的框架
先说说与技能无关的收获吧,毕业设计做的是一个《最小物联网系统》,考虑到我们专业老师没有这方面知识,答辩时会带来问题,尽量往这方面靠拢。当我毕业后,这个项目已经有过百个 Star 了,这样易上手的东西还是比较受欢迎的(PS:不过这种硬件相关的项目通常受限于GitHub上硬件开发工程师比较少的困扰)。
毕业后一个月收到 PACKT 出版社的邮件(PS:他们是在 GitHub 上找到我的),内容是关于 Review 一本物联网书籍,即在《从 Review 到翻译 IT书籍》中提到的《Learning Internet of Things》。作为一个四级没过的"物联网专家",去审阅一本英文的物联网书籍。。。
当然,后来是审阅完了,书上有我的英文简介。
一个月前,收到 MANNING 出版社的邮件(PS:也是在 GitHub 上),关于 Review 一本物联网书籍的目录,并提出建议。
也因此带来了其他更多的东西,当然不是这里的主题。在这里,我们就不讨论各种骚扰邮件,或者中文合作。从没有想象过,我也可以在英语世界有一片小天地。
这些告诉我们,GitHub 上找一个你擅长的主题,那么会有很多人找上你的。
过去写过一篇《如何通过 GitHub 提升自己》的文章,现在只想说三点:
- 测试
- 更多的测试
- 更多的、更多的、更多的测试
没有测试的项目是很扯淡的,除非你的项目只有一个函数,然后那个函数返回Hello,World
。
如果你的项目代码有上千行,如果你能保证测试覆盖率可以达到95%以的话,那么我想你的项目不会有太复杂的函数。假使有这样的函数,那么它也是被测试覆盖住的。
如果你在用心做这个项目,那么你看到代码写得不好也会试着改进,即重构。当有了一些,你的技能会不断提升。你开始会试着接触更多的东西,如 stub,如 mock,如 fakeserver。
有一天,你会发现你离不开测试。
然后就会相信:那些没有写测试的项目都是在耍流氓
上面我们说的都是我们可以收获到的东西,我们开始尝试就意味着我们知道它可能给我们带来好处。上面已经提到很多可以提升自己的例子了,这里再说说其他的。
我们可以从中获取到不同的知识、内容、信息。每个人都可以从别人的代码中学习,当我们需要构建一个库的时候,我们可以在上面寻找不同的库和代码来实现我们的功能。如当我在实现一个库的时候,我会在 GitHub 上找到相应的组件:
- Promise 支持
- Class 类(PS:没有一个好的类使用的方式)
- Template 一个简单的模板引擎
- Router 用来控制页面的路由
- Ajax 基本的 Ajax Get/Post 请求
越来越多的人因为 GitHub 获得工作,因为他们的做的东西正好符合一些公司的要求。那么,这些公司在寻找代码的时候,就会试着邀请他们。
因而,在 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.
它还是什么?
- 网站
- 免费博客
- 管理配置文件
- 收集资料
- 简历
- 管理代码片段
- 托管编程环境
- 写作
等等。看上去像是大餐,但是你还需要了解点什么?
jQuery1在发布版本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
- ...
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,并实践一些敏捷软件开发是一个很有意思的事情.我们可以在上面做很多事情,从测试到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'
});
});
代码来源: https://github.com/phodal/lettuce
上面的测试用例,清清楚楚地写明了用法,虽然写得有点扯。
等等,测试是用来干什么的。那么,先说说我为什么会想去写测试吧:
- 我不希望每次做完一个个新功能的时候,再手动地去测试一个个功能。(自动化测试)
- 我不希望在重构的时候发现破坏了原来的功能,而我还一无所知。
- 我不敢push代码,因为我没有把握。
虽然,我不是TDD的死忠,测试的目的是保证功能正常,TDD没法让我们写出质量更高的代码。但是有时TDD是不错的,可以让我们写出逻辑更简单地代码。
也许你已经知道了Selenium
、Jasmine
、Cucumber
等等的框架,看到过类似于下面的测试
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
代码来源: https://github.com/phodal/lettuce
看上去似乎每个测试都很小,不过补完每一个测试之后我们就得到了测试覆盖率
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
代码来源: https://github.com/phodal/lettuce
我们把这些集成到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);
};
代码来源: https://github.com/phodal/lettuce
在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)
这就意味着我们可以对上面的代码进行重构,他们是重复的代码。
###重构
不想在这里说太多关于重构
的东西,可以参考Martin Flower的《重构》一书去多了解一些重构的细节。
这时想说的是,只有代码被测试覆盖住了,那么才能保证重构的过程没有出错。
人们出于不同的目的来创建开源项目,可不论目的是什么,过程都是一样的。
- 首先,我们需要为我们的项目取一个名字。
- 然后,为我们的开源项目选择一个合适的 LICENSE
- 然后再去创建项目
取名字,从来就不是一件容易的事。
因此,我就长话短说,一般就是取一个有意义的名字,当然没有意义也没有任何问题。
通常而言,如果自己计划有一系列的开源项目,那么我们可以保持一定的命名规则。
在二十世纪而七十年代末和八十年代初,为了防止自己的软件被竞争对手所使用,大多数厂家停止分发其软件源代码,并开始使用版权和限制性软件许可证,来限制或者禁止软件源代码的复制或再分配。随后,Richard Matthew Stallman(Richard Matthew Stallman)发起了自由软件运动,他开创了 Copyleft 的概念:使用版权法的原则来保护使用、修改和分发自由软件的权利,并且是描述这些术语的自由软件许可证的主要作者。最为人所称道的是GPL(被广泛使用的自由软件协议)。2
(PS:关于自由软件及 RMS 的更多信息、历史,可以阅读《若为自由故:自由软件之父 - 理查德 斯托曼传》)
随后,便诞生了开源软件的概念,开源的要求比自由软件宽松一些3。迄今发布的自由软件源代码都是开源软件,而并非所有的开源软件都是自由软件。这是因为不同的许可(协议)赋予用户不同的权利,如 GPL 协议强制要求开源修改过源码的代码,而宽松一点的 MIT 则不会有这种要求。
如下是不同开源许可证的市场占有率及使用情况。
又比如,在我们看到的一些外版书籍上,如果拥有代码。那么作者一般就会在前言或者类似的位置里,指明书中代码的版权所属。如:
也许你需要在自己的程序或文档中用到本书的代码,但除非大篇幅地使用,否则不必与我们联系取得授权。例如,用本书中的几段代码编写程序无需请求许可,blabla。
于是,选择一个合理的 LICENSE,就变成了一个有趣的话题。为此,笔者做了一个如何进行开源协议选型的流程图:
简单地来说,这些 License 之间是一些权利的区别,如当你把代码放置到公有领域,就意味着任何人可以修改,并且不需要标明出注;可如果你想要别人标明出处及作者,你就需要 MIT 协议;而你希望别人闭源的话,那么你就需要 MPL 协议等等。
那么,下面让我们简单地介绍一下不同的几个协议。
WTFPL(Do What The Fuck You Want To Public License,中文译名:你他妈的想干嘛就干嘛公共许可证)是一种不太常用的、极度放任的自由软件许可证。它的条款基本等同于贡献到公有领域。4
这就意味着,对于拿到这些代码的其他人,他们想怎么修改就可以怎么修改。
由于 GPL 的传染性,便意味着,他人引用我们的代码时,其所写的代码也需要使用 GPL 开源。即:GPL 是有 “传染性” 的 “病毒” ,因为 GPL 条款规定演绎作品也必须是 GPL 的。
而如果我们只针对的是,他人可以使用库,而不开源,则可以用 LGPL。但是修改库则不适用。
因此,一般而言,我使用的是 MIT 协议。至少我保留了一个署名权,即你可以修改我的代码,但是在 LICENSE 里必须加上我的名字。
选用 MIT 特别有意思,特别是在最近几年里,发生过:
等等。这告诫了我们,如果你不想要有这种经历,那么就不要用 MIT 了。
是的,当我写 Markdown 的时候,考虑到未来会以纸质书的形式出现,便会使用 CC-BY-NC-ND 协议:
- CC -> Creative Commons
- BY -> 署名(英语:Attribution,by)
- NC -> 非商业性使用(英语:NonCommercial)
- ND -> 禁止演绎(英语:NoDerivs)。
即,任何人可以使用我写的电子书来自由复制、散布、展示及演出,但是不得用于商业用途(作者本人可以)。它可以随意地放在他的博客上,他的各个文章里。但是必须标明出自,并且不得改变、转变或更改本作品。
如果你不介意的话,你可以使用公有领域(Public Domain)。可是这样一来,万一有一天,别人直接拿你的作品出书,你就骂爹了。
从一般开发者的角度来看,Git 有以下功能:
- 从服务器上克隆数据库(包括代码和版本信息)到单机上。
- 在自己的机器上创建分支,修改代码。
- 在单机上自己创建的分支上提交代码。
- 在单机上合并分支。
- 新建一个分支,把服务器上最新版的代码 fetch 下来,然后跟自己的主分支合并。
- 生成补丁(patch),把补丁发送给主开发者。
- 看主开发者的反馈,如果主开发者发现两个一般开发者之间有冲突(他们之间可以合作解决的冲突),就会要求他们先解决冲突,然后再由其中一个人提交。如果主开发者可以自己解决,或者没有冲突,就通过。
- 一般开发者之间解决冲突的方法,开发者之间可以使用 pull 命令解决冲突,解决完冲突之后再向主开发者提交补丁。
从主开发者的角度(假设主开发者不用开发代码)看,Git 有以下功能:
- 查看邮件或者通过其它方式查看一般开发者的提交状态。
- 打上补丁,解决冲突(可以自己解决,也可以要求开发者之间解决以后再重新提交,如果是开源项目,还要决定哪些补丁有用,哪些不用)。
- 向公共服务器提交结果,然后通知所有开发人员。
如果是第一次使用 Git,你需要设置署名和邮箱:
$ git config --global user.name "用户名"
$ git config --global user.email "电子邮箱"
将代码仓库 clone 到本地,其实就是将代码复制到你的机器里,并交由 Git 来管理:
$ git clone git@github.com:someone/symfony-docs-chs.git
你可以修改复制到本地的代码了(symfony-docs-chs 项目里都是 rst 格式的文档)。当你觉得完成了一定的工作量,想做个阶段性的提交:
向这个本地的代码仓库添加当前目录的所有改动:
$ git add .
或者只是添加某个文件:
$ git add -p
我们可以输入
$git status
来看现在的状态,如下图是添加之前的:
下面是添加之后 的
可以看到状态的变化是从黄色到绿色,即 unstage 到 add。
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.
它还是什么?
- 网站
- 免费博客
- 管理配置文件
- 收集资料
- 简历
- 管理代码片段
- 托管编程环境
- 写作
等等。看上去像是大餐,但是你还需要了解点什么?
jQuery1 在发布版本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
- ...
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。
接着,我们试试在上面创建一个项目:
就会有下面的提醒:
它提供多种方式的创建方法:
…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 git@github.com:phodal/github-roam.git
git push -u origin master
…or push an existing repository from the command line
git remote add origin git@github.com:phodal/github-roam.git
git push -u origin master
如果你完成了上面的步骤之后,那么我想你想知道你需要怎样的项目。
之前曾经分析过一些 GitHub 的用户行为,现在我们先来说说 GitHub 上的 Star 吧。(截止:2015年3月9日23时。)
用户 | 项目名 | Language | Star | Url |
---|---|---|---|---|
twbs | Bootstrap | CSS | 78490 | https://github.com/twbs/bootstrap |
vhf | free-programming books | - | 37240 | https://github.com/vhf/free-programming-books |
angular | angular.js | JavaScript | 36,061 | https://github.com/angular/angular.js |
mbostock | d3 | JavaScript | 35,257 | https://github.com/mbostock/d3 |
joyent | node | JavaScript | 35,077 | https://github.com/joyent/node |
上面列出来的是前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
- 系统:如
Linux
、hhvm
、docker
- 配置集:如
dotfiles
- 辅助工具:如
oh-my-zsh
- 工具:如
Homewbrew
和Bower
- 资料收集:如
free programming books
,You-Dont-Know-JS
,Font-Awesome
- 其他:简历如
Resume
除了创建项目之外,我们也可以创建 Pull Request 来做贡献。
我的第一个 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 即 Contributor License Agreement,在为一些大的组织、机构提交 Pull Request 的时候,可能需要签署这个协议。他们会在你的 Pull Request 里问你,只有你到他们的网站去注册并同意协议才会接受你的 PR。
以下是我为 Google 提交的一个 PR
以及 Eclipse 的一个 PR
他们都要求我签署 CLA。
如何用好 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'
});
});
代码来源:https://github.com/phodal/lettuce
上面的测试用例,清清楚楚地写明了用法,虽然写得有点扯。
等等,测试是用来干什么的。那么,先说说我为什么会想去写测试吧:
- 我不希望每次做完一个个新功能的时候,再手动地去测试一个个功能。(自动化测试)
- 我不希望在重构的时候发现破坏了原来的功能,而我还一无所知。
- 我不敢push代码,因为我没有把握。
虽然,我不是 TDD 的死忠,测试的目的是保证功能正常,TDD 没法让我们写出质量更高的代码。但是有时TDD是不错的,可以让我们写出逻辑更简单地代码。
也许你已经知道了Selenium
、Jasmine
、Cucumber
等等的框架,看到过类似于下面的测试
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
代码来源:https://github.com/phodal/lettuce
看上去似乎每个测试都很小,不过补完每一个测试之后我们就得到了测试覆盖率
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
来跑我们的测试
虽然 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
代码来源:https://github.com/phodal/lettuce
我们把这些集成到 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);
};
代码来源:https://github.com/phodal/lettuce
在 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
是指定测试的目录。
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 是一个优秀的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 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 | Smells --------|------|--------------|-------------|----------|---------|--------------------- A | lib/coap/coap_request_handler.js | 24 | 0 | 6 | 2.6 | 46.4% | 0 A | lib/coap/coap_result_helper.js | 14 | 0 | 2 | 3.4 | 80.0% | 0 A | lib/coap/coap_server.js | 16 | 0 | 5 | 5.2 | 44.0% | 0 A | lib/database/db_factory.js | 8 | 0 | 3 | 3.8 | 92.3% | 0 A | lib/database/iot_db.js | 7 | 0 | 6 | 1.0 | 58.8% | 0 A | lib/database/mongodb_helper.js | 63 | 0 | 11 | 4.5 | 35.0% | 0 C | lib/database/sqlite_helper.js | 32 | 86 | 10 | 4.5 | 35.0% | 2 B | lib/rest/rest_helper.js | 19 | 62 | 3 | 4.7 | 37.5% | 2 A | lib/rest/rest_server.js | 17 | 0 | 2 | 8.6 | 88.9% | 0 A | lib/url_handler.js | 9 | 0 | 5 | 2.2 | 94.1% | 0
分享得到的最后的结果是:
![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);
};
重构完后的代码比原来还长,这似乎是个问题~~
受 Growth 3.0 开发的影响,最近更新文章的频率会有所降低。今天,让我们来谈谈一个好的 Git、SVN 提交信息是怎样规范出来的。
在团队协作中,使用版本管理工具 Git、SVN 几乎都是这个行业的标准。当我们提交代码的时候,需要编写提交信息(commit message)。
而提交信息的主要用途是:告诉这个项目的人,这次代码提交里做了些什么。如,我更新了 React Native Elements 的版本,那么它就可以是:[T] upgrade react native elements
。对应的我修改的代码就是:package.json
和 yarn.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 什么的。而开源应用、开源库需要有对应的 CHANGELOG,则添加了什么功能、修改了什么等等。毕竟有很多东西是由社区来维护的。
因此,这里以做得比较好的开源项目 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
的规范,建议采用相似的形式。
我们需要为我们的项目创建一个文档,通常我们可以将核心代码以外的东西都称为文档:
- README
- 文档
- 示例
- 测试
通常这个会在项目的最上方会有一个项目的简介,如下图所示:
README 通常会显示在 GitHub 项目的下面,如下图所示:
通常一个好的 README 会让你立马对项目产生兴趣。
如下面的内容是 React 项目的简介:
下面的内容写清楚了他们的用途:
- 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 上,也就有了 Artisan Stack 计划。
每个程序员都不可避免地是一个 Coder,一个没有掌握好技能的 Coder,算不上是手工艺人,但是手工艺人,需要有创造性的方法。
为了更好的代码。
在经历了一年多的工作之后,我平时的主要工作就是修 Bug。刚开始的时候觉得无聊,后来才发现修 Bug 需要更好的技术。有时候你可能要面对着一坨一坨的代码,有时候你可能要花几天的时间去阅读代码。而你重写那几十行代码可能只会花上你不到一天的时间。但是如果你没办法理解当时为什么这么做,你的修改只会带来更多的 Bug。修 Bug,更多的是维护代码。还是前人总结的那句话对:
写代码容易,读代码难。
假设我们写这些代码只要半天,而别人读起来要一天。为什么不试着用一天的时候去写这些代码,让别人花半天或者更少的时间来理解。
如果你的代码已经上线,虽然是一坨坨的。但是不要轻易尝试没有测试的重构
。
从前端开始的原因在于,写得一坨坨且最不容易测试的代码都在前端。
让我们来看看我们的第一个训练,相当有挑战性。
代码及 setup 请见 GitHub:js-refactor
uMarkdown
是一个用于将 Markdown 转化为HTML的库。代码看上去就像一个很典型的过程代码:
/* code */
while ((stra = micromarkdown.regexobject.code.exec(str)) !== null) {
str = str.replace(stra[0], '<code>\n' + micromarkdown.htmlEncode(stra[1]).replace(/\n/gm, '<br/>').replace(/\ /gm, ' ') + '</code>\n');
}
/* headlines */
while ((stra = micromarkdown.regexobject.headline.exec(str)) !== null) {
count = stra[1].length;
str = str.replace(stra[0], '<h' + count + '>' + stra[2] + '</h' + count + '>' + '\n');
}
/* mail */
while ((stra = micromarkdown.regexobject.mail.exec(str)) !== null) {
str = str.replace(stra[0], '<a href="mailto:' + stra[1] + '">' + stra[1] + '</a>');
}
选这个做重构的开始,不仅仅是因为之前在写 EchoesWorks 的时候进行了很多的重构。而且它更适合于重构到设计模式
的理论。让我们在重构完之后,给作者进行 pull request 吧。
Markdown 的解析过程,有点类似于Pipe and Filters
模式(架构模式)。
Filter 即我们在代码中看到的正规表达式集:
regexobject: {
headline: /^(\#{1,6})([^\#\n]+)$/m,
code: /\s\`\`\`\n?([^`]+)\`\`\`/g
他会匹配对应的 Markdown 类型,随后进行替换和处理。而str
,就是管理口的输入和输出。
他会匹配对应的 Markdown 类型,随后进行替换和处理。而str
,就是管理口的输入和输出。
接着,我们就可以对其进行简单的重构。
(PS:推荐用 WebStrom 来做重构,自带重构功能)
作为一个示例,我们先提出 codeHandler 方法,即将上面的
/* code */
while ((stra = micromarkdown.regexobject.code.exec(str)) !== null) {
str = str.replace(stra[0], '<code>\n' + micromarkdown.htmlEncode(stra[1]).replace(/\n/gm, '<br/>').replace(/\ /gm, ' ') + '</code>\n');
}
提取方法成
codeFilter: function (str, stra) {
return str.replace(stra[0], '<code>\n' + micromarkdown.htmlEncode(stra[1]).replace(/\n/gm, '<br/>').replace(/\ /gm, ' ') + '</code>\n');
},
while 语句就成了
while ((stra = regexobject.code.exec(str)) !== null) {
str = this.codeFilter(str, stra);
}
然后,运行所有的测试。
grunt test
同理我们就可以 mail
、headline
等方法进行重构。接着就会变成类似于下面的代码,
/* code */
while ((execStr = regExpObject.code.exec(str)) !== null) {
str = codeHandler(str, execStr);
}
/* headlines */
while ((execStr = regExpObject.headline.exec(str)) !== null) {
str = headlineHandler(str, execStr);
}
/* lists */
while ((execStr = regExpObject.lists.exec(str)) !== null) {
str = listHandler(str, execStr);
}
/* tables */
while ((execStr = regExpObject.tables.exec(str)) !== null) {
str = tableHandler(str, execStr, strict);
}
然后你也看到了,上面有一堆重复的代码,接着让我们用 JavaScript 的奇技淫巧
,即apply方法,把上面的重复代码变成。
['code', 'headline', 'lists', 'tables', 'links', 'mail', 'url', 'smlinks', 'hr'].forEach(function (type) {
while ((stra = regexobject[type].exec(str)) !== null) {
str = that[(type + 'Handler')].apply(that, [stra, str, strict]);
}
});
进行测试,blabla,都是过的。
Markdown
✓ should parse h1~h3
✓ should parse link
✓ should special link
✓ should parse font style
✓ should parse code
✓ should parse ul list
✓ should parse ul table
✓ should return correctly class name
快来试试吧,https://github.com/artisanstack/js-refactor
是时候讨论这个 Refactor 利器了,最初看到这个重构的过程是从 ThoughtWorks 郑大晔校开始的,只是之前对于 Java 的另外一个编辑器 Eclipse 的坏感。。这些在目前已经不是很重要了,试试这个公司里面应用广泛的编辑器。
开发的流程大致就是这样子的,测试先行算是推荐的。
编写测试->功能代码->修改测试->重构
上次在和 buddy 聊天的时候,才知道测试在功能简单的时候是后行的,在功能复杂不知道怎么下手的时候是先行的。
开始之前请原谅我对于 Java 语言的一些无知,然后,看一下我写的 Main 函数:
package com.phodal.learing;
public class Main {
public static void main(String[] args) {
int c=new Cal().add(1,2);
int d=new Cal2().sub(2,1);
System.out.println("Hello,s");
System.out.println(c);
System.out.println(d);
}
}
代码写得还好(自我感觉),先不管 Cal 和 Cal2 两个类。大部分都能看懂,除了 c, d 不知道他们表达的是什么意思,于是。
快捷键:Shift+F6
作用:重命名
- 把光标丢到 int c 中的 c,按下 Shift + F6,输入 result_add
- 把光标移到 int d 中的 d,按下 Shift + F6,输入 result_sub
于是就有
package com.phodal.learing;
public class Main {
public static void main(String[] args) {
int result_add=new Cal().add(1,2);
int result_sub=new Cal2().sub(2,1);
System.out.println("Hello,s");
System.out.println(result_add);
System.out.println(result_sub);
}
}
快捷键:Alt+command+m
作用:扩展方法
- 选中 System.out.println(result_add);
- 按下 Alt + command + m
- 在弹出的窗口中输入 mprint
于是有了
public static void main(String[] args) {
int result_add=new Cal().add(1,2);
int result_sub=new Cal2().sub(2,1);
System.out.println("Hello,s");
mprint(result_add);
mprint(result_sub);
}
private static void mprint(int result_sub) {
System.out.println(result_sub);
}
似乎我们不应该这样对待 System.out.println,那么让我们内联回去
快捷键:Alt + command + n
作用:内联方法
- 选中 main 中的 mprint
- Alt + command + n
- 选中 Inline all invocations and remove the method(2 occurrences) 点确定
然后我们等于什么也没有做了~~:
public static void main(String[] args) {
int result_add=new Cal().add(1,2);
int result_sub=new Cal2().sub(2,1);
System.out.println("Hello,s");
System.out.println(result_add);
System.out.println(result_sub);
}
似乎这个例子不是很好,但是够用来说明了。
开始之前让我们先看看 Cal2 类:
public class Cal2 extends Cal {
public int sub(int a,int b){
return a-b;
}
}
以及 Cal2 的父类 Cal
public class Cal {
public int add(int a,int b){
return a+b;
}
}
最后的结果,就是将 Cal2 类中的 sub 方法,提到父类:
public class Cal {
public int add(int a,int b){
return a+b;
}
public int sub(int a,int b){
return a-b;
}
}
而我们所要做的就是鼠标右键
快捷键
Mac:木有
Windows/Linux:木有
或者:Shift
+Alt
+command
+T
再选择 Replace Temp with Query
鼠标:Refactor | Replace Temp with Query
过多的临时变量会让我们写出更长的函数,函数不应该太多,以便使功能单一。这也是重构的另外的目的所在,只有函数专注于其功能,才会更容易读懂。
以书中的代码为例
import java.lang.System;
public class replaceTemp {
public void count() {
double basePrice = _quantity * _itemPrice;
if (basePrice > 1000) {
return basePrice * 0.95;
} else {
return basePrice * 0.98;
}
}
}
选中 basePrice
很愉快地拿鼠标点上面的重构
便会返回
import java.lang.System;
public class replaceTemp {
public void count() {
if (basePrice() > 1000) {
return basePrice() * 0.95;
} else {
return basePrice() * 0.98;
}
}
private double basePrice() {
return _quantity * _itemPrice;
}
}
而实际上我们也可以
-
选中
_quantity * _itemPrice
-
对其进行
Extrace Method
-
选择
basePrice
再Inline Method
在Intellij IDEA的文档中对此是这样的例子
public class replaceTemp {
public void method() {
String str = "str";
String aString = returnString().concat(str);
System.out.println(aString);
}
}
接着我们选中 aString
,再打开重构菜单,或者
Command
+Alt
+Shift
+T
再选中 Replace Temp with Query
便会有下面的结果:
import java.lang.String;
public class replaceTemp {
public void method() {
String str = "str";
System.out.println(aString(str));
}
private String aString(String str) {
return returnString().concat(str);
}
}
虽然接触的 TDD 时间不算短,然而真正在实践 TDD 上的时候少之又少。除去怎么教人 TDD,就是与人结对编程时的 switch,或许是受限于当前的开发流程。
偶然间在开发一个物联网相关的开源项目——Lan的时候,重拾了这个过程。不得不说提到的一点是,在我们的开发流程中测试是由相关功能开发人员写的,有时候测试是一种很具挑战性的工作。久而久之,为自己的开源项目写测试变成一种自然而然的事。有时没有测试,反而变得没有安全感。
之前正在重写一个物联网的服务端,主要便是结合 CoAP、MQTT、HTTP 等协议构成一个物联网的云服务。现在,主要的任务是集中于协议与授权。由于,不同协议间的授权是不一样的,最开始的时候我先写了一个 http put 授权的功能,而在起先的时候是如何测试的呢?
curl --user root:root -X PUT -d '{ "dream": 1 }' -H "Content-Type: application/json" http://localhost:8899/topics/test
我只要顺利在 request 中看有无 req.headers.authorization
,我便可以继续往下,接着给个判断。毕竟,我们对 HTTP 协议还是蛮清楚的。
if (!req.headers.authorization) {
res.statusCode = 401;
res.setHeader('WWW-Authenticate', 'Basic realm="Secure Area"');
return res.end('Unauthorized');
}
可是除了 HTTP 协议,还有 MQTT 和 CoAP。对于 MQTT 协议来说,那还算好,毕竟自带授权,如:
mosquitto_pub -u root -P root -h localhost -d -t lettuce -m "Hello, MQTT. This is my first message."
便可以让我们简单地完成这个功能,然而有的协议是没有这样的功能如 CoAP 协议中是用 Option 来进行授权的。现在的工具如 libcoap 只能有如下的简单功能
coap-client -m get coap://127.0.0.1:5683/topics/zero -T
于是,先写了个测试脚本来验证功能。
var coap = require('coap');
var request = coap.request;
var req = request({hostname: 'localhost',port:5683,pathname: '',method: 'POST'});
...
req.setHeader("Accept", "application/json");
req.setOption('Block2', [new Buffer('phodal'), new Buffer('phodal')]);
...
req.end();
写完测试脚本后发现不对了,这个不应该是测试的代码吗?于是将其放到了 spec 中,接着发现了上面的全部功能的实现过程为什么不用 TDD 实现呢?
测试驱动开发是一个很"古老"的程序开发方法,然而由于国内的开发流程的问题——即开发人员负责功能的测试,导致这么好的一项技术没有在国内推广。
测试驱动开发的主要过程是:
- 先写功能的测试
- 实现功能代码
- 提交代码(commit -> 保证功能正常)
- 重构功能代码
而对于这样的一个物联网项目来说,我已经有了几个有利的前提:
- 已经有了原型
- 框架设计
通常在我的理解下,TDD 是可有可无的。既然我知道了我要实现的大部分功能,而且我也知道如何实现。与此同时,对 Code Smell 也保持着警惕、要保证功能被测试覆盖。那么,总的来说 TDD 带来的价值并不大。
然而,在当前这种情况下,我知道我想要的功能,但是我并不理解其深层次的功能。我需要花费大量的时候来理解,它为什么是这样的,需要先有一些脚本来知道它是怎么工作的。TDD 变显得很有价值,换句话来说,在现有的情况下,TDD 对于我们不了解的一些事情,可以驱动出更多的开发。毕竟在我们完成测试脚本之后,我们也会发现这些测试脚本成为了代码的一部分。
在这种理想的情况下,我们为什么不 TDD 呢?
twill was initially designed for testing Web sites, although since then people have also figured out that it's good for browsing unsuspecting Web sites.
之所以说轻量的原因是他是拿命令行测试的,还有 DSL,还有 Python。
除此之外,还可以拿它做压力测试,这种压力测试和一般的不一样。可以模拟整个过程,比如同时有多少人登陆你的网站。
不过,它有一个限制是没有 JavaScript。
看了一下源码,大概原理就是用 requests
下载 html,接着用 lxml
解析 html,比较有意思的是内嵌了一个 DSL
。
这是一个 Python 的库。
pip install twill
1.启动我们的应用。
2.进入 twill shell
twill-sh
-= Welcome to twill! =-
current page: *empty page*
3.打开网页
>> go http://127.0.0.1:5000/login
==> at http://127.0.0.1:5000/login
current page: http://127.0.0.1:5000/login
4.显示表单
>> showforms
Form #1
## ## __Name__________________ __Type___ __ID________ __Value__________________
1 csrf_token hidden csrf_token 1423387196##5005bdf3496e09b8e2fbf450 ...
2 email email email None
3 password password password None
4 login submit (None) 登入
current page: http://127.0.0.1:5000/login
5.填充表单
formclear 1
fv 1 email test@tes.com
fv 1 password test
6.修改 action
formaction 1 http://127.0.0.1:5000/login
7.提交表单
>> submit
Note: submit is using submit button: name="login", value="登入"
current page: http://127.0.0.1:5000/
发现重定向到首页了。
当然我们也可以用脚本直接来测试 login.twill
:
go http://127.0.0.1:5000/login
showforms
formclear 1
fv 1 email test@tes.com
fv 1 password test
formaction 1 http://127.0.0.1:5000/login
submit
go http://127.0.0.1:5000/logout
运行
twill-sh login.twill
结果
>> EXECUTING FILE login.twill
AT LINE: login.twill:0
==> at http://127.0.0.1:5000/login
AT LINE: login.twill:2
Form #1
## ## __Name__________________ __Type___ __ID________ __Value__________________
1 csrf_token hidden csrf_token 1423387345##7a000b612fef39aceab5ca54 ...
2 email email email None
3 password password password None
4 login submit (None) 登入
AT LINE: login.twill:3
AT LINE: login.twill:4
AT LINE: login.twill:5
AT LINE: login.twill:6
Setting action for form (<Element form at 0x10e7cbb50>,) to ('http://127.0.0.1:5000/login',)
AT LINE: login.twill:7
Note: submit is using submit button: name="login", value="登入"
AT LINE: login.twill:9
==> at http://127.0.0.1:5000/login
--
1 of 1 files SUCCEEDED.
一个成功的测试诞生了。
实践了一下怎么用 sinon 去 fake server,还没用 respondWith,于是写一下。
这里需要用到 sinon 框架来测试。
当我们 fetch 的时候,我们就可以返回我们想要 fake 的结果。
var data = {"id":1,"name":"Rice","type":"Good","price":12,"quantity":1,"description":"Made in China"};
beforeEach(function() {
this.server = sinon.fakeServer.create();
this.rices = new Rices();
this.server.respondWith(
"GET",
"http://localhost:8080/all/rice",
[
200,
{"Content-Type": "application/json"},
JSON.stringify(data)
]
);
});
于是在 afterEach 的时候,我们需要恢复这个 server。
afterEach(function() {
this.server.restore();
});
接着写一个 jasmine 测试来测试
describe("Collection Test", function() {
it("should get data from the url", function() {
this.rices.fetch();
this.server.respond();
var result = JSON.parse(JSON.stringify(this.rices.models[0]));
expect(result["id"])
.toEqual(1);
expect(result["price"])
.toEqual(12);
expect(result)
.toEqual(data);
});
});
除了擅长编写 md 电子书来攒 Star,我还写了一系列的开源软件,也掌握了一些项目运营的技巧。
开源并不是你把软件、README 写好就行了,还有详细的文档、示例程序等等。
开源也不是你的项目好了,就会有一堆人参与进来。
开源还要你帮助别人解决 Bug,……。
人们做事都是有原因的,即动机。再举例一下,如果你的项目不够火,别人都没听过,那么写到简历上可能没啥用。
开源需要一些营销的技巧,这些技巧可以帮你吸引关注。举个简单的例子,司徒正美的 avalon 框架出身得很早,也 MV* 方面也做得很不错,但是在 marketing 上就……。以至于国内的很多前端,都不了解这个框架,要不今天在国内可能就是 AVRR 四大框架了。
Vue 不是因为好用,而一下子火了。这一点我印象特别深,当时在 GitHub Trending 上看到了这个项目,发现它还不能很好地 work。
而如文章 《FIRST WEEK OF LAUNCHING VUE.JS》所说,项目刚开始的时候作者做了一系列的营销计划:
- HackerNews
- Reddit /r/javascript
- EchoJS
- The DailyJS blog
- JavaScript Weekly
- Maintain a project Twitter account(维护项目的 Vue 账户)
除此,文中还提到了一篇文章《How to Spread The Word About Your Code》 。
这一点相当的有意思,如果你的想法好的话,那么大家都会肯定,点下链接,为你来个 Star。那么,你就获得更好的动力去做这件事。项目也在开头的时候,获得了相当多的关注。而如果大家觉得你的项目没有新意的话,那么你懂的~。
除此,还有一种可能是,你的 ID 不够 fancy,即你在社区的影响上比较少。此时,就需要一点点慢慢积累人气了。当你积累了一些人气,你就能和松本行弘一样,在创建 mRuby 的时候就有 1000+ 的 Star。并且,在社区上还有一些相关的文章介绍,各个头条也由他的粉丝发了上去。如,一年多以前,我创建了 mole 项目。
当时,是为了给自己做一个基于 GitHub 云笔记的工具,在完成度到一定程度的时候。我在我的微信公从号上发了相关的介绍,第二天就有 100+ 的 Star 了,还接收到一些鼓舞的话语。对应于国内则有:
- 极客头条
- 掘金
- 开发者头条
- v2ex
- 知乎
- 不成器的微博
所以,你觉得呢?
在一个开源项目里,README 是最重要的内容。它快速地介绍了这个项目,并决定了它能不能吸引用户:
- 这个项目做什么?
- 它解决了什么问题
- 它有什么特性
- hello, world 示例
GitHub 的 Description 是我们在 Hacking News、GitHub Trneding 等等,第一时间看到的介绍。也是我们能快速介绍给别人的东西,如下图所示:
这一句话,必须简单明了也介绍,它是干什么的。
如 Angular 的一句话方案是:One framework. Mobile & desktop.
而 React 是:A declarative, efficient, and flexible JavaScript library for building user interfaces.
Vue 则是:A progressive, incrementally-adoptable JavaScript framework for building UI on the web.
上面的一句话描述,它不能很好地说明,它能解决什么问题。
如下是今天在 GitHub Trending 上榜的 RPC 项目的简介:
Most machines on internet communicate with each other via TCP/IP. However TCP/IP only guarantees reliable data transmissions, we need to abstract more to build services:
以上便是这个项目能解决的问题,不过这个项目能解决的问题倒是比较长,哈哈哈。
当我们有 A、B、C 几个不同的框架的时候,作为一个开发人员,就需要对比他们的特性。如下是 Go 语言实现的 MQTT 示例:
这个项目只支持的 Qos 级别为 0。如果我们需要的级别是 1,那么就不能用这个项目了。
又比如 lodash 项目:
Lodash makes JavaScript easier by taking the hassle out of working with arrays, numbers, objects, strings, etc. Lodash’s modular methods are great for:
- Iterating arrays, objects, & strings
- Manipulating & testing values
- Creating composite functions
你会怎么写?脸皮够厚的话,可以直接写一下,与其它项目的对比,blabla:
当然了,这种事不能太过,要不然会招来一堆黑。
在我们看完了上面的介绍之后,紧接着接一个 hello, world 的示例。在运行 hello, world 之前,我们可能需要一些额外的安装工作,如:
npm install koa
如 Koa 的示例:
const Koa = require('koa');
const app = new Koa();
// response
app.use(ctx => {
ctx.body = 'Hello Koa';
});
app.listen(3000);
作为一个程序员,你应该懂得它的重要性。
好在这里的安装工作只有两步,而不是:
对于那些需要复杂的安装过程的软件,应该简化安装过程,如提供 Docker 镜像,或者直接提供一个可运行的 Demo 环境。以免用户在看完 README 之后,直接放弃了使用该库。
好了,依一个开发人员的角度,如果上面的步骤一切顺利的话,接下来,便是使用这个开源项目来完成我们的功能。这个时候,我们开始转移注意力到文档上了。
由于,之前在某一个项目,经历过一个第三方 API 文档的大坑——文档中只罗列了 API 的用法。如下 Intellij Idea 生成的结构图:
文档中上,罗列了每个函数,以及每个函数需要的参数。我使用 Intellij Idea 直接反编译 jar 包,看到的信息都比文档多多了。文档上,没有任务示例,甚至连怎么初始化这个库的代码都没有。
WTF!
对于一个复杂的开源项目来说,文档上要写明安装、编译、配置等等的过程。如下图所示:
并且在我们发布包的时候,就要不断地去重复这个过程——如果你使用了自动化测试,那么这个过程便自动完成了。
如果我们的项目使用起来相当的简单,那么我们就可以只写一些示例代码即可。
并且,我们可以将文档直接入到代码里。它可以有效地减少文档不同步,带来的一些问题。
上图是使用了 JSDoc 的 Lodash 示例。
除了上面的示例,我们还可以录制一些视频,写一些文章说明项目的思考、架构等等。
示例代码本身也是文档的一部分,不要问我为什么~~。
反正,除了一个 hello, world,你还要有各种场景下的示例:
没有这么多示例,敢说自己是好用的开源项目?
到目前为止,我们做了一系列 markdown 相关的工作,却也还没有结束。要知道只有真正写过一系列开源项目的人,才能体会到什么是 markdown 程序员~~。
官方文档,一般要以比较正式的口吻来描述过程,这种写法相当的压抑。如果要用轻松诙谐的口气,那么就可以写一系列的技术文章。假如这是一个前端框架,那么我们可以介绍它如何与某个后端框架配合使用;又或者是,它与某个框架的对比等等。
好了,一切顺利了,那么下一步就是吸引更多的人参与到项目上来了。
要吸引其它开发人员来到你的项目,不是一件容易的事。
你需要不断地鼓励他/她们,并适时地帮他/她们解决问题,以避免他/她们在提 pull request 的过程中放弃了。这一点特别的有意思,当有一个开发人员发现了项目中的 bug,那么他/她会尝试去解决这个问题。与此同时,他/她还会为你的项目带来 pull request,但是在这个过程中,因为测试等等的问题,可能会阻碍他的 PR。这个时候,就需要我们主要去提示/教他们怎么做,又或者是帮他/她们解决完剩下的问题。那么,下次他/她提一个 PR 的时候,他/她就能解决问题了。
这一点可以在 README,以及介绍上体现:
哪怕只是一个错误字的 PR,那么你也可以 merge,啊哈哈~。然后,就有人帮你宣传了,『我给 xxx 项目一个 PR 了』。刚毕业的时候,我也是从这种类型的 PR 做起的~~。
Ubuntu
$ sudo apt-get install git-extras
Mac OS X with Homebrew
$ brew install git-extras
$ git-summary
project : github-roam
repo age : 2 years, 7 months
active : 40 days
commits : 124
files : 101
authors :
72 Fengda HUANG 58.1%
29 Fengda Huang 23.4%
8 Phodal HUANG 6.5%
3 Phodal Huang 2.4%
2 yangpei3720 1.6%
2 WangXiaolong 1.6%
2 TZS 1.6%
1 安正超 0.8%
1 Li 0.8%
1 Qiuer 0.8%
1 SCaffrey 0.8%
1 oncealong 0.8%
1 zminds 0.8%
由于日常用的开发工是 Intellij IDEA 企业版,所以就有点依赖于这个工具了。最常用的功能便是:修复 Bug 时,对于文件修改的追溯。了解某行代码修改的原因,对应的修改人等等。
Intellij IDEA
SourceTree 方便用来查看一些非我写的项目,寻找其中的一些问题。个中缘由便是:Intelli IDEA 刚打开某个项目的时候,需要花费大量的时间 index,只可惜现有的 SourceTree 客户端都需要登录 Atlassian 账户了。
gitflow 分支合并、查看
$ githug
********************************************************************************
* Githug *
********************************************************************************
No githug directory found, do you wish to create one? [yn] y
Welcome to Githug!
Name: init
Level: 1
Difficulty: *
A new directory, `git_hug`, has been created; initialize an empty repository in it.
$ githug play
********************************************************************************
* Githug *
********************************************************************************
Congratulations, you have solved the level!
Name: config
Level: 2
Difficulty: *
Set up your git name and email, this is important so that your commits can be identified.
#1: init
#2: config
#3: add
#4: commit
#5: clone
#6: clone_to_folder
#7: ignore
#8: include
#9: status
#10: number_of_files_committed
#11: rm
#12: rm_cached
#13: stash
#14: rename
#15: restructure
#16: log
#17: tag
#...
如何分析用户的数据是一个有趣的问题,特别是当我们有大量的数据的时候。除了 matlab
,我们还可以用 numpy
+ matplotlib
数据可以在这边寻找到
https://github.com/gmszone/ml
最后效果图
要解析的 JSON 文件位于data/2014-01-01-0.json
,大小 6.6M,显然我们可能需要用每次只读一行的策略,这足以解释为什么诸如 sublime 打开的时候很慢,而现在我们只需要里面的 JSON 数据中的创建时间。。
==, 这个文件代表什么?
2014年1月1日零时到一时,用户在 GitHub 上的操作,这里的用户指的是很多。。一共有 4814 条数据,从 commit、create 到 issues 都有。
import json
for line in open(jsonfile):
line = f.readline()
然后再解析 JSON
import dateutil.parser
lin = json.loads(line)
date = dateutil.parser.parse(lin["created_at"])
这里用到了 dateutil
,因为新鲜出炉的数据是 string 需要转换为 dateutil
,再到数据放到数组里头。最后有就有了 parse_data
def parse_data(jsonfile):
f = open(jsonfile, "r")
dataarray = []
datacount = 0
for line in open(jsonfile):
line = f.readline()
lin = json.loads(line)
date = dateutil.parser.parse(lin["created_at"])
datacount += 1
dataarray.append(date.minute)
minuteswithcount = [(x, dataarray.count(x)) for x in set(dataarray)]
f.close()
return minuteswithcount
下面这句代码就是将上面的解析为
minuteswithcount = [(x, dataarray.count(x)) for x in set(dataarray)]
这样的数组以便于解析
[(0, 92), (1, 67), (2, 86), (3, 73), (4, 76), (5, 67), (6, 61), (7, 71), (8, 62), (9, 71), (10, 70), (11, 79), (12, 62), (13, 67), (14, 76), (15, 67), (16, 74), (17, 48), (18, 78), (19, 73), (20, 89), (21, 62), (22, 74), (23, 61), (24, 71), (25, 49), (26, 59), (27, 59), (28, 58), (29, 74), (30, 69), (31, 59), (32, 89), (33, 67), (34, 66), (35, 77), (36, 64), (37, 71), (38, 75), (39, 66), (40, 62), (41, 77), (42, 82), (43, 95), (44, 77), (45, 65), (46, 59), (47, 60), (48, 54), (49, 66), (50, 74), (51, 61), (52, 71), (53, 90), (54, 64), (55, 67), (56, 67), (57, 55), (58, 68), (59, 91)]
开始之前需要安装matplotlib
sudo pip install matplotlib
然后引入这个库
import matplotlib.pyplot as plt
如上面的那个结果,只需要
plt.figure(figsize=(8,4))
plt.plot(x, y,label = files)
plt.legend()
plt.show()
最后代码可见
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json
import dateutil.parser
import numpy as np
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
def parse_data(jsonfile):
f = open(jsonfile, "r")
dataarray = []
datacount = 0
for line in open(jsonfile):
line = f.readline()
lin = json.loads(line)
date = dateutil.parser.parse(lin["created_at"])
datacount += 1
dataarray.append(date.minute)
minuteswithcount = [(x, dataarray.count(x)) for x in set(dataarray)]
f.close()
return minuteswithcount
def draw_date(files):
x = []
y = []
mwcs = parse_data(files)
for mwc in mwcs:
x.append(mwc[0])
y.append(mwc[1])
plt.figure(figsize=(8,4))
plt.plot(x, y,label = files)
plt.legend()
plt.show()
draw_date("data/2014-01-01-0.json")
继上篇之后,我们就可以分析用户的每周提交情况,以得出用户的真正的工具效率,每个程序员的工作时间可能是不一样的,如
这是我的每周情况,显然如果把星期六移到前面的话,随着工作时间的增长,在 GitHub 上的使用在下降,作为一个
a fulltime hacker who works best in the evening (around 8 pm).
不过这个是 osrc 的分析结果。
看一张分析后的结果
结果正好与我的情况相反?似乎图上是这么说的,但是数据上是这样的情况。
data
├── 2014-01-01-0.json
├── 2014-02-01-0.json
├── 2014-02-02-0.json
├── 2014-02-03-0.json
├── 2014-02-04-0.json
├── 2014-02-05-0.json
├── 2014-02-06-0.json
├── 2014-02-07-0.json
├── 2014-02-08-0.json
├── 2014-02-09-0.json
├── 2014-02-10-0.json
├── 2014-02-11-0.json
├── 2014-02-12-0.json
├── 2014-02-13-0.json
├── 2014-02-14-0.json
├── 2014-02-15-0.json
├── 2014-02-16-0.json
├── 2014-02-17-0.json
├── 2014-02-18-0.json
├── 2014-02-19-0.json
└── 2014-02-20-0.json
我们获取是每天晚上0点时的情况,至于为什么是0点,我想这里的数据量可能会比较少。除去1月1号的情况,就是上面的结果,在只有一周的情况时,总会以为因为在国内那时是假期,但是总觉得不是很靠谱,国内的程序员虽然很多,会在 GitHub 上活跃的可能没有那么多,直至列出每一周的数据时。
6570, 7420, 11274, 12073, 12160, 12378, 12897,
8474, 7984, 12933, 13504, 13763, 13544, 12940,
7119, 7346, 13412, 14008, 12555
重写了一个新的方法用于计算提交数,直至后面才意识到其实我们可以算行数就够了,但是方法上有点hack
def get_minutes_counts_with_id(jsonfile):
datacount, dataarray = handle_json(jsonfile)
minuteswithcount = [(x, dataarray.count(x)) for x in set(dataarray)]
return minuteswithcount
def handle_json(jsonfile):
f = open(jsonfile, "r")
dataarray = []
datacount = 0
for line in open(jsonfile):
line = f.readline()
lin = json.loads(line)
date = dateutil.parser.parse(lin["created_at"])
datacount += 1
dataarray.append(date.minute)
f.close()
return datacount, dataarray
def get_minutes_count_num(jsonfile):
datacount, dataarray = handle_json(jsonfile)
return datacount
def get_month_total():
"""
:rtype : object
"""
monthdaycount = []
for i in range(1, 20):
if i < 10:
filename = 'data/2014-02-0' + i.__str__() + '-0.json'
else:
filename = 'data/2014-02-' + i.__str__() + '-0.json'
monthdaycount.append(get_minutes_count_num(filename))
return monthdaycount
接着我们需要去遍历每个结果,后面的后面会发现这个效率真的是太低了,为什么木有多线程?
让我们的matplotlib来做这些图表的工作
if __name__ == '__main__':
results = pd.get_month_total()
print results
plt.figure(figsize=(8, 4))
plt.plot(results.__getslice__(0, 7), label="first week")
plt.plot(results.__getslice__(7, 14), label="second week")
plt.plot(results.__getslice__(14, 21), label="third week")
plt.legend()
plt.show()
蓝色的是第一周,绿色的是第二周,红色的是第三周就有了上面的结果。
我们还需要优化方法,以及多线程的支持。
让我们分析之前的程序,然后再想办法做出优化。网上看到一篇文章http://www.huyng.com/posts/python-performance-analysis/讲的就是分析这部分内容的。
我们创建了一个名为 userdata.db
的数据库文件,然后创建了一个表,里面有 owner, language, eventtype, name url
def init_db():
conn = sqlite3.connect('userdata.db')
c = conn.cursor()
c.execute('''CREATE TABLE userinfo (owner text, language text, eventtype text, name text, url text)''')
接着我们就可以查询数据,这里从结果讲起。
def get_count(username):
count = 0
userinfo = []
condition = 'select * from userinfo where owener = \'' + str(username) + '\''
for zero in c.execute(condition):
count += 1
userinfo.append(zero)
return count, userinfo
当我查询 gmszone
的时候,也就是我自己就会有如下的结果
(u'gmszone', u'ForkEvent', u'RESUME', u'TeX', u'https://github.com/gmszone/RESUME')
(u'gmszone', u'WatchEvent', u'iot-dashboard', u'JavaScript', u'https://github.com/gmszone/iot-dashboard')
(u'gmszone', u'PushEvent', u'wechat-wordpress', u'Ruby', u'https://github.com/gmszone/wechat-wordpress')
(u'gmszone', u'WatchEvent', u'iot', u'JavaScript', u'https://github.com/gmszone/iot')
(u'gmszone', u'CreateEvent', u'iot-doc', u'None', u'https://github.com/gmszone/iot-doc')
(u'gmszone', u'CreateEvent', u'iot-doc', u'None', u'https://github.com/gmszone/iot-doc')
(u'gmszone', u'PushEvent', u'iot-doc', u'TeX', u'https://github.com/gmszone/iot-doc')
(u'gmszone', u'PushEvent', u'iot-doc', u'TeX', u'https://github.com/gmszone/iot-doc')
(u'gmszone', u'PushEvent', u'iot-doc', u'TeX', u'https://github.com/gmszone/iot-doc')
109
一共有109个事件,有 Watch
, Create
, Push
, Fork
还有其他的,
项目主要有iot
, RESUME
, iot-dashboard
, wechat-wordpress
,
接着就是语言了,Tex
, Javascript
, Ruby
,接着就是项目的 url 了。
值得注意的是。
-rw-r--r-- 1 fdhuang staff 905M Apr 12 14:59 userdata.db
这个数据库文件有 905M,不过查询结果相当让人满意,至少相对于原来的结果来说。
Python 自带了对 SQLite3 的支持,然而我们还需要安装 SQLite3
brew install sqlite3
或者是
sudo port install sqlite3
或者是 Ubuntu 的
sudo apt-get install sqlite3
openSUSE 自然就是
sudo zypper install sqlite3
不过,用 yast2 也很不错,不是么。。
需要注意的是这里是需要 Python 2.7,起源于对 gzip 的上下文管理器的支持问题
def handle_gzip_file(filename):
userinfo = []
with gzip.GzipFile(filename) as f:
events = [line.decode("utf-8", errors="ignore") for line in f]
for n, line in enumerate(events):
try:
event = json.loads(line)
except:
continue
actor = event["actor"]
attrs = event.get("actor_attributes", {})
if actor is None or attrs.get("type") != "User":
continue
key = actor.lower()
repo = event.get("repository", {})
info = str(repo.get("owner")), str(repo.get("language")), str(event["type"]), str(repo.get("name")), str(
repo.get("url"))
userinfo.append(info)
return userinfo
def build_db_with_gzip():
init_db()
conn = sqlite3.connect('userdata.db')
c = conn.cursor()
year = 2014
month = 3
for day in range(1,31):
date_re = re.compile(r"([0-9]{4})-([0-9]{2})-([0-9]{2})-([0-9]+)\.json.gz")
fn_template = os.path.join("march",
"{year}-{month:02d}-{day:02d}-{n}.json.gz")
kwargs = {"year": year, "month": month, "day": day, "n": "*"}
filenames = glob.glob(fn_template.format(**kwargs))
for filename in filenames:
c.executemany('INSERT INTO userinfo VALUES (?,?,?,?,?)', handle_gzip_file(filename))
conn.commit()
c.close()
executemany
可以插入多条数据,对于我们的数据来说,一小时的文件大概有五六千个会符合我们上面的安装,也就是有 actor
又有 type
才是我们需要记录的数据,我们只需要统计用户的那些事件,而非全部的事件。
我们需要去遍历文件,然后找到合适的部分,这里只是要找2014-03-01
到2014-03-31
的全部事件,而光这些数据的 gz 文件就有 1.26G,同上面那些解压为 JSON 文件显得不合适,只能用遍历来处理。
这里参考了 osrc 项目中的写法,或者说直接复制过来。
首先是正规匹配
date_re = re.compile(r"([0-9]{4})-([0-9]{2})-([0-9]{2})-([0-9]+)\.json.gz")
不过主要的还是在于 glob.glob
glob是 Python 自己带的一个文件操作相关模块,用它可以查找符合自己目的的文件,就类似于Windows下的文件搜索,支持通配符操作。
这里也就用上了 gzip.GzipFile
又一个不错的东西。
最后代码可以见
更好的方案?
查询用户事件总数
import redis
r = redis.StrictRedis(host='localhost', port=6379, db=0)
pipe = pipe = r.pipeline()
pipe.zscore('osrc:user',"gmszone")
pipe.execute()
系统返回了 227.0
,试试别人。
>>> pipe.zscore('osrc:user',"dfm")
<redis.client.StrictPipeline object at 0x104fa7f50>
>>> pipe.execute()
[425.0]
>>>
看看主要是在哪一天提交的
>>> pipe.hgetall('osrc:user:gmszone:day')
<redis.client.StrictPipeline object at 0x104fa7f50>
>>> pipe.execute()
[{'1': '51', '0': '41', '3': '17', '2': '34', '5': '28', '4': '22', '6': '34'}]
结果大致如下图所示:
看看主要的事件是?
>>> pipe.zrevrange("osrc:user:gmszone:event".format("gmszone"), 0, -1,withscores=True)
<redis.client.StrictPipeline object at 0x104fa7f50>
>>> pipe.execute()
[[('PushEvent', 154.0), ('CreateEvent', 41.0), ('WatchEvent', 18.0), ('GollumEvent', 8.0), ('MemberEvent', 3.0), ('ForkEvent', 2.0), ('ReleaseEvent', 1.0)]]
>>>
蓝色的就是 push 事件,黄色的是 create 等等。
到这里我们算是知道了 OSRC 的数据库部分是如何工作的。
主要代码如下所示
def get_vector(user, pipe=None):
r = redis.StrictRedis(host='localhost', port=6379, db=0)
no_pipe = False
if pipe is None:
pipe = pipe = r.pipeline()
no_pipe = True
user = user.lower()
pipe.zscore(get_format("user"), user)
pipe.hgetall(get_format("user:{0}:day".format(user)))
pipe.zrevrange(get_format("user:{0}:event".format(user)), 0, -1,
withscores=True)
pipe.zcard(get_format("user:{0}:contribution".format(user)))
pipe.zcard(get_format("user:{0}:connection".format(user)))
pipe.zcard(get_format("user:{0}:repo".format(user)))
pipe.zcard(get_format("user:{0}:lang".format(user)))
pipe.zrevrange(get_format("user:{0}:lang".format(user)), 0, -1,
withscores=True)
if no_pipe:
return pipe.execute()
结果在上一篇中显示出来了,也就是
[227.0, {'1': '51', '0': '41', '3': '17', '2': '34', '5': '28', '4': '22', '6': '34'}, [('PushEvent', 154.0), ('CreateEvent', 41.0), ('WatchEvent', 18.0), ('GollumEvent', 8.0), ('MemberEvent', 3.0), ('ForkEvent', 2.0), ('ReleaseEvent', 1.0)], 0, 0, 0, 11, [('CSS', 74.0), ('JavaScript', 60.0), ('Ruby', 12.0), ('TeX', 6.0), ('Python', 6.0), ('Java', 5.0), ('C++', 5.0), ('Assembly', 5.0), ('C', 3.0), ('Emacs Lisp', 2.0), ('Arduino', 2.0)]]
有意思的是在这里生成了和自己相近的人
['alesdokshanin', 'hjiawei', 'andrewreedy', 'christj6', '1995eaton']
osrc 最有意思的一部分莫过于 flann,当然说的也是系统后台的设计的一个很关键及有意思的部分。
邻近算法是在这个分析过程中一个很有意思的东西。
邻近算法,或者说K最近邻(kNN,k-NearestNeighbor)分类算法可以说是整个数据挖掘分类技术中最简单的方法了。所谓K最近邻,就是k个最近的邻居的意思,说的是每个样本都可以用她最接近的k个邻居来代表。
换句话说,我们需要一些样本来当作我们的分析资料,这里东西用到的就是我们之前的。
[227.0, {'1': '51', '0': '41', '3': '17', '2': '34', '5': '28', '4': '22', '6': '34'}, [('PushEvent', 154.0), ('CreateEvent', 41.0), ('WatchEvent', 18.0), ('GollumEvent', 8.0), ('MemberEvent', 3.0), ('ForkEvent', 2.0), ('ReleaseEvent', 1.0)], 0, 0, 0, 11, [('CSS', 74.0), ('JavaScript', 60.0), ('Ruby', 12.0), ('TeX', 6.0), ('Python', 6.0), ('Java', 5.0), ('C++', 5.0), ('Assembly', 5.0), ('C', 3.0), ('Emacs Lisp', 2.0), ('Arduino', 2.0)]]
在代码中是构建了一个 points.h5 的文件来分析每个用户的 points,之后再记录到 hdf5 文件中。
[ 0.00438596 0.18061674 0.2246696 0.14977974 0.07488987 0.0969163
0.12334802 0.14977974 0. 0.18061674 0. 0. 0.
0.00881057 0. 0. 0.03524229 0. 0.
0.01321586 0. 0. 0. 0.6784141 0.
0.07929515 0.00440529 1. 1. 1. 0.08333333
0.26431718 0.02202643 0.05286344 0.02643172 0. 0.01321586
0.02202643 0. 0. 0. 0. 0. 0.
0. 0. 0.00881057 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0.00881057]
这里分析到用户的大部分行为,再找到与其行为相近的用户,主要的行为有下面这些:
- 每星期的情况
- 事件的类型
- 贡献的数量,连接以及语言
- 最多的语言
osrc 中用于解析的代码
def parse_vector(results):
points = np.zeros(nvector)
total = int(results[0])
points[0] = 1.0 / (total + 1)
# Week means.
for k, v in results[1].iteritems():
points[1 + int(k)] = float(v) / total
# Event types.
n = 8
for k, v in results[2]:
points[n + evttypes.index(k)] = float(v) / total
# Number of contributions, connections and languages.
n += nevts
points[n] = 1.0 / (float(results[3]) + 1)
points[n + 1] = 1.0 / (float(results[4]) + 1)
points[n + 2] = 1.0 / (float(results[5]) + 1)
points[n + 3] = 1.0 / (float(results[6]) + 1)
# Top languages.
n += 4
for k, v in results[7]:
if k in langs:
points[n + langs.index(k)] = float(v) / total
else:
# Unknown language.
points[-1] = float(v) / total
return points
这样也就返回我们需要的点数,然后我们可以用 get_points
来获取这些
def get_points(usernames):
r = redis.StrictRedis(host='localhost', port=6379, db=0)
pipe = r.pipeline()
results = get_vector(usernames)
points = np.zeros([len(usernames), nvector])
points = parse_vector(results)
return points
就会得到我们的相应的数据,接着找找和自己邻近的,看看结果。
[ 0.01298701 0.19736842 0. 0.30263158 0.21052632 0.19736842
0. 0.09210526 0. 0.22368421 0.01315789 0. 0.
0. 0. 0. 0.01315789 0. 0.
0.01315789 0. 0. 0. 0.73684211 0. 0.
0. 1. 1. 1. 0.2 0.42105263
0.09210526 0. 0. 0. 0. 0.23684211
0. 0. 0.03947368 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. ]
真看不出来两者有什么相似的地方 。。。。
重造轮子是重新创造一个已有的或是已被其他人优化的基本方法。
最近萌发了一个想法写游戏引擎,之前想着做一个 JavaScript 前端框架。看看,这个思路是怎么来的。
Lettuce 是一个简约的移动开发框架。
故事的出发点是这样的:写了很多代码,用的都是框架,最后不知道收获什么了
?事实也是如此,当自己做了一些项目之后,发现最后什么也没有收获到。于是,就想着做一个框架。
有这样的几个前提
- 为什么我只需要 jQuery 里的选择器、Ajax 要引入那么重的库呢?
- 为什么我只需要一个 Template,却想着用 Mustache
- 为什么我需要一个 Router,却要用 Backbone 呢?
- 为什么我需要的是一个 isObject 函数,却要用到整个 Underscore?
我想要的只是一个简单的功能,而我不想引入一个庞大的库。换句话说,我只需要不同库里面的一小部分功能,而不是一个库。
实际上想要的是:
构建一个库,里面从不同的库里面抽取出不同的函数。
这时候我参考了一本电子书《Build JavaScript FrameWork》,加上一些平时的需求,于是很快的就知道自己需要什么样的功能:
- Promise 支持
- Class类(PS:没有一个好的类使用的方式)
- Template 一个简单的模板引擎
- Router 用来控制页面的路由
- Ajax 基本的 Ajax Get/Post 请求
在做一些实际的项目中,还遇到了这样的一些功能支持:
- Effect 简单的一些页面效果
- AMD 支持
而我们有一个前提是要保持这个库尽可能的小、同时我们还需要有测试。
简单说说是如何实现一个简单的需求。
因为 Yeoman 可以生成一个简单的轮廓,所以我们可以用它来生成这个项目的骨架。
- Gulp
- Jasmine
在 GitHub 上搜索了一个看到了下面的几个结果:
- https://github.com/then/promise
- https://github.com/reactphp/promise
- https://github.com/kriskowal/q
- https://github.com/petkaantonov/bluebird
- https://github.com/cujojs/when
但是显然,他们都太重了。事实上,对于一个库来说,80% 的人只需要其中 20% 的代码。于是,找到了https://github.com/stackp/promisejs,看了看用法,这就是我们需要的功能:
function late(n) {
var p = new promise.Promise();
setTimeout(function() {
p.done(null, n);
}, n);
return p;
}
late(100).then(
function(err, n) {
return late(n + 200);
}
).then(
function(err, n) {
return late(n + 300);
}
).then(
function(err, n) {
return late(n + 400);
}
).then(
function(err, n) {
alert(n);
}
);
接着打开看看 Promise 对象,有我们需要的功能,但是又有一些功能超出我的需求。接着把自己不需要的需求去掉,这里函数最后就变成了
function Promise() {
this._callbacks = [];
}
Promise.prototype.then = function(func, context) {
var p;
if (this._isdone) {
p = func.apply(context, this.result);
} else {
p = new Promise();
this._callbacks.push(function () {
var res = func.apply(context, arguments);
if (res && typeof res.then === 'function') {
res.then(p.done, p);
}
});
}
return p;
};
Promise.prototype.done = function() {
this.result = arguments;
this._isdone = true;
for (var i = 0; i < this._callbacks.length; i++) {
this._callbacks[i].apply(null, arguments);
}
this._callbacks = [];
};
var promise = {
Promise: Promise
};
需要注意的是:License
,不同的软件有不同的 License,如 MIT、GPL 等等。最好能在遵循协议的情况下,使用别人的代码。
由于已经有了现有的很多库,所以就可以直接参照(抄)别人写的代码。
Lettuce.get = function (url, callback) {
Lettuce.send(url, 'GET', callback);
};
Lettuce.load = function (url, callback) {
Lettuce.send(url, 'GET', callback);
};
Lettuce.post = function (url, data, callback) {
Lettuce.send(url, 'POST', callback, data);
};
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);
};
所有让你直接看最新源码的文章都是在扯淡,你应该从“某个版本”开始阅读代码。
我们并不建议所有的读者都直接看最新的代码,正确的姿势应该是:
- clone 某个项目的代码到本地
- 查看这个项目的 release 列表
- 找到一个看得懂的 release 版本,如 1.0 或者更早的版本
- 读懂上一个版本的代码
- 向后阅读大版本的源码
- 读最新的源码
最好的在这个过程中,可以自己造轮子来实现一遍。
在我阅读的前端库、Python 后台库的过程中,我们都是以造轮子为目的展开的。所以在最开始的时候,我需要一个可以工作,并且拥有我想要的功能的版本。
紧接着,我就可以开始去实践这个版本中的一些功能,并理解他们是怎么工作的。再用 git
大法展开之前修改的内容,可以使用 IDE 自带的 Diff 工具:
或者类似于 SourceTree
这样的工具,来查看修改的内容。
在我们理解了基本的核心功能后,我们就可以向后查看大、中版本的更新内容了。
开始之前,我们希望大家对版本号管理有一些基本的认识。
我最早阅读的开始软件是 Linux,而下面则是 Linux 的 Release 过程:
表格源自一本书叫《Linux内核0.11(0.95)完全注释》,简单地再介绍一下:
- 版本 0.00 是一个 hello, world 程序
- 版本 0.01 包含了可以工作的代码
- 版本 0.11 是基本可以正常的版本
这里就要扯到《GNU 风格的版本号管理策略》:
- 项目初版本时,版本号可以为 0.1 或 0.1.0, 也可以为 1.0 或 1.0.0,如果你为人很低调,我想你会选择那个主版本号为 0 的方式;
- 当项目在进行了局部修改或 bug 修正时,主版本号和子版本号都不变,修正版本号加 1;
- 当项目在原有的基础上增加了部分功能时,主版本号不变,子版本号加 1,修正版本号复位为 0,因而可以被忽略掉;
- 当项目在进行了重大修改或局部修正累积较多,而导致项目整体发生全局变化时,主版本号加 1;
- 另外,编译版本号一般是编译器在编译过程中自动生成的,我们只定义其格式,并不进行人为控制。
因此,我们可以得到几个简单的结论:
- 我们需要阅读最早的有核心代码的版本
- 我们需要阅读 1.0 版本的 Release
- 往后每一次大的 Release 我们都需要了解一下
以 Flask 为例:
一、先 Clone 它。
二、从 Release 页面找到它的早期版本:
三、 从上面拿到它的提交号 8605cc3
,然后 checkout 到这次提交,查看功能。在这个版本里,一共有六百多行代码
还是有点长
四、我们可以找到它的最早版本:
然后查看它的 flask.py
文件,只有简单的三百多行,并且还包含一系列注释:
五、接着,再回过头去阅读
- 0.1 版本
- 。。。
- 最新的 0.10.1 版本
我也是蛮拼的,虽然我想的只是在 GitHub 上连击 100~200 天,然而到了今天也算不错。
在不停地造轮子的过程中,也不停地造车子。
在那篇连续冲击 365 天的文章出现之前,我们公司的大大(https://github.com/dreamhead)也曾经在公司内部说过,天天 commit 什么的。当然这不是我的动力,在连击 140 天之前
- 给过 Google 的
ngx_speed
、node-coap
等项目创建过 pull request - 也有
free-programming-books
、free-programming-books-zh_CN
这样的项目。 - 当然还有一个连击 20 天。
对比了一下 365 天连击的 commit,我发现我在 total 上整整多了近 0.5 倍。
同时这似乎也意味着,我每天的 commit 数与之相比多了很多。
在连击20的时候,有这样的问题:为了 commit 而 commit 代码,最后就放弃了。
而现在是为了填坑而 commit
,为自己挖了太多的想法。
当时我需要去印度接受毕业生培训,大概有 5 周左右,想着总不能空手而归。于是在国庆结束后有了第一次 commit,当时旅游归来,想着自己在不同的地方有不同的照片,于是这个 repo 的名字是 onmap——将自己的照片显示在地图上的拍摄地点(手机是 Lumia 920)。然而,中间因为修改账号的原因,丢失了 commit。
再从印度说起,当时主要维护三个 repo:
- 物联网的 CoAP 协议
- 一步步设计物联网系统的电子书
- 一个 Node.js + JS 的网站
说说最后一个,最后一个是练习的项目。因为当时培训比较无聊,业余时间比较多,英语不好,加上听不懂印度人的话。晚上基本上是在住的地方默默地写代码,所以当时的目标有这么几个:
- TDD
- 测试覆盖率
- 代码整洁
这也就是为什么那个 repo 有这样的一行:
做到 98% 的覆盖率也算蛮拼的,当然还有 Code Climate 也达到了 4.0,也有了 112 个 commits。因此也带来了一些提高:
- 提高了代码的质量(code climate 比 jslint 更注重重复代码等等一些 bad smell)。
- 对于 Mock、Stub、FakesServer 等用法有更好的掌握
- 可以持续地交付软件(版本管理、自动测试、CI、部署等等)
(PS:从印度回来之后,由于女朋友在泰国实习,有了更多的时间可以看书、写代码)
有意思的是越到中间的一些时间,commits 的次数上去了,除了一些简单的 pull request,还有一些新的轮子出现了。
这是上一星期的 commits,这也就意味着,在一星期里面,我需要在 8 个 repo 里切换。而现在我又有了一个新的 idea,这时就发现了一堆的问题:
- 今天工作在这个 repo 上,突然发现那个 repo 上有 issue,需要去修复,于是就放下了当前的代码。
- 在不同的 repo 间切换容易分散精力
- 很容易就发现有太多的功能可以实现,但是时间是有限的。
- 没有足够的空闲时间,除了周末。
- 希望去寻找那些有兴趣的人,然而却发现原来没有那么多时间去找人。
在经历了 100 天之后,似乎整个人都轻松了,毕竟目标是 100~200 天。似乎到现在,也不会有什么特殊的情怀,除了一些希冀。
当然,对于一个开源项目的作者来说,最好有下面的情况:
- 很多人知道了这个项目
- 很多人用它的项目。
- 在某些可以用这个项目快速解决问题的地方提到了这个项目
- 提了 bug、issue、问题。
- 提了 bug,并解决了。(PS:这是最理想的情况)
今天是我连续泡在 GitHub 上的第200天,也是蛮高兴的,终于到达了:
故事的背影是:去年国庆完后要去印度接受毕业生培训——就是那个神奇的国度。但是在去之前已经在项目待了九个多月,项目上的挑战越来越少,在印度的时间又算是比较多。便给自己设定了一个长期的 goal,即 100~200 天的 longest streak。
或许之前你看到过一篇文章让我们连击,那时已然 140 天,只是还是浑浑噩噩。到了今天,渐渐有了一个更清晰地思路。
先让我们来一下 ShowCase,然后再然后,下一篇我们再继续。
上面说到的培训一开始是用 Java 写的一个网站,有自动测试、CI、CD 等等。由于是内部组队培训,代码不能公开等等因素,加之做得无聊。顺手,拿 Node.js +RESTify 做了 Server,Backbone + RequireJS + jQuery 做了前台的逻辑。于是在那个日子里,也在维护一些旧的 repo,如 iot-coap、iot,前者是我拿到 WebStorm 开源 License 的 Repo,后者则是毕业设计。
对于这样一个项目也需要有测试、自动化测试、CI 等等。CI 用的是 Travics-CI。总体的技术构架如下:
前台:
- Backbone
- RequireJS
- Underscore
- Mustache
- Pure CSS
后台:
- RESTify
测试:
- Jasmine
- Chai
- Sinon
- Mocha
- Jasmine-jQuery
一直写到五星期的培训结束,只是没有自动部署。想想就觉得可以用 github-page 的项目多好~~。
过程中还有一些有意思的小项目,如:
代码:https://github.com/phodal/gmap-solr
这个可以从两部分说起:
原来的是
- Knockout
- RequireJS
- jQuery
- Gulp
代码:https://github.com/phodal/skillock
- D3.js
- Dagre-D3.js
- jquery.tooltipster.js
- jQuery
- Lettuce
- Knockout.js
- Require.js
代码:https://github.com/phodal/sherlock
- ElasticSearch
- Django
- Ionic
- OpenLayers 3
代码:https://github.com/phodal/django-elasticsearch
- React
- jsPDF
- jQuery
- RequireJS
- Showdown
代码:https://github.com/phodal/resume
- ElasticSearch
- Hadoop
- Pig
代码:https://github.com/phodal/learning-data/tree/master/nginx
虽然技术栈上主要集中在 Python、JavaScript,当然还有一些 Ruby、Pig、Shell、Java 的代码,只是我还是习惯用 Python 和 JavaScript。一些用到觉得不错的框架:
- Ionic:开始 Hybird 移动应用。
- Django:Python Web 开发利器。
- Flask:Python Web 开发小刀。
- RequireJS:管理 JS 依赖。
- Backbone:Model + View + Router。
- Angluar:...。
- Knockout:MVV*。
- React:据说会火。
- Cordova:Hybird 应用基础。
还应该有
- ElasticSearch
- Solr
- Hadoop
- Pig
- MongoDB
- Redis
给你一年的时间,你会怎样去提高你的水平???
正值这难得的 sick leave(万恶的空气),码文一篇来记念一个过去的 366 天里。尽管想的是在今年里写一个可持续的开源框架,但是到底这依赖于一个好的 idea。在我的 GitHub 孵化器 页面上似乎也没有一个特别让我满意的想法,虽然上面有各种不样有意思的 ideas。多数都是在过去的一年是完成的,然而有一些也是还没有做到的。
尽管一直在 GitHub 上连击看上去似乎是没有多大必要的,但是人总得有点追求。如果正是漫无目的,却又想着提高技术的同时,为什么不去试试?毕竟技术非常好、不需要太多练习的人只是少数,似乎这样的人是不存在的。大多数的人都是经过练习之后,才会达到别人口中的“技术好”。
这让我想起了充斥着各种气味的知乎上的一些问题,在一些智商被完虐的话题里,无一不是因为那些人学得比别人早——哪来的天才?所谓的天才,应该是未来的智能生命一般,一出生什么都知道。如果并非如此,那只是说明他练习到位了。
练习不到位便意味着,即使你练习的时候是一万小时的两倍,那也是无济于事的。如果你学得比别人晚,在很长的一段时间里(可能直到进棺材)输给别人是必然的——落后就要挨打。就好像我等毕业于一所二本垫底的学校里,如果在过去我一直保持着和别人(各种重点)一样的学习速度,那么我只能一直是 Loser。
需要注意的是,对你来说考上二本很难,并不是因为你比别人笨。教育资源分配不均的问题,在某种程度上导致了新的阶级制度的出现。如我的首页说的那样:THE ONLY FAIR IS NOT FAIR——唯一公平的是它是不公平的。我们可以做的还有很多——CREATE & SHARE。真正的不幸是,因为营养不良导致的教育问题。
于是在想明白了很多事的时候起,便有了 Re-Practise 这样的计划,而 365 天只是中间的一个产物。
虽说算法很重要,但是编码才是基础能力。算法与编程在某种程度上是不同的领域,算法编程是在编程上面的一级。算法写得再好,如果别人很难直接拿来复用,在别人眼里就是 shit。想出能 work 的代码一件简单的事,学会对其重构,使之变得更易读就是一件有意义的事。
于是,在某一时刻在 GitHub 上创建了一个组织,叫 Artisan Stack。当时想的是在 GitHub 寻找一些 JavaScript 项目,对其代码进行重构。但是到底是影响力不够哈,参与的人数比较少。
如果你懂得如何写出高可读的代码,那么我想你是不需要这个的,但是这意味着你花了更多的时候在思考上了。当谈论重构的时候,让我想起了 TDD(测试驱动开发)。即使不是 TDD,那么如果你写着测试,那也是可以重构的。(之前写过一些利用 Intellij IDEA 重构的文章:提炼函数、以查询取代临时变量、重构与 Intellij Idea 初探、内联函数)
在各种各样的文章里,我们看到过一些相关的内容,最好的参考莫过于《重构》一书。最基础不过的原则便是函数名,取名字很难,取别人能读懂的名字更难。其他的便有诸如长函数、过大的类、重复代码等等。在我有限的面试别人的经历里,这些问题都是最常见的。
而如果没有测试,其他都是扯淡。写好测试很难,写个测试算是一件容易的事。只是有些容易我们会为了测试而测试。
在我写 EchoesWorks 和 Lan 的过程中,我尽量去保证足够高的测试覆盖率。
从测试开始的 TDD,会保证方法是可测的。从功能到测试则可以提供工作次效率,但是只会让测试成为测试,而不是代码的一部分。
测试是代码的最后一公里。所以,尽可能的为你的 GitHub 上的项目添加测试。
初到 TW 时,Pair 时候总会有人教我如何开始编码,这应该也是一项基础的能力。结合日常,重新演绎一下这个过程:
- 有一个可衡量、可实现、过程可测的目标
- Tasking(即对要实现的目标过程进行分解)
- 一步步实现(如 TDD)
- 实现目标
放到当前的场景就是:
- 我想在 GitHub 上连击 365 天。对应于每一个时候段的目标都应该是可以衡量、测试的——即每天都会有 Contributions。
- 分解就是一个痛苦的过程。理想情况下,我们应该会有每天提交,但是这取决于你的 repo 的数量,如果没有新的 idea 出现,那么这个就变成为了 Contributions 而 Commit。
- 一步步实现
在我们实际工作中也是如此,接到一个任务,然后分解,一步步完成。不过实现会稍微复杂一些,因为事务总会有抢占和优先级的。
在上上一篇博客中《After 500:写了第 500 篇博客,然后呢?》也深刻地讨论了下这个问题,技术向来都是后发者优势。对于技术人员来说,也是如此,后发者占据很大的优势。
如果我们只是单纯地把我们的关注点仅仅放置于技术上,那么我们就不具有任何的优势。而依赖于我们的编程经验,我们可以在特定的时候创造一些框架。而架构的设计本身就是一件有意思的事,大抵是因为程序员都喜欢创造。(PS:之前曾经写过这样一篇文章,《对不起,我并不热爱编程,我只喜欢创造》)
创造是一种知识的再掌握过程。
回顾一下写 echoesworks 的过程,一开始我需要的是一个网页版的 PPT,当然这类的东西已经有很多了,如 impress.js、bespoke.js 等等。分析一下所需要的功能:markdown 解析器、键盘事件处理、Ajax、进度条显示、图片处理、Slide。我们可以在 GitHub 上找到各式各样的模块,我们所要做的就是将之结合在一样。在那之前,我试着用类似的原理写(组合)了 Lettuce。
组合相比于创造过程是一个更有挑战性的过程,我们需要在这过程去设计胶水来粘合这些代码,并在最终可以让他工作。这好比是我们在平时接触到的任务划分,每个人负责相应的模块,最后整合。
我在写 lan 的时候,也是类似的,但是不同的是我已经设计了一个清晰的架构图。
而在我们实现的编码过程也是如此,使用不同的框架,并且让他们能工作。如早期玩的 moqi.mobi,基于 Backbone、RequireJS、Underscore、Mustache、Pure CSS。在随后的时间里,用 React 替换了 View 层,就有了 backbone-react 的练习。
技术同人一样,需要不断地往高一级前进。我们只需要不断地 Re-Practise。
说业务好像不太适合程序员的口味,那就领域吧。不同行业的人,如百度、阿里、腾讯,他们的领域核心是不一样的。
而领域本身也是相似的,这可以解释为什么互联网公司都喜欢互相挖人,而一般都不会去华为、中兴等非互联网领域挖人。出了这个领域,你可能连个毕业生都不如。领域、业务同技术一样是不断强化知识的一个过程。Ritchie 先实现了 BCPL 语言,而后设计了 C 语言,而 BCPL 语言一开始是基于 CPL 语言。
领域本身也在不断进化。
这也是下一个值得提高的地方。
是时候写这个小结了。从不会写代码,到写代码是从 0 到 1 的过程,但是要从 1 到 60 都不是一件容易的事。无论是刷 GitHub 也好(不要是自动提交),或者是换工作也好,我们都在不断地练习。
而练习是要分成不同的几个步骤,不仅仅局限于技术:
- 编码
- 架构
- 设计
- 。。。
尽管之前已经有 100 天、200 天、365 天的文章,但是这不是一篇象征性的 500 天的文章。对这样的一个事物,每个人都会有不同听看法。有的会说这是一件好事,有的则不是。但是别人的看法终究不重要,因为了解你自己的只有你自己。别人都只是以他们的角度来提出观点。
在这 500 天里,我发现两点有意思的事,也是总结的时候才意识到的:
- 编程的情绪周期
- 有意图的练习
那么,当我们不断地练习的时候,我们就可以写出更好的代码。
我想你也听过一万小时天才理论的说法:要成为某个领域的专家,需要 10000 小时。而在这其中最重要的一点是有意图的练习——而不是一直重复性地用不同的语言去写一个相同的算法。如果我们有一天 8 小时的工作时间 + 2 小时的提高时间,那么我们还是需要 1000 天才能实现一万小时。
当然如果你连做梦也在写代码的话,那么我想 500 天就够了,哈哈~~。
虽然不是连击次数最多的,但是根据 Most active GitHub users 的结果来说,好似是大陆提交数最多的人,没有之一。再考虑到提交都是有意义的——不是机器刷出来的,不是有意识的去刷,我觉得还是有很大成就感的。
而要实现 500 天连击很重要的两点是:时间和 idea。但是我觉得 idea 并不是非常重要的,我们可以造轮子,这一点就是在早期我做得最多的一件事,不断地造轮子——如《造轮子与从Github生成轮子》一文中所说。除此,你还可以用《GitHub去管理你的idea》,每当你想到一个 Idea 以及完成一个 idea 的时间你就会多一次提交。
时间则是一件很讽刺的事,因为人们要加班。加班的原因,要么是因为工作的内容很有意思,要么是因为钱。如果不是因为钱的话,为什么不去换个工作呢?比如我司。看似两者间存在很多的对立,但是我总在想技术的提升可以在后期解决收入的问题,而不需要靠加班来解决这个问题。人总是要活着的,钱是必需的,但是程序员的收入都不低。
接着,我观察到了一些有意思的现象——编程的情绪周期也很明显。
所谓“情绪周期”,是指一个人的情绪高潮和低潮的交替过程所经历的时间。
如下图所示的就是情绪周期:
简单地来说,就是有一个时间段写代码的感觉超级爽,有一个时间段不想写代码,但是如果换一个说法就是:有一个时间段看书、写文档的感觉很爽,有一时间段不想看书、写文档的感觉。这也就是为什么在我的GitHub首页上的绿色各种花。不过因为《物联网周报》的原因,我会定期地更新一个相关的开源项目。
但是总来说,我习惯在一些时间造一些轮子、创建文档,这就是为什么我的GitHub会有一些开源电子书的缘故。
编程需要很长的学习时间,也需要很长的练习时间。尽管我是从小学编程,自认为天赋不错,但是突破了上个门槛还是花费了三四年的时间。其中的很大一部分原因是,没有找对一个合适的方向。而在这期间也没有好好的练习,随后的日子里我意识到我会遇到下一个门槛,便开始试图有意识的练习。
在我开始工作的时候,我写了一篇名为《重新思考工作》的文章。在文章中我提到了几点练习的点:
- 加强码代码的准确性
- 写出更整洁的代码
- 英语口语 (外企)
- 针对性的加强语言技能
在一些日子的练习后,我发现这还是太无聊了。天生就喜欢一些有意思的东西,有趣才更有激情吧~~。不过,像下图的打字练习还是挺有意思的:
还是能打出了一堆错误的字符。但是对比了一下大多数人的人,还算不错,至少是盲打。但是,还是存在着很大的提升空间。
随后,我开始一些错误的练习,如对设计模式和架构的练习。试图去练习一些在生产上用不到的设计模式,以及一些架构模式。而这时就意味着,需要生搬一些设计模式。最后,我开始以项目为目的的练习,这就是为什么我的GitHub上的提交数会有如此多的原因。
还有一种练习比较有意思,算是以工作为导向的练习。当我们预见到我们的项目需要某一些技术,我们可能在未来采用某些技术的时候,我们就需要开始预见性的练习这些技术。
好的一点是:这些项目可能在未来很受初学者欢迎。
每个人都有自己的方向,都有一个不错的发展路线,分享和创造都是不错的路。
THE ONLY FAIR IS NOT FAIR . ENJOY CREATE & SHARE.
刚毕业的时候,有一段时间我一直困惑于如何去提高编码能力——因为项目上做的东西多数时候和自己想要的是不一样的,我便想着自己去找一些有意思的东西做着玩,在这个过程中边练习技能。
如果你知道自己代码能力不够,为什么不花两年时间去提高这方面的能力呢?
编码是一件值得练习的事,你从书中、互联网上看到的那一个个的编程大牛无一不是从一点点的小技能积累起来的。从小接触可以让你有一个好的开始,一段好好的练习也会帮助你更好的前进。
记得我在最开始练习的时候,我分几个不同的阶段去练习:
- 按照《重构:改善即有代码的设计》一书边寻找一些 bad smell 的代码,一边想方设法去让代码变得优雅。
- 按照《设计模式》以及《重构与模式》来将代码重构成某种设计模式。
- 按照《面向模式的软件架构》去设计一些软件架构。
而这些并不是一种容易的事,很多时候有一些模式,我们都很难有一个好的实践。只是这些东西都不是一些可以生搬硬套的,我们更需要的是知道有这些东西的存在,以便于在某一天,我们可以从我们的仓库里将这些知识取出来。
我们的刻意练习加上我们的持之以恒总是会取得长足的进步。不过在我们练习之前,你需要有一个目标。这个目标可以是一个 Idea、一个设计模式、一个模仿等等,这些内容都可以以 Issue 的好好管理着。
在最开始我们下定目标的几天里,我们可以很容易做到这样的事。同样的,我们也可以很容易达到 21 天。只是,我们很容易在 21 天后失去一些目标。所以在练习开始之前,你需要创建一个帮助你提高技术的列表,然后一点点加以提高。比如说:
- 尝试使用 React + Redux + Koa 2、或者Angular 2 + TypeScript,这样我们就能凭此来学习新的技术。
- 尝试使用 CQRS 架构来设计 CMS,这样我们就可以练习在架构方面的能力。
在我们想到一点我们可以练习的技术的时候,这就是一个可以变成 Issue 管理的内容,我们就可以针对性的提高。
通常在这种情况下,我们知道自己不知道什么东西,当我们处于不知道自己不知道、不知道自己知道时,那我们就需要网上的各种技能图谱——如StuQ的技能图谱。
然后了解图谱上的一个个的内容,尽可能依照此构建自己的体系——以让自己走向知道自己不知道的地步,然后我们才依此来展开练习。
建议试试我们家的Growth哈,地址:http://growth.ren。
文章的剩下部分就让我分享一下:在这 723 天里,我创造出了哪些有意思的东西(PS:让我装逼一下)——其实我不仅仅只是 Markdown 写得好
时间:2014.10.08-2014.12.30
在这一段时间里,我创建的项目大部分都是一些物联网项目:
- iot-coap 一个基于 CoAP 协议的物联网
- designiot 即电子书《教你设计物联网系统》
- iot-document 收集一些物联网相关的资料,和 Awesome 不是一个性质
- iot 基于 PHP 框架 Laravel 的物联网
- iot-android 一个与 iot 项目相配套的 Android 程序
- 等等
正是这几个 IoT 项目,让 Packt 出版社找到了我,才有了后来和国内外出版社打交道的故事。也开始了技术审阅、翻译、写书的各种故事,想想就觉得这个开头真的很好。
期间还创建了一个很有意思的 Chrome 插件,叫 onebuttonapp——没错,就是模仿 Amazon 的一键下单写的。这个插件的目的就是难证当时在项目上用的 Backbone、Require.js 的这一套可以在插件上好好玩。
OnMap 项目是为了让我用 Nokia Lumia 920 拍照的照片,可以在地图上显示而创建的项目。
当然还有其他的一些小项目啦。
整个区间就是刷各种前端的技术栈,创建了各种有意思的项目:
- Lettuce框架,一个基于简单的 SPA 框架
- echoesworks,一个支持字幕、Markdown、动画的 Slide 框架
- diaonan,一个支持 CoAP、MQTT、HTTP 的物联网项目
- developer,收集各种 Web Developer 成长路线,以及读书图谱
期间还创建了几个混合应用项目:
- learning-ionic,程序语言答人,各种 hello, world 的小应用
- ionic-elasticsearch, Django ElasticSearch Ionic 打造 GIS 移动应用
- designiot-app,教你设计物联网 App 版
更多内容可以见我的 Idea 列表:https://github.com/phodal/ideas,我实在是不想写了。
我们有了 Growth 系列的电子书、App,还有 Mole,几个极具代表性的项目就够了。
- Growth,一款专注于 Web 开发者成长的应用,涵盖 Web 开发的流程及技术栈,Web 开发的学习路线、成长衡量等各方面。
- Growth:全栈增长工程师指南,一本关于如何成为全栈增长工程师的指南
- Growth:全栈增长工程师实战,在 Growth 中我们介绍的只是一系列的实践,而 Growth 实战则会带领读者去履行这些实践
停止这次连击,只是为了有一个更好的开始。
如果你也想提高自己,不妨从创建你的 ideas 项目开始,如我的 Ideas 项目一样,上面已经有了大量的 Idea。然后,我们还可以依据这一个个的项目,创建出一本电子书,即 ideabook。
Star 虽好,可不要贪杯哦。 两年前在做 Annual Review 订下一年的目标时,想着写一个开源框架。去年订下今年的目标时,仍然继续着这样的想法。今年又要制定下一年的目标,2333~~。
不久前,在 GitHub Ranking 上看到自己的 Star 数(Star 不是设计用于做“点赞”的,而是用来收藏的)时,发现已经快 20000 了。然后把自己的项目过了一遍,发现没有一个比较好的代表性框架,要么是应用,要么是电子书。
前 8 个项目里,除了 Growth 应用以外,其他的都是电子书内容——六本电子书加起来的 Star 数有 10619,果然是骗 Star 的。我只能尽力地去想想:为什么事情会变成这样了?
创建开源框架和创建开源项目是不一样的,前者你服务于开发者,后者你服务于用户。
两年前在做 Annual Review 的时候,想着未来的一年里可以做一个开源框架试试。那时刚毕业不久,对开源世界的各种游戏规则不是很了解:开源并不是将代码提交上去,然后就会一下子火起来。虽然我们可以在短期内赚上一些眼球,但是真正要将它采用到项目上的人不多。
当时,我遇到的最主要的问题是:想参与到项目的人并没有遇到足够的能力。你还需要花费大量的时间去教他们,鼓励 GitHub 新手并不是一件容易的事。有时我需要在接受他的 PR 后,再修改他的代码。并且人们提交 PR 可能是出于不同的原因。
然后,知道了开源世界还有一个游戏规则是:谁的影响力大,谁就能产生更广泛的影响。如 Virtual Dom 并不是 Facebook 首创的,但是却因为 FB 火的; 松本行弘在写下 mruby 的 README 时(印象中是这个项目),Star 数就已经过 1k 了。这种例子数不胜数,要么是在推广上花了力气,要么个人、公司有着更大的影响力。
一年前,稍微改变了下策略:暂时以培养人为主,同时想着做一个合适的开源框架——只是在今年看来,前端领域已经没有合适的地方可以造轮子了。
在 GitHub 上有一个很常见的问题是,大多数项目的维护者就是发起人——如果这个发起人发生意外了,那么这个项目怎么办。如果这是一个很火的项目,它就存在着巨大的风险;同时这可能也说明了,缺乏一套合理的机制。
你的开源项目不仅仅需要一个使用文档,还需要一个相关设计思想的文档、路线图、未来计划等等。
去年年底写总结的时候,想到可以 RePractise 文章为基础来培养人,于是就有了 Growth 的三个项目:
- 应用:Growth
- 电子书:《Growth:全栈增长工程师指南》
- 电子书:《Growth:全栈增长工程师实战》
如今 Growth 已经有了过万的用户,每天活跃的用户数也接近 300 了。第一步看上去很成功,但是下一步怎么走呢?
后来我开始在思索一个问题,创建一个开源框架是必须的吗?
在编写 Growth 电子书的时候,我发现一个好的软件工程实践远远比一个易上手的框架重要多了。框架本身是易变的东西,过去你在用 Backbone,现在你在用 React.js;过去你在用 Angular.js,现在你在用 Vue。会不会使用某个框架,并不是区分你是不是一个有经验的开发者的标准。
一直将焦点关注于学习不同的框架的使用是有问题的,一个在校生可以轻松地比你了解某个框架的原理——你白天在工作,而他整天在学习。这时你很容易就失去竞争力了,你需要从框架之外了解更深层次的东西。一个好的框架并不能让你写出一段好的代码。
如果中国人的思想不觉悟,即使治好了他们的病,也只是做毫无意义。
这算是我为自己在 GitHub 下的 Markdown 的自辩吧——谁让我一直有写作的冲动呢。
不过我仍然还有一些想法,只是还没有抽出足够的时间去思考这样的事。
GNU/Linux 的桌面。这是几年前的一个想法了,当时 GNU/Linux 的那些操作系统上都没有一个好玩的桌面,不过感觉这个坑太深了,就没有进行了。
家居智能中心。我仍然对于大学学的知识有点念念不忘,虽然已经写了一本书,但是硬件还是相当的刺激。唯一的问题是:连房子都没有,怎么做智能家居。
图形框架。这是我之前在做一个图形界面的时候,发现没有一个合适的框架可以满足我的要求。然后我就在想,还是自己做一个吧。
不过,最好的开源项目就是自己平时用的。于是,我开始将写各种工具来给自己使用——如现在在用的这篇微信编辑工具:mdpub。
最后,我做了一个简单的 HTML 5 动画来记录这一时刻,作为这一个里程碑的记念:
作为一个资深的咨询师、程序员,GitHub 是我用过的最好工具,因为 Google 并非总是那么好用。GitHub 是一个宝藏库,可没有藏宝图,GitHub 一1亿的仓库也和你没有关系。这么一些年下来,也算是掌握了一定的技巧,写篇文章记录一下,也就顺其自然了。
总结一句话便是:GitHub 来搜索 Google 搜索不到的。它们可以 work 的原因,都是因为我们想做的事情,已经有人已经走过。如果你走的是一条新的路,那么这篇文章对你来说,意义可能没有那么大。
在工作上使用新的技术,和自己平时的练习,终究差得有些远。工作的时候,我们偏向于目标编程,对于速度和时间的要求,要比自己业余时间要高得多。一旦有了这种压力,便会在 GitHub 上寻找相应的 Demo,了解原理、稍微尝试,再引入到项目中。
这时,便会按技术栈的关键字搜索,并按更新时间进行排序,以查找是否有合适的 Demo。
生命有限 ,如若是每次我们尝试一个新的技术,总得自己编写一个个 Demo。编写多个 Demo,都得花去个半天八小时的时间。如此一算,能花费在其它事情上的时间便更少了。若只是试用官方的 Demo,往往是比较容易的。可我们编写应用的时候,总得结合到当前的场合来。这时整合并不是一个轻松的工作,依赖冲突、引入第三方依赖等。
温馨提醒:对于简单的项目来说,自己直接写 Demo 会更加方便。 尝试项目需要成本,若是需要尝试使用多个项目,那么有可能就浪费时间。
无论是后端的微服务架构,还是前端应用,应用的架构正在变得复杂。后端微服务,需要结合一个个的框架,哪怕是 Spring Initializr
这样的工具,也只能帮助我们搭建项目。我们还需要配合其它工具,一起搭建出一个基本的系统。对于前端应用也是类似的,若是 Angular 这样大而全的框架,时间花费倒也是不多。如 React 这种需要组合的、小而美的框架,使用官方的 create-react-app
也很难做出我们想要的东西,寻找一个合适的脚手架是一个更好的选择。
这时,我们大抵可以,直接使用技术栈 + boilerplate
又或者是 starter
等关键词进行搜索,如 react boilerplate
。如果其中找到的组合技术栈,不符合自己的要求,那么再加上相应技术栈的关键字,如 react redux boilerplate
即可。有意思的是,在这时使用 Google 会比 GitHub 方便一些。
温馨提醒:我们需要衡量:修改脚手架的成本,是否比自己重头写快。
练习新的框架,我总习惯于,编写一系列相关的 DEMO 项目,然后使用 awesome-xxx 探索可能性。
Awesome-xxx 系列,是 GitHub 上最容易赚 Star 的类型。但凡是有一定知识度的领域、语言、框架等,都有自己的 awesome-xxx 系列的项目,如 awesome-python, awesome-iot, awesome-react 等等。在这样的项目里,都以一定的知识体系整理出来的,从索引和查阅上相应的方便。如果你想进入一个新的领域,会尝试新的东西就搜索 awesome xxx
吧。
温馨提醒:awesome-xxx 只意味着它们包含尽可能多的资料,并不代表它们拥有所有相关的库。
大学时,我在练习写嵌入式操作系统,uC/OS-II 对于初学者的我来说,太复杂了——有太多无关的代码。便在网上找寻相关的实现,也便是找到了一些,在那的基础上一点点完善操作系统。
学习一个成熟的框架,直接阅读现有源码的成本太高,毕竟也不经济。最好的方式,就是去造轮子。从模仿轮子之上,再去造轮子,是最省力气的方式。再配合 《造轮子与从Github生成轮子》 一文,怕是能写一系列的框架。而造一个相似轮子的想法,往往很多人都有。尤其是一个成熟的框架,往往有很多仿制品。
于是,当你想了解一个框架,造个轮子,不妨试试搜索 xxx-like
或者 xxx-like framework
,中文便是 仿 react 框架
或者 类 react
。如我们在 Google 上搜索 react-like
就会搜索到 inferno
。不过,按 GitHub 的尿性,要搜索到这样的框架,并不是一件容易的事。这时 Google 往往比 GitHub 搜索好用。
所以建议:平时上班休息时,搜索相关的轮子,回家就可以造轮子了。
GitHub 上拥有大量的学习资源,从各类的文章到笔记,还有各式各样的电子书。如:
- 只需要搜索:
类型 + 笔记
,如操作系统 笔记
就能找到一些操作系统相关的笔记。 - 只需要搜索:
书名
就能找到一些和这本书相关的资源,如重构 改善既有代码的设计
。
与此同时,GitHub 上还会搜索到各种 未经授权英文书籍的翻译,又或者是各种电子书的 PDF 版。作为多本书的作译者,当然不鼓励 GitHub 上找到一些盗版书。
而在 GitHub 上又有一些库,可以提供相应的学习资源,如 free-programming-books-zh_CN,即免费的编程中文书籍索引。
建议:请尊重版权,哈哈哈。
GitHub 上有太多这样的东西,尽管我没有能赶上个好时候,找到一个合适的密钥。有相关多的资料泄漏和数据库被扒,和 GitHub 上存在的密钥和密码有关。
不过,好在 GitHub 已经在着手解决这个问题:自动删除相关的提交、代码警告等等。
总有人,会将一些商用的代码,或者公司内部的代码,提交到 GitHub 上。如果你偶尔看到这样的代码,除了每一时间告诉作者,还可以偷偷 Clone 一下代码——虽然这样做不对,但是我还是想看。
如在 ThoughtWorks 的面试流程里,有一个步骤是代码编程的作业,个人的实现是不能公开出来的。接到一份作业的时候,总会去 GitHub 搜索相应的代码是否被提交了。提交了,倒是也得提醒一下相应的候选人。
过去,我在使用 Phaser 编写应用的时候,对应的粒子系统是收费的。由于我只是尝试这个粒子系统,便没有购买的想法。我一想 GitHub 上可能有,于是搜索了对应的 particle-storm.js
,然后就中奖了。就便愉愉快快地去写我的 Hello, World,最后发现它太耗费资源了,便放弃了。
建议:一旦你在 GitHub 上拿到别人的商用代码,请仅用于学习,并时刻保持低调。稍有不慎,有牢狱之灾。
当我们需要数据的时候,就会考虑写爬虫。于是 GitHub 上充满了各各样的式爬虫,除此还有得同学把爬虫数据都放在上面了。某次,当我在玩 ElasticSearch 搜索引擎的时候,突然需要一些真实的数据用来测试。便得找爬虫,就在 GitHub 上,找到了大众点评的一些爬虫。
这个关键词,就是:scrapy dianping.com
,得来不费功夫。
除此,在 AI 相当流行的今天也是如此,也可以搜索到其它同学训练好的模型。
试试你的 GitHub 搜索功能吧。
每天打开 GitHub Trending,都是各种面试指南,这样的生活真难受。如果你的项目是金子,那么请读读这篇文章。
GitHub 是一个非常有意思的地方,也常常变得非常有争议。有证据表明,GitHub 在某种程度上已经成为了简历的一部分。所谓的证据,便是培训班的人在帮助面试者美化 GitHub 页面——从 Vue 高仿各类项目,到淘宝买 Star 来粉饰门面。作为一个面试官,我向来是非常讨厌这样的行为。那么作为一个正直的开发人员,他/她们也越来越需要通过 GitHub 去证明自己的能力。否则,总有一天劣币驱逐良币,导致 GitHub Trending 上的项目越来越不堪入目。
出于这样的目的,我想为那些有真金白银的小伙伴写一篇攻略。至于其他/她人的看法倒是不重要,帮助那些金子从水底浮出来,才是我们应该做的。要是有太多的过于水的项目,每天打开 GitHub Trending,都是各种面试指南,那生活还叫生活吗?那叫被面试强迫的生活。
在 GitHub 获得 Star 的重点是,碰触人们的 G 点——人们只对和自己有关的事情感兴趣。或是证明自己是对这个感兴趣,或是觉得这个项目不错可以收藏,或者是觉得作者不容易鼓励一下作者。
当然了,我痛恨那些,投机取巧的人——在 GitHub 放置大量非自己创作的电子书、学术资料、课程,以获取 Star。
获得 Star 的核心是:你有人们想要的东西,你分享了人们想要的内容。这些内容可以是代码、文档、文章、资料、指南,只要它能帮助到其他/她人,那么它便是有价值的。当然了作为 GitHub 本身来说,那些通过 Git 和版本管理可以控制的内容,才更适合于这个平台上。
所以,当你手上拥有了人们想要的东西时,那么你就可以使用这份指南,来帮助你构建出更成功的项目。
作为一个 GitHub 上的“大 V”,我往往不需要花费太多的精力在项目宣传上。在 GitHub 上创建一个项目,然后 Star 就来了……。有时候会比较“无耻”,等到某个项目做得稳定的时候,再给自己一个 Star ,吸引更多的吃瓜群众。而后,写一系列的文章来介绍自己的项目。唉,做个开源项目不容易啊。
但是这些并不管用,因为有时候,我写的代码是大家丝毫不感兴趣的内容。如我最近写的 Serverless 密码管理器 MoPass:我在公众号上、博客上、知乎上写了文章来宣传这个项目,最后只吸引了一小部分人的注意——<= 25。毕竟,你觉得好的东西,那只是对你来说有用。对于其他/她人来说,这个密码管理器可能远远不如 1Password。
再举个成功的例子,最近我在思考:新项目的检查清单,即当我们来到或者开始一个项目的时候,我们需要做哪些事情,对应的还需要考虑什么因素。于是我在 GitHub 上创建了一个名为 New Project Checklist (https://github.com/phodal/new-project-checklist ) 的项目。我只是按自己的想法,在 README 上写下了要考虑的中英文因素,还没编写 Web 部分,就已经获得了 100+ 的 Star。与此同时,因为 Web 部分还没完成,所以我还没在我的博客、专栏上进行宣传。
我只是写了一个 README,然后 Star 就来了。但是,一般情况下,我们需要怎么做呢?
实际上,当我们在说获得 Star 的时候,我们说的是为自己的项目做推广。只是呢,获得 Star 是其中的一个结果产物,也就是说,我们在宣传项目的过程中,获得了关注度。至于推广本身来说,不同的人会有不同的看法。
事实上,GitHub 获取 Star 与我们日常了解的营销差不多,先将用户吸引到我们的 GitHub 页面,再让用户有关注的动力(这一点太难了)。
因此开始之前,我们先看张图就能知道怎么获取流量。如下是《GitHub 漫游指南》最近两周内的流量来源统计(GitHub 通过 Google Analysis 来统计):
从上图中可以看出,流量主要来源于几部分:
- GitHub 项目的直接访问
- GitHub 的直接访问
- 来源于知乎等社交网站的
- 来自于 GitHub Pages 的访问
- 来自其它社交网站的访问
总的来说,在这一周里,累计有 1,266 次访问,其中有 735 个独立访客。看这数据不错,而实际上 Star 率 就有点低。根据 Star History 网站(https://star-history.t9t.io ) 的统计,在过去的近两个月里,才涨了 38 个 Star。
从我的分析来看,大抵原因有两个:
- 用户看的都是 GitHub Pages 上的内容
- 从数量上来看,受众并不多
而我最近在玩的 New Project Checklist(https://github.com/phodal/new-project-checklist 的转化率看上去,还算可以:
在 999 个独立访客里,获得了 202 个 Star,转化率差不多是 20%——大家真的对这个项目感兴趣。
所以,让我们再强调一下核心的部分:你分享了人们想要的代码、内容。否则,你带来了大量的流量,并不一定能转化为你想要的关注度。
对于一个创造而言,自然而然的希望自己的项目能有人用。可能一点点的吐槽,都能帮助项目以更好的方式前进。这也就是我为自己项目宣传的意义,在创建项目的时候,我们往往只会按照自己的需要来创建项目。而非其他/她人的需求。因此当有一些新的需求出现时,可能会稍微地影响项目演进的方向。这些方向有好有坏,有时候反而会对自己更有帮助。
好了,回到我们的正题上,怎么去获取 Star?
当我们在为一个项目做宣传的时候,实际上我们做的事情类似于搜索引擎优化(Search Engine Optimization)。稍有不同的是,GitHub 在实践的过程中,帮助我们优化了很多细节。它可以让我们更关注于核心的要素。
实际上,在上一小节里,我们已经介绍了相关的内容。若是想获得来自于 Google 等搜索引擎的访问,那么要掌握的技巧有:
- 简单实用的项目名。项目名在 Google 搜索结果里是放在最前面的部分,它与 URL 同在。
- 写好项目的
Description
。不管怎样,你一定要为你的项目写好 Description,让看到的人知道它在做什么。 - 设置好相应的
topics
。GitHub 为项目设计了一个 Topics 页面,这些页面会被拉入相应的索引中,可以从 Google 等搜索引擎和 GitHub 中搜索到。 - 作为外链加入文章中。作为 SEO 技巧的一部分,你需要在你的博客和文章里,适当地引用你的 GitHub 项目,它会你的项目带来流量。
- 合适的外链标题。作为链接存在时,需要注意链接的标题(与项目主题一致),它会在某种程度上影响搜索结果。
这些只是一些基本的内容,算不上是技巧,但是做好基础很重要。
让我们再强调一下,好的 README 真的很重要,重要、重要!重要。
GitHub 是一个人的简历,而开源项目的 README,就好像是一个项目的简历。在这份简历里,你需要好好地写你的项目:
- 这个项目做什么??
- 它解决了什么问题?
- 它有什么特性 — hello, world 示例?
- 怎么使用这个项目?
- 这个项目使用的是什么协议,是否允许商用?
以我混迹在 GitHub 近 10 年的经验来看,老外最喜欢吹这个项目有什么特性了。与此同时,还会在这个项目上“画大饼”(Roadmap),即这个项目未来将有什么功能——为了实现这些功能,我们还需要你的关心、支持与厚爱。所以,如果你是在做一个惊天动地的项目,比如说你要实现一个自动化安装脚本,你可以在未来的功能里写上:
- AI 自动化安装(TODO)
这确实是个 TODO——即不吹,又吸引吃瓜群众。
作为一个混迹在各个社区的资深技术咨询师,分享相关的项目是我的一个常规操作。特别是,当看到一些人“无聊的聊天”,就会推荐上自己的新项目。当然,一般一个项目只会有一两次,频繁的分享便相当于 ** ,你懂的。
更新状态。当我在写一个大家感兴趣的开源项目时, 我会在我的社交账号上,如微博、知乎想法,定期的更新相关的状态。诸如:
万一有人感兴趣,就会随之而来——主要是我也不知道微博要怎么玩。
推荐自己的项目。作为一个在 GitHub 上有大量项目的开源作者,以及拥有大量文章的我。每次在微信群里,看到一些相关的问题,都会直接丢出我的开源项目。既装逼,又靠谱。
至于微信群的分享频率,要适度~,适量~。
既然我写了一个这么好的开源项目,那么最好的方式,还是写一篇文章介绍一下这个项目吧。blabla,写完了一篇项目的使用文档:
- 为什么需要这个项目?
- 这个项目是什么?
- 这个项目能解决什么问题?
- 这个项目要怎么用啊?
是不是写起来很简单?
未来在其它的文章中,有一些相关的话题,便可以稍微提及一些相关的项目。比如,在这篇文章里,我还介绍了好几个近期的项目。这些文章,除了在我的公众号上,还会发在我的博客(累计 100 万访问量)上,我的知乎专栏上,还有我的……上。它们结合起来,会形成一股强大的力量,即能吸引用户,又能在 SEO 上有一定的提升。
万一,我是说万一,你的项目上了 GitHub Trending。截个图,然后你可以再写一篇文章( 我的项目是如何上 GitHub Trending,毕竟上 Trending 很简单),发一条微博,写一个想法,录个小视频,大家快来看这是我的项目。
理论上上 GitHub Trending 会吸引来更多的用户——有大量的网站、自动化微博等,会每天去介绍这些新的上的 Trending 项目,没有意外的话,它会为你带来更多的流量——意味着更多的关注度。
事实上,如你所知,我在 GitHub 上获得大量 Star 的原因,并不是说我有一个优秀的项目。而在于我在持续的更新,持续不断地在 GitHub 上做自己喜欢的项目,投入时间分享相关的技巧,还有一系列相关的开源项目。
我们一直在持续变好,打造一个自由的互联网世界,打造一个个自己喜欢的工具。
我们是极客,我们热爱编程,我们热爱分享。
我觉得:在作者开源了源码的情况下,求 Star 并没有任何问题。
开源软件的源头是自由软件,而 RMS 创建自由软件的目的是,反对专利软件,即私有化的软件。如果一个开源项目,要你 Star 了,才公开源码,这才叫违反。
开源一个软件,并不意味着:你不能用这个开源软件追求任何利益。在所谓的开源运动里,一个开源软件是可以用来卖钱的。可在国内,这是很难的,大公司 如腾讯,可以轻轻松松地用你的软件,而不遵循 GPL 协议。
在这种时候,也没有法律来保护这些开源软件作者。你只能从道德上谴责他们,然后指望他们的领导来做出一些什么事。如之前的《知名公司(努比亚/中兴)拿我的开源软件( XXL-JOB)申请国家知识专利,我该怎么办?》事件。
并且对于大部分的开源软件作者来说,都不大可能像 OpenResty、Vue、emqtt 等软件的作者一样,可以从开源软件获得收益来支撑他们开发。还有一些少数人,还能从开源软件中获得一些利益,提高他们今年的 KPI。然后明年的工资,又会多涨一点点。
可多数人,并没有这样的可能性。我在 GitHub 上有接近 30k 的 Star(笑,有接近 20k 是属于电子书的,毕竟思想改变世界),它一点儿也不影响我涨工资。反而多了一个 GitHub “网红” 的称号,要知道在技术领域,“网红” 并不是一个好词。我观察过的大量开源爱好者,怕是比我还惨一些。明明做了很好的工作,因为宣传工作没有做好,连几个 Star 都没有,后来就弃坑了。
在这个时候,求 Star 就是让心里好受一些,『我做了这么多的事情,我希望得到一些认同』。如果我在一个微信群里,看了作者做了大量的提交,花费了一些心思。在这个时候,我是会去为作者点 Star 的。因为我的 GitHub 上粉丝比较多,所以往往会多带来几个 Star。
如果一个人在开源世界里,做了很多事情,连一个 Star 都没有。那么,他/她可能就会离开开源世界。当这种事情发生多了,那么开源世界的人就变少了。任何做开源工作的人,都是值得鼓励的——不论他们是出于什么目的。