前言 === 我的GitHub主页上写着加入的时间——``Joined on Nov 8, 2010``,那时才大一,在那之后的那么长的日子里我都没有登录过。也许是因为我学的不是计算机,到了今天——``2015.3.9``,我才发现这其实是程序员的社交网站。 过去,曾经有很长的一些时间我试过在GitHub上连击,也试着去了解别人是如何用好这个工具的。当然粉丝在GitHub上也是很重要的。 在这里,我会试着将我在GitHub上学到的东西一一分享出来。 ## 我与GitHub的故事 在我大四找工作的时候,试图去寻找一份硬件、物联网相关的工作(ps: 专业是电子信息工程)。尽管简历上写得满满的各种经历、经验,然而并没有卵用。跑了几场校园招聘会后,十份简历(ps: 事先已经有心里准备)一个也没有投出去——因为学校直接被拒。我对霸面什么的一点兴趣都没有,千里马需要伯乐。后来,我加入了[Martin Flower](https://martinfowler.com/)所在的公司,当然这是后话了。 这是一个残酷的世界,在学生时代,如果你长得不帅不高的话,那么多数的附加技能都是白搭(ps: 通常富的是看不到这篇文章的)。在工作时期,如果你上家没有名气,那么将会影响你下一份工作的待遇。而,很多东西却可以改变这些,GitHub就是其中一个。 注册GitHub的时候大概是大一的时候,我熟悉的时候已经是大四了,现在已经毕业一年了。在过去的近两年里,我试着以几个维度在GitHub上创建项目: 1. 快速上手框架来实战,即demo 2. 重构别人的代码 3. 创建自己可用的框架 4. 快速构建大型应用 5. 构建通用的框架 ### GitHub与收获 先说说**与技能无关的收获**吧,毕业设计做的是一个《[最小物联网系统](https://github.com/phodal/iot)》,考虑到我们专业老师没有这方面知识,答辩时会带来问题,尽量往这方面靠拢。当我毕业后,这个项目已经有过百个star了,这样易上手的东西还是比较受欢迎的(ps: 不过这种硬件相关的项目通常受限于GitHub上硬件开发工程师比较少的困扰)。 毕业后一个月收到PACKT出版社的邮件(ps: 他们是在github上找到我的),内容是关于Review一本[物联网](iot)书籍,即在《[从Review到翻译IT书籍](http://www.phodal.com/blog/review-it-books-with-translate-book/)》中提到的《Learning Internet of Things》。作为一个四级没过的"物联网专家",去审阅一本英文的物联网书籍。。。 当然,后来是审阅完了,书上有我的英文简介。 ![Phodal Huang Introduction](./img/phodal-intro.jpg) 一个月前,收到MANNING出版社的邮件(ps: 也是在github上),关于Review一本[物联网](iot)书籍的目录,并提出建议。 也因此带来了其他更多的东西,当然不是这里的主题。在这里,我们就不讨论各种骚扰邮件,或者中文合作。从没有想象过,我也可以在英语世界有一片小天地。 这些告诉我们,GitHub上找一个你擅长的主题,那么会有很多人找上你的。 ### GitHub与成长 过去写过一篇《[如何通过github提升自己](http://www.phodal.com/blog/use-github-grow-self/)》的文章,现在只想说三点: 1. 测试 2. 更多的测试 3. 更多的、更多的、更多的测试 没有测试的项目是很扯淡的,除非你的项目只有一个函数,然后那个函数返回``Hello,World``。 如果你的项目代码有上千行,如果你能保证测试覆盖率可以达到95%以的话,那么我想你的项目不会有太复杂的函数。假使有这样的函数,那么它也是被测试覆盖住的。 如果你在用心做这个项目,那么你看到代码写得不好也会试着改进,即重构。当有了一些,你的技能会不断提升。你开始会试着接触更多的东西,如stub,如mock,如fakeserver。 有一天,你会发现你离不开测试。 然后就会相信: **那些没有写测试的项目都是在耍流氓** ## 为什么你应该深入GitHub 上面我们说的都是我们可以收获到的东西,我们开始尝试就意味着我们知道它可能给我们带来好处。上面已经提到很多可以提升自己的例子了,这里再说说其他的。 ### 方便工作 我们可以从中获取到不同的知识、内容、信息。每个人都可以从别人的代码中学习,当我们需要构建一个库的时候,我们可以在上面寻找不同的库和代码来实现我们的功能。如当我在实现一个库的时候,我会在GitHub上找到相应的组件: - Promise 支持 - Class类(ps:没有一个好的类使用的方式) - Template 一个简单的模板引擎 - Router 用来控制页面的路由 - Ajax 基本的Ajax Get/Post请求 ### 获得一份工作 越来越多的人因为GitHub获得工作,因为他们的做的东西正好符合一些公司的要求。那么,这些公司在寻找代码的时候,就会试着邀请他们。 因而,在GitHub寻找合适的候选人,已经是一种趋势。 ### 扩大交际 如果我们想创造出更好、强大地框架时,那么认识更多的人可能会带来更多的帮助。有时候会同上面那一点一样的效果 创建开源项目 === 人们创建 取一个好的名字 --- 挑选好 LICENSE --- 官方主页 --- 一个好的例子是 GitHub Pages 然后,创建 # Git基本知识与GitHub使用 ## Git 从一般开发者的角度来看,git有以下功能: 1. 从服务器上克隆数据库(包括代码和版本信息)到单机上。 2. 在自己的机器上创建分支,修改代码。 3. 在单机上自己创建的分支上提交代码。 4. 在单机上合并分支。 5. 新建一个分支,把服务器上最新版的代码fetch下来,然后跟自己的主分支合并。 6. 生成补丁(patch),把补丁发送给主开发者。 7. 看主开发者的反馈,如果主开发者发现两个一般开发者之间有冲突(他们之间可以合作解决的冲突),就会要求他们先解决冲突,然后再由其中一个人提交。如果主开发者可以自己解决,或者没有冲突,就通过。 8. 一般开发者之间解决冲突的方法,开发者之间可以使用pull 命令解决冲突,解决完冲突之后再向主开发者提交补丁。 从主开发者的角度(假设主开发者不用开发代码)看,git有以下功能: 1. 查看邮件或者通过其它方式查看一般开发者的提交状态。 2. 打上补丁,解决冲突(可以自己解决,也可以要求开发者之间解决以后再重新提交,如果是开源项目,还要决定哪些补丁有用,哪些不用)。 3. 向公共服务器提交结果,然后通知所有开发人员。 ### 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 ``` 来看现在的状态,如下图是添加之前的: ![Before add](./img/before-add.png) 下面是添加之后 的 ![After add](./img/after-add.png) 可以看到状态的变化是从黄色到绿色,即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](./img/github-roam-create.jpg) 就会有下面的提醒: ![GitHub Roam](./img/project-init.jpg) 它提供多种方式的创建方法: > …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的用户行为,现在我们先来说说GitHub上的Star吧。(截止: 2015年3月9日23时。) 用户 | 项目名 | Language | Star | Url -----|---------- |----------|------|---- twbs | Bootstrap | CSS | 78490 | [https://github.com/twbs/bootstrap](https://github.com/twbs/bootstrap) vhf |free-programming books | - | 37240 | [https://github.com/vhf/free-programming-books](https://github.com/vhf/free-programming-books) angular | angular.js | JavaScript | 36,061 | [https://github.com/angular/angular.js](https://github.com/angular/angular.js) mbostock | d3 | JavaScript | 35,257 | [https://github.com/mbostock/d3](https://github.com/mbostock/d3) joyent | node | JavaScript | 35,077 | [https://github.com/joyent/node](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 除了创建项目之外,我们也可以创建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提交的一个PR ![Google CLA](./img/google-cla.png) 以及Eclipse的一个PR ![Eclipse CLA](./img/eclipse-cla.png) 他们都要求我签署CLA。 # 构建GitHub项目 ## 如何用好GitHub 如何用好GitHub,并实践一些敏捷软件开发是一个很有意思的事情.我们可以在上面做很多事情,从测试到CI,再到自动部署. ###敏捷软件开发 显然我是在扯淡,这和敏捷软件开发没有什么关系。不过我也不知道瀑布流是怎样的。说说我所知道的一个项目的组成吧: - 看板式管理应用程序(如trello,简单地说就是管理软件功能) - CI(持续集成) - 测试覆盖率 - 代码质量(code smell) 对于一个不是远程的团队(如只有一个人的项目) 来说,Trello、Jenkin、Jira不是必需的: > 你存在,我深深的脑海里 当只有一个人的时候,你只需要明确知道自己想要什么就够了。我们还需要的是CI、测试,以来提升代码的质量。 ### 测试 通常我们都会找Document,如果没有的话,你会找什么?看源代码,还是看测试? ```javascript 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](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](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``示例: ```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](https://github.com/phodal/lettuce) 我们把这些集成到``README.md``之后,就有了之前那张图。 CI对于一个开发者在不同城市开发同一项目上来说是很重要的,这意味着当你添加的部分功能有测试覆盖的时候,项目代码会更加强壮。 ### 代码质量 像``jslint``这类的工具,只能保证代码在语法上是正确的,但是不能保证你写了一堆bad smell的代码。 - 重复代码 - 过长的函数 - 等等 ``Code Climate``是一个与github集成的工具,我们不仅仅可以看到测试覆盖率,还有代码质量。 先看看上面的ajax类: ```javascript 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](https://github.com/phodal/lettuce) 在[Code Climate](https://codeclimate.com/github/phodal/lettuce/src/ajax.js)在出现了一堆问题 - 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](https://github.com/phodal/lettuce)为例,里面有: - 代码质量(Code Climate) - CI状态(Travis CI) - 测试覆盖率(96%) - 自动化测试(npm test) - 文档 按照[Web Developer路线图](https://github.com/phodal/awesome-developer)来说,我们还需要有: - 版本管理 - 自动部署 等等。 ### 代码模块化 在SkillTree的源码里,大致分为三部分: - namespace函数: 顾名思义 - Calculator也就是TalentTree,主要负责解析、生成url,头像,依赖等等 - Skill 主要是tips部分。 而这一些都在一个js里,对于一个库来说,是一件好事,但是对于一个项目来说,并非如此。 依赖的库有 - jQuery - Knockout 好在Knockout可以用Require.js进行管理,于是,使用了``Require.js``进行管理: ```html ``` ``main.js``配置如下: ```javascript 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。 ```javascript define(['scripts/Doc'], function(Doc) { 'use strict'; function Book(_e) { Doc.apply(this, arguments); } Book.prototype = new Doc(); return Book; }); ``` 而这里便是后面对其进行重构的内容。Doc类则是Skillock中类的一个缩影 ```javascript 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类里面返回的是函数: ```javascript return { getSkillsByHash: getSkillsByHash, getSkillById: getSkillById, prettyJoin: prettyJoin }; ``` 当然函数也是一个对象。 ### 自动化测试 一直习惯用Travis CI,于是也继续用Travis Ci,``.travis.yml``配置如下所示: ```yml language: node_js node_js: - "0.10" notifications: email: false branches: only: - gh-pages ``` 使用gh-pages的原因是,我们一push代码的时候,就可以自动测试、部署等等,好处一堆堆的。 接着我们需要在``package.json``里面添加脚本 ```javascript "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的测试: ```javascript /* 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了) ```javascript 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 | 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 < >`` ```javascript 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 < > 与 ```javascript 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); ``` 只是这是之前修改过的重复。。 原来的代码是这样的 ```javascript 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)); }); }; ``` 说的也是大量的重复,重构完的代码 ```javascript 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.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 什么的。而开源应用、开源库需要有对应的 CHANELOG,则添加了什么功能、修改了什么等等。毕竟有很多东西是由社区来维护的。 因此,这里以做得比较好的开源项目 Angular 中为例展示。Angular 团队建议采用以下的形式: ``` ():