mirror of
https://github.com/phodal/github
synced 2026-05-24 09:38:21 +00:00
3038 lines
232 KiB
HTML
3038 lines
232 KiB
HTML
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="generator" content="pandoc">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||
<title>GitHub 漫游指南 – </title>
|
||
<style type="text/css">code{white-space: pre;}</style>
|
||
<style type="text/css">
|
||
div.sourceCode { overflow-x: auto; }
|
||
table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode {
|
||
margin: 0; padding: 0; vertical-align: baseline; border: none; }
|
||
table.sourceCode { width: 100%; line-height: 100%; }
|
||
td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; }
|
||
td.sourceCode { padding-left: 5px; }
|
||
code > span.kw { color: #007020; font-weight: bold; } /* Keyword */
|
||
code > span.dt { color: #902000; } /* DataType */
|
||
code > span.dv { color: #40a070; } /* DecVal */
|
||
code > span.bn { color: #40a070; } /* BaseN */
|
||
code > span.fl { color: #40a070; } /* Float */
|
||
code > span.ch { color: #4070a0; } /* Char */
|
||
code > span.st { color: #4070a0; } /* String */
|
||
code > span.co { color: #60a0b0; font-style: italic; } /* Comment */
|
||
code > span.ot { color: #007020; } /* Other */
|
||
code > span.al { color: #ff0000; font-weight: bold; } /* Alert */
|
||
code > span.fu { color: #06287e; } /* Function */
|
||
code > span.er { color: #ff0000; font-weight: bold; } /* Error */
|
||
code > span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
|
||
code > span.cn { color: #880000; } /* Constant */
|
||
code > span.sc { color: #4070a0; } /* SpecialChar */
|
||
code > span.vs { color: #4070a0; } /* VerbatimString */
|
||
code > span.ss { color: #bb6688; } /* SpecialString */
|
||
code > span.im { } /* Import */
|
||
code > span.va { color: #19177c; } /* Variable */
|
||
code > span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
|
||
code > span.op { color: #666666; } /* Operator */
|
||
code > span.bu { } /* BuiltIn */
|
||
code > span.ex { } /* Extension */
|
||
code > span.pp { color: #bc7a00; } /* Preprocessor */
|
||
code > span.at { color: #7d9029; } /* Attribute */
|
||
code > span.do { color: #ba2121; font-style: italic; } /* Documentation */
|
||
code > span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
|
||
code > span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
|
||
code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
|
||
</style>
|
||
<link rel="stylesheet" href="style.css">
|
||
<!--[if lt IE 9]>
|
||
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
|
||
<![endif]-->
|
||
<meta name="viewport" content="width=device-width">
|
||
</head>
|
||
<body>
|
||
<p>
|
||
<h1>GitHub 漫游指南</h1>
|
||
<p>项目首页: <a href="https://github.com/phodal/github-roam">GitHub 漫游指南</a></p>
|
||
<p>By <a href="https://www.phodal.com">Phodal Huang</a>(微博、知乎、GitHub、SegmentFault: @<a href="http://weibo.com/phodal">phodal</a>)
|
||
</p>
|
||
<p>微信公众号</p>
|
||
<img src="./img/qrcode.jpg" alt=""/>
|
||
</p>
|
||
<div style="width:800px">
|
||
|
||
<iframe src="http://ghbtns.com/github-btn.html?user=phodal&repo=github-roam&type=watch&count=true"
|
||
allowtransparency="true" frameborder="0" scrolling="0" width="110px" height="20px"></iframe>
|
||
<div>
|
||
<nav id="TOC">
|
||
<ul>
|
||
<li><a href="#前言">前言</a><ul>
|
||
<li><a href="#我与github的故事">我与GitHub的故事</a><ul>
|
||
<li><a href="#github与收获">GitHub与收获</a></li>
|
||
<li><a href="#github与成长">GitHub与成长</a></li>
|
||
</ul></li>
|
||
<li><a href="#为什么你应该深入github">为什么你应该深入GitHub</a><ul>
|
||
<li><a href="#方便工作">方便工作</a></li>
|
||
<li><a href="#获得一份工作">获得一份工作</a></li>
|
||
<li><a href="#扩大交际">扩大交际</a></li>
|
||
</ul></li>
|
||
</ul></li>
|
||
<li><a href="#创建开源项目">创建开源项目</a><ul>
|
||
<li><a href="#取一个好的名字">取一个好的名字</a></li>
|
||
<li><a href="#挑选好-license">挑选好 LICENSE</a><ul>
|
||
<li><a href="#公有领域">公有领域</a></li>
|
||
<li><a href="#gpl">GPL</a></li>
|
||
<li><a href="#mit">MIT</a></li>
|
||
<li><a href="#creative-commons">Creative Commons</a></li>
|
||
</ul></li>
|
||
</ul></li>
|
||
<li><a href="#git基本知识与github使用">Git基本知识与GitHub使用</a><ul>
|
||
<li><a href="#git">Git</a><ul>
|
||
<li><a href="#git初入">Git初入</a></li>
|
||
</ul></li>
|
||
<li><a href="#github">GitHub</a><ul>
|
||
<li><a href="#版本管理与软件部署">版本管理与软件部署</a></li>
|
||
<li><a href="#github与git">GitHub与Git</a></li>
|
||
<li><a href="#在github创建项目">在GitHub创建项目</a></li>
|
||
</ul></li>
|
||
<li><a href="#github流行项目分析">GitHub流行项目分析</a></li>
|
||
<li><a href="#pull-request">Pull Request</a><ul>
|
||
<li><a href="#我的第一个pr">我的第一个PR</a></li>
|
||
<li><a href="#cla">CLA</a></li>
|
||
</ul></li>
|
||
</ul></li>
|
||
<li><a href="#构建-github-项目">构建 GitHub 项目</a><ul>
|
||
<li><a href="#如何用好-github">如何用好 GitHub</a><ul>
|
||
<li><a href="#敏捷软件开发">敏捷软件开发</a></li>
|
||
<li><a href="#测试">测试</a></li>
|
||
<li><a href="#ci">CI</a></li>
|
||
<li><a href="#代码质量">代码质量</a></li>
|
||
</ul></li>
|
||
<li><a href="#模块分离与测试">模块分离与测试</a><ul>
|
||
<li><a href="#代码模块化">代码模块化</a></li>
|
||
<li><a href="#自动化测试">自动化测试</a></li>
|
||
<li><a href="#jshint">Jshint</a></li>
|
||
<li><a href="#mocha">Mocha</a></li>
|
||
<li><a href="#测试示例">测试示例</a></li>
|
||
</ul></li>
|
||
<li><a href="#代码质量与重构">代码质量与重构</a><ul>
|
||
<li><a href="#code-climate">Code Climate</a></li>
|
||
<li><a href="#代码的坏味道">代码的坏味道</a></li>
|
||
</ul></li>
|
||
</ul></li>
|
||
<li><a href="#git-提交信息及几种不同的规范">Git 提交信息及几种不同的规范</a><ul>
|
||
<li><a href="#工作写法">工作写法</a></li>
|
||
<li><a href="#常规写法">常规写法</a></li>
|
||
<li><a href="#开源应用开源库写法">开源应用、开源库写法</a></li>
|
||
</ul></li>
|
||
<li><a href="#创建项目文档">创建项目文档</a><ul>
|
||
<li><a href="#readme">README</a></li>
|
||
<li><a href="#官方首页与在线文档">官方首页与在线文档</a></li>
|
||
<li><a href="#可用示例">可用示例</a></li>
|
||
</ul></li>
|
||
<li><a href="#改善-github-项目代码质量重构">改善 GitHub 项目代码质量:重构</a><ul>
|
||
<li><a href="#为什么重构">为什么重构?</a></li>
|
||
<li><a href="#重构umarkdown">重构uMarkdown</a><ul>
|
||
<li><a href="#代码说明">代码说明</a></li>
|
||
</ul></li>
|
||
<li><a href="#intellij-idea重构">Intellij Idea重构</a><ul>
|
||
<li><a href="#rename">Rename</a></li>
|
||
<li><a href="#extract-method">Extract Method</a></li>
|
||
<li><a href="#inline-method">Inline Method</a></li>
|
||
<li><a href="#pull-members-up">Pull Members Up</a></li>
|
||
<li><a href="#重构之以查询取代临时变量">重构之以查询取代临时变量</a></li>
|
||
</ul></li>
|
||
</ul></li>
|
||
<li><a href="#改善-github-项目代码质量测试">改善 GitHub 项目代码质量:测试</a><ul>
|
||
<li><a href="#tdd">TDD</a><ul>
|
||
<li><a href="#一次测试驱动开发">一次测试驱动开发</a></li>
|
||
<li><a href="#说说tdd">说说TDD</a></li>
|
||
<li><a href="#tdd思考">TDD思考</a></li>
|
||
</ul></li>
|
||
<li><a href="#功能测试">功能测试</a><ul>
|
||
<li><a href="#轻量级网站测试twill">轻量级网站测试TWill</a></li>
|
||
<li><a href="#twill-登陆测试">Twill 登陆测试</a></li>
|
||
<li><a href="#twill-测试脚本">Twill 测试脚本</a></li>
|
||
</ul></li>
|
||
<li><a href="#fake-server">Fake Server</a></li>
|
||
</ul></li>
|
||
<li><a href="#如何推广">如何推广</a><ul>
|
||
<li><a href="#marketing-first">Marketing First</a></li>
|
||
<li><a href="#编写一个好的-readme">编写一个好的 README</a><ul>
|
||
<li><a href="#这个项目做什么一句话文案">这个项目做什么——一句话文案</a></li>
|
||
<li><a href="#它解决了什么问题">它解决了什么问题</a></li>
|
||
<li><a href="#它有什么特性">它有什么特性</a></li>
|
||
<li><a href="#安装及hello-world-示例">安装及hello, world 示例</a></li>
|
||
</ul></li>
|
||
<li><a href="#技术文档">技术文档</a><ul>
|
||
<li><a href="#技术文档-1">技术文档</a></li>
|
||
<li><a href="#更多的示例程序">更多的示例程序</a></li>
|
||
<li><a href="#编写技术文章书籍">编写技术文章、书籍</a></li>
|
||
</ul></li>
|
||
<li><a href="#鼓励吸引贡献者">鼓励、吸引贡献者</a></li>
|
||
</ul></li>
|
||
<li><a href="#开源项目维护">开源项目维护</a><ul>
|
||
<li><a href="#release">Release</a></li>
|
||
</ul></li>
|
||
<li><a href="#git-与-github-工具推荐">Git 与 GitHub 工具推荐</a><ul>
|
||
<li><a href="#git-命令行增强">Git 命令行增强</a><ul>
|
||
<li><a href="#diff-so-fancy"><a href="https://github.com/so-fancy/diff-so-fancy">diff-so-fancy</a></a></li>
|
||
<li><a href="#git-extras"><a href="https://github.com/tj/git-extras">git-extras</a></a></li>
|
||
</ul></li>
|
||
<li><a href="#intellij-idea">Intellij IDEA</a></li>
|
||
<li><a href="#gitgithub桌面增强">Git、GitHub桌面增强</a><ul>
|
||
<li><a href="#sourcetree">SourceTree</a></li>
|
||
<li><a href="#github-desktop">GitHub Desktop</a></li>
|
||
</ul></li>
|
||
<li><a href="#git-娱乐">Git 娱乐</a><ul>
|
||
<li><a href="#githug">githug</a></li>
|
||
<li><a href="#gource">Gource</a></li>
|
||
</ul></li>
|
||
</ul></li>
|
||
<li><a href="#github用户分析">GitHub用户分析</a><ul>
|
||
<li><a href="#生成图表">生成图表</a><ul>
|
||
<li><a href="#数据解析">数据解析</a></li>
|
||
<li><a href="#matplotlib">Matplotlib</a></li>
|
||
</ul></li>
|
||
<li><a href="#每周分析">每周分析</a><ul>
|
||
<li><a href="#python-github-每周情况分析">python github 每周情况分析</a></li>
|
||
<li><a href="#python-数据分析">Python 数据分析</a></li>
|
||
<li><a href="#python-matplotlib图表">Python Matplotlib图表</a></li>
|
||
</ul></li>
|
||
<li><a href="#存储到数据库中">存储到数据库中</a><ul>
|
||
<li><a href="#sqlite3">SQLite3</a></li>
|
||
<li><a href="#数据导入">数据导入</a></li>
|
||
<li><a href="#redis">Redis</a></li>
|
||
</ul></li>
|
||
<li><a href="#邻近算法与相似用户">邻近算法与相似用户</a></li>
|
||
</ul></li>
|
||
<li><a href="#如何在github寻找灵感fork">如何在GitHub“寻找灵感(fork)”</a><ul>
|
||
<li><a href="#lettuce构建过程">Lettuce构建过程</a><ul>
|
||
<li><a href="#需求">需求</a></li>
|
||
<li><a href="#计划">计划</a></li>
|
||
<li><a href="#实现第一个需求">实现第一个需求</a></li>
|
||
<li><a href="#实现第二个需求">实现第二个需求</a></li>
|
||
</ul></li>
|
||
</ul></li>
|
||
<li><a href="#如何以正确的姿势阅读开源软件代码">如何以“正确的姿势”阅读开源软件代码</a><ul>
|
||
<li><a href="#阅读过程">阅读过程</a></li>
|
||
<li><a href="#示例">示例</a></li>
|
||
</ul></li>
|
||
<li><a href="#github连击">GitHub连击</a><ul>
|
||
<li><a href="#天">100天</a><ul>
|
||
<li><a href="#天的提升">40天的提升</a></li>
|
||
<li><a href="#天的挑战">100天的挑战</a></li>
|
||
<li><a href="#天的希冀">140天的希冀</a></li>
|
||
</ul></li>
|
||
<li><a href="#天的showcase">200天的Showcase</a><ul>
|
||
<li><a href="#一些项目简述">一些项目简述</a></li>
|
||
<li><a href="#google-map-solr-polygon-搜索">google map solr polygon 搜索</a></li>
|
||
<li><a href="#技能树">技能树</a></li>
|
||
</ul></li>
|
||
<li><a href="#天-1">365天</a><ul>
|
||
<li><a href="#编程的基础能力">编程的基础能力</a></li>
|
||
<li><a href="#技术与框架设计">技术与框架设计</a></li>
|
||
<li><a href="#领域与练习">领域与练习</a></li>
|
||
<li><a href="#其他-1">其他</a></li>
|
||
</ul></li>
|
||
<li><a href="#天-2">500天</a><ul>
|
||
<li><a href="#天与10000小时">500天与10000小时</a></li>
|
||
<li><a href="#编程的情绪周期">编程的情绪周期</a></li>
|
||
<li><a href="#有意图的练习">有意图的练习</a></li>
|
||
<li><a href="#预见性练习">预见性练习</a></li>
|
||
<li><a href="#小结">小结</a></li>
|
||
</ul></li>
|
||
<li><a href="#天里">365*2-7天里</a><ul>
|
||
<li><a href="#编码的练习">编码的练习</a></li>
|
||
<li><a href="#see-you-again">See you Again</a></li>
|
||
</ul></li>
|
||
</ul></li>
|
||
<li><a href="#github-里程碑">GitHub 里程碑</a><ul>
|
||
<li><a href="#写在github-的第-19999-个-star-时">写在GitHub 的第 19999 个 star 时</a><ul>
|
||
<li><a href="#从创建开源框架说起">从创建开源框架说起</a></li>
|
||
<li><a href="#下一个开源项目">下一个开源项目</a></li>
|
||
</ul></li>
|
||
</ul></li>
|
||
</ul>
|
||
</nav>
|
||
<h1 id="前言">前言</h1>
|
||
<p>我的GitHub主页上写着加入的时间——<code>Joined on Nov 8, 2010</code>,那时才大一,在那之后的那么长的日子里我都没有登录过。也许是因为我学的不是计算机,到了今天——<code>2015.3.9</code>,我才发现这其实是程序员的社交网站。</p>
|
||
<p>过去,曾经有很长的一些时间我试过在GitHub上连击,也试着去了解别人是如何用好这个工具的。当然粉丝在GitHub上也是很重要的。</p>
|
||
<p>在这里,我会试着将我在GitHub上学到的东西一一分享出来。</p>
|
||
<h2 id="我与github的故事">我与GitHub的故事</h2>
|
||
<p>在我大四找工作的时候,试图去寻找一份硬件、物联网相关的工作(ps: 专业是电子信息工程)。尽管简历上写得满满的各种经历、经验,然而并没有卵用。跑了几场校园招聘会后,十份简历(ps: 事先已经有心里准备)一个也没有投出去——因为学校直接被拒。我对霸面什么的一点兴趣都没有,千里马需要伯乐。后来,我加入了<a href="https://martinfowler.com/">Martin Flower</a>所在的公司,当然这是后话了。</p>
|
||
<p>这是一个残酷的世界,在学生时代,如果你长得不帅不高的话,那么多数的附加技能都是白搭(ps: 通常富的是看不到这篇文章的)。在工作时期,如果你上家没有名气,那么将会影响你下一份工作的待遇。而,很多东西却可以改变这些,GitHub就是其中一个。</p>
|
||
<p>注册GitHub的时候大概是大一的时候,我熟悉的时候已经是大四了,现在已经毕业一年了。在过去的近两年里,我试着以几个维度在GitHub上创建项目:</p>
|
||
<ol type="1">
|
||
<li>快速上手框架来实战,即demo</li>
|
||
<li>重构别人的代码</li>
|
||
<li>创建自己可用的框架</li>
|
||
<li>快速构建大型应用</li>
|
||
<li>构建通用的框架</li>
|
||
</ol>
|
||
<h3 id="github与收获">GitHub与收获</h3>
|
||
<p>先说说<strong>与技能无关的收获</strong>吧,毕业设计做的是一个《<a href="https://github.com/phodal/iot">最小物联网系统</a>》,考虑到我们专业老师没有这方面知识,答辩时会带来问题,尽量往这方面靠拢。当我毕业后,这个项目已经有过百个star了,这样易上手的东西还是比较受欢迎的(ps: 不过这种硬件相关的项目通常受限于GitHub上硬件开发工程师比较少的困扰)。</p>
|
||
<p>毕业后一个月收到PACKT出版社的邮件(ps: 他们是在github上找到我的),内容是关于Review一本<a href="iot">物联网</a>书籍,即在《<a href="http://www.phodal.com/blog/review-it-books-with-translate-book/">从Review到翻译IT书籍</a>》中提到的《Learning Internet of Things》。作为一个四级没过的“物联网专家”,去审阅一本英文的物联网书籍。。。</p>
|
||
<p>当然,后来是审阅完了,书上有我的英文简介。</p>
|
||
<figure>
|
||
<img src="./img/phodal-intro.jpg" alt="Phodal Huang Introduction" /><figcaption>Phodal Huang Introduction</figcaption>
|
||
</figure>
|
||
<p>一个月前,收到MANNING出版社的邮件(ps: 也是在github上),关于Review一本<a href="iot">物联网</a>书籍的目录,并提出建议。</p>
|
||
<p>也因此带来了其他更多的东西,当然不是这里的主题。在这里,我们就不讨论各种骚扰邮件,或者中文合作。从没有想象过,我也可以在英语世界有一片小天地。</p>
|
||
<p>这些告诉我们,GitHub上找一个你擅长的主题,那么会有很多人找上你的。</p>
|
||
<h3 id="github与成长">GitHub与成长</h3>
|
||
<p>过去写过一篇《<a href="http://www.phodal.com/blog/use-github-grow-self/">如何通过github提升自己</a>》的文章,现在只想说三点:</p>
|
||
<ol type="1">
|
||
<li>测试</li>
|
||
<li>更多的测试</li>
|
||
<li>更多的、更多的、更多的测试</li>
|
||
</ol>
|
||
<p>没有测试的项目是很扯淡的,除非你的项目只有一个函数,然后那个函数返回<code>Hello,World</code>。</p>
|
||
<p>如果你的项目代码有上千行,如果你能保证测试覆盖率可以达到95%以的话,那么我想你的项目不会有太复杂的函数。假使有这样的函数,那么它也是被测试覆盖住的。</p>
|
||
<p>如果你在用心做这个项目,那么你看到代码写得不好也会试着改进,即重构。当有了一些,你的技能会不断提升。你开始会试着接触更多的东西,如stub,如mock,如fakeserver。</p>
|
||
<p>有一天,你会发现你离不开测试。</p>
|
||
<p>然后就会相信: <strong>那些没有写测试的项目都是在耍流氓</strong></p>
|
||
<h2 id="为什么你应该深入github">为什么你应该深入GitHub</h2>
|
||
<p>上面我们说的都是我们可以收获到的东西,我们开始尝试就意味着我们知道它可能给我们带来好处。上面已经提到很多可以提升自己的例子了,这里再说说其他的。</p>
|
||
<h3 id="方便工作">方便工作</h3>
|
||
<p>我们可以从中获取到不同的知识、内容、信息。每个人都可以从别人的代码中学习,当我们需要构建一个库的时候,我们可以在上面寻找不同的库和代码来实现我们的功能。如当我在实现一个库的时候,我会在GitHub上找到相应的组件:</p>
|
||
<ul>
|
||
<li>Promise 支持</li>
|
||
<li>Class类(ps:没有一个好的类使用的方式)</li>
|
||
<li>Template 一个简单的模板引擎</li>
|
||
<li>Router 用来控制页面的路由</li>
|
||
<li>Ajax 基本的Ajax Get/Post请求</li>
|
||
</ul>
|
||
<h3 id="获得一份工作">获得一份工作</h3>
|
||
<p>越来越多的人因为GitHub获得工作,因为他们的做的东西正好符合一些公司的要求。那么,这些公司在寻找代码的时候,就会试着邀请他们。</p>
|
||
<p>因而,在GitHub寻找合适的候选人,已经是一种趋势。</p>
|
||
<h3 id="扩大交际">扩大交际</h3>
|
||
<p>如果我们想创造出更好、强大地框架时,那么认识更多的人可能会带来更多的帮助。有时候会同上面那一点一样的效果</p>
|
||
<h1 id="创建开源项目">创建开源项目</h1>
|
||
<p>人们出于不同的目的来创建开源项目,可不论目的是什么,过程都是一样的。</p>
|
||
<ol type="1">
|
||
<li>首先,我们需要为我们的项目取一个名字。</li>
|
||
<li>然后,为我们的开源项目选择一个合适的 LICENSE</li>
|
||
<li>然后再去创建项目</li>
|
||
</ol>
|
||
<h2 id="取一个好的名字">取一个好的名字</h2>
|
||
<p>取名字,从来就不是一件容易的事。</p>
|
||
<p>因此,我就长话短说,一般就是取一个有意义的名字,当然没有意义也没有任何问题。</p>
|
||
<p>通常而言,如果自己计划有一系列的开源项目,那么我们可以保持一定的命令规则。</p>
|
||
<h2 id="挑选好-license">挑选好 LICENSE</h2>
|
||
<blockquote>
|
||
<p>在二十世纪而七十年代末和八十年代初,为了防止自己的软件被竞争对手所使用,大多数厂家停止分发其软件源代码,并开始使用版权和限制性软件许可证,来限制或者禁止软件源代码的复制或再分配。随后,Richard Matthew Stallman(Richard Matthew Stallman)发起了自由软件运动,他开创了 Copyleft 的概念:使用版权法的原则来保护使用、修改和分发自由软件的权利,并且是描述这些术语的自由软件许可证的主要作者。最为人所称道的是GPL(被z广泛使用的自由软件协议)。<a href="#fn1" class="footnoteRef" id="fnref1"><sup>1</sup></a></p>
|
||
</blockquote>
|
||
<p>(PS:关于自由软件及 RMS 的更多信息、历史,可以阅读《若为自由故:自由软件之父 - 理查德 斯托曼传》)</p>
|
||
<p>随后,便诞生了开源软件的概念,开源的要求比自由软件宽松一些<a href="#fn2" class="footnoteRef" id="fnref2"><sup>2</sup></a>。迄今发布的自由软件源代码都是开源软件,而并非所有的开源软件都是自由软件。这是因为不同的许可(协议)赋予用户不同的权利,如 GPL 协议强制要求开源修改过源码的代码,而宽松一点的 MIT 则不会有这种要求。</p>
|
||
<p>如下是不同开源许可证的市场占有率及使用情况。</p>
|
||
<figure>
|
||
<img src="./img/permissive-vs-copylift-license-2.jpg" alt="License 使用情况" /><figcaption>License 使用情况</figcaption>
|
||
</figure>
|
||
<p>又比如,在我们看到的一些外版书籍上,如果拥有代码。那么作者一般就会在前言或者类似的位置里,指明书中代码的版权所属。如:<code>也许你需要在自己的程序或文档中用到本书的代码,但除非大篇幅地使用,否则不必与我们联系取得授权。例如,用本书中的几段代码编写程序无需请求许可,blabla</code>。</p>
|
||
<p>于是,选择一个合理的 LICENSE,就变成了一个有趣的话题。为此,笔者做了一个如何进行开源协议选型的流程图:</p>
|
||
<p><a href="https://github.com/phodal/licenses"><img src="./img/licenses.png" alt="如何选择 License" /></a></p>
|
||
<p>简单地来说,这些 License 之间是一些权利的区别,如当你把代码放置到公有领域,就意味着任何人可以修改,并且不需要标明出注;可如果你想要别人标明出处及作者,你就需要 MIT 协议;而你希望别人闭源的话,那么你就需要 MPL 协议等等。</p>
|
||
<p>那么,下面让我们简单地介绍一下不同的几个协议。</p>
|
||
<h3 id="公有领域">公有领域</h3>
|
||
<blockquote>
|
||
<p>WTFPL(Do What The Fuck You Want To Public License,中文译名:你他妈的想干嘛就干嘛公共许可证)是一种不太常用的、极度放任的自由软件许可证。它的条款基本等同于贡献到公有领域。<a href="#fn3" class="footnoteRef" id="fnref3"><sup>3</sup></a></p>
|
||
</blockquote>
|
||
<p>这就意味着,对于拿到这些代码的其他人,他们想怎么修改就可以怎么修改。</p>
|
||
<p>这取决于</p>
|
||
<h3 id="gpl">GPL</h3>
|
||
<p>由于 GPL 的传染性,便意味着,他人引用我们的代码时,其所写的代码也需要使用 GPL 开源。即:GPL 是有 “传染性” 的 “病毒” ,因为 GPL 条款规定演绎作品也必须是 GPL 的。</p>
|
||
<p>而如果我们只针对的是,他人可以使用库,而不开源,则可以用 LGPL。但是修改库则不适用。</p>
|
||
<h3 id="mit">MIT</h3>
|
||
<p>因此,一般而言,我使用的是 MIT 协议。至少我保留了一个署名权,即你可以修改我的代码,但是在 LICENSE 里必须加上我的名字。</p>
|
||
<p>选用 MIT 特别有意思,特别是在最近几年里,发生过:</p>
|
||
<ul>
|
||
<li><a href="https://zhuanlan.zhihu.com/p/25739512">iView “抄袭” Element UI 事件</a></li>
|
||
<li><a href="https://github.com/JackyAndroid/AndroidTVLauncher/issues/22">AndroidTVLauncher “抄袭” 事件</a></li>
|
||
</ul>
|
||
<p>等等。这告诫了我们,如果你不想要有这种经历,那么就不要用 MIT 了。</p>
|
||
<h3 id="creative-commons">Creative Commons</h3>
|
||
<p>是的,当我写 Markdown 的时候,考虑到未来会以纸质书的形式出现,便会使用 CC-BY-NC-ND 协议:</p>
|
||
<ul>
|
||
<li>CC -> Creative Commons</li>
|
||
<li>BY -> 署名(英语:Attribution,by)</li>
|
||
<li>NC -> 非商业性使用(英语:NonCommercial)</li>
|
||
<li>ND -> 禁止演绎(英语:NoDerivs)。</li>
|
||
</ul>
|
||
<p>即,任何人可以使用我写的电子书来自由复制、散布、展示及演出,但是不得用于商业用途(作者本人可以)。它可以随意地放在他的博客上,他的各个文章里。但是必须标明出自,并且不得改变、转变或更改本作品。</p>
|
||
<p>如果你不介意的话,你可以使用公有领域(Public Domain)。可是这样一来,万一有一天,别人直接拿的作品出书,你就骂爹了。</p>
|
||
<h1 id="git基本知识与github使用">Git基本知识与GitHub使用</h1>
|
||
<h2 id="git">Git</h2>
|
||
<p>从一般开发者的角度来看,git有以下功能:</p>
|
||
<ol type="1">
|
||
<li>从服务器上克隆数据库(包括代码和版本信息)到单机上。</li>
|
||
<li>在自己的机器上创建分支,修改代码。</li>
|
||
<li>在单机上自己创建的分支上提交代码。</li>
|
||
<li>在单机上合并分支。</li>
|
||
<li>新建一个分支,把服务器上最新版的代码fetch下来,然后跟自己的主分支合并。</li>
|
||
<li>生成补丁(patch),把补丁发送给主开发者。</li>
|
||
<li>看主开发者的反馈,如果主开发者发现两个一般开发者之间有冲突(他们之间可以合作解决的冲突),就会要求他们先解决冲突,然后再由其中一个人提交。如果主开发者可以自己解决,或者没有冲突,就通过。</li>
|
||
<li>一般开发者之间解决冲突的方法,开发者之间可以使用pull 命令解决冲突,解决完冲突之后再向主开发者提交补丁。</li>
|
||
</ol>
|
||
<p>从主开发者的角度(假设主开发者不用开发代码)看,git有以下功能:</p>
|
||
<ol type="1">
|
||
<li>查看邮件或者通过其它方式查看一般开发者的提交状态。</li>
|
||
<li>打上补丁,解决冲突(可以自己解决,也可以要求开发者之间解决以后再重新提交,如果是开源项目,还要决定哪些补丁有用,哪些不用)。</li>
|
||
<li>向公共服务器提交结果,然后通知所有开发人员。</li>
|
||
</ol>
|
||
<h3 id="git初入">Git初入</h3>
|
||
<p>如果是第一次使用Git,你需要设置署名和邮箱:</p>
|
||
<pre><code>$ git config --global user.name "用户名"
|
||
$ git config --global user.email "电子邮箱"</code></pre>
|
||
<p>将代码仓库clone到本地,其实就是将代码复制到你的机器里,并交由Git来管理:</p>
|
||
<pre><code>$ git clone git@github.com:someone/symfony-docs-chs.git</code></pre>
|
||
<p>你可以修改复制到本地的代码了(symfony-docs-chs项目里都是rst格式的文档)。当你觉得完成了一定的工作量,想做个阶段性的提交:</p>
|
||
<p>向这个本地的代码仓库添加当前目录的所有改动:</p>
|
||
<pre><code>$ git add .</code></pre>
|
||
<p>或者只是添加某个文件:</p>
|
||
<pre><code>$ git add -p</code></pre>
|
||
<p>我们可以输入</p>
|
||
<pre><code>$git status</code></pre>
|
||
<p>来看现在的状态,如下图是添加之前的:</p>
|
||
<figure>
|
||
<img src="./img/before-add.png" alt="Before add" /><figcaption>Before add</figcaption>
|
||
</figure>
|
||
<p>下面是添加之后 的</p>
|
||
<figure>
|
||
<img src="./img/after-add.png" alt="After add" /><figcaption>After add</figcaption>
|
||
</figure>
|
||
<p>可以看到状态的变化是从黄色到绿色,即unstage到add。</p>
|
||
<h2 id="github">GitHub</h2>
|
||
<p>Wiki百科上是这么说的</p>
|
||
<blockquote>
|
||
<p>GitHub 是一个共享虚拟主机服务,用于存放使用Git版本控制的软件代码和内容项目。它由GitHub公司(曾称Logical Awesome)的开发者Chris Wanstrath、PJ Hyett和Tom Preston-Werner 使用Ruby on Rails编写而成。</p>
|
||
</blockquote>
|
||
<p>当然让我们看看官方的介绍:</p>
|
||
<blockquote>
|
||
<p>GitHub is the best place to share code with friends, co-workers, classmates, and complete strangers. Over eight million people use GitHub to build amazing things together.</p>
|
||
</blockquote>
|
||
<p>它还是什么?</p>
|
||
<ul>
|
||
<li>网站</li>
|
||
<li>免费博客</li>
|
||
<li>管理配置文件</li>
|
||
<li>收集资料</li>
|
||
<li>简历</li>
|
||
<li>管理代码片段</li>
|
||
<li>托管编程环境</li>
|
||
<li>写作</li>
|
||
</ul>
|
||
<p>等等。看上去像是大餐,但是你还需要了解点什么?</p>
|
||
<h3 id="版本管理与软件部署">版本管理与软件部署</h3>
|
||
<p>jQuery[^jQuery]在发布版本<code>2.1.3</code>,一共有152个commit。我们可以看到如下的提交信息:</p>
|
||
<ul>
|
||
<li>Ajax: Always use script injection in globalEval … bbdfbb4</li>
|
||
<li>Effects: Reintroduce use of requestAnimationFrame … 72119e0</li>
|
||
<li>Effects: Improve raf logic … 708764f</li>
|
||
<li>Build: Move test to appropriate module fbdbb6f</li>
|
||
<li>Build: Update commitplease dev dependency</li>
|
||
<li>…</li>
|
||
</ul>
|
||
<h3 id="github与git">GitHub与Git</h3>
|
||
<blockquote>
|
||
<p>Git是一个分布式的版本控制系统,最初由Linus Torvalds编写,用作Linux内核代码的管理。在推出后,Git在其它项目中也取得了很大成功,尤其是在Ruby社区中。目前,包括Rubinius、Merb和Bitcoin在内的很多知名项目都使用了Git。Git同样可以被诸如Capistrano和Vlad the Deployer这样的部署工具所使用。</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>GitHub可以托管各种git库,并提供一个web界面,但与其它像 SourceForge或Google Code这样的服务不同,GitHub的独特卖点在于从另外一个项目进行分支的简易性。为一个项目贡献代码非常简单:首先点击项目站点的“fork”的按钮,然后将代码检出并将修改加入到刚才分出的代码库中,最后通过内建的“pull request”机制向项目负责人申请代码合并。已经有人将GitHub称为代码玩家的MySpace。</p>
|
||
</blockquote>
|
||
<h3 id="在github创建项目">在GitHub创建项目</h3>
|
||
<p>接着,我们试试在上面创建一个项目:</p>
|
||
<figure>
|
||
<img src="./img/github-roam-create.jpg" alt="GitHub Roam" /><figcaption>GitHub Roam</figcaption>
|
||
</figure>
|
||
<p>就会有下面的提醒:</p>
|
||
<figure>
|
||
<img src="./img/project-init.jpg" alt="GitHub Roam" /><figcaption>GitHub Roam</figcaption>
|
||
</figure>
|
||
<p>它提供多种方式的创建方法:</p>
|
||
<blockquote>
|
||
<p>…or create a new repository on the command line</p>
|
||
</blockquote>
|
||
<pre><code>echo "# github-roam" >> README.md
|
||
git init
|
||
git add README.md
|
||
git commit -m "first commit"
|
||
git remote add origin git@github.com:phodal/github-roam.git
|
||
git push -u origin master</code></pre>
|
||
<blockquote>
|
||
<p>…or push an existing repository from the command line</p>
|
||
</blockquote>
|
||
<pre><code>git remote add origin git@github.com:phodal/github-roam.git
|
||
git push -u origin master</code></pre>
|
||
<p>如果你完成了上面的步骤之后,那么我想你想知道你需要怎样的项目。</p>
|
||
<h2 id="github流行项目分析">GitHub流行项目分析</h2>
|
||
<p>之前曾经分析过一些GitHub的用户行为,现在我们先来说说GitHub上的Star吧。(截止: 2015年3月9日23时。)</p>
|
||
<table style="width:56%;">
|
||
<colgroup>
|
||
<col style="width: 8%" />
|
||
<col style="width: 15%" />
|
||
<col style="width: 15%" />
|
||
<col style="width: 9%" />
|
||
<col style="width: 6%" />
|
||
</colgroup>
|
||
<thead>
|
||
<tr class="header">
|
||
<th>用户</th>
|
||
<th>项目名</th>
|
||
<th>Language</th>
|
||
<th>Star</th>
|
||
<th>Url</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="odd">
|
||
<td>twbs</td>
|
||
<td>Bootstrap</td>
|
||
<td>CSS</td>
|
||
<td>78490</td>
|
||
<td><a href="https://github.com/twbs/bootstrap" class="uri">https://github.com/twbs/bootstrap</a></td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td>vhf</td>
|
||
<td>free-programming books</td>
|
||
<td>-</td>
|
||
<td>37240</td>
|
||
<td><a href="https://github.com/vhf/free-programming-books" class="uri">https://github.com/vhf/free-programming-books</a></td>
|
||
</tr>
|
||
<tr class="odd">
|
||
<td>angular</td>
|
||
<td>angular.js</td>
|
||
<td>JavaScript</td>
|
||
<td>36,061</td>
|
||
<td><a href="https://github.com/angular/angular.js" class="uri">https://github.com/angular/angular.js</a></td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td>mbostock</td>
|
||
<td>d3</td>
|
||
<td>JavaScript</td>
|
||
<td>35,257</td>
|
||
<td><a href="https://github.com/mbostock/d3" class="uri">https://github.com/mbostock/d3</a></td>
|
||
</tr>
|
||
<tr class="odd">
|
||
<td>joyent</td>
|
||
<td>node</td>
|
||
<td>JavaScript</td>
|
||
<td>35,077</td>
|
||
<td><a href="https://github.com/joyent/node" class="uri">https://github.com/joyent/node</a></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>上面列出来的是前5的,看看大于1万个stars的项目的分布,一共有82个:</p>
|
||
<table>
|
||
<thead>
|
||
<tr class="header">
|
||
<th>语言</th>
|
||
<th>项目数</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="odd">
|
||
<td>JavaScript</td>
|
||
<td>37</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td>Ruby</td>
|
||
<td>6</td>
|
||
</tr>
|
||
<tr class="odd">
|
||
<td>CSS</td>
|
||
<td>6</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td>Python</td>
|
||
<td>4</td>
|
||
</tr>
|
||
<tr class="odd">
|
||
<td>HTML</td>
|
||
<td>3</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td>C++</td>
|
||
<td>3</td>
|
||
</tr>
|
||
<tr class="odd">
|
||
<td>VimL</td>
|
||
<td>2</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td>Shell</td>
|
||
<td>2</td>
|
||
</tr>
|
||
<tr class="odd">
|
||
<td>Go</td>
|
||
<td>2</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td>C</td>
|
||
<td>2</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>类型分布:</p>
|
||
<ul>
|
||
<li>库和框架: 如<code>jQuery</code></li>
|
||
<li>系统: 如<code>Linux</code>、<code>hhvm</code>、<code>docker</code></li>
|
||
<li>配置集: 如<code>dotfiles</code></li>
|
||
<li>辅助工具: 如<code>oh-my-zsh</code></li>
|
||
<li>工具: 如<code>Homewbrew</code>和<code>Bower</code></li>
|
||
<li>资料收集: 如<code>free programming books</code>,<code>You-Dont-Know-JS</code>,<code>Font-Awesome</code></li>
|
||
<li>其他:简历如<code>Resume</code></li>
|
||
</ul>
|
||
<h2 id="pull-request">Pull Request</h2>
|
||
<p>除了创建项目之外,我们也可以创建Pull Request来做贡献。</p>
|
||
<h3 id="我的第一个pr">我的第一个PR</h3>
|
||
<p>我的第一个PR是给一个小的Node的CoAP相关的库的Pull Request。原因比较简单,是因为它的README.md写错了,导致我无法办法进行下一步。</p>
|
||
<pre><code> const dgram = require('dgram')
|
||
- , coapPacket = require('coap-packet')
|
||
+ , package = require('coap-packet')</code></pre>
|
||
<p>很简单,却又很有用的步骤,另外一个也是:</p>
|
||
<pre><code> else
|
||
cat << END
|
||
$0: error: module ngx_pagespeed requires the pagespeed optimization library.
|
||
-Look in obj/autoconf.err for more details.
|
||
+Look in objs/autoconf.err for more details.
|
||
END
|
||
exit 1
|
||
fi</code></pre>
|
||
<h3 id="cla">CLA</h3>
|
||
<p>CLA即Contributor License Agreement,在为一些大的组织、机构提交Pull Request的时候,可能需要签署这个协议。他们会在你的Pull Request里问你,只有你到他们的网站去注册并同意协议才会接受你的PR。</p>
|
||
<p>以下是我为Google提交的一个PR</p>
|
||
<figure>
|
||
<img src="./img/google-cla.png" alt="Google CLA" /><figcaption>Google CLA</figcaption>
|
||
</figure>
|
||
<p>以及Eclipse的一个PR</p>
|
||
<figure>
|
||
<img src="./img/eclipse-cla.png" alt="Eclipse CLA" /><figcaption>Eclipse CLA</figcaption>
|
||
</figure>
|
||
<p>他们都要求我签署CLA。</p>
|
||
<h1 id="构建-github-项目">构建 GitHub 项目</h1>
|
||
<h2 id="如何用好-github">如何用好 GitHub</h2>
|
||
<p>如何用好 GitHub,并实践一些敏捷软件开发是一个很有意思的事情.我们可以在上面做很多事情,从测试到CI,再到自动部署.</p>
|
||
<h3 id="敏捷软件开发">敏捷软件开发</h3>
|
||
<p>显然我是在扯淡,这和敏捷软件开发没有什么关系。不过我也不知道瀑布流是怎样的。说说我所知道的一个项目的组成吧:</p>
|
||
<ul>
|
||
<li>看板式管理应用程序(如trello,简单地说就是管理软件功能)</li>
|
||
<li>CI(持续集成)</li>
|
||
<li>测试覆盖率</li>
|
||
<li>代码质量(code smell)</li>
|
||
</ul>
|
||
<p>对于一个不是远程的团队(如只有一个人的项目) 来说,Trello、Jenkin、Jira不是必需的:</p>
|
||
<blockquote>
|
||
<p>你存在,我深深的脑海里</p>
|
||
</blockquote>
|
||
<p>当只有一个人的时候,你只需要明确知道自己想要什么就够了。我们还需要的是CI、测试,以来提升代码的质量。</p>
|
||
<h3 id="测试">测试</h3>
|
||
<p>通常我们都会找Document,如果没有的话,你会找什么?看源代码,还是看测试?</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="at">it</span>(<span class="st">"specifying response when you need it"</span><span class="op">,</span> <span class="kw">function</span> (done) <span class="op">{</span>
|
||
<span class="kw">var</span> doneFn <span class="op">=</span> <span class="va">jasmine</span>.<span class="at">createSpy</span>(<span class="st">"success"</span>)<span class="op">;</span>
|
||
|
||
<span class="va">lettuce</span>.<span class="at">get</span>(<span class="st">'/some/cool/url'</span><span class="op">,</span> <span class="kw">function</span> (result) <span class="op">{</span>
|
||
<span class="at">expect</span>(result).<span class="at">toEqual</span>(<span class="st">"awesome response"</span>)<span class="op">;</span>
|
||
<span class="at">done</span>()<span class="op">;</span>
|
||
<span class="op">}</span>)<span class="op">;</span>
|
||
|
||
<span class="at">expect</span>(<span class="va">jasmine</span>.<span class="va">Ajax</span>.<span class="va">requests</span>.<span class="at">mostRecent</span>().<span class="at">url</span>).<span class="at">toBe</span>(<span class="st">'/some/cool/url'</span>)<span class="op">;</span>
|
||
<span class="at">expect</span>(doneFn).<span class="va">not</span>.<span class="at">toHaveBeenCalled</span>()<span class="op">;</span>
|
||
|
||
<span class="va">jasmine</span>.<span class="va">Ajax</span>.<span class="va">requests</span>.<span class="at">mostRecent</span>().<span class="at">respondWith</span>(<span class="op">{</span>
|
||
<span class="st">"status"</span><span class="op">:</span> <span class="dv">200</span><span class="op">,</span>
|
||
<span class="st">"contentType"</span><span class="op">:</span> <span class="st">'text/plain'</span><span class="op">,</span>
|
||
<span class="st">"responseText"</span><span class="op">:</span> <span class="st">'awesome response'</span>
|
||
<span class="op">}</span>)<span class="op">;</span>
|
||
<span class="op">}</span>)<span class="op">;</span></code></pre></div>
|
||
<p>代码来源: <a href="https://github.com/phodal/lettuce" class="uri">https://github.com/phodal/lettuce</a></p>
|
||
<p>上面的测试用例,清清楚楚地写明了用法,虽然写得有点扯。</p>
|
||
<p>等等,测试是用来干什么的。那么,先说说我为什么会想去写测试吧:</p>
|
||
<ul>
|
||
<li>我不希望每次做完一个个新功能的时候,再手动地去测试一个个功能。(自动化测试)</li>
|
||
<li>我不希望在重构的时候发现破坏了原来的功能,而我还一无所知。</li>
|
||
<li>我不敢push代码,因为我没有把握。</li>
|
||
</ul>
|
||
<p>虽然,我不是TDD的死忠,测试的目的是保证功能正常,TDD没法让我们写出质量更高的代码。但是有时TDD是不错的,可以让我们写出逻辑更简单地代码。</p>
|
||
<p>也许你已经知道了<code>Selenium</code>、<code>Jasmine</code>、<code>Cucumber</code>等等的框架,看到过类似于下面的测试</p>
|
||
<pre><code> Ajax
|
||
✓ specifying response when you need it
|
||
✓ specifying html when you need it
|
||
✓ should be post to some where
|
||
Class
|
||
✓ respects instanceof
|
||
✓ inherits methods (also super)
|
||
✓ extend methods
|
||
Effect
|
||
✓ should be able fadein elements
|
||
✓ should be able fadeout elements</code></pre>
|
||
<p>代码来源: <a href="https://github.com/phodal/lettuce" class="uri">https://github.com/phodal/lettuce</a></p>
|
||
<p>看上去似乎每个测试都很小,不过补完每一个测试之后我们就得到了测试覆盖率</p>
|
||
<table style="width:68%;">
|
||
<colgroup>
|
||
<col style="width: 8%" />
|
||
<col style="width: 18%" />
|
||
<col style="width: 15%" />
|
||
<col style="width: 16%" />
|
||
<col style="width: 9%" />
|
||
</colgroup>
|
||
<thead>
|
||
<tr class="header">
|
||
<th>File</th>
|
||
<th>Statements</th>
|
||
<th>Branches</th>
|
||
<th>Functions</th>
|
||
<th>Lines</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="odd">
|
||
<td>lettuce.js</td>
|
||
<td>98.58% (209 / 212)</td>
|
||
<td>82.98%(78 / 94)</td>
|
||
<td>100.00% (54 / 54)</td>
|
||
<td>98.58% (209 / 212)</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>本地测试都通过了,于是我们添加了<code>Travis-CI</code>来跑我们的测试</p>
|
||
<h3 id="ci">CI</h3>
|
||
<p>虽然node.js不算是一门语言,但是因为我们用的node,下面的是一个简单的<code>.travis.yml</code>示例:</p>
|
||
<div class="sourceCode"><pre class="sourceCode yml"><code class="sourceCode yaml"><span class="fu">language:</span> node_js
|
||
<span class="fu">node_js:</span>
|
||
<span class="kw">-</span> <span class="st">"0.10"</span>
|
||
|
||
<span class="fu">notifications:</span>
|
||
<span class="fu">email:</span> false
|
||
|
||
<span class="fu">before_install:</span> npm install -g grunt-cli
|
||
<span class="fu">install:</span> npm install
|
||
<span class="fu">after_success:</span> CODECLIMATE_REPO_TOKEN=321480822fc37deb0de70a11931b4cb6a2a3cc411680e8f4569936ac8ffbb0ab codeclimate < coverage/lcov.info</code></pre></div>
|
||
<p>代码来源: <a href="https://github.com/phodal/lettuce" class="uri">https://github.com/phodal/lettuce</a></p>
|
||
<p>我们把这些集成到<code>README.md</code>之后,就有了之前那张图。</p>
|
||
<p>CI对于一个开发者在不同城市开发同一项目上来说是很重要的,这意味着当你添加的部分功能有测试覆盖的时候,项目代码会更加强壮。</p>
|
||
<h3 id="代码质量">代码质量</h3>
|
||
<p>像<code>jslint</code>这类的工具,只能保证代码在语法上是正确的,但是不能保证你写了一堆bad smell的代码。</p>
|
||
<ul>
|
||
<li>重复代码</li>
|
||
<li>过长的函数</li>
|
||
<li>等等</li>
|
||
</ul>
|
||
<p><code>Code Climate</code>是一个与github集成的工具,我们不仅仅可以看到测试覆盖率,还有代码质量。</p>
|
||
<p>先看看上面的ajax类:</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">Lettuce</span>.<span class="at">get</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
|
||
<span class="va">Lettuce</span>.<span class="at">send</span>(url<span class="op">,</span> <span class="st">'GET'</span><span class="op">,</span> callback)<span class="op">;</span>
|
||
<span class="op">};</span>
|
||
|
||
<span class="va">Lettuce</span>.<span class="at">send</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> method<span class="op">,</span> callback<span class="op">,</span> data) <span class="op">{</span>
|
||
data <span class="op">=</span> data <span class="op">||</span> <span class="kw">null</span><span class="op">;</span>
|
||
<span class="kw">var</span> request <span class="op">=</span> <span class="kw">new</span> <span class="at">XMLHttpRequest</span>()<span class="op">;</span>
|
||
<span class="cf">if</span> (callback <span class="kw">instanceof</span> Function) <span class="op">{</span>
|
||
<span class="va">request</span>.<span class="at">onreadystatechange</span> <span class="op">=</span> <span class="kw">function</span> () <span class="op">{</span>
|
||
<span class="cf">if</span> (<span class="va">request</span>.<span class="at">readyState</span> <span class="op">===</span> <span class="dv">4</span> <span class="op">&&</span> (<span class="va">request</span>.<span class="at">status</span> <span class="op">===</span> <span class="dv">200</span> <span class="op">||</span> <span class="va">request</span>.<span class="at">status</span> <span class="op">===</span> <span class="dv">0</span>)) <span class="op">{</span>
|
||
<span class="at">callback</span>(<span class="va">request</span>.<span class="at">responseText</span>)<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
<span class="op">};</span>
|
||
<span class="op">}</span>
|
||
<span class="va">request</span>.<span class="at">open</span>(method<span class="op">,</span> url<span class="op">,</span> <span class="kw">true</span>)<span class="op">;</span>
|
||
<span class="cf">if</span> (data <span class="kw">instanceof</span> Object) <span class="op">{</span>
|
||
data <span class="op">=</span> <span class="va">JSON</span>.<span class="at">stringify</span>(data)<span class="op">;</span>
|
||
<span class="va">request</span>.<span class="at">setRequestHeader</span>(<span class="st">'Content-Type'</span><span class="op">,</span> <span class="st">'application/json'</span>)<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
<span class="va">request</span>.<span class="at">setRequestHeader</span>(<span class="st">'X-Requested-With'</span><span class="op">,</span> <span class="st">'XMLHttpRequest'</span>)<span class="op">;</span>
|
||
<span class="va">request</span>.<span class="at">send</span>(data)<span class="op">;</span>
|
||
<span class="op">};</span></code></pre></div>
|
||
<p>代码来源: <a href="https://github.com/phodal/lettuce" class="uri">https://github.com/phodal/lettuce</a></p>
|
||
<p>在<a href="https://codeclimate.com/github/phodal/lettuce/src/ajax.js">Code Climate</a>在出现了一堆问题</p>
|
||
<ul>
|
||
<li>Missing “use strict” statement. (Line 2)</li>
|
||
<li>Missing “use strict” statement. (Line 14)</li>
|
||
<li>‘Lettuce’ is not defined. (Line 5)</li>
|
||
</ul>
|
||
<p>而这些都是小问题啦,有时可能会有</p>
|
||
<ul>
|
||
<li>Similar code found in two :expression_statement nodes (mass = 86)</li>
|
||
</ul>
|
||
<p>这就意味着我们可以对上面的代码进行重构,他们是重复的代码。</p>
|
||
<h2 id="模块分离与测试">模块分离与测试</h2>
|
||
<p>在之前说到</p>
|
||
<blockquote>
|
||
<p>奋斗了近半个月后,将fork的代码读懂、重构、升级版本、调整,添加新功能、添加测试、添加CI、添加分享之后,终于almost finish。</p>
|
||
</blockquote>
|
||
<p>今天就来说说是怎样做的。</p>
|
||
<p>以之前造的<a href="https://github.com/phodal/lettuce">Lettuce</a>为例,里面有:</p>
|
||
<ul>
|
||
<li>代码质量(Code Climate)</li>
|
||
<li>CI状态(Travis CI)</li>
|
||
<li>测试覆盖率(96%)</li>
|
||
<li>自动化测试(npm test)</li>
|
||
<li>文档</li>
|
||
</ul>
|
||
<p>按照<a href="https://github.com/phodal/awesome-developer">Web Developer路线图</a>来说,我们还需要有:</p>
|
||
<ul>
|
||
<li>版本管理</li>
|
||
<li>自动部署</li>
|
||
</ul>
|
||
<p>等等。</p>
|
||
<h3 id="代码模块化">代码模块化</h3>
|
||
<p>在SkillTree的源码里,大致分为三部分:</p>
|
||
<ul>
|
||
<li>namespace函数: 顾名思义</li>
|
||
<li>Calculator也就是TalentTree,主要负责解析、生成url,头像,依赖等等</li>
|
||
<li>Skill 主要是tips部分。</li>
|
||
</ul>
|
||
<p>而这一些都在一个js里,对于一个库来说,是一件好事,但是对于一个项目来说,并非如此。</p>
|
||
<p>依赖的库有</p>
|
||
<ul>
|
||
<li>jQuery</li>
|
||
<li>Knockout</li>
|
||
</ul>
|
||
<p>好在Knockout可以用Require.js进行管理,于是,使用了<code>Require.js</code>进行管理:</p>
|
||
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><script</span><span class="ot"> type=</span><span class="st">"text/javascript"</span><span class="ot"> data-main=</span><span class="st">"app/scripts/main.js"</span><span class="ot"> src=</span><span class="st">"app/lib/require.js"</span><span class="kw">></script></span></code></pre></div>
|
||
<p><code>main.js</code>配置如下:</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">require</span>.<span class="at">config</span>(<span class="op">{</span>
|
||
<span class="dt">baseUrl</span><span class="op">:</span> <span class="st">'app'</span><span class="op">,</span>
|
||
<span class="dt">paths</span><span class="op">:{</span>
|
||
<span class="dt">jquery</span><span class="op">:</span> <span class="st">'lib/jquery'</span><span class="op">,</span>
|
||
<span class="dt">json</span><span class="op">:</span> <span class="st">'lib/json'</span><span class="op">,</span>
|
||
<span class="dt">text</span><span class="op">:</span> <span class="st">'lib/text'</span>
|
||
<span class="op">}</span>
|
||
<span class="op">}</span>)<span class="op">;</span>
|
||
|
||
<span class="at">require</span>([<span class="st">'scripts/ko-bindings'</span>])<span class="op">;</span>
|
||
|
||
<span class="at">require</span>([<span class="st">'lib/knockout'</span><span class="op">,</span> <span class="st">'scripts/TalentTree'</span><span class="op">,</span> <span class="st">'json!data/web.json'</span>]<span class="op">,</span> <span class="kw">function</span>(ko<span class="op">,</span> TalentTree<span class="op">,</span> TalentData) <span class="op">{</span>
|
||
<span class="st">'use strict'</span><span class="op">;</span>
|
||
<span class="kw">var</span> vm <span class="op">=</span> <span class="kw">new</span> <span class="at">TalentTree</span>(TalentData)<span class="op">;</span>
|
||
<span class="va">ko</span>.<span class="at">applyBindings</span>(vm)<span class="op">;</span>
|
||
<span class="op">}</span>)<span class="op">;</span></code></pre></div>
|
||
<p>text、json插件主要是用于处理web.json,即用json来处理技能,于是不同的类到了不同的js文件。</p>
|
||
<pre><code>.
|
||
|____Book.js
|
||
|____Doc.js
|
||
|____ko-bindings.js
|
||
|____Link.js
|
||
|____main.js
|
||
|____Skill.js
|
||
|____TalentTree.js
|
||
|____Utils.js</code></pre>
|
||
<p>加上了后来的推荐阅读书籍等等。而Book和Link都是继承自Doc。</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="at">define</span>([<span class="st">'scripts/Doc'</span>]<span class="op">,</span> <span class="kw">function</span>(Doc) <span class="op">{</span>
|
||
<span class="st">'use strict'</span><span class="op">;</span>
|
||
<span class="kw">function</span> <span class="at">Book</span>(_e) <span class="op">{</span>
|
||
<span class="va">Doc</span>.<span class="at">apply</span>(<span class="kw">this</span><span class="op">,</span> arguments)<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
<span class="va">Book</span>.<span class="at">prototype</span> <span class="op">=</span> <span class="kw">new</span> <span class="at">Doc</span>()<span class="op">;</span>
|
||
|
||
<span class="cf">return</span> Book<span class="op">;</span>
|
||
<span class="op">}</span>)<span class="op">;</span> </code></pre></div>
|
||
<p>而这里便是后面对其进行重构的内容。Doc类则是Skillock中类的一个缩影</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="at">define</span>([]<span class="op">,</span> <span class="kw">function</span>() <span class="op">{</span>
|
||
<span class="st">'use strict'</span><span class="op">;</span>
|
||
<span class="kw">var</span> Doc <span class="op">=</span> <span class="kw">function</span> (_e) <span class="op">{</span>
|
||
<span class="kw">var</span> e <span class="op">=</span> _e <span class="op">||</span> <span class="op">{};</span>
|
||
<span class="kw">var</span> self <span class="op">=</span> <span class="kw">this</span><span class="op">;</span>
|
||
|
||
<span class="va">self</span>.<span class="at">label</span> <span class="op">=</span> <span class="va">e</span>.<span class="at">label</span> <span class="op">||</span> (<span class="va">e</span>.<span class="at">url</span> <span class="op">||</span> <span class="st">'Learn more'</span>)<span class="op">;</span>
|
||
<span class="va">self</span>.<span class="at">url</span> <span class="op">=</span> <span class="va">e</span>.<span class="at">url</span> <span class="op">||</span> <span class="st">'javascript:void(0)'</span><span class="op">;</span>
|
||
<span class="op">};</span>
|
||
|
||
<span class="cf">return</span> Doc<span class="op">;</span>
|
||
<span class="op">}</span>)<span class="op">;</span></code></pre></div>
|
||
<p>或者说这是一个AMD的Class应该有的样子。考虑到this的隐性绑定,作者用了self=this来避免这个问题。最后Return了这个对象,我们在调用的就需要new一个。大部分在代码中返回的都是对象,除了在Utils类里面返回的是函数:</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="cf">return</span> <span class="op">{</span>
|
||
<span class="dt">getSkillsByHash</span><span class="op">:</span> getSkillsByHash<span class="op">,</span>
|
||
<span class="dt">getSkillById</span><span class="op">:</span> getSkillById<span class="op">,</span>
|
||
<span class="dt">prettyJoin</span><span class="op">:</span> prettyJoin
|
||
<span class="op">};</span></code></pre></div>
|
||
<p>当然函数也是一个对象。</p>
|
||
<h3 id="自动化测试">自动化测试</h3>
|
||
<p>一直习惯用Travis CI,于是也继续用Travis Ci,<code>.travis.yml</code>配置如下所示:</p>
|
||
<div class="sourceCode"><pre class="sourceCode yml"><code class="sourceCode yaml"><span class="fu">language:</span> node_js
|
||
<span class="fu">node_js:</span>
|
||
<span class="kw">-</span> <span class="st">"0.10"</span>
|
||
|
||
<span class="fu">notifications:</span>
|
||
<span class="fu">email:</span> false
|
||
|
||
<span class="fu">branches:</span>
|
||
<span class="fu">only:</span>
|
||
<span class="kw">-</span> gh-pages</code></pre></div>
|
||
<p>使用gh-pages的原因是,我们一push代码的时候,就可以自动测试、部署等等,好处一堆堆的。</p>
|
||
<p>接着我们需要在<code>package.json</code>里面添加脚本</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="st">"scripts"</span><span class="op">:</span> <span class="op">{</span>
|
||
<span class="st">"test"</span><span class="op">:</span> <span class="st">"mocha"</span>
|
||
<span class="op">}</span></code></pre></div>
|
||
<p>这样当我们push代码的时候便会自动跑所有的测试。因为mocha的主要配置是用<code>mocha.opts</code>,所以我们还需要配置一下<code>mocha.opts</code></p>
|
||
<pre><code>--reporter spec
|
||
--ui bdd
|
||
--growl
|
||
--colors
|
||
test/spec </code></pre>
|
||
<p>最后的<code>test/spec</code>是指定测试的目录。</p>
|
||
<h3 id="jshint">Jshint</h3>
|
||
<blockquote>
|
||
<p>JSLint定义了一组编码约定,这比ECMA定义的语言更为严格。这些编码约定汲取了多年来的丰富编码经验,并以一条年代久远的编程原则 作为宗旨:能做并不意味着应该做。JSLint会对它认为有的编码实践加标志,另外还会指出哪些是明显的错误,从而促使你养成好的 JavaScript编码习惯。</p>
|
||
</blockquote>
|
||
<p>当我们的js写得不合理的时候,这时测试就无法通过:</p>
|
||
<pre><code>line 5 col 25 A constructor name should start with an uppercase letter.
|
||
line 21 col 62 Strings must use singlequote.</code></pre>
|
||
<p>这是一种驱动写出更规范js的方法。</p>
|
||
<h3 id="mocha">Mocha</h3>
|
||
<blockquote>
|
||
<p>Mocha 是一个优秀的JS测试框架,支持TDD/BDD,结合 should.js/expect/chai/better-assert,能轻松构建各种风格的测试用例。</p>
|
||
</blockquote>
|
||
<p>最后的效果如下所示:</p>
|
||
<pre><code>Book,Link
|
||
Book Test
|
||
✓ should return book label & url
|
||
Link Test
|
||
✓ should return link label & url</code></pre>
|
||
<h3 id="测试示例">测试示例</h3>
|
||
<p>简单地看一下Book的测试:</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="co">/* global describe, it */</span>
|
||
|
||
<span class="kw">var</span> requirejs <span class="op">=</span> <span class="at">require</span>(<span class="st">"requirejs"</span>)<span class="op">;</span>
|
||
<span class="kw">var</span> assert <span class="op">=</span> <span class="at">require</span>(<span class="st">"assert"</span>)<span class="op">;</span>
|
||
<span class="kw">var</span> should <span class="op">=</span> <span class="at">require</span>(<span class="st">"should"</span>)<span class="op">;</span>
|
||
<span class="va">requirejs</span>.<span class="at">config</span>(<span class="op">{</span>
|
||
<span class="dt">baseUrl</span><span class="op">:</span> <span class="st">'app/'</span><span class="op">,</span>
|
||
<span class="dt">nodeRequire</span><span class="op">:</span> require
|
||
<span class="op">}</span>)<span class="op">;</span>
|
||
|
||
<span class="at">describe</span>(<span class="st">'Book,Link'</span><span class="op">,</span> <span class="kw">function</span> () <span class="op">{</span>
|
||
<span class="kw">var</span> Book<span class="op">,</span> Link<span class="op">;</span>
|
||
<span class="at">before</span>(<span class="kw">function</span> (done) <span class="op">{</span>
|
||
<span class="at">requirejs</span>([<span class="st">'scripts/Book'</span>、]<span class="op">,</span> <span class="kw">function</span> (Book_Class) <span class="op">{</span>
|
||
Book <span class="op">=</span> Book_Class<span class="op">;</span>
|
||
<span class="at">done</span>()<span class="op">;</span>
|
||
<span class="op">}</span>)<span class="op">;</span>
|
||
<span class="op">}</span>)<span class="op">;</span>
|
||
|
||
<span class="at">describe</span>(<span class="st">'Book Test'</span><span class="op">,</span> <span class="kw">function</span> () <span class="op">{</span>
|
||
<span class="at">it</span>(<span class="st">'should return book label & url'</span><span class="op">,</span> <span class="kw">function</span> () <span class="op">{</span>
|
||
<span class="kw">var</span> book_name <span class="op">=</span> <span class="st">'Head First HTML与CSS'</span><span class="op">;</span>
|
||
<span class="kw">var</span> url <span class="op">=</span> <span class="st">'http://www.phodal.com'</span><span class="op">;</span>
|
||
<span class="kw">var</span> books <span class="op">=</span> <span class="op">{</span>
|
||
<span class="dt">label</span><span class="op">:</span> book_name<span class="op">,</span>
|
||
<span class="dt">url</span><span class="op">:</span> url
|
||
<span class="op">};</span>
|
||
|
||
<span class="kw">var</span> _book <span class="op">=</span> <span class="kw">new</span> <span class="at">Book</span>(books)<span class="op">;</span>
|
||
<span class="va">_book</span>.<span class="va">label</span>.<span class="va">should</span>.<span class="at">equal</span>(book_name)<span class="op">;</span>
|
||
<span class="va">_book</span>.<span class="va">url</span>.<span class="va">should</span>.<span class="at">equal</span>(url)<span class="op">;</span>
|
||
<span class="op">}</span>)<span class="op">;</span>
|
||
<span class="op">}</span>)<span class="op">;</span>
|
||
<span class="op">}</span>)<span class="op">;</span></code></pre></div>
|
||
<p>因为我们用<code>require.js</code>来管理浏览器端,在后台写测试来测试的时候,我们也需要用他来管理我们的依赖,这也就是为什么这个测试这么长的原因,多数情况下一个测试类似于这样子的。(用Jasmine似乎会是一个更好的主意,但是用习惯Jasmine了)</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="at">describe</span>(<span class="st">'Book Test'</span><span class="op">,</span> <span class="kw">function</span> () <span class="op">{</span>
|
||
<span class="at">it</span>(<span class="st">'should return book label & url'</span><span class="op">,</span> <span class="kw">function</span> () <span class="op">{</span>
|
||
<span class="kw">var</span> book_name <span class="op">=</span> <span class="st">'Head First HTML与CSS'</span><span class="op">;</span>
|
||
<span class="kw">var</span> url <span class="op">=</span> <span class="st">'http://www.phodal.com'</span><span class="op">;</span>
|
||
<span class="kw">var</span> books <span class="op">=</span> <span class="op">{</span>
|
||
<span class="dt">label</span><span class="op">:</span> book_name<span class="op">,</span>
|
||
<span class="dt">url</span><span class="op">:</span> url
|
||
<span class="op">};</span>
|
||
|
||
<span class="kw">var</span> _book <span class="op">=</span> <span class="kw">new</span> <span class="at">Book</span>(books)<span class="op">;</span>
|
||
<span class="va">_book</span>.<span class="va">label</span>.<span class="va">should</span>.<span class="at">equal</span>(book_name)<span class="op">;</span>
|
||
<span class="va">_book</span>.<span class="va">url</span>.<span class="va">should</span>.<span class="at">equal</span>(url)<span class="op">;</span>
|
||
<span class="op">}</span>)<span class="op">;</span>
|
||
<span class="op">}</span>)<span class="op">;</span></code></pre></div>
|
||
<p>最后的断言,也算是测试的核心,保证测试是有用的。</p>
|
||
<h2 id="代码质量与重构">代码质量与重构</h2>
|
||
<ul>
|
||
<li>当你写了一大堆代码,你没有意识到里面有一大堆重复。</li>
|
||
<li>当你写了一大堆测试,却不知道覆盖率有多少。</li>
|
||
</ul>
|
||
<p>这就是个问题了,于是偶然间看到了一个叫code climate的网站。</p>
|
||
<h3 id="code-climate">Code Climate</h3>
|
||
<blockquote>
|
||
<p>Code Climate consolidates the results from a suite of static analysis tools into a single, real-time report, giving your team the information it needs to identify hotspots, evaluate new approaches, and improve code quality.</p>
|
||
</blockquote>
|
||
<p>Code Climate整合一组静态分析工具的结果到一个单一的,实时的报告,让您的团队需要识别热点,探讨新的方法,提高代码质量的信息。</p>
|
||
<p>简单地来说:</p>
|
||
<ul>
|
||
<li>对我们的代码评分</li>
|
||
<li>找出代码中的坏味道</li>
|
||
</ul>
|
||
<p>于是,我们先来了个例子</p>
|
||
<table>
|
||
<thead>
|
||
<tr class="header">
|
||
<th>Rating</th>
|
||
<th>Name</th>
|
||
<th>Complexity</th>
|
||
<th>Duplication</th>
|
||
<th>Churn</th>
|
||
<th>C/M</th>
|
||
<th>Coverage</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="odd">
|
||
<td>A</td>
|
||
<td>lib/coap/coap_request_handler.js</td>
|
||
<td>24</td>
|
||
<td>0</td>
|
||
<td>6</td>
|
||
<td>2.6</td>
|
||
<td>46.4%</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td>A</td>
|
||
<td>lib/coap/coap_result_helper.js</td>
|
||
<td>14</td>
|
||
<td>0</td>
|
||
<td>2</td>
|
||
<td>3.4</td>
|
||
<td>80.0%</td>
|
||
</tr>
|
||
<tr class="odd">
|
||
<td>A</td>
|
||
<td>lib/coap/coap_server.js</td>
|
||
<td>16</td>
|
||
<td>0</td>
|
||
<td>5</td>
|
||
<td>5.2</td>
|
||
<td>44.0%</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td>A</td>
|
||
<td>lib/database/db_factory.js</td>
|
||
<td>8</td>
|
||
<td>0</td>
|
||
<td>3</td>
|
||
<td>3.8</td>
|
||
<td>92.3%</td>
|
||
</tr>
|
||
<tr class="odd">
|
||
<td>A</td>
|
||
<td>lib/database/iot_db.js</td>
|
||
<td>7</td>
|
||
<td>0</td>
|
||
<td>6</td>
|
||
<td>1.0</td>
|
||
<td>58.8%</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td>A</td>
|
||
<td>lib/database/mongodb_helper.js</td>
|
||
<td>63</td>
|
||
<td>0</td>
|
||
<td>11</td>
|
||
<td>4.5</td>
|
||
<td>35.0%</td>
|
||
</tr>
|
||
<tr class="odd">
|
||
<td>C</td>
|
||
<td>lib/database/sqlite_helper.js</td>
|
||
<td>32</td>
|
||
<td>86</td>
|
||
<td>10</td>
|
||
<td>4.5</td>
|
||
<td>35.0%</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td>B</td>
|
||
<td>lib/rest/rest_helper.js</td>
|
||
<td>19</td>
|
||
<td>62</td>
|
||
<td>3</td>
|
||
<td>4.7</td>
|
||
<td>37.5%</td>
|
||
</tr>
|
||
<tr class="odd">
|
||
<td>A</td>
|
||
<td>lib/rest/rest_server.js</td>
|
||
<td>17</td>
|
||
<td>0</td>
|
||
<td>2</td>
|
||
<td>8.6</td>
|
||
<td>88.9%</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td>A</td>
|
||
<td>lib/url_handler.js</td>
|
||
<td>9</td>
|
||
<td>0</td>
|
||
<td>5</td>
|
||
<td>2.2</td>
|
||
<td>94.1%</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>分享得到的最后的结果是:</p>
|
||
<p>[Coverage][1]</p>
|
||
<h3 id="代码的坏味道">代码的坏味道</h3>
|
||
<p>于是我们就打开<code>lib/database/sqlite_helper.js</code>,因为其中有两个坏味道</p>
|
||
<p>Similar code found in two :expression_statement nodes (mass = 86)</p>
|
||
<p>在代码的 <code>lib/database/sqlite_helper.js:58…61 < ></code></p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"> <span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">deleteData</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
|
||
<span class="st">'use strict'</span><span class="op">;</span>
|
||
<span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">"DELETE FROM "</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">" where "</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getKeyFromURL</span>(url) <span class="op">+</span> <span class="st">"="</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getValueFromURL</span>(url)<span class="op">;</span>
|
||
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">basic</span>(sql_command<span class="op">,</span> callback)<span class="op">;</span></code></pre></div>
|
||
<p>lib/database/sqlite_helper.js:64…67 < ></p>
|
||
<p>与</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">getData</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
|
||
<span class="st">'use strict'</span><span class="op">;</span>
|
||
<span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">"SELECT * FROM "</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">" where "</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getKeyFromURL</span>(url) <span class="op">+</span> <span class="st">"="</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getValueFromURL</span>(url)<span class="op">;</span>
|
||
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">basic</span>(sql_command<span class="op">,</span> callback)<span class="op">;</span></code></pre></div>
|
||
<p>只是这是之前修改过的重复。。</p>
|
||
<p>原来的代码是这样的</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">postData</span> <span class="op">=</span> <span class="kw">function</span> (block<span class="op">,</span> callback) <span class="op">{</span>
|
||
<span class="st">'use strict'</span><span class="op">;</span>
|
||
<span class="kw">var</span> db <span class="op">=</span> <span class="kw">new</span> <span class="va">sqlite3</span>.<span class="at">Database</span>(<span class="va">config</span>.<span class="at">db_name</span>)<span class="op">;</span>
|
||
<span class="kw">var</span> str <span class="op">=</span> <span class="kw">this</span>.<span class="at">parseData</span>(<span class="va">config</span>.<span class="at">keys</span>)<span class="op">;</span>
|
||
<span class="kw">var</span> string <span class="op">=</span> <span class="kw">this</span>.<span class="at">parseData</span>(block)<span class="op">;</span>
|
||
|
||
<span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">"insert or replace into "</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">" ("</span> <span class="op">+</span> str <span class="op">+</span> <span class="st">") VALUES ("</span> <span class="op">+</span> string <span class="op">+</span> <span class="st">");"</span><span class="op">;</span>
|
||
<span class="va">db</span>.<span class="at">all</span>(sql_command<span class="op">,</span> <span class="kw">function</span> (err) <span class="op">{</span>
|
||
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">errorHandler</span>(err)<span class="op">;</span>
|
||
<span class="va">db</span>.<span class="at">close</span>()<span class="op">;</span>
|
||
<span class="at">callback</span>()<span class="op">;</span>
|
||
<span class="op">}</span>)<span class="op">;</span>
|
||
<span class="op">};</span>
|
||
|
||
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">deleteData</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
|
||
<span class="st">'use strict'</span><span class="op">;</span>
|
||
<span class="kw">var</span> db <span class="op">=</span> <span class="kw">new</span> <span class="va">sqlite3</span>.<span class="at">Database</span>(<span class="va">config</span>.<span class="at">db_name</span>)<span class="op">;</span>
|
||
<span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">"DELETE FROM "</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">" where "</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getKeyFromURL</span>(url) <span class="op">+</span> <span class="st">"="</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getValueFromURL</span>(url)<span class="op">;</span>
|
||
<span class="va">db</span>.<span class="at">all</span>(sql_command<span class="op">,</span> <span class="kw">function</span> (err) <span class="op">{</span>
|
||
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">errorHandler</span>(err)<span class="op">;</span>
|
||
<span class="va">db</span>.<span class="at">close</span>()<span class="op">;</span>
|
||
<span class="at">callback</span>()<span class="op">;</span>
|
||
<span class="op">}</span>)<span class="op">;</span>
|
||
<span class="op">};</span>
|
||
|
||
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">getData</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
|
||
<span class="st">'use strict'</span><span class="op">;</span>
|
||
<span class="kw">var</span> db <span class="op">=</span> <span class="kw">new</span> <span class="va">sqlite3</span>.<span class="at">Database</span>(<span class="va">config</span>.<span class="at">db_name</span>)<span class="op">;</span>
|
||
<span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">"SELECT * FROM "</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">" where "</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getKeyFromURL</span>(url) <span class="op">+</span> <span class="st">"="</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getValueFromURL</span>(url)<span class="op">;</span>
|
||
<span class="va">db</span>.<span class="at">all</span>(sql_command<span class="op">,</span> <span class="kw">function</span> (err<span class="op">,</span> rows) <span class="op">{</span>
|
||
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">errorHandler</span>(err)<span class="op">;</span>
|
||
<span class="va">db</span>.<span class="at">close</span>()<span class="op">;</span>
|
||
<span class="at">callback</span>(<span class="va">JSON</span>.<span class="at">stringify</span>(rows))<span class="op">;</span>
|
||
<span class="op">}</span>)<span class="op">;</span>
|
||
<span class="op">};</span></code></pre></div>
|
||
<p>说的也是大量的重复,重构完的代码</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">basic</span> <span class="op">=</span> <span class="kw">function</span>(sql<span class="op">,</span> db_callback)<span class="op">{</span>
|
||
<span class="st">'use strict'</span><span class="op">;</span>
|
||
<span class="kw">var</span> db <span class="op">=</span> <span class="kw">new</span> <span class="va">sqlite3</span>.<span class="at">Database</span>(<span class="va">config</span>.<span class="at">db_name</span>)<span class="op">;</span>
|
||
<span class="va">db</span>.<span class="at">all</span>(sql<span class="op">,</span> <span class="kw">function</span> (err<span class="op">,</span> rows) <span class="op">{</span>
|
||
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">errorHandler</span>(err)<span class="op">;</span>
|
||
<span class="va">db</span>.<span class="at">close</span>()<span class="op">;</span>
|
||
<span class="at">db_callback</span>(<span class="va">JSON</span>.<span class="at">stringify</span>(rows))<span class="op">;</span>
|
||
<span class="op">}</span>)<span class="op">;</span>
|
||
|
||
<span class="op">};</span>
|
||
|
||
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">postData</span> <span class="op">=</span> <span class="kw">function</span> (block<span class="op">,</span> callback) <span class="op">{</span>
|
||
<span class="st">'use strict'</span><span class="op">;</span>
|
||
<span class="kw">var</span> str <span class="op">=</span> <span class="kw">this</span>.<span class="at">parseData</span>(<span class="va">config</span>.<span class="at">keys</span>)<span class="op">;</span>
|
||
<span class="kw">var</span> string <span class="op">=</span> <span class="kw">this</span>.<span class="at">parseData</span>(block)<span class="op">;</span>
|
||
|
||
<span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">"insert or replace into "</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">" ("</span> <span class="op">+</span> str <span class="op">+</span> <span class="st">") VALUES ("</span> <span class="op">+</span> string <span class="op">+</span> <span class="st">");"</span><span class="op">;</span>
|
||
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">basic</span>(sql_command<span class="op">,</span> callback)<span class="op">;</span>
|
||
<span class="op">};</span>
|
||
|
||
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">deleteData</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
|
||
<span class="st">'use strict'</span><span class="op">;</span>
|
||
<span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">"DELETE FROM "</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">" where "</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getKeyFromURL</span>(url) <span class="op">+</span> <span class="st">"="</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getValueFromURL</span>(url)<span class="op">;</span>
|
||
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">basic</span>(sql_command<span class="op">,</span> callback)<span class="op">;</span>
|
||
<span class="op">};</span>
|
||
|
||
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">getData</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
|
||
<span class="st">'use strict'</span><span class="op">;</span>
|
||
<span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">"SELECT * FROM "</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">" where "</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getKeyFromURL</span>(url) <span class="op">+</span> <span class="st">"="</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getValueFromURL</span>(url)<span class="op">;</span>
|
||
<span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">basic</span>(sql_command<span class="op">,</span> callback)<span class="op">;</span>
|
||
<span class="op">};</span></code></pre></div>
|
||
<p>重构完后的代码比原来还长,这似乎是个问题~~</p>
|
||
<h1 id="git-提交信息及几种不同的规范">Git 提交信息及几种不同的规范</h1>
|
||
<blockquote>
|
||
<p>受 Growth 3.0 开发的影响,最近更新文章的频率会有所降低。今天,让我们来谈谈一个好的 Git、SVN 提交信息是怎样规范出来的。</p>
|
||
</blockquote>
|
||
<p>在团队协作中,使用版本管理工具 Git、SVN 几乎都是这个行业的标准。当我们提交代码的时候,需要编写提交信息(commit message)。</p>
|
||
<p>而提交信息的主要用途是:<strong>告诉这个项目的人,这次代码提交里做了些什么</strong>。如,我更新了 React Native Elements 的版本,那么它就可以是:<code>[T] upgrade react native elements</code>。对应的我修改的代码就是:<code>package.json</code> 和 <code>yarn.lock</code> 中的文件。一般来说,建议<strong>小步提交</strong>,即按自己的 Tasking 步骤来的提交,每一小步都有对应的提交信息。这样做的主要目的是:<strong>防止一次修改中,修改过多的文件,导致后期修改、维护、撤销等等困难</strong>。</p>
|
||
<p>而对于不同的团队来说,都会遵循一定的规范,本文主要会介绍以下几种写法:</p>
|
||
<ul>
|
||
<li>工作写法</li>
|
||
<li>常规写法</li>
|
||
<li>开源库写法</li>
|
||
</ul>
|
||
<p>那么,先从我习惯的做法说起。</p>
|
||
<h2 id="工作写法">工作写法</h2>
|
||
<p>在我的第一个项目里,我们使用 Jira 作为看板工具,Bamboo 作为持续集成服务器,并采用结对编程的方式进行。</p>
|
||
<p>在 Jira 里每一个功能卡都有对应的卡号,而 Bamboo 支持使用 Jira 的任务卡号关联的功能。即在持续构建服务器上示例对应的任务卡号,即相应的提交人。</p>
|
||
<p>因此,这个时候我们的规范稍微有一些特别:</p>
|
||
<pre><code>[任务卡号] xx & xx: do something </code></pre>
|
||
<p>比如:<code>[PHODAL-0001] ladohp & phodal: update documents</code>,解释如下:</p>
|
||
<ul>
|
||
<li><code>PHODAL-0001</code>,业务的任务卡号,它可以帮我们找到某个业务修改的原因,即点出相应 bug 的来源</li>
|
||
<li><code>ladohp & phodal</code> ,结对编程的两个人的名字,后者(phodal)一般是写代码的人,出于礼貌就放在后面了。由于 Git 的提交人只显示一个,所以写上两个的名字。当提交的人不在时,就可以问另外一个人修改的原因。</li>
|
||
<li><code>update documents</code>,我们做了什么事情</li>
|
||
</ul>
|
||
<p>缺点:而对于采用看板的团队来说,并不存在任务卡号这种东西,因此就需要一种额外的作法。</p>
|
||
<h2 id="常规写法">常规写法</h2>
|
||
<p>对于我来说,我则习惯这种的写法:</p>
|
||
<pre><code>[任务分类] 主要修改组件(可选):修改内容</code></pre>
|
||
<p>示例 1,<code>[T] tabs: add icons</code> 。其中的 <code>T</code> 表示这是一个技术卡,<code>tabs</code> 表示修改的是 Tabs,<code>add icons</code> 则表示添加了图标。</p>
|
||
<p>示例 2,<code>[SkillTree] detail: add link data</code>。其中的 <code>SkillTree</code> 表示修改的是技能树 Tab 下的内容,<code>detail</code> 则表示修改的是详情页,<code>add link data</code> 则表示是添加了技能的数据</p>
|
||
<p>这样做的主要原因是,它可以轻松也帮我** filter 出相应业务的内容**。</p>
|
||
<p>缺点:要这样做需要团队达到一致,因此付出一些额外的成本。</p>
|
||
<h2 id="开源应用开源库写法">开源应用、开源库写法</h2>
|
||
<p>与我们日常工作稍有不同的是:工作中的 Release 计划一般都是事先安排好的,不需要一些 CHANGELOG 什么的。而开源应用、开源库需要有对应的 CHANELOG,则添加了什么功能、修改了什么等等。毕竟有很多东西是由社区来维护的。</p>
|
||
<p>因此,这里以做得比较好的开源项目 Angular 中为例展示。Angular 团队建议采用以下的形式:</p>
|
||
<pre><code><type>(<scope>): <subject>
|
||
<BLANK LINE>
|
||
<body>
|
||
<BLANK LINE>
|
||
<footer></code></pre>
|
||
<p>诸如:<code>docs(changelog): update change log to beta.5</code> 中:</p>
|
||
<ul>
|
||
<li>docs 则对应修改的类型</li>
|
||
<li>changelog 则是影响的范围</li>
|
||
<li>subject 则是对应做的事件</li>
|
||
</ul>
|
||
<p>对应的类型有:</p>
|
||
<ul>
|
||
<li>build: 影响构建系统或外部依赖关系的更改(示例范围:gulp,broccoli,npm)</li>
|
||
<li>ci: 更改我们的持续集成文件和脚本(示例范围:Travis,Circle,BrowserStack,SauceLabs)</li>
|
||
<li>docs: 仅文档更改</li>
|
||
<li>feat: 一个新功能</li>
|
||
<li>fix: 修复错误</li>
|
||
<li>perf: 改进性能的代码更改</li>
|
||
<li>refactor: 代码更改,既不修复错误也不添加功能</li>
|
||
<li>style: 不影响代码含义的变化(空白,格式化,缺少分号等)</li>
|
||
<li>test: 添加缺失测试或更正现有测试</li>
|
||
</ul>
|
||
<p>同时还对应了 20+ 的 Scope,可以说这种提交比上面的提交更有挑战。</p>
|
||
<p>(以上的 10 个类型,感谢 Google Translate 提供的快速翻译支持)</p>
|
||
<p>而这样做的优点是,它可以轻松地生成一个 CHANGELOG。与些同时还有一个名为 <code>Conventional Commits</code> 的规范,建议采用相似的形式。</p>
|
||
<h1 id="创建项目文档">创建项目文档</h1>
|
||
<p>我们需要为我们的项目创建一个文档,通常我们可以将核心代码以外的东西都称为文档:</p>
|
||
<ol type="1">
|
||
<li>README</li>
|
||
<li>文档</li>
|
||
<li>示例</li>
|
||
<li>测试</li>
|
||
</ol>
|
||
<p>通常这个会在项目的最上方会有一个项目的简介,如下图所示:</p>
|
||
<figure>
|
||
<img src="./img/github-intro.png" alt="GitHub Project Introduction" /><figcaption>GitHub Project Introduction</figcaption>
|
||
</figure>
|
||
<h2 id="readme">README</h2>
|
||
<p>README通常会显示在GitHub项目的下面,如下图所示:</p>
|
||
<figure>
|
||
<img src="./img/readme-example.png" alt="GitHub README" /><figcaption>GitHub README</figcaption>
|
||
</figure>
|
||
<p>通常一个好的README会让你立马对项目产生兴趣。</p>
|
||
<p>如下面的内容是React项目的简介:</p>
|
||
<figure>
|
||
<img src="./img/react-intro.png" alt="React README" /><figcaption>React README</figcaption>
|
||
</figure>
|
||
<p>下面的内容写清楚了他们的用途:</p>
|
||
<ul>
|
||
<li><strong>Just the UI:</strong> 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.</li>
|
||
<li><strong>Virtual DOM:</strong> 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 <a href="https://facebook.github.io/react-native/">React Native</a>.</li>
|
||
<li><strong>Data flow:</strong> React implements one-way reactive data flow which reduces boilerplate and is easier to reason about than traditional data binding.</li>
|
||
</ul>
|
||
<p>通常在这个README里,还会有:</p>
|
||
<ul>
|
||
<li>针对人群</li>
|
||
<li>安装指南</li>
|
||
<li>示例</li>
|
||
<li>运行的平台</li>
|
||
<li>如何参与贡献</li>
|
||
<li>协议</li>
|
||
</ul>
|
||
<h2 id="官方首页与在线文档">官方首页与在线文档</h2>
|
||
<p>很多开源项目都会有自己的网站,并在上面有一个文档,而有的则会放在<a href="https://readthedocs.org/" class="uri">https://readthedocs.org/</a>。</p>
|
||
<blockquote>
|
||
<p>Read the Docs 托管文档,让文档可以被全文搜索和更易查找。您可以导入您使用任何常用的版本控制系统管理的文档,包括 Mercurial、Git、Subversion 和 Bazaar。 我们支持 webhooks,因此可以在您提交代码时自动构建文档。并且同样也支持版本功能,因此您可以构建来自您代码仓库中某个标签或分支的文档。查看完整的功能列表 。</p>
|
||
</blockquote>
|
||
<p>在一个开源项目中,良好和专业的文档是相当重要的,有时他可能会比软件还会重要。因为如果一个开源项目好用的话,多数人可能不会去查看软件的代码。这就意味着,多数时候他在和你的文档打交道。文档一般会有:API 文档、 配置文档、帮助文档、用户手册、教程等等</p>
|
||
<p>写文档的软件有很多,如Markdown、Doxygen、Docbook等等。</p>
|
||
<h2 id="可用示例">可用示例</h2>
|
||
<p>一个简单上手的示例非常重要,特别是通常我们是在为着某个目的而去使用一个开源项目的是时候,我们希望能马上使用到我们的项目中。</p>
|
||
<p>你希望看到的是,你打开浏览器,输入下面的代码,然后<strong>It Works</strong>:</p>
|
||
<pre><code>var HelloMessage = React.createClass({
|
||
render: function() {
|
||
return <div>Hello {this.props.name}</div>;
|
||
}
|
||
});
|
||
|
||
React.render(
|
||
<HelloMessage name="John" />,
|
||
document.getElementById('container')
|
||
);</code></pre>
|
||
<p>而不是需要繁琐的步骤才能进行下一步。</p>
|
||
<h1 id="改善-github-项目代码质量重构">改善 GitHub 项目代码质量:重构</h1>
|
||
<p>或许你应该知道了,重构是怎样的,你也知道重构能带来什么。在我刚开始学重构和设计模式的时候,我需要去找一些好的示例,以便于我更好的学习。有时候不得不创造一些更好的场景,来实现这些功能。</p>
|
||
<p>有一天,我发现当我需要我一次又一次地重复讲述某些内容,于是我就计划着把这些应该掌握的技能放到GitHub上,也就有了<a href="https://github.com/phodal-archive/artisanstack.github.io">Artisan Stack</a> 计划。</p>
|
||
<p>每个程序员都不可避免地是一个Coder,一个没有掌握好技能的Coder,算不上是手工艺人,但是是手工人。</p>
|
||
<p>艺,需要有创造性的方法。</p>
|
||
<h2 id="为什么重构">为什么重构?</h2>
|
||
<blockquote>
|
||
<p>为了更好的代码。</p>
|
||
</blockquote>
|
||
<p>在经历了一年多的工作之后,我平时的主要工作就是修Bug。刚开始的时候觉得无聊,后来才发现修Bug需要更好的技术。有时候你可能要面对着一坨一坨的代码,有时候你可能要花几天的时间去阅读代码。而,你重写那几十代码可能只会花上你不到一天的时间。但是如果你没办法理解当时为什么这么做,你的修改只会带来更多的bug。修Bug,更多的是维护代码。还是前人总结的那句话对:</p>
|
||
<blockquote>
|
||
<p>写代码容易,读代码难。</p>
|
||
</blockquote>
|
||
<p>假设我们写这些代码只要半天,而别人读起来要一天。为什么不试着用一天的时候去写这些代码,让别人花半天或者更少的时间来理解。</p>
|
||
<p>如果你的代码已经上线,虽然是一坨坨的。但是不要轻易尝试,<code>没有测试的重构</code>。</p>
|
||
<p>从前端开始的原因在于,写得一坨坨且最不容易测试的代码都在前端。</p>
|
||
<p>让我们来看看我们的第一个训练,相当有挑战性。</p>
|
||
<h2 id="重构umarkdown">重构uMarkdown</h2>
|
||
<p>代码及setup请见github: <a href="https://github.com/artisanstack/js-refactor">js-refactor</a></p>
|
||
<h3 id="代码说明">代码说明</h3>
|
||
<p><code>uMarkdown</code>是一个用于将Markdown转化为HTML的库。代码看上去就像一个很典型的过程代码:</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="co">/* code */</span>
|
||
<span class="cf">while</span> ((stra <span class="op">=</span> <span class="va">micromarkdown</span>.<span class="va">regexobject</span>.<span class="va">code</span>.<span class="at">exec</span>(str)) <span class="op">!==</span> <span class="kw">null</span>) <span class="op">{</span>
|
||
str <span class="op">=</span> <span class="va">str</span>.<span class="at">replace</span>(stra[<span class="dv">0</span>]<span class="op">,</span> <span class="st">'<code></span><span class="sc">\n</span><span class="st">'</span> <span class="op">+</span> <span class="va">micromarkdown</span>.<span class="at">htmlEncode</span>(stra[<span class="dv">1</span>]).<span class="at">replace</span>(<span class="ss">/</span><span class="sc">\n</span><span class="ss">/gm</span><span class="op">,</span> <span class="st">'<br/>'</span>).<span class="at">replace</span>(<span class="ss">/</span><span class="sc">\ </span><span class="ss">/gm</span><span class="op">,</span> <span class="st">'&nbsp;'</span>) <span class="op">+</span> <span class="st">'</code></span><span class="sc">\n</span><span class="st">'</span>)<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
|
||
<span class="co">/* headlines */</span>
|
||
<span class="cf">while</span> ((stra <span class="op">=</span> <span class="va">micromarkdown</span>.<span class="va">regexobject</span>.<span class="va">headline</span>.<span class="at">exec</span>(str)) <span class="op">!==</span> <span class="kw">null</span>) <span class="op">{</span>
|
||
count <span class="op">=</span> stra[<span class="dv">1</span>].<span class="at">length</span><span class="op">;</span>
|
||
str <span class="op">=</span> <span class="va">str</span>.<span class="at">replace</span>(stra[<span class="dv">0</span>]<span class="op">,</span> <span class="st">'<h'</span> <span class="op">+</span> count <span class="op">+</span> <span class="st">'>'</span> <span class="op">+</span> stra[<span class="dv">2</span>] <span class="op">+</span> <span class="st">'</h'</span> <span class="op">+</span> count <span class="op">+</span> <span class="st">'>'</span> <span class="op">+</span> <span class="st">'</span><span class="sc">\n</span><span class="st">'</span>)<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
|
||
<span class="co">/* mail */</span>
|
||
<span class="cf">while</span> ((stra <span class="op">=</span> <span class="va">micromarkdown</span>.<span class="va">regexobject</span>.<span class="va">mail</span>.<span class="at">exec</span>(str)) <span class="op">!==</span> <span class="kw">null</span>) <span class="op">{</span>
|
||
str <span class="op">=</span> <span class="va">str</span>.<span class="at">replace</span>(stra[<span class="dv">0</span>]<span class="op">,</span> <span class="st">'<a href="mailto:'</span> <span class="op">+</span> stra[<span class="dv">1</span>] <span class="op">+</span> <span class="st">'">'</span> <span class="op">+</span> stra[<span class="dv">1</span>] <span class="op">+</span> <span class="st">'</a>'</span>)<span class="op">;</span>
|
||
<span class="op">}</span></code></pre></div>
|
||
<p>选这个做重构的开始,不仅仅是因为之前在写<a href="https://github.com/phodal/echoesworks">EchoesWorks</a>的时候进行了很多的重构。而且它更适合于,<code>重构到设计模式</code>的理论。让我们在重构完之后,给作者进行pull request吧。</p>
|
||
<p>Markdown的解析过程,有点类似于<code>Pipe and Filters</code>模式(架构模式)。</p>
|
||
<p>Filter即我们在代码中看到的正规表达式集:</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript">regexobject<span class="op">:</span> <span class="op">{</span>
|
||
<span class="dt">headline</span><span class="op">:</span> <span class="ss">/</span><span class="sc">^(\#{1,6})([^\#\n]+)$</span><span class="ss">/m</span><span class="op">,</span>
|
||
<span class="dt">code</span><span class="op">:</span> <span class="ss">/</span><span class="sc">\s\`\`\`\n?([^`]+)\`\`\`</span><span class="ss">/g</span></code></pre></div>
|
||
<p>他会匹配对应的Markdown类型,随后进行替换和处理。而``str```,就是管理口的输入和输出。</p>
|
||
<p>接着,我们就可以对其进行简单的重构。</p>
|
||
<p>(ps: 推荐用WebStrom来做重构,自带重构功能)</p>
|
||
<p>作为一个示例,我们先提出codeHandler方法,即将上面的</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="co">/* code */</span>
|
||
<span class="cf">while</span> ((stra <span class="op">=</span> <span class="va">micromarkdown</span>.<span class="va">regexobject</span>.<span class="va">code</span>.<span class="at">exec</span>(str)) <span class="op">!==</span> <span class="kw">null</span>) <span class="op">{</span>
|
||
str <span class="op">=</span> <span class="va">str</span>.<span class="at">replace</span>(stra[<span class="dv">0</span>]<span class="op">,</span> <span class="st">'<code></span><span class="sc">\n</span><span class="st">'</span> <span class="op">+</span> <span class="va">micromarkdown</span>.<span class="at">htmlEncode</span>(stra[<span class="dv">1</span>]).<span class="at">replace</span>(<span class="ss">/</span><span class="sc">\n</span><span class="ss">/gm</span><span class="op">,</span> <span class="st">'<br/>'</span>).<span class="at">replace</span>(<span class="ss">/</span><span class="sc">\ </span><span class="ss">/gm</span><span class="op">,</span> <span class="st">'&nbsp;'</span>) <span class="op">+</span> <span class="st">'</code></span><span class="sc">\n</span><span class="st">'</span>)<span class="op">;</span>
|
||
<span class="op">}</span></code></pre></div>
|
||
<p>提取方法成</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript">codeFilter<span class="op">:</span> <span class="kw">function</span> (str<span class="op">,</span> stra) <span class="op">{</span>
|
||
<span class="cf">return</span> <span class="va">str</span>.<span class="at">replace</span>(stra[<span class="dv">0</span>]<span class="op">,</span> <span class="st">'<code></span><span class="sc">\n</span><span class="st">'</span> <span class="op">+</span> <span class="va">micromarkdown</span>.<span class="at">htmlEncode</span>(stra[<span class="dv">1</span>]).<span class="at">replace</span>(<span class="ss">/</span><span class="sc">\n</span><span class="ss">/gm</span><span class="op">,</span> <span class="st">'<br/>'</span>).<span class="at">replace</span>(<span class="ss">/</span><span class="sc">\ </span><span class="ss">/gm</span><span class="op">,</span> <span class="st">'&nbsp;'</span>) <span class="op">+</span> <span class="st">'</code></span><span class="sc">\n</span><span class="st">'</span>)<span class="op">;</span>
|
||
<span class="op">},</span> </code></pre></div>
|
||
<p>while语句就成了</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="cf">while</span> ((stra <span class="op">=</span> <span class="va">regexobject</span>.<span class="va">code</span>.<span class="at">exec</span>(str)) <span class="op">!==</span> <span class="kw">null</span>) <span class="op">{</span>
|
||
str <span class="op">=</span> <span class="kw">this</span>.<span class="at">codeFilter</span>(str<span class="op">,</span> stra)<span class="op">;</span>
|
||
<span class="op">}</span></code></pre></div>
|
||
<p>然后,运行所有的测试。</p>
|
||
<pre><code>grunt test</code></pre>
|
||
<p>同理我们就可以<code>mail</code>、<code>headline</code>等方法进行重构。接着就会变成类似于下面的代码,</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="co">/* code */</span>
|
||
<span class="cf">while</span> ((execStr <span class="op">=</span> <span class="va">regExpObject</span>.<span class="va">code</span>.<span class="at">exec</span>(str)) <span class="op">!==</span> <span class="kw">null</span>) <span class="op">{</span>
|
||
str <span class="op">=</span> <span class="at">codeHandler</span>(str<span class="op">,</span> execStr)<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
|
||
<span class="co">/* headlines */</span>
|
||
<span class="cf">while</span> ((execStr <span class="op">=</span> <span class="va">regExpObject</span>.<span class="va">headline</span>.<span class="at">exec</span>(str)) <span class="op">!==</span> <span class="kw">null</span>) <span class="op">{</span>
|
||
str <span class="op">=</span> <span class="at">headlineHandler</span>(str<span class="op">,</span> execStr)<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
|
||
<span class="co">/* lists */</span>
|
||
<span class="cf">while</span> ((execStr <span class="op">=</span> <span class="va">regExpObject</span>.<span class="va">lists</span>.<span class="at">exec</span>(str)) <span class="op">!==</span> <span class="kw">null</span>) <span class="op">{</span>
|
||
str <span class="op">=</span> <span class="at">listHandler</span>(str<span class="op">,</span> execStr)<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
|
||
<span class="co">/* tables */</span>
|
||
<span class="cf">while</span> ((execStr <span class="op">=</span> <span class="va">regExpObject</span>.<span class="va">tables</span>.<span class="at">exec</span>(str)) <span class="op">!==</span> <span class="kw">null</span>) <span class="op">{</span>
|
||
str <span class="op">=</span> <span class="at">tableHandler</span>(str<span class="op">,</span> execStr<span class="op">,</span> strict)<span class="op">;</span>
|
||
<span class="op">}</span></code></pre></div>
|
||
<p>然后你也看到了,上面有一堆重复的代码,接着让我们用JavaScript的<code>奇技淫巧</code>,即apply方法,把上面的重复代码变成。</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript">[<span class="st">'code'</span><span class="op">,</span> <span class="st">'headline'</span><span class="op">,</span> <span class="st">'lists'</span><span class="op">,</span> <span class="st">'tables'</span><span class="op">,</span> <span class="st">'links'</span><span class="op">,</span> <span class="st">'mail'</span><span class="op">,</span> <span class="st">'url'</span><span class="op">,</span> <span class="st">'smlinks'</span><span class="op">,</span> <span class="st">'hr'</span>].<span class="at">forEach</span>(<span class="kw">function</span> (type) <span class="op">{</span>
|
||
<span class="cf">while</span> ((stra <span class="op">=</span> regexobject[type].<span class="at">exec</span>(str)) <span class="op">!==</span> <span class="kw">null</span>) <span class="op">{</span>
|
||
str <span class="op">=</span> that[(type <span class="op">+</span> <span class="st">'Handler'</span>)].<span class="at">apply</span>(that<span class="op">,</span> [stra<span class="op">,</span> str<span class="op">,</span> strict])<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
<span class="op">}</span>)<span class="op">;</span></code></pre></div>
|
||
<p>进行测试,blabla,都是过的。</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"> Markdown
|
||
✓ should parse h1<span class="op">~</span>h3
|
||
✓ should parse link
|
||
✓ should special link
|
||
✓ should parse font style
|
||
✓ should parse code
|
||
✓ should parse ul list
|
||
✓ should parse ul table
|
||
✓ should <span class="cf">return</span> correctly <span class="kw">class</span> name</code></pre></div>
|
||
<p>快来试试吧, <a href="https://github.com/artisanstack/js-refactor" class="uri">https://github.com/artisanstack/js-refactor</a></p>
|
||
<p>是时候讨论这个Refactor利器了,最初看到这个重构的过程是从ThoughtWorks郑大晔校开始的,只是之前对于Java的另外一个编辑器Eclipse的坏感。。这些在目前已经不是很重要了,试试这个公司里面应用广泛的编辑器。</p>
|
||
<h2 id="intellij-idea重构">Intellij Idea重构</h2>
|
||
<p>开发的流程大致就是这样子的,测试先行算是推荐的。</p>
|
||
<pre><code>编写测试->功能代码->修改测试->重构</code></pre>
|
||
<p>上次在和buddy聊天的时候,才知道测试在功能简单的时候是后行的,在功能复杂不知道怎么下手的时候是先行的。</p>
|
||
<p>开始之前请原谅我对于Java语言的一些无知,然后,看一下我写的Main函数:</p>
|
||
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">package</span><span class="im"> com.phodal.learing;</span>
|
||
|
||
<span class="kw">public</span> <span class="kw">class</span> Main {
|
||
|
||
<span class="kw">public</span> <span class="dt">static</span> <span class="dt">void</span> <span class="fu">main</span>(<span class="bu">String</span>[] args) {
|
||
<span class="dt">int</span> c=<span class="kw">new</span> <span class="fu">Cal</span>().<span class="fu">add</span>(<span class="dv">1</span>,<span class="dv">2</span>);
|
||
<span class="dt">int</span> d=<span class="kw">new</span> <span class="fu">Cal2</span>().<span class="fu">sub</span>(<span class="dv">2</span>,<span class="dv">1</span>);
|
||
<span class="bu">System</span>.<span class="fu">out</span>.<span class="fu">println</span>(<span class="st">"Hello,s"</span>);
|
||
<span class="bu">System</span>.<span class="fu">out</span>.<span class="fu">println</span>(c);
|
||
<span class="bu">System</span>.<span class="fu">out</span>.<span class="fu">println</span>(d);
|
||
}
|
||
}</code></pre></div>
|
||
<p>代码写得还好(自我感觉),先不管Cal和Cal2两个类。大部分都能看懂,除了c,d不知道他们表达的是什么意思,于是。</p>
|
||
<h3 id="rename">Rename</h3>
|
||
<p><strong>快捷键:Shift+F6</strong></p>
|
||
<p><strong>作用:重命名</strong></p>
|
||
<ul>
|
||
<li>把光标丢到int c中的c,按下shift+f6,输入result_add</li>
|
||
<li>把光标移到int d中的d,按下shift+f6,输入result_sub</li>
|
||
</ul>
|
||
<p>于是就有</p>
|
||
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">package</span><span class="im"> com.phodal.learing;</span>
|
||
|
||
<span class="kw">public</span> <span class="kw">class</span> Main {
|
||
|
||
<span class="kw">public</span> <span class="dt">static</span> <span class="dt">void</span> <span class="fu">main</span>(<span class="bu">String</span>[] args) {
|
||
<span class="dt">int</span> result_add=<span class="kw">new</span> <span class="fu">Cal</span>().<span class="fu">add</span>(<span class="dv">1</span>,<span class="dv">2</span>);
|
||
<span class="dt">int</span> result_sub=<span class="kw">new</span> <span class="fu">Cal2</span>().<span class="fu">sub</span>(<span class="dv">2</span>,<span class="dv">1</span>);
|
||
<span class="bu">System</span>.<span class="fu">out</span>.<span class="fu">println</span>(<span class="st">"Hello,s"</span>);
|
||
<span class="bu">System</span>.<span class="fu">out</span>.<span class="fu">println</span>(result_add);
|
||
<span class="bu">System</span>.<span class="fu">out</span>.<span class="fu">println</span>(result_sub);
|
||
}
|
||
}</code></pre></div>
|
||
<h3 id="extract-method">Extract Method</h3>
|
||
<p><strong>快捷键:alt+command+m</strong></p>
|
||
<p><strong>作用:扩展方法</strong></p>
|
||
<ul>
|
||
<li>选中System.out.println(result_add);</li>
|
||
<li>按下alt+command+m</li>
|
||
<li>在弹出的窗口中输入mprint</li>
|
||
</ul>
|
||
<p>于是有了</p>
|
||
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">public</span> <span class="dt">static</span> <span class="dt">void</span> <span class="fu">main</span>(<span class="bu">String</span>[] args) {
|
||
<span class="dt">int</span> result_add=<span class="kw">new</span> <span class="fu">Cal</span>().<span class="fu">add</span>(<span class="dv">1</span>,<span class="dv">2</span>);
|
||
<span class="dt">int</span> result_sub=<span class="kw">new</span> <span class="fu">Cal2</span>().<span class="fu">sub</span>(<span class="dv">2</span>,<span class="dv">1</span>);
|
||
<span class="bu">System</span>.<span class="fu">out</span>.<span class="fu">println</span>(<span class="st">"Hello,s"</span>);
|
||
<span class="fu">mprint</span>(result_add);
|
||
<span class="fu">mprint</span>(result_sub);
|
||
}
|
||
|
||
<span class="kw">private</span> <span class="dt">static</span> <span class="dt">void</span> <span class="fu">mprint</span>(<span class="dt">int</span> result_sub) {
|
||
<span class="bu">System</span>.<span class="fu">out</span>.<span class="fu">println</span>(result_sub);
|
||
}</code></pre></div>
|
||
<p>似乎我们不应该这样对待System.out.println,那么让我们内联回去</p>
|
||
<h3 id="inline-method">Inline Method</h3>
|
||
<p><strong>快捷键:alt+command+n</strong></p>
|
||
<p><strong>作用:内联方法</strong></p>
|
||
<ul>
|
||
<li>选中main中的mprint</li>
|
||
<li>alt+command+n</li>
|
||
<li>选中Inline all invocations and remove the method(2 occurrences) 点确定</li>
|
||
</ul>
|
||
<p>然后我们等于什么也没有做了~~:</p>
|
||
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">public</span> <span class="dt">static</span> <span class="dt">void</span> <span class="fu">main</span>(<span class="bu">String</span>[] args) {
|
||
<span class="dt">int</span> result_add=<span class="kw">new</span> <span class="fu">Cal</span>().<span class="fu">add</span>(<span class="dv">1</span>,<span class="dv">2</span>);
|
||
<span class="dt">int</span> result_sub=<span class="kw">new</span> <span class="fu">Cal2</span>().<span class="fu">sub</span>(<span class="dv">2</span>,<span class="dv">1</span>);
|
||
<span class="bu">System</span>.<span class="fu">out</span>.<span class="fu">println</span>(<span class="st">"Hello,s"</span>);
|
||
<span class="bu">System</span>.<span class="fu">out</span>.<span class="fu">println</span>(result_add);
|
||
<span class="bu">System</span>.<span class="fu">out</span>.<span class="fu">println</span>(result_sub);
|
||
}</code></pre></div>
|
||
<p>似乎这个例子不是很好,但是够用来说明了。</p>
|
||
<h3 id="pull-members-up">Pull Members Up</h3>
|
||
<p>开始之前让我们先看看Cal2类:</p>
|
||
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">public</span> <span class="kw">class</span> Cal2 <span class="kw">extends</span> Cal {
|
||
|
||
<span class="kw">public</span> <span class="dt">int</span> <span class="fu">sub</span>(<span class="dt">int</span> a,<span class="dt">int</span> b){
|
||
<span class="kw">return</span> a-b;
|
||
}
|
||
}</code></pre></div>
|
||
<p>以及Cal2的父类Cal</p>
|
||
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">public</span> <span class="kw">class</span> Cal {
|
||
|
||
<span class="kw">public</span> <span class="dt">int</span> <span class="fu">add</span>(<span class="dt">int</span> a,<span class="dt">int</span> b){
|
||
<span class="kw">return</span> a+b;
|
||
}
|
||
|
||
}</code></pre></div>
|
||
<p>最后的结果,就是将Cal2类中的sub方法,提到父类:</p>
|
||
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">public</span> <span class="kw">class</span> Cal {
|
||
|
||
<span class="kw">public</span> <span class="dt">int</span> <span class="fu">add</span>(<span class="dt">int</span> a,<span class="dt">int</span> b){
|
||
<span class="kw">return</span> a+b;
|
||
}
|
||
|
||
<span class="kw">public</span> <span class="dt">int</span> <span class="fu">sub</span>(<span class="dt">int</span> a,<span class="dt">int</span> b){
|
||
<span class="kw">return</span> a-b;
|
||
}
|
||
}</code></pre></div>
|
||
<p>而我们所要做的就是鼠标右键</p>
|
||
<h3 id="重构之以查询取代临时变量">重构之以查询取代临时变量</h3>
|
||
<p>快捷键</p>
|
||
<p>Mac: 木有</p>
|
||
<p>Windows/Linux: 木有</p>
|
||
<p>或者: <code>Shift</code>+<code>alt</code>+<code>command</code>+<code>T</code> 再选择 <code>Replace Temp with Query</code></p>
|
||
<p>鼠标: <strong>Refactor</strong> | <code>Replace Temp with Query</code></p>
|
||
<h4 id="重构之前">重构之前</h4>
|
||
<p>过多的临时变量会让我们写出更长的函数,函数不应该太多,以便使功能单一。这也是重构的另外的目的所在,只有函数专注于其功能,才会更容易读懂。</p>
|
||
<p>以书中的代码为例</p>
|
||
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">import</span><span class="im"> java.lang.System;</span>
|
||
|
||
<span class="kw">public</span> <span class="kw">class</span> replaceTemp {
|
||
<span class="kw">public</span> <span class="dt">void</span> <span class="fu">count</span>() {
|
||
<span class="dt">double</span> basePrice = _quantity * _itemPrice;
|
||
<span class="kw">if</span> (basePrice > <span class="dv">1000</span>) {
|
||
<span class="kw">return</span> basePrice * <span class="fl">0.95</span>;
|
||
} <span class="kw">else</span> {
|
||
<span class="kw">return</span> basePrice * <span class="fl">0.98</span>;
|
||
}
|
||
}
|
||
}</code></pre></div>
|
||
<h4 id="重构">重构</h4>
|
||
<p>选中<code>basePrice</code>很愉快地拿鼠标点上面的重构</p>
|
||
<figure>
|
||
<img src="./img/replace.jpg" alt="Replace Temp With Query" /><figcaption>Replace Temp With Query</figcaption>
|
||
</figure>
|
||
<p>便会返回</p>
|
||
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">import</span><span class="im"> java.lang.System;</span>
|
||
|
||
<span class="kw">public</span> <span class="kw">class</span> replaceTemp {
|
||
<span class="kw">public</span> <span class="dt">void</span> <span class="fu">count</span>() {
|
||
<span class="kw">if</span> (<span class="fu">basePrice</span>() > <span class="dv">1000</span>) {
|
||
<span class="kw">return</span> <span class="fu">basePrice</span>() * <span class="fl">0.95</span>;
|
||
} <span class="kw">else</span> {
|
||
<span class="kw">return</span> <span class="fu">basePrice</span>() * <span class="fl">0.98</span>;
|
||
}
|
||
}
|
||
|
||
<span class="kw">private</span> <span class="dt">double</span> <span class="fu">basePrice</span>() {
|
||
<span class="kw">return</span> _quantity * _itemPrice;
|
||
}
|
||
}</code></pre></div>
|
||
<p>而实际上我们也可以</p>
|
||
<ol type="1">
|
||
<li><p>选中</p>
|
||
<p>_quantity * _itemPrice</p></li>
|
||
<li><p>对其进行<code>Extrace Method</code></p></li>
|
||
<li><p>选择<code>basePrice</code>再<code>Inline Method</code></p></li>
|
||
</ol>
|
||
<h4 id="intellij-idea重构-1">Intellij IDEA重构</h4>
|
||
<p>在Intellij IDEA的文档中对此是这样的例子</p>
|
||
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">public</span> <span class="kw">class</span> replaceTemp {
|
||
|
||
<span class="kw">public</span> <span class="dt">void</span> <span class="fu">method</span>() {
|
||
<span class="bu">String</span> str = <span class="st">"str"</span>;
|
||
<span class="bu">String</span> aString = <span class="fu">returnString</span>().<span class="fu">concat</span>(str);
|
||
<span class="bu">System</span>.<span class="fu">out</span>.<span class="fu">println</span>(aString);
|
||
}
|
||
|
||
}</code></pre></div>
|
||
<p>接着我们选中<code>aString</code>,再打开重构菜单,或者</p>
|
||
<p><code>Command</code>+<code>Alt</code>+<code>Shift</code>+<code>T</code> 再选中Replace Temp with Query</p>
|
||
<p>便会有下面的结果:</p>
|
||
<pre class="javas"><code>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);
|
||
}
|
||
|
||
}</code></pre>
|
||
<h1 id="改善-github-项目代码质量测试">改善 GitHub 项目代码质量:测试</h1>
|
||
<h2 id="tdd">TDD</h2>
|
||
<p>虽然接触的TDD时间不算短,然而真正在实践TDD上的时候少之又少。除去怎么教人TDD,就是与人结对编程时的switch,或许是受限于当前的开发流程。</p>
|
||
<p>偶然间在开发一个物联网相关的开源项目——<a href="https://github.com/phodal/lan">Lan</a>的时候,重拾了这个过程。不得不说提到的一点是,在我们的开发流程中<strong>测试是由相关功能开发人员写的</strong>,有时候测试是一种很具挑战性的工作。久而久之,为自己的开源项目写测试变成一种自然而然的事。有时没有测试,反而变得<strong>没有安全感</strong>。</p>
|
||
<h3 id="一次测试驱动开发">一次测试驱动开发</h3>
|
||
<p>之前正在重写一个<a href="http://www.phodal.com/iot">物联网</a>的服务端,主要便是结合CoAP、MQTT、HTTP等协议构成一个物联网的云服务。现在,主要的任务是集中于协议与授权。由于,不同协议间的授权是不一样的,最开始的时候我先写了一个http put授权的功能,而在起先的时候是如何测试的呢?</p>
|
||
<pre><code>curl --user root:root -X PUT -d '{ "dream": 1 }' -H "Content-Type: application/json" http://localhost:8899/topics/test</code></pre>
|
||
<p>我只要顺利在request中看有无<code>req.headers.authorization</code>,我便可以继续往下,接着给个判断。毕竟,我们对HTTP协议还是蛮清楚的。</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="cf">if</span> (<span class="op">!</span><span class="va">req</span>.<span class="va">headers</span>.<span class="at">authorization</span>) <span class="op">{</span>
|
||
<span class="va">res</span>.<span class="at">statusCode</span> <span class="op">=</span> <span class="dv">401</span><span class="op">;</span>
|
||
<span class="va">res</span>.<span class="at">setHeader</span>(<span class="st">'WWW-Authenticate'</span><span class="op">,</span> <span class="st">'Basic realm="Secure Area"'</span>)<span class="op">;</span>
|
||
<span class="cf">return</span> <span class="va">res</span>.<span class="at">end</span>(<span class="st">'Unauthorized'</span>)<span class="op">;</span>
|
||
<span class="op">}</span></code></pre></div>
|
||
<p>可是除了HTTP协议,还有MQTT和CoAP。对于MQTT协议来说,那还算好,毕竟自带授权,如:</p>
|
||
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">mosquitto_pub</span> -u root -P root -h localhost -d -t lettuce -m <span class="st">"Hello, MQTT. This is my first message."</span></code></pre></div>
|
||
<p>便可以让我们简单地完成这个功能,然而有的协议是没有这样的功能如CoAP协议中是用Option来进行授权的。现在的工具如libcoap只能有如下的简单功能</p>
|
||
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">coap-client</span> -m get coap://127.0.0.1:5683/topics/zero -T</code></pre></div>
|
||
<p>于是,先写了个测试脚本来验证功能。</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="kw">var</span> coap <span class="op">=</span> <span class="at">require</span>(<span class="st">'coap'</span>)<span class="op">;</span>
|
||
<span class="kw">var</span> request <span class="op">=</span> <span class="va">coap</span>.<span class="at">request</span><span class="op">;</span>
|
||
<span class="kw">var</span> req <span class="op">=</span> <span class="at">request</span>(<span class="op">{</span><span class="dt">hostname</span><span class="op">:</span> <span class="st">'localhost'</span><span class="op">,</span><span class="dt">port</span><span class="op">:</span><span class="dv">5683</span><span class="op">,</span><span class="dt">pathname</span><span class="op">:</span> <span class="st">''</span><span class="op">,</span><span class="dt">method</span><span class="op">:</span> <span class="st">'POST'</span><span class="op">}</span>)<span class="op">;</span>
|
||
|
||
...
|
||
|
||
<span class="va">req</span>.<span class="at">setHeader</span>(<span class="st">"Accept"</span><span class="op">,</span> <span class="st">"application/json"</span>)<span class="op">;</span>
|
||
<span class="va">req</span>.<span class="at">setOption</span>(<span class="st">'Block2'</span><span class="op">,</span> [<span class="kw">new</span> <span class="at">Buffer</span>(<span class="st">'phodal'</span>)<span class="op">,</span> <span class="kw">new</span> <span class="at">Buffer</span>(<span class="st">'phodal'</span>)])<span class="op">;</span>
|
||
|
||
...
|
||
|
||
<span class="va">req</span>.<span class="at">end</span>()<span class="op">;</span></code></pre></div>
|
||
<p>写完测试脚本后发现不对了,这个不应该是测试的代码吗? 于是将其放到了spec中,接着发现了上面的全部功能的实现过程为什么不用TDD实现呢?</p>
|
||
<h3 id="说说tdd">说说TDD</h3>
|
||
<p>测试驱动开发是一个很“古老”的程序开发方法,然而由于国内的开发流程的问题——即开发人员负责功能的测试,导致这么好的一项技术没有在国内推广。</p>
|
||
<p>测试驱动开发的主要过程是:</p>
|
||
<ol type="1">
|
||
<li>先写功能的测试</li>
|
||
<li>实现功能代码</li>
|
||
<li>提交代码(commit -> 保证功能正常)</li>
|
||
<li>重构功能代码</li>
|
||
</ol>
|
||
<p>而对于这样的一个物联网项目来说,我已经有了几个有利的前提:</p>
|
||
<ol type="1">
|
||
<li>已经有了原型</li>
|
||
<li>框架设计</li>
|
||
</ol>
|
||
<h3 id="tdd思考">TDD思考</h3>
|
||
<p>通常在我的理解下,TDD是可有可无的。既然我知道了我要实现的大部分功能,而且我也知道如何实现。与此同时,对Code Smell也保持着警惕、要保证功能被测试覆盖。那么,总的来说TDD带来的价值并不大。</p>
|
||
<p>然而,在当前这种情况下,我知道我想要的功能,但是我并不理解其深层次的功能。我需要花费大量的时候来理解,它为什么是这样的,需要先有一些脚本来知道它是怎么工作的。TDD变显得很有价值,换句话来说,在现有的情况下,TDD对于我们不了解的一些事情,可以驱动出更多的开发。毕竟在我们完成测试脚本之后,我们也会发现这些测试脚本成为了代码的一部分。</p>
|
||
<p>在这种理想的情况下,我们为什么不TDD呢?</p>
|
||
<h2 id="功能测试">功能测试</h2>
|
||
<h3 id="轻量级网站测试twill">轻量级网站测试TWill</h3>
|
||
<blockquote>
|
||
<p>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.</p>
|
||
</blockquote>
|
||
<p>之所以说轻量的原因是他是拿命令行测试的,还有DSL,还有Python。</p>
|
||
<p>除此之外,还可以拿它做压力测试,这种压力测试和一般的不一样。可以模拟整个过程,比如同时有多少人登陆你的网站。</p>
|
||
<p>不过,它有一个限制是没有JavaScript。</p>
|
||
<p>看了一下源码,大概原理就是用<code>requests</code>下载html,接着用<code>lxml</code>解析html,比较有意思的是内嵌了一个<code>DSL</code>。</p>
|
||
<p>这是一个Python的库。</p>
|
||
<pre><code> pip install twill</code></pre>
|
||
<h3 id="twill-登陆测试">Twill 登陆测试</h3>
|
||
<p>1.启动我们的应用。</p>
|
||
<p>2.进入twill shell</p>
|
||
<pre><code>twill-sh
|
||
-= Welcome to twill! =-
|
||
current page: *empty page*</code></pre>
|
||
<p>3.打开网页</p>
|
||
<pre><code>>> 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</code></pre>
|
||
<p>4.显示表单</p>
|
||
<pre><code> >> 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</code></pre>
|
||
<p>5.填充表单</p>
|
||
<pre><code>formclear 1
|
||
fv 1 email test@tes.com
|
||
fv 1 password test</code></pre>
|
||
<p>6.修改action</p>
|
||
<pre><code>formaction 1 http://127.0.0.1:5000/login</code></pre>
|
||
<p>7.提交表单</p>
|
||
<pre><code>>> submit
|
||
Note: submit is using submit button: name="login", value="登入"
|
||
current page: http://127.0.0.1:5000/</code></pre>
|
||
<p>发现重定向到首页了。</p>
|
||
<h3 id="twill-测试脚本">Twill 测试脚本</h3>
|
||
<p>当然我们也可以用脚本直接来测试<code>login.twill</code>:</p>
|
||
<pre><code>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</code></pre>
|
||
<p>运行</p>
|
||
<pre><code> twill-sh login.twill</code></pre>
|
||
<p>结果</p>
|
||
<pre><code>>> 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.</code></pre>
|
||
<p>一个成功的测试诞生了。</p>
|
||
<h2 id="fake-server">Fake Server</h2>
|
||
<p>实践了一下怎么用sinon去fake server,还没用respondWith,于是写一下。</p>
|
||
<p>这里需要用到sinon框架来测试。</p>
|
||
<p>当我们fetch的时候,我们就可以返回我们想要fake的结果。</p>
|
||
<pre><code> 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)
|
||
]
|
||
);
|
||
});</code></pre>
|
||
<p>于是在 afterEach 的时候,我们需要恢复这个server。</p>
|
||
<pre><code>afterEach(function() {
|
||
this.server.restore();
|
||
});</code></pre>
|
||
<p>接着写一个jasmine测试来测试</p>
|
||
<pre><code>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);
|
||
});
|
||
});</code></pre>
|
||
<h1 id="如何推广">如何推广</h1>
|
||
<p>除了擅长编写 md 电子书来攒 star,我还写了一系列的开源软件,也掌握了一些项目运营的技巧。</p>
|
||
<p><strong>开源并不是你把软件、README 写好就行了,还有详细的文档、示例程序等等</strong>。</p>
|
||
<p><strong>开源也不是你的项目好了,就会有一堆人参与进来</strong>。</p>
|
||
<p><strong>开源还要你帮助别人解决 Bug,……</strong>。</p>
|
||
<p><strong>人们做事都是有原因的</strong>,即动机。再举例一下,如果你的项目不够火,别人都没听过,那么<strong>写到简历上可能没啥用</strong>。</p>
|
||
<h2 id="marketing-first">Marketing First</h2>
|
||
<p>开源需要一些营销的技巧,这些技巧可以帮你吸引关注。举个简单的例子,司徒正美的 <a href="https://github.com/RubyLouvre/avalon">avalon</a> 框架出身得很早,也 MV* 方面也做得很不错,但是在 marketing 上就……。以至于国内的很多前端,都不了解这个框架,要不今天在国内可能就是 AVRR 四大框架了。</p>
|
||
<p>Vue 不是因为好用,而一下子火了。这一点我印象特别深,当时在 GitHub Trending 上看到了这个项目,发现它还不能很好地 work。</p>
|
||
<p>而如文章 《<a href="http://blog.evanyou.me/2014/02/11/first-week-of-launching-an-oss-project/">FIRST WEEK OF LAUNCHING VUE.JS</a>》所说,项目刚开始的时候作者做了一系列的营销计划:</p>
|
||
<ul>
|
||
<li>HackerNews</li>
|
||
<li>Reddit /r/javascript</li>
|
||
<li>EchoJS</li>
|
||
<li>The DailyJS blog</li>
|
||
<li>JavaScript Weekly</li>
|
||
<li>Maintain a project Twitter account(维护项目的 Vue 账户)</li>
|
||
</ul>
|
||
<p>除此,文中还提到了一篇文章《<a href="https://hacks.mozilla.org/2013/05/how-to-spread-the-word-about-your-code/?utm_source=statuscode&utm_medium=email">How to Spread The Word About Your Code</a>》 。</p>
|
||
<p>这一点相当的有意思,如果你的想法好的话,那么大家都会肯定,点下链接,为你来个 star。那么,你就获得更好的动力去做这件事。项目也在开头的时候,获得了相当多的关注。而如果大家觉得你的项目没有新意的话,那么你懂的~。</p>
|
||
<p>除此,还有一种可能是,你的 ID 不够 fancy,即你在社区的影响上比较少。此时,就需要<strong>一点点慢慢积累人气</strong>了。当你积累了一些人气,你就能和松本行弘一样,在创建 mRuby 的时候就有 1000+ 的 star。并且,在社区上还有一些相关的文章介绍,各个头条也由他的粉丝发了上去。如,一年多以前,我创建了 <a href="https://github.com/phodal/mole">mole</a> 项目。</p>
|
||
<figure>
|
||
<img src="./img/mole.png" alt="Mole" /><figcaption>Mole</figcaption>
|
||
</figure>
|
||
<p>当时,是为了给自己做一个基于 GitHub 云笔记的工具,在完成度到一定程度的时候。我在我的微信公从号上发了相关的介绍,第二天就有 100+ 的 star 了,还接收至最一些鼓舞的话语。对应于国内则有:</p>
|
||
<ul>
|
||
<li>极客头条</li>
|
||
<li>掘金</li>
|
||
<li>开发者头条</li>
|
||
<li>v2ex</li>
|
||
<li>知乎</li>
|
||
<li>不成器的微博</li>
|
||
</ul>
|
||
<p>所以,你觉得呢?</p>
|
||
<h2 id="编写一个好的-readme">编写一个好的 README</h2>
|
||
<p>在一个开源项目里,README 是最重要的内容。它快速地介绍了这个项目,并决定了它能不能吸引用户:</p>
|
||
<ul>
|
||
<li><strong>这个项目做什么?</strong></li>
|
||
<li><strong>它解决了什么问题</strong></li>
|
||
<li><strong>它有什么特性</strong> — <strong>hello, world 示例</strong></li>
|
||
</ul>
|
||
<h3 id="这个项目做什么一句话文案">这个项目做什么——一句话文案</h3>
|
||
<p>GitHub 的 Description 是我们在 Hacking News、GitHub Trneding 等等,第一时间看到的介绍。也是我们能快速介绍给别人的东西,如下图所示:</p>
|
||
<figure>
|
||
<img src="./img/github-trending-example.png" alt="GitHub Trending" /><figcaption>GitHub Trending</figcaption>
|
||
</figure>
|
||
<p>这一句话,必须简单明了也介绍,它是干什么的。</p>
|
||
<p>如 Angular 的一句话方案是:One framework. Mobile & desktop.</p>
|
||
<p>而 React 是:A declarative, efficient, and flexible JavaScript library for building user interfaces.</p>
|
||
<p>Vue 则是:A progressive, incrementally-adoptable JavaScript framework for building UI on the web.</p>
|
||
<h3 id="它解决了什么问题">它解决了什么问题</h3>
|
||
<p>上面的一句话描述,它不能很好地说明,它能解决什么问题。</p>
|
||
<p>如下是今天在 GitHub Trending 上榜的 RPC 项目的简介:</p>
|
||
<blockquote>
|
||
<p>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:</p>
|
||
</blockquote>
|
||
<figure>
|
||
<img src="./img/rpc-example.png" alt="RPC 开源项目" /><figcaption>RPC 开源项目</figcaption>
|
||
</figure>
|
||
<p>以上便是这个项目能解决的问题,不过这个项目能解决的问题倒是比较长,哈哈哈。</p>
|
||
<h3 id="它有什么特性">它有什么特性</h3>
|
||
<p>当我们有 A、B、C 几个不同的框架的时候,作为一个开发人员,就需要对比他们的特性,。如下是 Go 语言实现的 MQTT 示例:</p>
|
||
<figure>
|
||
<img src="./img/go-mqtt.png" alt="GO MQTT 示例" /><figcaption>GO MQTT 示例</figcaption>
|
||
</figure>
|
||
<p>这个项目只支持的 Qos 级别为 0。如果我们需要的级别是 1,那么就不能用这个项目了。</p>
|
||
<p>又比如 lodash 项目:</p>
|
||
<blockquote>
|
||
<p>Lodash makes JavaScript easier by taking the hassle out of working with arrays, numbers, objects, strings, etc. Lodash’s modular methods are great for:</p>
|
||
</blockquote>
|
||
<ul>
|
||
<li>Iterating arrays, objects, & strings</li>
|
||
<li>Manipulating & testing values</li>
|
||
<li>Creating composite functions</li>
|
||
</ul>
|
||
<p>你会怎么写?脸皮够厚的话,可以直接写一下,与其它项目的对比,blabla:</p>
|
||
<figure>
|
||
<img src="./img/comparison.png" alt="对比其它项目" /><figcaption>对比其它项目</figcaption>
|
||
</figure>
|
||
<p>当然了,这种事不能太过,要不是会招来一堆黑。</p>
|
||
<h3 id="安装及hello-world-示例">安装及hello, world 示例</h3>
|
||
<p>在我们看完了上面的介绍之后,紧接着接一个 hello, world 的示例。在运行 hello, world 之前,我们可能需要一些额外的安装工作,如:</p>
|
||
<pre><code>npm install koa</code></pre>
|
||
<p>如 Koa 的示例:</p>
|
||
<pre><code>const Koa = require('koa');
|
||
const app = new Koa();
|
||
|
||
// response
|
||
app.use(ctx => {
|
||
ctx.body = 'Hello Koa';
|
||
});
|
||
|
||
app.listen(3000);</code></pre>
|
||
<p>作为一个程序员,你应该懂得它的重要性。</p>
|
||
<p>好在这里的安装工作只有两步,而不是:</p>
|
||
<figure>
|
||
<img src="./img/lan-example.png" alt="Lan 安装过程" /><figcaption>Lan 安装过程</figcaption>
|
||
</figure>
|
||
<p>对于那些需要复杂的安装过程的软件,应该简化安装过程,如提供 Docker 镜像,或者直接提供一个可运行的 Demo 环境。以免用户在看完 README 之后,直接放弃了使用该库。</p>
|
||
<h2 id="技术文档">技术文档</h2>
|
||
<p>好了,依一个开发人员的角度,如果上面的步骤一切顺利的话,接下来,便是使用这个开源项目来完成我们的功能。这个时候,我们开始转移注意力到文档上了。</p>
|
||
<p>由于,之前在某一个项目,经历过一个第三方 API 文档的大坑——文档中只罗列了 API 的用法。如下 Intellij Idea 生成的结构图:</p>
|
||
<figure>
|
||
<img src="./img/api-examples.png" alt="API 示例" /><figcaption>API 示例</figcaption>
|
||
</figure>
|
||
<p>文档中上,罗列了每个函数,以及每个函数需要的参数。我使用 Intellij Idea 直接反编译 jar 包,看到的信息都比文档多多了。文档上,没有任务示例,甚至连怎么初始化这个库的代码都没有。</p>
|
||
<p>WTF!</p>
|
||
<h3 id="技术文档-1">技术文档</h3>
|
||
<p>对于一个复杂的开源项目来说,文档上要写明安装、编译、配置等等的过程。如下图所示:</p>
|
||
<figure>
|
||
<img src="./img/python-social-auth-example.png" alt="Python Social Auth 文档" /><figcaption>Python Social Auth 文档</figcaption>
|
||
</figure>
|
||
<p>并且在我们发布包的时候,就要不断地去重复这个过程——如果你使用了自动化测试,那么这个过程便自动完成了。</p>
|
||
<p>如果我们的项目使用起来相当的简单,那么我们就可以只写一些示例代码即可。</p>
|
||
<p>并且,我们可以将文档直接入到代码里。它可以有效地减少文档不同步,带来的一些问题。</p>
|
||
<figure>
|
||
<img src="./img/lodash-code-example.png" alt="Lodash 示例" /><figcaption>Lodash 示例</figcaption>
|
||
</figure>
|
||
<p>上图是使用了 jsdoc 的 Lodash 示例。</p>
|
||
<p>除了上面的示例,我们还可以录制一些视频,写一些文章说明项目的思考、架构等等。</p>
|
||
<h3 id="更多的示例程序">更多的示例程序</h3>
|
||
<p>示例代码本身也是文档的一部分,不要问我为什么~~。</p>
|
||
<p>反正,除了一个 hello, world,你还要有各种场景下的示例:</p>
|
||
<figure>
|
||
<img src="./img/redux-examples.png" alt="Redux" /><figcaption>Redux</figcaption>
|
||
</figure>
|
||
<p>没有这么多示例,敢说自己是好用的开源项目?</p>
|
||
<h3 id="编写技术文章书籍">编写技术文章、书籍</h3>
|
||
<p>到目前为止,我们做了一系列 markdown 相关的工作,却也还没有结束。要知道只有真正写过一系列开源项目的人,才能体会到什么是 markdown 程序员~~。</p>
|
||
<p>官方文档,一般要以比较正式的口吻来描述过程,这种写法相当的压抑。如果要用轻松诙谐的口气,那么就可以写一系列的技术文章。假如这是一个前端框架,那么我们可以介绍它如何与某个后端框架配合使用;又或者是,它与某个框架的对比等等。</p>
|
||
<p>好了,一切顺利了,那么下一步就是吸引更多的人参与到项目上来了。</p>
|
||
<h2 id="鼓励吸引贡献者">鼓励、吸引贡献者</h2>
|
||
<p>要吸引其它开发人员来到你的项目,不是一件容易的事。</p>
|
||
<p>你需要不断地鼓励他/她们,并适时地帮他/她们解决问题,以避免他/她们在提 pull request 的过程中放弃了。这一点特别的有意思,当有一个开发人员发现了项目中的 bug,那么他/她会尝试去解决这个问题。与此同时,他/她还会为你的项目带来 pull request,但是在这个过程中,因为测试等等的问题,可能会阻碍他的 PR。这个时候,就需要我们主要去提示/教他们怎么做,又或者是帮他/她们解决完剩下的问题。那么,下次他/她提一个 PR 的时候,他/她就能解决问题了。</p>
|
||
<p>这一点可以在 README,以及介绍上体现:</p>
|
||
<figure>
|
||
<img src="./img/feel-free-to.png" alt="Feel free to contribute!" /><figcaption>Feel free to contribute!</figcaption>
|
||
</figure>
|
||
<p>哪怕只是一个错误字的 PR,那么你也可以 merge,啊哈哈~。然后,就有人帮你宣传了,『我给 xxx 项目一个 PR 了』。刚毕业的时候,我也是从这种类型的 PR 做起的~~。</p>
|
||
<h1 id="开源项目维护">开源项目维护</h1>
|
||
<h2 id="release">Release</h2>
|
||
<h1 id="git-与-github-工具推荐">Git 与 GitHub 工具推荐</h1>
|
||
<h2 id="git-命令行增强">Git 命令行增强</h2>
|
||
<h3 id="diff-so-fancy"><a href="https://github.com/so-fancy/diff-so-fancy">diff-so-fancy</a></h3>
|
||
<figure>
|
||
<img src="./img/git-diff-screenshot.png" alt="diff so fancy 截图" /><figcaption>diff so fancy 截图</figcaption>
|
||
</figure>
|
||
<h3 id="git-extras"><a href="https://github.com/tj/git-extras">git-extras</a></h3>
|
||
<p><strong>Ubuntu</strong></p>
|
||
<pre><code>$ sudo apt-get install git-extras</code></pre>
|
||
<p><strong>Mac OS X with Homebrew</strong></p>
|
||
<pre><code>$ brew install git-extras</code></pre>
|
||
<pre><code>$ 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%</code></pre>
|
||
<h2 id="intellij-idea">Intellij IDEA</h2>
|
||
<p>由于日常用的开发工是 Intellij IDEA 企业版,所以就有点依赖于这个工具了。最常用的功能便是:<strong>修复 Bug 时,对于文件修改的追溯</strong>。了解某行代码修改的原因,对应的修改人等等。</p>
|
||
<p>Intellij IDEA</p>
|
||
<h2 id="gitgithub桌面增强">Git、GitHub桌面增强</h2>
|
||
<h3 id="sourcetree">SourceTree</h3>
|
||
<p>SourceTree 方便用来查看一些非我写的项目,寻找其中的一些问题。个中缘由便是:<strong>Intelli IDEA 刚打开某个项目的时候,需要花费大量的时间 index</strong>,只可惜现有的 SourceTree 客户端都需要登录 Atlassian 账户了。</p>
|
||
<p>gitflow 分支合并、查看</p>
|
||
<figure>
|
||
<img src="./img/sourcetree.jpg" alt="SourceTree 截图" /><figcaption>SourceTree 截图</figcaption>
|
||
</figure>
|
||
<h3 id="github-desktop">GitHub Desktop</h3>
|
||
<figure>
|
||
<img src="./img/github-desktop.jpg" alt="GitHub Desktop" /><figcaption>GitHub Desktop</figcaption>
|
||
</figure>
|
||
<h2 id="git-娱乐">Git 娱乐</h2>
|
||
<h3 id="githug">githug</h3>
|
||
<pre><code>$ 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.</code></pre>
|
||
<pre><code>$ 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.</code></pre>
|
||
<pre><code>#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
|
||
#...</code></pre>
|
||
<h3 id="gource">Gource</h3>
|
||
<figure>
|
||
<img src="./img/gource.jpg" alt="Gource 历史" /><figcaption>Gource 历史</figcaption>
|
||
</figure>
|
||
<h1 id="github用户分析">GitHub用户分析</h1>
|
||
<h2 id="生成图表">生成图表</h2>
|
||
<p>如何分析用户的数据是一个有趣的问题,特别是当我们有大量的数据的时候。除了<code>matlab</code>,我们还可以用<code>numpy</code>+<code>matplotlib</code></p>
|
||
<p>数据可以在这边寻找到</p>
|
||
<p><a href="https://github.com/gmszone/ml" class="uri">https://github.com/gmszone/ml</a></p>
|
||
<p>最后效果图</p>
|
||
<figure>
|
||
<img src="./img/2014-01-01.png" alt="2014 01 01" /><figcaption>2014 01 01</figcaption>
|
||
</figure>
|
||
<p>要解析的json文件位于<code>data/2014-01-01-0.json</code>,大小6.6M,显然我们可能需要用每次只读一行的策略,这足以解释为什么诸如sublime打开的时候很慢,而现在我们只需要里面的json数据中的创建时间。。</p>
|
||
<p>==, 这个文件代表什么?</p>
|
||
<p><strong>2014年1月1日零时到一时,用户在github上的操作,这里的用户指的是很多。。一共有4814条数据,从commit、create到issues都有。</strong></p>
|
||
<h3 id="数据解析">数据解析</h3>
|
||
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="im">import</span> json
|
||
<span class="cf">for</span> line <span class="kw">in</span> <span class="bu">open</span>(jsonfile):
|
||
line <span class="op">=</span> f.readline()</code></pre></div>
|
||
<p>然后再解析json</p>
|
||
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="im">import</span> dateutil.parser
|
||
|
||
lin <span class="op">=</span> json.loads(line)
|
||
date <span class="op">=</span> dateutil.parser.parse(lin[<span class="st">"created_at"</span>])</code></pre></div>
|
||
<p>这里用到了<code>dateutil</code>,因为新鲜出炉的数据是string需要转换为<code>dateutil</code>,再到数据放到数组里头。最后有就有了<code>parse_data</code></p>
|
||
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="kw">def</span> parse_data(jsonfile):
|
||
f <span class="op">=</span> <span class="bu">open</span>(jsonfile, <span class="st">"r"</span>)
|
||
dataarray <span class="op">=</span> []
|
||
datacount <span class="op">=</span> <span class="dv">0</span>
|
||
|
||
<span class="cf">for</span> line <span class="kw">in</span> <span class="bu">open</span>(jsonfile):
|
||
line <span class="op">=</span> f.readline()
|
||
lin <span class="op">=</span> json.loads(line)
|
||
date <span class="op">=</span> dateutil.parser.parse(lin[<span class="st">"created_at"</span>])
|
||
datacount <span class="op">+=</span> <span class="dv">1</span>
|
||
dataarray.append(date.minute)
|
||
|
||
minuteswithcount <span class="op">=</span> [(x, dataarray.count(x)) <span class="cf">for</span> x <span class="kw">in</span> <span class="bu">set</span>(dataarray)]
|
||
f.close()
|
||
<span class="cf">return</span> minuteswithcount</code></pre></div>
|
||
<p>下面这句代码就是将上面的解析为</p>
|
||
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python">minuteswithcount <span class="op">=</span> [(x, dataarray.count(x)) <span class="cf">for</span> x <span class="kw">in</span> <span class="bu">set</span>(dataarray)]</code></pre></div>
|
||
<p>这样的数组以便于解析</p>
|
||
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python">[(<span class="dv">0</span>, <span class="dv">92</span>), (<span class="dv">1</span>, <span class="dv">67</span>), (<span class="dv">2</span>, <span class="dv">86</span>), (<span class="dv">3</span>, <span class="dv">73</span>), (<span class="dv">4</span>, <span class="dv">76</span>), (<span class="dv">5</span>, <span class="dv">67</span>), (<span class="dv">6</span>, <span class="dv">61</span>), (<span class="dv">7</span>, <span class="dv">71</span>), (<span class="dv">8</span>, <span class="dv">62</span>), (<span class="dv">9</span>, <span class="dv">71</span>), (<span class="dv">10</span>, <span class="dv">70</span>), (<span class="dv">11</span>, <span class="dv">79</span>), (<span class="dv">12</span>, <span class="dv">62</span>), (<span class="dv">13</span>, <span class="dv">67</span>), (<span class="dv">14</span>, <span class="dv">76</span>), (<span class="dv">15</span>, <span class="dv">67</span>), (<span class="dv">16</span>, <span class="dv">74</span>), (<span class="dv">17</span>, <span class="dv">48</span>), (<span class="dv">18</span>, <span class="dv">78</span>), (<span class="dv">19</span>, <span class="dv">73</span>), (<span class="dv">20</span>, <span class="dv">89</span>), (<span class="dv">21</span>, <span class="dv">62</span>), (<span class="dv">22</span>, <span class="dv">74</span>), (<span class="dv">23</span>, <span class="dv">61</span>), (<span class="dv">24</span>, <span class="dv">71</span>), (<span class="dv">25</span>, <span class="dv">49</span>), (<span class="dv">26</span>, <span class="dv">59</span>), (<span class="dv">27</span>, <span class="dv">59</span>), (<span class="dv">28</span>, <span class="dv">58</span>), (<span class="dv">29</span>, <span class="dv">74</span>), (<span class="dv">30</span>, <span class="dv">69</span>), (<span class="dv">31</span>, <span class="dv">59</span>), (<span class="dv">32</span>, <span class="dv">89</span>), (<span class="dv">33</span>, <span class="dv">67</span>), (<span class="dv">34</span>, <span class="dv">66</span>), (<span class="dv">35</span>, <span class="dv">77</span>), (<span class="dv">36</span>, <span class="dv">64</span>), (<span class="dv">37</span>, <span class="dv">71</span>), (<span class="dv">38</span>, <span class="dv">75</span>), (<span class="dv">39</span>, <span class="dv">66</span>), (<span class="dv">40</span>, <span class="dv">62</span>), (<span class="dv">41</span>, <span class="dv">77</span>), (<span class="dv">42</span>, <span class="dv">82</span>), (<span class="dv">43</span>, <span class="dv">95</span>), (<span class="dv">44</span>, <span class="dv">77</span>), (<span class="dv">45</span>, <span class="dv">65</span>), (<span class="dv">46</span>, <span class="dv">59</span>), (<span class="dv">47</span>, <span class="dv">60</span>), (<span class="dv">48</span>, <span class="dv">54</span>), (<span class="dv">49</span>, <span class="dv">66</span>), (<span class="dv">50</span>, <span class="dv">74</span>), (<span class="dv">51</span>, <span class="dv">61</span>), (<span class="dv">52</span>, <span class="dv">71</span>), (<span class="dv">53</span>, <span class="dv">90</span>), (<span class="dv">54</span>, <span class="dv">64</span>), (<span class="dv">55</span>, <span class="dv">67</span>), (<span class="dv">56</span>, <span class="dv">67</span>), (<span class="dv">57</span>, <span class="dv">55</span>), (<span class="dv">58</span>, <span class="dv">68</span>), (<span class="dv">59</span>, <span class="dv">91</span>)]</code></pre></div>
|
||
<h3 id="matplotlib">Matplotlib</h3>
|
||
<p>开始之前需要安装``matplotlib</p>
|
||
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">sudo</span> pip install matplotlib</code></pre></div>
|
||
<p>然后引入这个库</p>
|
||
<pre><code> import matplotlib.pyplot as plt</code></pre>
|
||
<p>如上面的那个结果,只需要</p>
|
||
<pre><code class="python">
|
||
plt.figure(figsize=(8,4))
|
||
plt.plot(x, y,label = files)
|
||
plt.legend()
|
||
plt.show()
|
||
</code></pre>
|
||
<p>最后代码可见</p>
|
||
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="co">#!/usr/bin/env python</span>
|
||
<span class="co"># -*- coding: utf-8 -*-</span>
|
||
|
||
<span class="im">import</span> json
|
||
<span class="im">import</span> dateutil.parser
|
||
<span class="im">import</span> numpy <span class="im">as</span> np
|
||
<span class="im">import</span> matplotlib.mlab <span class="im">as</span> mlab
|
||
<span class="im">import</span> matplotlib.pyplot <span class="im">as</span> plt
|
||
|
||
|
||
<span class="kw">def</span> parse_data(jsonfile):
|
||
f <span class="op">=</span> <span class="bu">open</span>(jsonfile, <span class="st">"r"</span>)
|
||
dataarray <span class="op">=</span> []
|
||
datacount <span class="op">=</span> <span class="dv">0</span>
|
||
|
||
<span class="cf">for</span> line <span class="kw">in</span> <span class="bu">open</span>(jsonfile):
|
||
line <span class="op">=</span> f.readline()
|
||
lin <span class="op">=</span> json.loads(line)
|
||
date <span class="op">=</span> dateutil.parser.parse(lin[<span class="st">"created_at"</span>])
|
||
datacount <span class="op">+=</span> <span class="dv">1</span>
|
||
dataarray.append(date.minute)
|
||
|
||
minuteswithcount <span class="op">=</span> [(x, dataarray.count(x)) <span class="cf">for</span> x <span class="kw">in</span> <span class="bu">set</span>(dataarray)]
|
||
f.close()
|
||
<span class="cf">return</span> minuteswithcount
|
||
|
||
|
||
<span class="kw">def</span> draw_date(files):
|
||
x <span class="op">=</span> []
|
||
y <span class="op">=</span> []
|
||
mwcs <span class="op">=</span> parse_data(files)
|
||
<span class="cf">for</span> mwc <span class="kw">in</span> mwcs:
|
||
x.append(mwc[<span class="dv">0</span>])
|
||
y.append(mwc[<span class="dv">1</span>])
|
||
|
||
plt.figure(figsize<span class="op">=</span>(<span class="dv">8</span>,<span class="dv">4</span>))
|
||
plt.plot(x, y,label <span class="op">=</span> files)
|
||
plt.legend()
|
||
plt.show()
|
||
|
||
draw_date(<span class="st">"data/2014-01-01-0.json"</span>)</code></pre></div>
|
||
<h2 id="每周分析">每周分析</h2>
|
||
<p>继上篇之后,我们就可以分析用户的每周提交情况,以得出用户的真正的工具效率,每个程序员的工作时间可能是不一样的,如</p>
|
||
<figure>
|
||
<img src="./img/phodal-results.png" alt="Phodal Huang’s Report" /><figcaption>Phodal Huang’s Report</figcaption>
|
||
</figure>
|
||
<p>这是我的每周情况,显然如果把星期六移到前面的话,随着工作时间的增长,在github上的使用在下降,作为一个</p>
|
||
<pre><code> a fulltime hacker who works best in the evening (around 8 pm).</code></pre>
|
||
<p>不过这个是osrc的分析结果。</p>
|
||
<h3 id="python-github-每周情况分析">python github 每周情况分析</h3>
|
||
<p>看一张分析后的结果</p>
|
||
<figure>
|
||
<img src="./img/feb-results.png" alt="Feb Results" /><figcaption>Feb Results</figcaption>
|
||
</figure>
|
||
<p>结果正好与我的情况相反?似乎图上是这么说的,但是数据上是这样的情况。</p>
|
||
<pre><code>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</code></pre>
|
||
<p>我们获取是每天晚上0点时的情况,至于为什么是0点,我想这里的数据量可能会比较少。除去1月1号的情况,就是上面的结果,在只有一周的情况时,总会以为因为在国内那时是假期,但是总觉得不是很靠谱,国内的程序员虽然很多,会在github上活跃的可能没有那么多,直至列出每一周的数据时。</p>
|
||
<pre><code> 6570, 7420, 11274, 12073, 12160, 12378, 12897,
|
||
8474, 7984, 12933, 13504, 13763, 13544, 12940,
|
||
7119, 7346, 13412, 14008, 12555</code></pre>
|
||
<h3 id="python-数据分析">Python 数据分析</h3>
|
||
<p>重写了一个新的方法用于计算提交数,直至后面才意识到其实我们可以算行数就够了,但是方法上有点hack</p>
|
||
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="kw">def</span> get_minutes_counts_with_id(jsonfile):
|
||
datacount, dataarray <span class="op">=</span> handle_json(jsonfile)
|
||
minuteswithcount <span class="op">=</span> [(x, dataarray.count(x)) <span class="cf">for</span> x <span class="kw">in</span> <span class="bu">set</span>(dataarray)]
|
||
<span class="cf">return</span> minuteswithcount
|
||
|
||
|
||
<span class="kw">def</span> handle_json(jsonfile):
|
||
f <span class="op">=</span> <span class="bu">open</span>(jsonfile, <span class="st">"r"</span>)
|
||
dataarray <span class="op">=</span> []
|
||
datacount <span class="op">=</span> <span class="dv">0</span>
|
||
|
||
<span class="cf">for</span> line <span class="kw">in</span> <span class="bu">open</span>(jsonfile):
|
||
line <span class="op">=</span> f.readline()
|
||
lin <span class="op">=</span> json.loads(line)
|
||
date <span class="op">=</span> dateutil.parser.parse(lin[<span class="st">"created_at"</span>])
|
||
datacount <span class="op">+=</span> <span class="dv">1</span>
|
||
dataarray.append(date.minute)
|
||
|
||
f.close()
|
||
<span class="cf">return</span> datacount, dataarray
|
||
|
||
|
||
<span class="kw">def</span> get_minutes_count_num(jsonfile):
|
||
datacount, dataarray <span class="op">=</span> handle_json(jsonfile)
|
||
<span class="cf">return</span> datacount
|
||
|
||
|
||
<span class="kw">def</span> get_month_total():
|
||
<span class="co">"""</span>
|
||
|
||
<span class="co"> :rtype : object</span>
|
||
<span class="co"> """</span>
|
||
monthdaycount <span class="op">=</span> []
|
||
<span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">1</span>, <span class="dv">20</span>):
|
||
<span class="cf">if</span> i <span class="op"><</span> <span class="dv">10</span>:
|
||
filename <span class="op">=</span> <span class="st">'data/2014-02-0'</span> <span class="op">+</span> i.<span class="fu">__str__</span>() <span class="op">+</span> <span class="st">'-0.json'</span>
|
||
<span class="cf">else</span>:
|
||
filename <span class="op">=</span> <span class="st">'data/2014-02-'</span> <span class="op">+</span> i.<span class="fu">__str__</span>() <span class="op">+</span> <span class="st">'-0.json'</span>
|
||
monthdaycount.append(get_minutes_count_num(filename))
|
||
<span class="cf">return</span> monthdaycount</code></pre></div>
|
||
<p>接着我们需要去遍历每个结果,后面的后面会发现这个效率真的是太低了,为什么木有多线程?</p>
|
||
<h3 id="python-matplotlib图表">Python Matplotlib图表</h3>
|
||
<p>让我们的matplotlib来做这些图表的工作</p>
|
||
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">'__main__'</span>:
|
||
results <span class="op">=</span> pd.get_month_total()
|
||
<span class="bu">print</span> results
|
||
|
||
plt.figure(figsize<span class="op">=</span>(<span class="dv">8</span>, <span class="dv">4</span>))
|
||
plt.plot(results.<span class="fu">__getslice__</span>(<span class="dv">0</span>, <span class="dv">7</span>), label<span class="op">=</span><span class="st">"first week"</span>)
|
||
plt.plot(results.<span class="fu">__getslice__</span>(<span class="dv">7</span>, <span class="dv">14</span>), label<span class="op">=</span><span class="st">"second week"</span>)
|
||
plt.plot(results.<span class="fu">__getslice__</span>(<span class="dv">14</span>, <span class="dv">21</span>), label<span class="op">=</span><span class="st">"third week"</span>)
|
||
plt.legend()
|
||
plt.show()</code></pre></div>
|
||
<p>蓝色的是第一周,绿色的是第二周,红色的是第三周就有了上面的结果。</p>
|
||
<p>我们还需要优化方法,以及多线程的支持。</p>
|
||
<p>让我们分析之前的程序,然后再想办法做出优化。网上看到一篇文章<a href="http://www.huyng.com/posts/python-performance-analysis/" class="uri">http://www.huyng.com/posts/python-performance-analysis/</a>讲的就是分析这部分内容的。</p>
|
||
<h2 id="存储到数据库中">存储到数据库中</h2>
|
||
<h3 id="sqlite3">SQLite3</h3>
|
||
<p>我们创建了一个名为<code>userdata.db</code>的数据库文件,然后创建了一个表,里面有owner,language,eventtype,name url</p>
|
||
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="kw">def</span> init_db():
|
||
conn <span class="op">=</span> sqlite3.<span class="ex">connect</span>(<span class="st">'userdata.db'</span>)
|
||
c <span class="op">=</span> conn.cursor()
|
||
c.execute(<span class="st">'''CREATE TABLE userinfo (owner text, language text, eventtype text, name text, url text)'''</span>)</code></pre></div>
|
||
<p>接着我们就可以查询数据,这里从结果讲起。</p>
|
||
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="kw">def</span> get_count(username):
|
||
count <span class="op">=</span> <span class="dv">0</span>
|
||
userinfo <span class="op">=</span> []
|
||
condition <span class="op">=</span> <span class="st">'select * from userinfo where owener = </span><span class="ch">\'</span><span class="st">'</span> <span class="op">+</span> <span class="bu">str</span>(username) <span class="op">+</span> <span class="st">'</span><span class="ch">\'</span><span class="st">'</span>
|
||
<span class="cf">for</span> zero <span class="kw">in</span> c.execute(condition):
|
||
count <span class="op">+=</span> <span class="dv">1</span>
|
||
userinfo.append(zero)
|
||
|
||
<span class="cf">return</span> count, userinfo</code></pre></div>
|
||
<p>当我查询<code>gmszone</code>的时候,也就是我自己就会有如下的结果</p>
|
||
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="kw">(</span><span class="ex">u</span><span class="st">'gmszone'</span>, u<span class="st">'ForkEvent'</span>, u<span class="st">'RESUME'</span>, u<span class="st">'TeX'</span>, u<span class="st">'https://github.com/gmszone/RESUME'</span><span class="kw">)</span>
|
||
<span class="kw">(</span><span class="ex">u</span><span class="st">'gmszone'</span>, u<span class="st">'WatchEvent'</span>, u<span class="st">'iot-dashboard'</span>, u<span class="st">'JavaScript'</span>, u<span class="st">'https://github.com/gmszone/iot-dashboard'</span><span class="kw">)</span>
|
||
<span class="kw">(</span><span class="ex">u</span><span class="st">'gmszone'</span>, u<span class="st">'PushEvent'</span>, u<span class="st">'wechat-wordpress'</span>, u<span class="st">'Ruby'</span>, u<span class="st">'https://github.com/gmszone/wechat-wordpress'</span><span class="kw">)</span>
|
||
<span class="kw">(</span><span class="ex">u</span><span class="st">'gmszone'</span>, u<span class="st">'WatchEvent'</span>, u<span class="st">'iot'</span>, u<span class="st">'JavaScript'</span>, u<span class="st">'https://github.com/gmszone/iot'</span><span class="kw">)</span>
|
||
<span class="kw">(</span><span class="ex">u</span><span class="st">'gmszone'</span>, u<span class="st">'CreateEvent'</span>, u<span class="st">'iot-doc'</span>, u<span class="st">'None'</span>, u<span class="st">'https://github.com/gmszone/iot-doc'</span><span class="kw">)</span>
|
||
<span class="kw">(</span><span class="ex">u</span><span class="st">'gmszone'</span>, u<span class="st">'CreateEvent'</span>, u<span class="st">'iot-doc'</span>, u<span class="st">'None'</span>, u<span class="st">'https://github.com/gmszone/iot-doc'</span><span class="kw">)</span>
|
||
<span class="kw">(</span><span class="ex">u</span><span class="st">'gmszone'</span>, u<span class="st">'PushEvent'</span>, u<span class="st">'iot-doc'</span>, u<span class="st">'TeX'</span>, u<span class="st">'https://github.com/gmszone/iot-doc'</span><span class="kw">)</span>
|
||
<span class="kw">(</span><span class="ex">u</span><span class="st">'gmszone'</span>, u<span class="st">'PushEvent'</span>, u<span class="st">'iot-doc'</span>, u<span class="st">'TeX'</span>, u<span class="st">'https://github.com/gmszone/iot-doc'</span><span class="kw">)</span>
|
||
<span class="kw">(</span><span class="ex">u</span><span class="st">'gmszone'</span>, u<span class="st">'PushEvent'</span>, u<span class="st">'iot-doc'</span>, u<span class="st">'TeX'</span>, u<span class="st">'https://github.com/gmszone/iot-doc'</span><span class="kw">)</span>
|
||
<span class="ex">109</span></code></pre></div>
|
||
<p>一共有109个事件,有<code>Watch</code>,<code>Create</code>,<code>Push</code>,<code>Fork</code>还有其他的, 项目主要有<code>iot</code>,<code>RESUME</code>,<code>iot-dashboard</code>,<code>wechat-wordpress</code>, 接着就是语言了,<code>Tex</code>,<code>Javascript</code>,<code>Ruby</code>,接着就是项目的url了。</p>
|
||
<p>值得注意的是。</p>
|
||
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">-rw-r--r--</span> 1 fdhuang staff 905M Apr 12 14:59 userdata.db</code></pre></div>
|
||
<p>这个数据库文件有<strong>905M</strong>,不过查询结果相当让人满意,至少相对于原来的结果来说。</p>
|
||
<p>Python自带了对SQLite3的支持,然而我们还需要安装SQLite3</p>
|
||
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">brew</span> install sqlite3</code></pre></div>
|
||
<p>或者是</p>
|
||
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">sudo</span> port install sqlite3</code></pre></div>
|
||
<p>或者是Ubuntu的</p>
|
||
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">sudo</span> apt-get install sqlite3</code></pre></div>
|
||
<p>openSUSE自然就是</p>
|
||
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">sudo</span> zypper install sqlite3</code></pre></div>
|
||
<p>不过,用yast2也很不错,不是么。。</p>
|
||
<h3 id="数据导入">数据导入</h3>
|
||
<p>需要注意的是这里是需要python2.7,起源于对gzip的上下文管理器的支持问题</p>
|
||
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="kw">def</span> handle_gzip_file(filename):
|
||
userinfo <span class="op">=</span> []
|
||
<span class="cf">with</span> gzip.GzipFile(filename) <span class="im">as</span> f:
|
||
events <span class="op">=</span> [line.decode(<span class="st">"utf-8"</span>, errors<span class="op">=</span><span class="st">"ignore"</span>) <span class="cf">for</span> line <span class="kw">in</span> f]
|
||
|
||
<span class="cf">for</span> n, line <span class="kw">in</span> <span class="bu">enumerate</span>(events):
|
||
<span class="cf">try</span>:
|
||
event <span class="op">=</span> json.loads(line)
|
||
<span class="cf">except</span>:
|
||
|
||
<span class="cf">continue</span>
|
||
|
||
actor <span class="op">=</span> event[<span class="st">"actor"</span>]
|
||
attrs <span class="op">=</span> event.get(<span class="st">"actor_attributes"</span>, {})
|
||
<span class="cf">if</span> actor <span class="kw">is</span> <span class="va">None</span> <span class="kw">or</span> attrs.get(<span class="st">"type"</span>) <span class="op">!=</span> <span class="st">"User"</span>:
|
||
<span class="cf">continue</span>
|
||
|
||
key <span class="op">=</span> actor.lower()
|
||
|
||
repo <span class="op">=</span> event.get(<span class="st">"repository"</span>, {})
|
||
info <span class="op">=</span> <span class="bu">str</span>(repo.get(<span class="st">"owner"</span>)), <span class="bu">str</span>(repo.get(<span class="st">"language"</span>)), <span class="bu">str</span>(event[<span class="st">"type"</span>]), <span class="bu">str</span>(repo.get(<span class="st">"name"</span>)), <span class="bu">str</span>(
|
||
repo.get(<span class="st">"url"</span>))
|
||
userinfo.append(info)
|
||
|
||
<span class="cf">return</span> userinfo
|
||
|
||
<span class="kw">def</span> build_db_with_gzip():
|
||
init_db()
|
||
conn <span class="op">=</span> sqlite3.<span class="ex">connect</span>(<span class="st">'userdata.db'</span>)
|
||
c <span class="op">=</span> conn.cursor()
|
||
|
||
year <span class="op">=</span> <span class="dv">2014</span>
|
||
month <span class="op">=</span> <span class="dv">3</span>
|
||
|
||
<span class="cf">for</span> day <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">1</span>,<span class="dv">31</span>):
|
||
date_re <span class="op">=</span> re.<span class="bu">compile</span>(<span class="vs">r"([0-9]</span><span class="sc">{4}</span><span class="vs">)-([0-9]</span><span class="sc">{2}</span><span class="vs">)-([0-9]</span><span class="sc">{2}</span><span class="vs">)-([0-9]+)\.json.gz"</span>)
|
||
|
||
fn_template <span class="op">=</span> os.path.join(<span class="st">"march"</span>,
|
||
<span class="co">"{year}-{month:02d}-{day:02d}-{n}.json.gz"</span>)
|
||
kwargs <span class="op">=</span> {<span class="st">"year"</span>: year, <span class="st">"month"</span>: month, <span class="st">"day"</span>: day, <span class="st">"n"</span>: <span class="st">"*"</span>}
|
||
filenames <span class="op">=</span> glob.glob(fn_template.<span class="bu">format</span>(<span class="op">**</span>kwargs))
|
||
|
||
<span class="cf">for</span> filename <span class="kw">in</span> filenames:
|
||
c.executemany(<span class="st">'INSERT INTO userinfo VALUES (?,?,?,?,?)'</span>, handle_gzip_file(filename))
|
||
|
||
conn.commit()
|
||
c.close()</code></pre></div>
|
||
<p><code>executemany</code>可以插入多条数据,对于我们的数据来说,一小时的文件大概有五六千个会符合我们上面的安装,也就是有<code>actor</code>又有<code>type</code>才是我们需要记录的数据,我们只需要统计用户的那些事件,而非全部的事件。</p>
|
||
<p>我们需要去遍历文件,然后找到合适的部分,这里只是要找<code>2014-03-01</code>到<code>2014-03-31</code>的全部事件,而光这些数据的gz文件就有1.26G,同上面那些解压为json文件显得不合适,只能用遍历来处理。</p>
|
||
<p>这里参考了osrc项目中的写法,或者说直接复制过来。</p>
|
||
<p>首先是正规匹配</p>
|
||
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python">date_re <span class="op">=</span> re.<span class="bu">compile</span>(<span class="vs">r"([0-9]</span><span class="sc">{4}</span><span class="vs">)-([0-9]</span><span class="sc">{2}</span><span class="vs">)-([0-9]</span><span class="sc">{2}</span><span class="vs">)-([0-9]+)\.json.gz"</span>)</code></pre></div>
|
||
<p>不过主要的还是在于<code>glob.glob</code></p>
|
||
<blockquote>
|
||
<p>glob是python自己带的一个文件操作相关模块,用它可以查找符合自己目的的文件,就类似于Windows下的文件搜索,支持通配符操作。</p>
|
||
</blockquote>
|
||
<p>这里也就用上了<code>gzip.GzipFile</code>又一个不错的东西。</p>
|
||
<p>最后代码可以见</p>
|
||
<p><a href="http://github.com/gmszone/ml">github.com/gmszone/ml</a></p>
|
||
<p>更好的方案?</p>
|
||
<h3 id="redis">Redis</h3>
|
||
<p>查询用户事件总数</p>
|
||
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="im">import</span> redis
|
||
r <span class="op">=</span> redis.StrictRedis(host<span class="op">=</span><span class="st">'localhost'</span>, port<span class="op">=</span><span class="dv">6379</span>, db<span class="op">=</span><span class="dv">0</span>)
|
||
pipe <span class="op">=</span> pipe <span class="op">=</span> r.pipeline()
|
||
pipe.zscore(<span class="st">'osrc:user'</span>,<span class="st">"gmszone"</span>)
|
||
pipe.execute()</code></pre></div>
|
||
<p>系统返回了<code>227.0</code>,试试别人。</p>
|
||
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="op">>>></span> <span class="ex">pipe.zscore</span>(<span class="st">'osrc:user'</span>,<span class="st">"dfm"</span>)
|
||
<span class="op"><</span><span class="ex">redis.client.StrictPipeline</span> object at 0x104fa7f<span class="op">50></span>
|
||
<span class="op">>>></span> <span class="ex">pipe.execute</span>()
|
||
[<span class="ex">425.0</span>]
|
||
<span class="op">>>></span></code></pre></div>
|
||
<p>看看主要是在哪一天提交的</p>
|
||
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="op">>>></span> pipe.hgetall(<span class="st">'osrc:user:gmszone:day'</span>)
|
||
<span class="op"><</span>redis.client.StrictPipeline <span class="bu">object</span> at <span class="bn">0x104fa7f50</span><span class="op">></span>
|
||
<span class="op">>>></span> pipe.execute()
|
||
[{<span class="st">'1'</span>: <span class="st">'51'</span>, <span class="st">'0'</span>: <span class="st">'41'</span>, <span class="st">'3'</span>: <span class="st">'17'</span>, <span class="st">'2'</span>: <span class="st">'34'</span>, <span class="st">'5'</span>: <span class="st">'28'</span>, <span class="st">'4'</span>: <span class="st">'22'</span>, <span class="st">'6'</span>: <span class="st">'34'</span>}]</code></pre></div>
|
||
<p>结果大致如下图所示:</p>
|
||
<figure>
|
||
<img src="./img/smtwtfs.png" alt="SMTWTFS" /><figcaption>SMTWTFS</figcaption>
|
||
</figure>
|
||
<p>看看主要的事件是?</p>
|
||
<pre><code>>>> 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)]]
|
||
>>></code></pre>
|
||
<figure>
|
||
<img src="./img/main-events.png" alt="Main Event" /><figcaption>Main Event</figcaption>
|
||
</figure>
|
||
<p>蓝色的就是push事件,黄色的是create等等。</p>
|
||
<p>到这里我们算是知道了OSRC的数据库部分是如何工作的。</p>
|
||
<h4 id="redis-查询">Redis 查询</h4>
|
||
<p>主要代码如下所示</p>
|
||
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="kw">def</span> get_vector(user, pipe<span class="op">=</span><span class="va">None</span>):
|
||
|
||
r <span class="op">=</span> redis.StrictRedis(host<span class="op">=</span><span class="st">'localhost'</span>, port<span class="op">=</span><span class="dv">6379</span>, db<span class="op">=</span><span class="dv">0</span>)
|
||
no_pipe <span class="op">=</span> <span class="va">False</span>
|
||
<span class="cf">if</span> pipe <span class="kw">is</span> <span class="va">None</span>:
|
||
pipe <span class="op">=</span> pipe <span class="op">=</span> r.pipeline()
|
||
no_pipe <span class="op">=</span> <span class="va">True</span>
|
||
|
||
user <span class="op">=</span> user.lower()
|
||
pipe.zscore(get_format(<span class="st">"user"</span>), user)
|
||
pipe.hgetall(get_format(<span class="st">"user:</span><span class="sc">{0}</span><span class="st">:day"</span>.<span class="bu">format</span>(user)))
|
||
pipe.zrevrange(get_format(<span class="st">"user:</span><span class="sc">{0}</span><span class="st">:event"</span>.<span class="bu">format</span>(user)), <span class="dv">0</span>, <span class="op">-</span><span class="dv">1</span>,
|
||
withscores<span class="op">=</span><span class="va">True</span>)
|
||
pipe.zcard(get_format(<span class="st">"user:</span><span class="sc">{0}</span><span class="st">:contribution"</span>.<span class="bu">format</span>(user)))
|
||
pipe.zcard(get_format(<span class="st">"user:</span><span class="sc">{0}</span><span class="st">:connection"</span>.<span class="bu">format</span>(user)))
|
||
pipe.zcard(get_format(<span class="st">"user:</span><span class="sc">{0}</span><span class="st">:repo"</span>.<span class="bu">format</span>(user)))
|
||
pipe.zcard(get_format(<span class="st">"user:</span><span class="sc">{0}</span><span class="st">:lang"</span>.<span class="bu">format</span>(user)))
|
||
pipe.zrevrange(get_format(<span class="st">"user:</span><span class="sc">{0}</span><span class="st">:lang"</span>.<span class="bu">format</span>(user)), <span class="dv">0</span>, <span class="op">-</span><span class="dv">1</span>,
|
||
withscores<span class="op">=</span><span class="va">True</span>)
|
||
|
||
<span class="cf">if</span> no_pipe:
|
||
<span class="cf">return</span> pipe.execute()</code></pre></div>
|
||
<p>结果在上一篇中显示出来了,也就是</p>
|
||
<pre><code>[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)]]</code></pre>
|
||
<p>有意思的是在这里生成了和自己相近的人</p>
|
||
<pre><code>['alesdokshanin', 'hjiawei', 'andrewreedy', 'christj6', '1995eaton']</code></pre>
|
||
<p>osrc最有意思的一部分莫过于flann,当然说的也是系统后台的设计的一个很关键及有意思的部分。</p>
|
||
<h2 id="邻近算法与相似用户">邻近算法与相似用户</h2>
|
||
<p>邻近算法是在这个分析过程中一个很有意思的东西。</p>
|
||
<blockquote>
|
||
<p>邻近算法,或者说K最近邻(kNN,k-NearestNeighbor)分类算法可以说是整个数据挖掘分类技术中最简单的方法了。所谓K最近邻,就是k个最近的邻居的意思,说的是每个样本都可以用她最接近的k个邻居来代表。</p>
|
||
</blockquote>
|
||
<p>换句话说,我们需要一些样本来当作我们的分析资料,这里东西用到的就是我们之前的。</p>
|
||
<pre><code>[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)]]</code></pre>
|
||
<p>在代码中是构建了一个points.h5的文件来分析每个用户的points,之后再记录到hdf5文件中。</p>
|
||
<pre><code>[ 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]</code></pre>
|
||
<p>这里分析到用户的大部分行为,再找到与其行为相近的用户,主要的行为有下面这些:</p>
|
||
<ul>
|
||
<li>每星期的情况</li>
|
||
<li>事件的类型</li>
|
||
<li>贡献的数量,连接以及语言</li>
|
||
<li>最多的语言</li>
|
||
</ul>
|
||
<p>osrc中用于解析的代码</p>
|
||
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="kw">def</span> parse_vector(results):
|
||
points <span class="op">=</span> np.zeros(nvector)
|
||
total <span class="op">=</span> <span class="bu">int</span>(results[<span class="dv">0</span>])
|
||
|
||
points[<span class="dv">0</span>] <span class="op">=</span> <span class="fl">1.0</span> <span class="op">/</span> (total <span class="op">+</span> <span class="dv">1</span>)
|
||
|
||
<span class="co"># Week means.</span>
|
||
<span class="cf">for</span> k, v <span class="kw">in</span> results[<span class="dv">1</span>].iteritems():
|
||
points[<span class="dv">1</span> <span class="op">+</span> <span class="bu">int</span>(k)] <span class="op">=</span> <span class="bu">float</span>(v) <span class="op">/</span> total
|
||
|
||
<span class="co"># Event types.</span>
|
||
n <span class="op">=</span> <span class="dv">8</span>
|
||
<span class="cf">for</span> k, v <span class="kw">in</span> results[<span class="dv">2</span>]:
|
||
points[n <span class="op">+</span> evttypes.index(k)] <span class="op">=</span> <span class="bu">float</span>(v) <span class="op">/</span> total
|
||
|
||
<span class="co"># Number of contributions, connections and languages.</span>
|
||
n <span class="op">+=</span> nevts
|
||
points[n] <span class="op">=</span> <span class="fl">1.0</span> <span class="op">/</span> (<span class="bu">float</span>(results[<span class="dv">3</span>]) <span class="op">+</span> <span class="dv">1</span>)
|
||
points[n <span class="op">+</span> <span class="dv">1</span>] <span class="op">=</span> <span class="fl">1.0</span> <span class="op">/</span> (<span class="bu">float</span>(results[<span class="dv">4</span>]) <span class="op">+</span> <span class="dv">1</span>)
|
||
points[n <span class="op">+</span> <span class="dv">2</span>] <span class="op">=</span> <span class="fl">1.0</span> <span class="op">/</span> (<span class="bu">float</span>(results[<span class="dv">5</span>]) <span class="op">+</span> <span class="dv">1</span>)
|
||
points[n <span class="op">+</span> <span class="dv">3</span>] <span class="op">=</span> <span class="fl">1.0</span> <span class="op">/</span> (<span class="bu">float</span>(results[<span class="dv">6</span>]) <span class="op">+</span> <span class="dv">1</span>)
|
||
|
||
<span class="co"># Top languages.</span>
|
||
n <span class="op">+=</span> <span class="dv">4</span>
|
||
<span class="cf">for</span> k, v <span class="kw">in</span> results[<span class="dv">7</span>]:
|
||
<span class="cf">if</span> k <span class="kw">in</span> langs:
|
||
points[n <span class="op">+</span> langs.index(k)] <span class="op">=</span> <span class="bu">float</span>(v) <span class="op">/</span> total
|
||
<span class="cf">else</span>:
|
||
<span class="co"># Unknown language.</span>
|
||
points[<span class="op">-</span><span class="dv">1</span>] <span class="op">=</span> <span class="bu">float</span>(v) <span class="op">/</span> total
|
||
|
||
<span class="cf">return</span> points</code></pre></div>
|
||
<p>这样也就返回我们需要的点数,然后我们可以用<code>get_points</code>来获取这些</p>
|
||
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="kw">def</span> get_points(usernames):
|
||
r <span class="op">=</span> redis.StrictRedis(host<span class="op">=</span><span class="st">'localhost'</span>, port<span class="op">=</span><span class="dv">6379</span>, db<span class="op">=</span><span class="dv">0</span>)
|
||
pipe <span class="op">=</span> r.pipeline()
|
||
|
||
results <span class="op">=</span> get_vector(usernames)
|
||
points <span class="op">=</span> np.zeros([<span class="bu">len</span>(usernames), nvector])
|
||
points <span class="op">=</span> parse_vector(results)
|
||
<span class="cf">return</span> points</code></pre></div>
|
||
<p>就会得到我们的相应的数据,接着找找和自己邻近的,看看结果。</p>
|
||
<pre><code>[ 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. ]</code></pre>
|
||
<p>真看不出来两者有什么相似的地方 。。。。</p>
|
||
<h1 id="如何在github寻找灵感fork">如何在GitHub“寻找灵感(fork)”</h1>
|
||
<blockquote>
|
||
<p>重造轮子是重新创造一个已有的或是已被其他人优化的基本方法。</p>
|
||
</blockquote>
|
||
<p>最近萌发了一个想法写游戏引擎,之前想着做一个JavaScript前端框架。看看,这个思路是怎么来的。</p>
|
||
<h2 id="lettuce构建过程">Lettuce构建过程</h2>
|
||
<blockquote>
|
||
<p>Lettuce是一个简约的移动开发框架。</p>
|
||
</blockquote>
|
||
<p>故事的出发点是这样的:<code>写了很多代码,用的都是框架,最后不知道收获什么了</code>?事实也是如此,当自己做了一些项目之后,发现最后什么也没有收获到。于是,就想着做一个框架。</p>
|
||
<h3 id="需求">需求</h3>
|
||
<p>有这样的几个前提</p>
|
||
<ul>
|
||
<li>为什么我只需要jQuery里的选择器、Ajax要引入那么重的库呢?</li>
|
||
<li>为什么我只需要一个Template,却想着用Mustache</li>
|
||
<li>为什么我需要一个Router,却要用Backbone呢?</li>
|
||
<li>为什么我需要的是一个isObject函数,却要用到整个Underscore?</li>
|
||
</ul>
|
||
<p>我想要的只是一个简单的功能,而我不想引入一个庞大的库。换句话说,我只需要不同库里面的一小部分功能,而不是一个库。</p>
|
||
<p>实际上想要的是:</p>
|
||
<blockquote>
|
||
<p>构建一个库,里面从不同的库里面抽取出不同的函数。</p>
|
||
</blockquote>
|
||
<h3 id="计划">计划</h3>
|
||
<p>这时候我参考了一本电子书《Build JavaScript FrameWork》,加上一些平时的需求,于是很快的就知道自己需要什么样的功能:</p>
|
||
<ul>
|
||
<li>Promise 支持</li>
|
||
<li>Class类(ps:没有一个好的类使用的方式)</li>
|
||
<li>Template 一个简单的模板引擎</li>
|
||
<li>Router 用来控制页面的路由</li>
|
||
<li>Ajax 基本的Ajax Get/Post请求</li>
|
||
</ul>
|
||
<p>在做一些实际的项目中,还遇到了这样的一些功能支持:</p>
|
||
<ul>
|
||
<li>Effect 简单的一些页面效果</li>
|
||
<li>AMD支持</li>
|
||
</ul>
|
||
<p>而我们有一个前提是要保持这个库尽可能的小、同时我们还需要有测试。</p>
|
||
<h3 id="实现第一个需求">实现第一个需求</h3>
|
||
<p>简单说说是如何实现一个简单的需求。</p>
|
||
<h4 id="生成框架">生成框架</h4>
|
||
<p>因为Yeoman可以生成一个简单的轮廓,所以我们可以用它来生成这个项目的骨架。</p>
|
||
<ul>
|
||
<li>Gulp</li>
|
||
<li>Jasmine</li>
|
||
</ul>
|
||
<h4 id="寻找">寻找</h4>
|
||
<p>在GitHub上搜索了一个看到了下面的几个结果:</p>
|
||
<ul>
|
||
<li><a href="https://github.com/then/promise" class="uri">https://github.com/then/promise</a></li>
|
||
<li><a href="https://github.com/reactphp/promise" class="uri">https://github.com/reactphp/promise</a></li>
|
||
<li><a href="https://github.com/kriskowal/q" class="uri">https://github.com/kriskowal/q</a></li>
|
||
<li><a href="https://github.com/petkaantonov/bluebird" class="uri">https://github.com/petkaantonov/bluebird</a></li>
|
||
<li><a href="https://github.com/cujojs/when" class="uri">https://github.com/cujojs/when</a></li>
|
||
</ul>
|
||
<p>但是显然,他们都太重了。事实上,对于一个库来说,80%的人只需要其中20%的代码。于是,找到了<a href="https://github.com/stackp/promisejs" class="uri">https://github.com/stackp/promisejs</a>,看了看用法,这就是我们需要的功能:</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="kw">function</span> <span class="at">late</span>(n) <span class="op">{</span>
|
||
<span class="kw">var</span> p <span class="op">=</span> <span class="kw">new</span> <span class="va">promise</span>.<span class="at">Promise</span>()<span class="op">;</span>
|
||
<span class="at">setTimeout</span>(<span class="kw">function</span>() <span class="op">{</span>
|
||
<span class="va">p</span>.<span class="at">done</span>(<span class="kw">null</span><span class="op">,</span> n)<span class="op">;</span>
|
||
<span class="op">},</span> n)<span class="op">;</span>
|
||
<span class="cf">return</span> p<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
|
||
<span class="at">late</span>(<span class="dv">100</span>).<span class="at">then</span>(
|
||
<span class="kw">function</span>(err<span class="op">,</span> n) <span class="op">{</span>
|
||
<span class="cf">return</span> <span class="at">late</span>(n <span class="op">+</span> <span class="dv">200</span>)<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
).<span class="at">then</span>(
|
||
<span class="kw">function</span>(err<span class="op">,</span> n) <span class="op">{</span>
|
||
<span class="cf">return</span> <span class="at">late</span>(n <span class="op">+</span> <span class="dv">300</span>)<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
).<span class="at">then</span>(
|
||
<span class="kw">function</span>(err<span class="op">,</span> n) <span class="op">{</span>
|
||
<span class="cf">return</span> <span class="at">late</span>(n <span class="op">+</span> <span class="dv">400</span>)<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
).<span class="at">then</span>(
|
||
<span class="kw">function</span>(err<span class="op">,</span> n) <span class="op">{</span>
|
||
<span class="at">alert</span>(n)<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
)<span class="op">;</span></code></pre></div>
|
||
<p>接着打开看看Promise对象,有我们需要的功能,但是又有一些功能超出我的需求。接着把自己不需要的需求去掉,这里函数最后就变成了</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="kw">function</span> <span class="at">Promise</span>() <span class="op">{</span>
|
||
<span class="kw">this</span>.<span class="at">_callbacks</span> <span class="op">=</span> []<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
|
||
<span class="va">Promise</span>.<span class="va">prototype</span>.<span class="at">then</span> <span class="op">=</span> <span class="kw">function</span>(func<span class="op">,</span> context) <span class="op">{</span>
|
||
<span class="kw">var</span> p<span class="op">;</span>
|
||
<span class="cf">if</span> (<span class="kw">this</span>.<span class="at">_isdone</span>) <span class="op">{</span>
|
||
p <span class="op">=</span> <span class="va">func</span>.<span class="at">apply</span>(context<span class="op">,</span> <span class="kw">this</span>.<span class="at">result</span>)<span class="op">;</span>
|
||
<span class="op">}</span> <span class="cf">else</span> <span class="op">{</span>
|
||
p <span class="op">=</span> <span class="kw">new</span> <span class="at">Promise</span>()<span class="op">;</span>
|
||
<span class="kw">this</span>.<span class="va">_callbacks</span>.<span class="at">push</span>(<span class="kw">function</span> () <span class="op">{</span>
|
||
<span class="kw">var</span> res <span class="op">=</span> <span class="va">func</span>.<span class="at">apply</span>(context<span class="op">,</span> arguments)<span class="op">;</span>
|
||
<span class="cf">if</span> (res <span class="op">&&</span> <span class="kw">typeof</span> <span class="va">res</span>.<span class="at">then</span> <span class="op">===</span> <span class="st">'function'</span>) <span class="op">{</span>
|
||
<span class="va">res</span>.<span class="at">then</span>(<span class="va">p</span>.<span class="at">done</span><span class="op">,</span> p)<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
<span class="op">}</span>)<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
<span class="cf">return</span> p<span class="op">;</span>
|
||
<span class="op">};</span>
|
||
|
||
<span class="va">Promise</span>.<span class="va">prototype</span>.<span class="at">done</span> <span class="op">=</span> <span class="kw">function</span>() <span class="op">{</span>
|
||
<span class="kw">this</span>.<span class="at">result</span> <span class="op">=</span> arguments<span class="op">;</span>
|
||
<span class="kw">this</span>.<span class="at">_isdone</span> <span class="op">=</span> <span class="kw">true</span><span class="op">;</span>
|
||
<span class="cf">for</span> (<span class="kw">var</span> i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op"><</span> <span class="kw">this</span>.<span class="va">_callbacks</span>.<span class="at">length</span><span class="op">;</span> i<span class="op">++</span>) <span class="op">{</span>
|
||
<span class="kw">this</span>.<span class="at">_callbacks</span>[i].<span class="at">apply</span>(<span class="kw">null</span><span class="op">,</span> arguments)<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
<span class="kw">this</span>.<span class="at">_callbacks</span> <span class="op">=</span> []<span class="op">;</span>
|
||
<span class="op">};</span>
|
||
|
||
<span class="kw">var</span> promise <span class="op">=</span> <span class="op">{</span>
|
||
<span class="dt">Promise</span><span class="op">:</span> Promise
|
||
<span class="op">};</span></code></pre></div>
|
||
<p>需要注意的是: <code>License</code>,不同的软件有不同的License,如MIT、GPL等等。最好能在遵循协议的情况下,使用别人的代码。</p>
|
||
<h3 id="实现第二个需求">实现第二个需求</h3>
|
||
<p>由于已经有了现有的很多库,所以就可以直接参照(抄)别人写的代码。</p>
|
||
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">Lettuce</span>.<span class="at">get</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
|
||
<span class="va">Lettuce</span>.<span class="at">send</span>(url<span class="op">,</span> <span class="st">'GET'</span><span class="op">,</span> callback)<span class="op">;</span>
|
||
<span class="op">};</span>
|
||
|
||
<span class="va">Lettuce</span>.<span class="at">load</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
|
||
<span class="va">Lettuce</span>.<span class="at">send</span>(url<span class="op">,</span> <span class="st">'GET'</span><span class="op">,</span> callback)<span class="op">;</span>
|
||
<span class="op">};</span>
|
||
|
||
<span class="va">Lettuce</span>.<span class="at">post</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> data<span class="op">,</span> callback) <span class="op">{</span>
|
||
<span class="va">Lettuce</span>.<span class="at">send</span>(url<span class="op">,</span> <span class="st">'POST'</span><span class="op">,</span> callback<span class="op">,</span> data)<span class="op">;</span>
|
||
<span class="op">};</span>
|
||
|
||
<span class="va">Lettuce</span>.<span class="at">send</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> method<span class="op">,</span> callback<span class="op">,</span> data) <span class="op">{</span>
|
||
data <span class="op">=</span> data <span class="op">||</span> <span class="kw">null</span><span class="op">;</span>
|
||
<span class="kw">var</span> request <span class="op">=</span> <span class="kw">new</span> <span class="at">XMLHttpRequest</span>()<span class="op">;</span>
|
||
<span class="cf">if</span> (callback <span class="kw">instanceof</span> Function) <span class="op">{</span>
|
||
<span class="va">request</span>.<span class="at">onreadystatechange</span> <span class="op">=</span> <span class="kw">function</span> () <span class="op">{</span>
|
||
<span class="cf">if</span> (<span class="va">request</span>.<span class="at">readyState</span> <span class="op">===</span> <span class="dv">4</span> <span class="op">&&</span> (<span class="va">request</span>.<span class="at">status</span> <span class="op">===</span> <span class="dv">200</span> <span class="op">||</span> <span class="va">request</span>.<span class="at">status</span> <span class="op">===</span> <span class="dv">0</span>)) <span class="op">{</span>
|
||
<span class="at">callback</span>(<span class="va">request</span>.<span class="at">responseText</span>)<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
<span class="op">};</span>
|
||
<span class="op">}</span>
|
||
<span class="va">request</span>.<span class="at">open</span>(method<span class="op">,</span> url<span class="op">,</span> <span class="kw">true</span>)<span class="op">;</span>
|
||
<span class="cf">if</span> (data <span class="kw">instanceof</span> Object) <span class="op">{</span>
|
||
data <span class="op">=</span> <span class="va">JSON</span>.<span class="at">stringify</span>(data)<span class="op">;</span>
|
||
<span class="va">request</span>.<span class="at">setRequestHeader</span>(<span class="st">'Content-Type'</span><span class="op">,</span> <span class="st">'application/json'</span>)<span class="op">;</span>
|
||
<span class="op">}</span>
|
||
<span class="va">request</span>.<span class="at">setRequestHeader</span>(<span class="st">'X-Requested-With'</span><span class="op">,</span> <span class="st">'XMLHttpRequest'</span>)<span class="op">;</span>
|
||
<span class="va">request</span>.<span class="at">send</span>(data)<span class="op">;</span>
|
||
<span class="op">};</span></code></pre></div>
|
||
<h1 id="如何以正确的姿势阅读开源软件代码">如何以“正确的姿势”阅读开源软件代码</h1>
|
||
<blockquote>
|
||
<p>所有让你直接看最新源码的文章都是在扯淡,你应该从“某个版本”开始阅读代码。</p>
|
||
</blockquote>
|
||
<p>我们并不建议所有的读者都直接看最新的代码,正确的姿势应该是:</p>
|
||
<ul>
|
||
<li>clone某个项目的代码到本地</li>
|
||
<li>查看这个项目的release列表</li>
|
||
<li>找到一个看得懂的release版本,如1.0或者更早的版本</li>
|
||
<li>读懂上一个版本的代码</li>
|
||
<li>向后阅读大版本的源码</li>
|
||
<li>读最新的源码</li>
|
||
</ul>
|
||
<p>最好的在这个过程中,<strong>可以自己造轮子来实现一遍</strong>。</p>
|
||
<h2 id="阅读过程">阅读过程</h2>
|
||
<p>在我阅读的前端库、Python后台库的过程中,我们都是以造轮子为目的展开的。所以在最开始的时候,我需要一个可以工作,并且拥有我想要的功能的版本。</p>
|
||
<figure>
|
||
<img src="./img/it-works-cms.png" alt="it-works-cms.png" /><figcaption>it-works-cms.png</figcaption>
|
||
</figure>
|
||
<p>紧接着,我就可以开始去实践这个版本中的一些功能,并理解他们是怎么工作的。再用<code>git</code>大法展开之前修改的内容,可以使用IDE自带的Diff工具:</p>
|
||
<figure>
|
||
<img src="./img/pycharm-diff.jpg" alt="pycharm-diff.jpg" /><figcaption>pycharm-diff.jpg</figcaption>
|
||
</figure>
|
||
<p>或者类似于<code>SourceTree</code>这样的工具,来查看修改的内容。</p>
|
||
<p>在我们理解了基本的核心功能后,我们就可以向后查看大、中版本的更新内容了。</p>
|
||
<p>开始之前,我们希望大家对版本号管理有一些基本的认识。 ## 版本号管理</p>
|
||
<p>我最早阅读的开始软件是Linux,而下面则是Linux的Release过程:</p>
|
||
<figure>
|
||
<img src="./img/linux-history.png" alt="linux-history.png" /><figcaption>linux-history.png</figcaption>
|
||
</figure>
|
||
<p>表格源自一本书叫《Linux内核0.11(0.95)完全注释》,简单地再介绍一下: - 版本0.00是一个hello,world程序 - 版本0.01包含了可以工作的代码 - 版本0.11是基本可以正常的版本</p>
|
||
<p>这里就要扯到《GNU 风格的版本号管理策略》:</p>
|
||
<p>1.项目初版本时,版本号可以为 0.1 或 0.1.0, 也可以为 1.0 或 1.0.0,如果你为人很低调,我想你会选择那个主版本号为 0 的方式; 2.当项目在进行了局部修改或 bug 修正时,主版本号和子版本号都不变,修正版本号加 1; 3. 当项目在原有的基础上增加了部分功能时,主版本号不变,子版本号加 1,修正版本号复位为 0,因而可以被忽略掉; 4.当项目在进行了重大修改或局部修正累积较多,而导致项目整体发生全局变化时,主版本号加 1; 5.另外,编译版本号一般是编译器在编译过程中自动生成的,我们只定义其格式,并不进行人为控制。</p>
|
||
<p>因此,我们可以得到几个简单的结论: - 我们需要阅读最早的有核心代码的版本 - 我们需要阅读1.0版本的Release - 往后每一次大的Release我们都需要了解一下</p>
|
||
<h2 id="示例">示例</h2>
|
||
<p>以Flask为例:</p>
|
||
<p>一、先Clone它。</p>
|
||
<figure>
|
||
<img src="./img/clone-flask.png" alt="clone-flask.png" /><figcaption>clone-flask.png</figcaption>
|
||
</figure>
|
||
<p>二、从Release页面找到它的早期版本:</p>
|
||
<figure>
|
||
<img src="./img/flask.png" alt="flask.png" /><figcaption>flask.png</figcaption>
|
||
</figure>
|
||
<p>三、 从上面拿到它的提交号<code>8605cc3</code>,然后checkout到这次提交,查看功能。在这个版本里,一共有六百多行代码</p>
|
||
<figure>
|
||
<img src="./img/flask-0.1.png" alt="flask-0.1.png" /><figcaption>flask-0.1.png</figcaption>
|
||
</figure>
|
||
<p>还是有点长</p>
|
||
<p>四、我们可以找到它的最早版本:</p>
|
||
<figure>
|
||
<img src="./img/flask-init.png" alt="flask-init.png" /><figcaption>flask-init.png</figcaption>
|
||
</figure>
|
||
<p>然后查看它的<code>flask.py</code>文件,只有简单的三百多行,并且还包含一系列注释:</p>
|
||
<figure>
|
||
<img src="./img/flask-init.png" alt="flask-init.png" /><figcaption>flask-init.png</figcaption>
|
||
</figure>
|
||
<p>五、接着,再回过头去阅读</p>
|
||
<ul>
|
||
<li>0.1版本</li>
|
||
<li>。。。</li>
|
||
<li>最新的0.10.1版本</li>
|
||
</ul>
|
||
<h1 id="github连击">GitHub连击</h1>
|
||
<h2 id="天">100天</h2>
|
||
<p>我也是蛮拼的,虽然我想的只是在GitHub上连击100~200天,然而到了今天也算不错。</p>
|
||
<figure>
|
||
<img src="./img/longest-streak.png" alt="Longest Streak" /><figcaption>Longest Streak</figcaption>
|
||
</figure>
|
||
<p><code>在不停地造轮子的过程中,也不停地造车子。</code></p>
|
||
<p>在那篇连续冲击365天的文章出现之前,我们公司的大大(<a href="https://github.com/dreamhead" class="uri">https://github.com/dreamhead</a>)也曾经在公司内部说过,天天commit什么的。当然这不是我的动力,在连击140天之前</p>
|
||
<ul>
|
||
<li>给过google的<code>ngx_speed</code>、<code>node-coap</code>等项目创建过pull request</li>
|
||
<li>也有<code>free-programming-books</code>、<code>free-programming-books-zh_CN</code>这样的项目。</li>
|
||
<li>当然还有一个连击20天。</li>
|
||
</ul>
|
||
<p>对比了一下365天连击的commit,我发现我在total上整整多了近0.5倍。</p>
|
||
<figure>
|
||
<img src="./img/365-streak.jpg" alt="365 Streak" /><figcaption>365 Streak</figcaption>
|
||
</figure>
|
||
<p>同时这似乎也意味着,我每天的commit数与之相比多了很多。</p>
|
||
<p>在连击20的时候,有这样的问题: <em>为了commit而commit代码</em>,最后就放弃了。</p>
|
||
<p>而现在是<code>为了填坑而commit</code>,为自己挖了太多的想法。</p>
|
||
<h3 id="天的提升">40天的提升</h3>
|
||
<p>当时我需要去印度接受毕业生培训,大概有5周左右,想着总不能空手而归。于是在国庆结束后有了第一次commit,当时旅游归来,想着自己在不同的地方有不同的照片,于是这个repo的名字是 <a href="https://github.com/phodal/onmap">onmap</a>——将自己的照片显示在地图上的拍摄地点(手机是Lumia 920)。然而,中间因为修改账号的原因,丢失了commit。</p>
|
||
<p>再从印度说起,当时主要维护三个repo:</p>
|
||
<ul>
|
||
<li>物联网的CoAP协议</li>
|
||
<li><a href="https://github.com/phodal/designiot">一步步设计物联网系统</a>的电子书</li>
|
||
<li>一个Node.js + JS的网站</li>
|
||
</ul>
|
||
<p>说说最后一个,最后一个是练习的项目。因为当时培训比较无聊,业余时间比较多,英语不好,加上听不懂印度人的话。晚上基本上是在住的地方默默地写代码,所以当时的目标有这么几个:</p>
|
||
<ul>
|
||
<li>TDD</li>
|
||
<li>测试覆盖率</li>
|
||
<li>代码整洁</li>
|
||
</ul>
|
||
<p>这也就是为什么那个repo有这样的一行:</p>
|
||
<figure>
|
||
<img src="./img/repo-status.png" alt="Repo Status" /><figcaption>Repo Status</figcaption>
|
||
</figure>
|
||
<p>做到98%的覆盖率也算蛮拼的,当然还有Code Climate也达到了4.0,也有了112个commits。因此也带来了一些提高:</p>
|
||
<ul>
|
||
<li>提高了代码的质量(code climate比jslint更注重重复代码等等一些bad smell)。</li>
|
||
<li>对于Mock、Stub、FakesServer等用法有更好的掌握</li>
|
||
<li>可以持续地交付软件(版本管理、自动测试、CI、部署等等)</li>
|
||
</ul>
|
||
<h3 id="天的挑战">100天的挑战</h3>
|
||
<p>(ps:从印度回来之后,由于女朋友在泰国实习,有了更多的时间可以看书、写代码)</p>
|
||
<p>有意思的是越到中间的一些时间,commits的次数上去了,除了一些简单的pull request,还有一些新的轮子出现了。</p>
|
||
<figure>
|
||
<img src="./img/problem.jpg" alt="Problem" /><figcaption>Problem</figcaption>
|
||
</figure>
|
||
<p>这是上一星期的commits,这也就意味着,在一星期里面,我需要在8个repo里切换。而现在我又有了一个新的idea,这时就发现了一堆的问题:</p>
|
||
<ul>
|
||
<li>今天工作在这个repo上,突然发现那个repo上有issue,需要去修复,于是就放下了当前的代码。</li>
|
||
<li>在不同的repo间切换容易分散精力</li>
|
||
<li>很容易就发现有太多的功能可以实现,但是时间是有限的。</li>
|
||
<li>没有足够的空闲时间,除了周末。</li>
|
||
<li>希望去寻找那些有兴趣的人,然而却发现原来没有那么多时间去找人。</li>
|
||
</ul>
|
||
<h3 id="天的希冀">140天的希冀</h3>
|
||
<p>在经历了100天之后,似乎整个人都轻松了,毕竟目标是100~200天。似乎到现在,也不会有什么特殊的情怀,除了一些希冀。</p>
|
||
<p>当然,对于一个开源项目的作者来说,最好有下面的情况:</p>
|
||
<ul>
|
||
<li>很多人知道了这个项目</li>
|
||
<li>很多人用它的项目。</li>
|
||
<li>在某些可以用这个项目快速解决问题的地方提到了这个项目</li>
|
||
<li>提了bug、issue、问题。</li>
|
||
<li>提了bug,并解决了。(ps:这是最理想的情况)</li>
|
||
</ul>
|
||
<h2 id="天的showcase">200天的Showcase</h2>
|
||
<p>今天是我连续泡在GitHub上的第200天,也是蛮高兴的,终于到达了:</p>
|
||
<figure>
|
||
<img src="./img/github-200-days.png" alt="GitHub 200 days" /><figcaption>GitHub 200 days</figcaption>
|
||
</figure>
|
||
<p>故事的背影是: 去年国庆完后要去印度接受毕业生培训——就是那个神奇的国度。但是在去之前已经在项目待了九个多月,项目上的挑战越来越少,在印度的时间又算是比较多。便给自己设定了一个长期的goal,即100~200天的longest streak。</p>
|
||
<p>或许之前你看到过一篇文章<a href="https://github.com/phodal/github-roam/blob/master/chapters/12-streak-your-github.md">让我们连击</a>,那时已然140天,只是还是浑浑噩噩。到了今天,渐渐有了一个更清晰地思路。</p>
|
||
<p>先让我们来一下ShowCase,然后再然后,下一篇我们再继续。</p>
|
||
<h3 id="一些项目简述">一些项目简述</h3>
|
||
<p>上面说到的培训一开始是用Java写的一个网站,有自动测试、CI、CD等等。由于是内部组队培训,代码不能公开等等因素,加之做得无聊。顺手,拿Node.js +RESTify 做了Server,Backbone + RequireJS + jQuery 做了前台的逻辑。于是在那个日子里,也在维护一些旧的repo,如<a href="https://github.com/phodal/iot-coap">iot-coap</a>、<a href="https://github.com/phodal/iot">iot</a>,前者是我拿到WebStorm开源License的Repo,后者则是毕业设计。</p>
|
||
<p>对于这样一个项目也需要有测试、自动化测试、CI等等。CI用的是Travics-CI。总体的技术构架如下:</p>
|
||
<h4 id="技术栈">技术栈</h4>
|
||
<p>前台:</p>
|
||
<ul>
|
||
<li>Backbone</li>
|
||
<li>RequireJS</li>
|
||
<li>Underscore</li>
|
||
<li>Mustache</li>
|
||
<li>Pure CSS</li>
|
||
</ul>
|
||
<p>后台:</p>
|
||
<ul>
|
||
<li>RESTify</li>
|
||
</ul>
|
||
<p>测试:</p>
|
||
<ul>
|
||
<li>Jasmine</li>
|
||
<li>Chai</li>
|
||
<li>Sinon</li>
|
||
<li>Mocha</li>
|
||
<li>Jasmine-jQuery</li>
|
||
</ul>
|
||
<p>一直写到五星期的培训结束, 只是没有自动部署。想想就觉得可以用github-page的项目多好~~。</p>
|
||
<p>过程中还有一些有意思的小项目,如:</p>
|
||
<h3 id="google-map-solr-polygon-搜索">google map solr polygon 搜索</h3>
|
||
<p><a href="http://www.phodal.com/blog/google-map-width-solr-use-polygon-search/">google map solr polygon 搜索</a></p>
|
||
<figure>
|
||
<img src="./img/solr.png" alt="google map solr" /><figcaption>google map solr</figcaption>
|
||
</figure>
|
||
<p>代码: <a href="https://github.com/phodal/gmap-solr" class="uri">https://github.com/phodal/gmap-solr</a></p>
|
||
<h3 id="技能树">技能树</h3>
|
||
<p>这个可以从两部分说起:</p>
|
||
<h4 id="重构skill-tree">重构Skill Tree</h4>
|
||
<p>原来的是</p>
|
||
<ul>
|
||
<li>Knockout</li>
|
||
<li>RequireJS</li>
|
||
<li>jQuery</li>
|
||
<li>Gulp</li>
|
||
</ul>
|
||
<figure>
|
||
<img src="./img/skilltree.jpg" alt="Skill Tree" /><figcaption>Skill Tree</figcaption>
|
||
</figure>
|
||
<p>代码: <a href="https://github.com/phodal/skillock" class="uri">https://github.com/phodal/skillock</a></p>
|
||
<h4 id="技能树sherlock">技能树Sherlock</h4>
|
||
<ul>
|
||
<li>D3.js</li>
|
||
<li>Dagre-D3.js</li>
|
||
<li>jquery.tooltipster.js</li>
|
||
<li>jQuery</li>
|
||
<li>Lettuce</li>
|
||
<li>Knockout.js</li>
|
||
<li>Require.js</li>
|
||
</ul>
|
||
<figure>
|
||
<img src="./img/sherlock.png" alt="Sherlock skill tree" /><figcaption>Sherlock skill tree</figcaption>
|
||
</figure>
|
||
<p>代码: <a href="https://github.com/phodal/sherlock" class="uri">https://github.com/phodal/sherlock</a></p>
|
||
<h4 id="django-ionic-elasticsearch-地图搜索">Django Ionic ElasticSearch 地图搜索</h4>
|
||
<figure>
|
||
<img src="./img/elasticsearch_ionit_map.jpg" alt="Django Elastic Search" /><figcaption>Django Elastic Search</figcaption>
|
||
</figure>
|
||
<ul>
|
||
<li>ElasticSearch</li>
|
||
<li>Django</li>
|
||
<li>Ionic</li>
|
||
<li>OpenLayers 3</li>
|
||
</ul>
|
||
<p>代码: <a href="https://github.com/phodal/django-elasticsearch" class="uri">https://github.com/phodal/django-elasticsearch</a></p>
|
||
<h4 id="简历生成器">简历生成器</h4>
|
||
<figure>
|
||
<img src="./img/resume.png" alt="Resume" /><figcaption>Resume</figcaption>
|
||
</figure>
|
||
<ul>
|
||
<li>React</li>
|
||
<li>jsPDF</li>
|
||
<li>jQuery</li>
|
||
<li>RequireJS</li>
|
||
<li>Showdown</li>
|
||
</ul>
|
||
<p>代码: <a href="https://github.com/phodal/resume" class="uri">https://github.com/phodal/resume</a></p>
|
||
<h4 id="nginx-大数据学习">Nginx 大数据学习</h4>
|
||
<figure>
|
||
<img src="./img/nginx_pig.jpg" alt="Nginx Pig" /><figcaption>Nginx Pig</figcaption>
|
||
</figure>
|
||
<ul>
|
||
<li>ElasticSearch</li>
|
||
<li>Hadoop</li>
|
||
<li>Pig</li>
|
||
</ul>
|
||
<p>代码: <a href="https://github.com/phodal/learning-data/tree/master/nginx" class="uri">https://github.com/phodal/learning-data/tree/master/nginx</a></p>
|
||
<h4 id="其他">其他</h4>
|
||
<p>虽然技术栈上主要集中在Python、JavaScript,当然还有一些Ruby、Pig、Shell、Java的代码,只是我还是习惯用Python和JavaScript。一些用到觉得不错的框架:</p>
|
||
<ul>
|
||
<li>Ionic: 开始Hybird移动应用。</li>
|
||
<li>Django: Python Web开发利器。</li>
|
||
<li>Flask: Python Web开发小刀。</li>
|
||
<li>RequireJS: 管理js依赖。</li>
|
||
<li>Backbone: Model + View + Router。</li>
|
||
<li>Angluar: …。</li>
|
||
<li>Knockout: MVV*。</li>
|
||
<li>React: 据说会火。</li>
|
||
<li>Cordova: Hybird应用基础。</li>
|
||
</ul>
|
||
<p>还应该有:</p>
|
||
<ul>
|
||
<li>ElasticSearch</li>
|
||
<li>Solr</li>
|
||
<li>Hadoop</li>
|
||
<li>Pig</li>
|
||
<li>MongoDB</li>
|
||
<li>Redis</li>
|
||
</ul>
|
||
<h2 id="天-1">365天</h2>
|
||
<blockquote>
|
||
<p>给你一年的时间,你会怎样去提高你的水平???</p>
|
||
</blockquote>
|
||
<figure>
|
||
<img src="./img/github-365.jpg" alt="GitHub 365" /><figcaption>GitHub 365</figcaption>
|
||
</figure>
|
||
<p>正值这难得的sick leave(万恶的空气),码文一篇来记念一个过去的366天里。尽管想的是在今年里写一个可持续的开源框架,但是到底这依赖于一个好的idea。在我的<a href="http://github.com/phodal/ideas">GitHub 孵化器</a> 页面上似乎也没有一个特别让我满意的想法,虽然上面有各种不样有意思的ideas。多数都是在过去的一年是完成的,然而有一些也是还没有做到的。</p>
|
||
<p>尽管一直在GitHub上连击看上去似乎是没有多大必要的,但是人总得有点追求。如果正是漫无目的,却又想着提高技术的同时,为什么不去试试?毕竟技术非常好、不需要太多练习的人只是少数,似乎这样的人是不存在的。大多数的人都是经过练习之后,才会达到别人口中的“技术好”。</p>
|
||
<p>这让我想起了充斥着各种气味的知乎上的一些问题,在一些智商被完虐的话题里,无一不是因为那些人学得比别人早——哪来的天才?所谓的天才,应该是未来的智能生命一般,一出生什么都知道。如果并非如此,那只是说明他练习到位了。</p>
|
||
<p>练习不到位便意味着,即使你练习的时候是一万小时的两倍,那也是无济于事的。如果你学得比别人晚,在<strong>很长的一段时间里</strong>(可能直到进棺材)输给别人是必然的——落后就要挨打。就好像我等毕业于一所二本垫底的学校里,如果在过去我一直保持着和别人(各种重点)一样的学习速度,那么我只能一直是Loser。</p>
|
||
<p>需要注意的是,对你来说考上二本很难,并不是因为你比别人笨。教育资源分配不均的问题,在某种程度上导致了新的阶级制度的出现。如<a href="https://www.phodal.com/">我的首页</a>说的那样: <strong>THE ONLY FAIR IS NOT FAIR</strong>——唯一公平的是它是不公平的。我们可以做的还有很多——<strong>CREATE & SHARE</strong>。真正的不幸是,因为营养不良导致的教育问题。</p>
|
||
<p>于是在想明白了很多事的时候起,便有了Re-Practise这样的计划,而365天只是中间的一个产物。</p>
|
||
<h3 id="编程的基础能力">编程的基础能力</h3>
|
||
<p>虽说算法很重要,但是编码才是基础能力。算法与编程在某种程度上是不同的领域,算法编程是在编程上面的一级。算法写得再好,如果别人很难直接拿来复用,在别人眼里就是shit。想出能work的代码一件简单的事,学会对其重构,使之变得更易读就是一件有意义的事。</p>
|
||
<p>于是,在某一时刻在GitHub上创建了一个组织,叫<a href="https://github.com/artisanstack">Artisan Stack</a>。当时想的是在GitHub寻找一些JavaScript项目,对其代码进行重构。但是到底是影响力不够哈,参与的人数比较少。</p>
|
||
<h4 id="重构-1">重构</h4>
|
||
<p>如果你懂得如何写出高可读的代码,那么我想你是不需要这个的,但是这意味着你花了更多的时候在思考上了。当谈论重构的时候,让我想起了TDD(测试驱动开发)。即使不是TDD,那么如果你写着测试,那也是可以重构的。(之前写过一些利用Intellij IDEA重构的文章:<a href="https://www.phodal.com/blog/intellij-idea-refactor-extract-method/">提炼函数</a>、<a href="https://www.phodal.com/blog/intellij-idea-refactor-replace-temp-with-query/">以查询取代临时变量</a>、<a href="https://www.phodal.com/blog/thoughtworks-refactor-and-intellij-idea/">重构与Intellij Idea初探</a>、<a href="https://www.phodal.com/blog/intellij-idea-refactor-inline-method/">内联函数</a>)</p>
|
||
<p>在各种各样的文章里,我们看到过一些相关的内容,最好的参考莫过于《重构》一书。最基础不过的原则便是函数名,取名字很难,取别人能读懂的名字更难。其他的便有诸如长函数、过大的类、重复代码等等。在我有限的面试别人的经历里,这些问题都是最常见的。</p>
|
||
<h4 id="测试-1">测试</h4>
|
||
<p>而如果没有测试,其他都是扯淡。写好测试很难,写个测试算是一件容易的事。只是有些容易我们会为了测试而测试。</p>
|
||
<p>在我写<a href="https://github.com/echoesworks/echoesworks">EchoesWorks</a>和<a href="https://github.com/phodal/lan">Lan</a>的过程中,我尽量去保证足够高的测试覆盖率。</p>
|
||
<figure>
|
||
<img src="./img/lan.png" alt="lan" /><figcaption>lan</figcaption>
|
||
</figure>
|
||
<figure>
|
||
<img src="./img/echoesworks.png" alt="EchoesWorks" /><figcaption>EchoesWorks</figcaption>
|
||
</figure>
|
||
<p>从测试开始的TDD,会保证方法是可测的。从功能到测试则可以提供工作次效率,但是只会让测试成为测试,而不是代码的一部分。</p>
|
||
<p>测试是代码的最后一公里。所以,尽可能的为你的GitHub上的项目添加测试。</p>
|
||
<h4 id="编码的过程">编码的过程</h4>
|
||
<p>初到TW时,Pair时候总会有人教我如何开始编码,这应该也是一项基础的能力。结合日常,重新演绎一下这个过程:</p>
|
||
<ol type="1">
|
||
<li>有一个可衡量、可实现、过程可测的目标</li>
|
||
<li>Tasking (即对要实现的目标过程进行分解)</li>
|
||
<li>一步步实现 (如TDD)</li>
|
||
<li>实现目标</li>
|
||
</ol>
|
||
<p>放到当前的场景就是:</p>
|
||
<ol type="1">
|
||
<li>我想在GitHub上连击365天。对应于每一个时候段的目标都应该是可以衡量、测试的——即每天都会有Contributions。</li>
|
||
<li>分解就是一个痛苦的过程。理想情况下,我们应该会有每天提交,但是这取决于你的repo的数量,如果没有新的idea出现,那么这个就变成为了Contributions而Commit。</li>
|
||
<li>一步步实现</li>
|
||
</ol>
|
||
<p>在我们实际工作中也是如此,接到一个任务,然后分解,一步步完成。不过实现会稍微复杂一些,因为事务总会有抢占和优先级的。</p>
|
||
<h3 id="技术与框架设计">技术与框架设计</h3>
|
||
<p>在上上一篇博客中《<a href="https://www.phodal.com/blog/after-500-blogposts-analytics-after-tech/">After 500: 写了第500篇博客,然后呢?</a>》也深刻地讨论了下这个问题,技术向来都是后发者优势。对于技术人员来说,也是如此,后发者占据很大的优势。</p>
|
||
<p>如果我们只是单纯地把我们的关注点仅仅放置于技术上,那么我们就不具有任何的优势。而依赖于我们的编程经验,我们可以在特定的时候创造一些框架。而架构的设计本身就是一件有意思的事,大抵是因为程序员都喜欢创造。(ps:之前曾经写过这样一篇文章,《<a href="https://www.phodal.com/blog/sorry-i-don't-like-programming/">对不起,我并不热爱编程,我只喜欢创造</a>》)</p>
|
||
<p><strong>创造是一种知识的再掌握过程。</strong></p>
|
||
<p>回顾一下写echoesworks的过程,一开始我需要的是一个网页版的PPT,当然这类的东西已经有很多了,如impress.js、bespoke.js等等。分析一下所需要的功能:markdown解析器、键盘事件处理、Ajax、进度条显示、图片处理、Slide。我们可以在GitHub上找到各式各样的模块,我们所要做的就是将之结合在一样。在那之前,我试着用类似的原理写(组合)了<a href="https://github.com/phodal/lettuce">Lettuce</a>。</p>
|
||
<p>组合相比于创造过程是一个更有挑战性的过程,我们需要在这过程去设计胶水来粘合这些代码,并在最终可以让他工作。这好比是我们在平时接触到的任务划分,每个人负责相应的模块,最后整合。</p>
|
||
<p>我在写<a href="https://github.com/phodal/lan">lan</a>的时候,也是类似的,但是不同的是我已经设计了一个清晰的架构图。</p>
|
||
<figure>
|
||
<img src="./img/lan-iot.jpg" alt="Lan IoT" /><figcaption>Lan IoT</figcaption>
|
||
</figure>
|
||
<p>而在我们实现的编码过程也是如此,使用不同的框架,并且让他们能工作。如早期玩的<a href="https://github.com/echoesworks/moqi.mobi">moqi.mobi</a>,基于Backbone、RequireJS、Underscore、Mustache、Pure CSS。在随后的时间里,用React替换了View层,就有了<a href="https://github.com/phodal/backbone-react">backbone-react</a>的练习。</p>
|
||
<p>技术同人一样,需要不断地往高一级前进。我们只需要不断地Re-Practise。</p>
|
||
<h3 id="领域与练习">领域与练习</h3>
|
||
<p>说业务好像不太适合程序员的口味,那就领域吧。不同行业的人,如百度、阿里、腾讯,他们的领域核心是不一样的。</p>
|
||
<p>而领域本身也是相似的,这可以解释为什么互联网公司都喜欢互相挖人,而一般都不会去华为、中兴等非互联网领域挖人。出了这个领域,你可能连个毕业生都不如。领域、业务同技术一样是不断强化知识的一个过程。Ritchie先实现了BCPL语言,而后设计了C语言,而BCPL语言一开始是基于CPL语言。</p>
|
||
<p>领域本身也在不断进化。</p>
|
||
<p>这也是下一个值得提高的地方。</p>
|
||
<h3 id="其他-1">其他</h3>
|
||
<p>是时候写这个小结了。从不会写代码,到写代码是从0到1的过程,但是要从1到60都不是一件容易的事。无论是刷GitHub也好(不要是自动提交),或者是换工作也好,我们都在不断地练习。</p>
|
||
<p>而练习是要分成不同的几个步骤,不仅仅局限于技术:</p>
|
||
<ol type="1">
|
||
<li>编码</li>
|
||
<li>架构</li>
|
||
<li>设计</li>
|
||
<li>。。。</li>
|
||
</ol>
|
||
<hr />
|
||
<h2 id="天-2">500天</h2>
|
||
<p>尽管之前已经有100天、200天、365天的文章,但是这不是一篇象征性的500天的文章。对这样的一个事物,每个人都会有不同听看法。有的会说这是一件好事,有的则不是。但是别人的看法终究不重要,因为了解你自己的只有你自己。别人都只是以他们的角度来提出观点。</p>
|
||
<p>在这500天里,我发现两点有意思的事,也是总结的时候才意识到的:</p>
|
||
<ol type="1">
|
||
<li>编程的情绪周期</li>
|
||
<li>有意图的练习</li>
|
||
</ol>
|
||
<p>那么,当我们不断地练习的时候,我们就可以写出更好的代码。</p>
|
||
<p>我想你也听过一万小时天才理论的说法:要成为某个领域的专家,需要10000小时。而在这其中每重要的一点是有意图的练习——而不是一直重复性地用不同的语言去写一个相同的算法。如果我们有一天8小时的工作时间 + 2 小时的提高时间,那么我们还是需要1000天才能实现一万小时。</p>
|
||
<h3 id="天与10000小时">500天与10000小时</h3>
|
||
<p>当然如果你连做梦也在写代码的话,那么我想500天就够了,哈哈~~。</p>
|
||
<figure>
|
||
<img src="./img/github-500.jpg" alt="Gtihub 500" /><figcaption>Gtihub 500</figcaption>
|
||
</figure>
|
||
<p>虽然不是连击次数最多的,但是根据<a href="http://git.io/top">Most active GitHub users</a>的结果来说,好似是大陆提交数最多的人,没有之一。再考虑到提交都是有意义的——不是机器刷出来的,不是有意识的去刷,我觉得还是有很大成就感的。</p>
|
||
<p>而要实现500天连击很重要的两点是:时间和idea。但是我觉得idea并不是非常重要的,我们可以造轮子,这一点就是在早期我做得最多的一件事,不断地造轮子——如《<a href="https://www.phodal.com/blog/create-framework-from-github/">造轮子与从Github生成轮子</a>》一文中所说。除此,你还可以用《<a href="https://www.phodal.com/blog/use-github-manage-idea/">GitHub去管理你的idea</a>》,每当你想到一个Idea以及完成一个idea的时间你就会多一次提交。</p>
|
||
<p>时间则是一件很讽刺的事,因为人们要加班。加班的原因,要么是因为工作的内容很有意思,要么是因为钱。如果不是因为钱的话,为什么不去换个工作呢?比如我司。看似两者间存在很多的对立,但是我总在想技术的提升可以在后期解决收入的问题,而不需要靠加班来解决这个问题。人总是要活着的,钱是必需的,但是程序员的收入都不低。</p>
|
||
<h3 id="编程的情绪周期">编程的情绪周期</h3>
|
||
<p>接着,我观察到了一些有意思的现象——编程的情绪周期也很明显。</p>
|
||
<blockquote>
|
||
<p>所谓“情绪周期”,是指一个人的情绪高潮和低潮的交替过程所经历的时间。</p>
|
||
</blockquote>
|
||
<p>如下图所示的就是情绪周期:</p>
|
||
<figure>
|
||
<img src="./img/qingxu.jpg" alt="情绪周期" /><figcaption>情绪周期</figcaption>
|
||
</figure>
|
||
<p>简单地来说,就是<strong>有一个时间段写代码的感觉超级爽,有一个时间段不想写代码</strong>,但是如果换一个说法就是:<strong>有一个时间段看书、写文档的感觉很爽,有一时间段不想看书、写文档的感觉</strong>。这也就是为什么在我的GitHub首页上的绿色各种花。不过因为《物联网周报》的原因,我会定期地更新一个相关的开源项目。</p>
|
||
<p>但是总来说,我习惯在一些时间造一些轮子、创建文档,这就是为什么我的GitHub会有一些开源电子书的缘故。</p>
|
||
<h3 id="有意图的练习">有意图的练习</h3>
|
||
<p>编程需要很长的学习时间,也需要很长的练习时间。尽管我是从小学编程,自认为天赋不错,但是突破了上个门槛还是花费了三四年的时间。其中的很大一部分原因是,没有找对一个合适的方向。而在这期间也没有好好的练习,随后的日子里我意识到我会遇到下一个门槛,便开始试图有意识的练习。</p>
|
||
<p>在我开始工作的时候,我写了一篇名为《<a href="https://www.phodal.com/blog/rethink-about-the-work/">重新思考工作</a>》的文章。在文章中我提到了几点练习的点:</p>
|
||
<ul>
|
||
<li>加强码代码的准确性</li>
|
||
<li>写出更整洁的代码</li>
|
||
<li>英语口语 (外企)</li>
|
||
<li>针对性的加强语言技能</li>
|
||
</ul>
|
||
<p>在一些日子的练习后,我发现这还是太无聊了。天生就喜欢一些有意思的东西,有趣才更有激情吧~~。不过,像下图的打字练习还是挺有意思的:</p>
|
||
<figure>
|
||
<img src="./img/huovd.png" alt="打字练习" /><figcaption>打字练习</figcaption>
|
||
</figure>
|
||
<p>还是能打出了一堆错误的字符。但是对比了一下大多数人的人,还算不错,至少是盲打。但是,还是存在着很大的提升空间。</p>
|
||
<p>随后,我开始一些错误的练习,如对设计模式和架构的练习。试图去练习一些在生产上用不到的设计模式,以及一些架构模式。而这时就意味着,需要生搬一些设计模式。最后,我开始以项目为目的的练习,这就是为什么我的GitHub上的提交数会有如此多的原因。</p>
|
||
<h3 id="预见性练习">预见性练习</h3>
|
||
<p>还有一种练习比较有意思,算是以工作为导向的练习。当我们预见到我们的项目需要某一些技术,我们可能在未来采用某些技术的时候,我们就需要开始预见性的练习这些技术。</p>
|
||
<p>好的一点是:这些项目可能在未来很受初学者欢迎。</p>
|
||
<h3 id="小结">小结</h3>
|
||
<p>每个人都有自己的方向,都有一个不错的发展路线,分享和创造都是不错的路。</p>
|
||
<p>THE ONLY FAIR IS NOT FAIR . ENJOY CREATE & SHARE.</p>
|
||
<h2 id="天里">365*2-7天里</h2>
|
||
<p>刚毕业的时候,有一段时间我一直困惑于如何去提高编码能力——因为项目上做的东西多数时候和自己想要的是不一样的,我便想着自己去找一些有意思的东西做着玩,在这个过程中边练习技能。</p>
|
||
<blockquote>
|
||
<p>如果你知道自己代码能力不够,为什么不花两年时间去提高这方面的能力呢?</p>
|
||
</blockquote>
|
||
<h3 id="编码的练习">编码的练习</h3>
|
||
<p>编码是一件值得练习的事,你从书中、互联网上看到的那一个个的编程大牛无一不是从一点点的小技能积累起来的。从小接触可以让你有一个好的开始,一段好好的练习也会帮助你更好的前进。</p>
|
||
<p>记得我在最开始练习的时候,我分几个不同的阶段去练习:</p>
|
||
<ul>
|
||
<li>按照《重构:改善即有代码的设计》一书边寻找一些 bad smell 的代码,一边想方设法去让代码变得优雅。</li>
|
||
<li>按照《设计模式》以及《重构与模式》来将代码重构成某种设计模式。</li>
|
||
<li>按照《面向模式的软件架构》去设计一些软件架构。</li>
|
||
</ul>
|
||
<p>而这些并不是一种容易的事,很多时候有一些模式,我们都很难有一个好的实践。只是这些东西都不是一些可以生搬硬套的,我们更需要的是知道有这些东西的存在,以便于在某一天,我们可以从我们的仓库里将这些知识取出来。</p>
|
||
<figure>
|
||
<img src="./img/10000.png" alt="10000 hours" /><figcaption>10000 hours</figcaption>
|
||
</figure>
|
||
<p>我们的刻意练习加上我们的持之以恒总是会取得长足的进步。不过在我们练习之前,你需要有一个目标。这个目标可以是一个 Idea、一个设计模式、一个模仿等等,这些内容都可以以 Issue 的好好管理着。</p>
|
||
<p>在最开始我们下定目标的几天里,我们可以很容易做到这样的事。同样的,我们也可以很容易达到 21 天。只是,我们很容易在 21 天后失去一些目标。所以在练习开始之前,你需要创建一个帮助你提高技术的列表,然后一点点加以提高。比如说:</p>
|
||
<ol type="1">
|
||
<li>尝试使用 React + Redux + Koa 2、或者Angular 2 + TypeScript,这样我们就能凭此来学习新的技术。</li>
|
||
<li>尝试使用 CQRS 架构来设计 CMS,这样我们就可以练习在架构方面的能力。</li>
|
||
</ol>
|
||
<p>在我们想到一点我们可以练习的技术的时候,这就是一个可以变成 Issue 管理的内容,我们就可以针对性的提高。</p>
|
||
<p>通常在这种情况下,我们知道自己不知道什么东西,当我们处于不知道自己不知道、不知道自己知道时,那我们就需要网上的各种技能图谱——如StuQ的技能图谱。</p>
|
||
<figure>
|
||
<img src="./img/skillmap.png" alt="skilmap" /><figcaption>skilmap</figcaption>
|
||
</figure>
|
||
<p>然后了解图谱上的一个个的内容,尽可能依照此构建自己的体系——以让自己走向知道自己不知道的地步,然后我们才依此来展开练习。</p>
|
||
<p>建议试试我们家的Growth哈,地址:http://growth.ren。</p>
|
||
<p>文章的剩下部分就让我分享一下:在这723天里,我创造出了哪些有意思的东西(ps:让我装逼一下)——其实我不仅仅只是 Markdown 写得好</p>
|
||
<h4 id="年">2014年</h4>
|
||
<p>时间:2014.10.08-2014.12.30</p>
|
||
<figure>
|
||
<img src="./img/2014.png" alt="2014.png" /><figcaption>2014.png</figcaption>
|
||
</figure>
|
||
<p>在这一段时间里,我创建的项目大部分都是一些物联网项目:</p>
|
||
<ul>
|
||
<li><a href="https://github.com/phodal/iot-coap">iot-coap</a> 一个基于CoAP协议的物联网</li>
|
||
<li><a href="https://github.com/phodal/designiot">designiot</a> 即电子书《教你设计物联网系统》</li>
|
||
<li><a href="https://github.com/phodal/awesome-iot-document">iot-document</a> 收集一些物联网相关的资料,和Awesome不是一个性质</li>
|
||
<li><a href="https://github.com/phodal/iot">iot</a> 基于PHP框架Laravel的物联网</li>
|
||
<li>iot-android 一个与iot项目相配套的Android程序</li>
|
||
<li>等等</li>
|
||
</ul>
|
||
<p>正是这几个IoT项目,让Packt出版社找到了我,才有了后来和国内外出版社打交道的故事。也开始了技术审阅、翻译、写书的各种故事,想想就觉得这个开头真的很好。</p>
|
||
<p>期间还创建了一个很有意思的Chrome插件,叫onebuttonapp——没错,就是模仿Amazon的一键下单写的。这个插件的目的就是难证当时在项目上用的Backbone、Require.js的这一套可以在插件上好好玩。</p>
|
||
<p>OnMap项目是为了让我用Nokia Lumia 920拍照的照片,可以在地图上显示而创建的项目。</p>
|
||
<p>当然还有其他的一些小项目啦。</p>
|
||
<h4 id="年-1">2015年</h4>
|
||
<figure>
|
||
<img src="./img/2015.png" alt="2015.png" /><figcaption>2015.png</figcaption>
|
||
</figure>
|
||
<p>整个区间就是刷各种前端的技术栈,创建了各种有意思的项目:</p>
|
||
<ul>
|
||
<li><a href="https://github.com/phodal/lettuce">Lettuce框架</a>,一个基于简单的SPA框架</li>
|
||
<li><a href="https://github.com/phodal/echoesworks">echoesworks</a>,一个支持字幕、Markdown、动画的Slide框架</li>
|
||
<li><a href="https://github.com/phodal/diaonan">diaonan</a>,一个支持CoAP、MQTT、HTTP的物联网项目</li>
|
||
<li><a href="https://github.com/phodal/developer">developer</a>,收集各种 Web Developer 成长路线,以及读书图谱</li>
|
||
</ul>
|
||
<p>期间还创建了几个混合应用项目:</p>
|
||
<ul>
|
||
<li><a href="https://github.com/phodal/learning-ionic">learning-ionic</a>,程序语言答人,各种hello,world的小应用</li>
|
||
<li><a href="https://github.com/phodal/ionic-elasticsearch">ionic-elasticsearch</a>, Django ElasticSearch Ionic 打造 GIS 移动应用</li>
|
||
<li><a href="https://github.com/phodal/designiot-app">designiot-app</a>,教你设计物联网APP版</li>
|
||
</ul>
|
||
<p>更多内容可以见我的Idea列表:<a href="https://github.com/phodal/ideas" class="uri">https://github.com/phodal/ideas</a>,我实在是不想写了。</p>
|
||
<h4 id="年-2">2016年</h4>
|
||
<figure>
|
||
<img src="./img/2016.png" alt="2016.png" /><figcaption>2016.png</figcaption>
|
||
</figure>
|
||
<p>我们有了Growth系列的电子书、APP,还有Mole,几个极具代表性的项目就够了。</p>
|
||
<ul>
|
||
<li><a href="https://github.com/phodal/growth">Growth</a>,一款专注于Web开发者成长的应用,涵盖Web开发的流程及技术栈,Web开发的学习路线、成长衡量等各方面。</li>
|
||
<li><a href="https://github.com/phodal/growth-ebook">Growth: 全栈增长工程师指南</a>,一本关于如何成为全栈增长工程师的指南</li>
|
||
<li><a href="https://github.com/phodal/growth-in-action">Growth: 全栈增长工程师实战</a>,在Growth中我们介绍的只是一系列的实践,而Growth实战则会带领读者去履行这些实践</li>
|
||
</ul>
|
||
<h3 id="see-you-again">See you Again</h3>
|
||
<p>停止这次连击,只是为了有一个更好的开始。</p>
|
||
<p>如果你也想提高自己,不妨从创建你的 ideas 项目开始,如我的<a href="https://github.com/phodal/ideas">Ideas</a>项目一样,上面已经有了大量的 Idea。然后,我们还可以依据这一个个的项目,创建出一本电子书,即 <a href="https://github.com/phodal/ideabook">ideabook</a>。</p>
|
||
<h1 id="github-里程碑">GitHub 里程碑</h1>
|
||
<h2 id="写在github-的第-19999-个-star-时">写在GitHub 的第 19999 个 star 时</h2>
|
||
<blockquote>
|
||
<p>Star 虽好,可不要贪杯哦。 两年前在做 Annual Review 订下一年的目标时,想着写一个开源框架。去年订下今年的目标时,仍然继续着这样的想法。今年又要制定下一年的目标,2333~~。</p>
|
||
</blockquote>
|
||
<p>不久前,在 GitHub Ranking 上看到自己的 star 数(star 不是设计用于做“点赞”的,而是用来收藏的)时,发现已经快 20000 了。然后把自己的项目过了一遍,发现没有一个比较好的<strong>代表性框架,</strong>要么是应用,要么是电子书。</p>
|
||
<p>前 8 个项目里,除了 Growth 应用以外,其他的都是电子书内容——六本电子书加起来的 star 数有 <strong>10619</strong>,果然是骗 star 的。我只能尽力地去想想:为什么事情会变成这样了?</p>
|
||
<h3 id="从创建开源框架说起">从创建开源框架说起</h3>
|
||
<p>创建开源框架和创建创建开源项目是不一样的,前者你服务于开发者,后者你服务于用户。</p>
|
||
<p>两年前在做 Annual Review 的时候,想着未来的一年里可以做一个开源框架试试。那时刚毕业不久,对开源世界的各种游戏规则不是很了解:<strong>开源并不是将代码提交上去,然后就会一下子火起来</strong>。虽然我们可以在短期内赚上一些眼球,但是真正要将它采用到项目上的人不多。</p>
|
||
<p>当时,我遇到的最主要的问题是:<strong>想参与到项目的人并没有遇到足够的能力</strong>。你还需要花费大量的时间去教他们,鼓励 GitHub 新手并不是一件容易的事。有时我需要在接受他的 PR 后,再修改他的代码。并且人们提交 PR 可能是出于不同的原因。</p>
|
||
<p>然后,知道了开源世界还有一个游戏规则是:<strong>谁的影响力大,谁就能产生更广泛的影响</strong>。如 Virtual Dom 并不是 Facebook 首创的,但是却因为 FB 火的; 松本行弘在写下 mruby 的 README 时(印象中是这个项目),star 数就已经过 1k 了。这种例子数不胜数,要么是在推广上花了力气,要么个人、公司有着更大的影响力。</p>
|
||
<p>一年前,稍微改变了下策略:暂时以<strong>培养人为主</strong>,同时想着做一个合适的开源框架——只是在今年看来,前端领域已经没有合适的地方可以造轮子了。</p>
|
||
<p>在 GitHub 上有一个很常见的问题是,<strong>大多部分项目的维护者就是发起人</strong>——如果这个发起人发生意外了,那么这个项目怎么办。如果这是一个很火的项目,它就存在着巨大的风险;同时这可能也说明了,缺乏一套合理的机制。</p>
|
||
<p>你的开源项目不仅仅需要一个使用文档,还需要一个相关设计思想的文档、路线图、未来计划等等。</p>
|
||
<p>去年年底写总结的时候,想到可以 RePractise 文章为基础来培养人,于是就有了 Growth 的三个项目:</p>
|
||
<ul>
|
||
<li>应用:<a href="https://github.com/phodal/growth">Growth</a></li>
|
||
<li>电子书:《<a href="https://github.com/phodal/growth-ebook">Growth:全栈增长工程师指南</a>》</li>
|
||
<li>电子书:《<a href="https://github.com/phodal/growth-in-action">Growth:全栈增长工程师实战</a>》</li>
|
||
</ul>
|
||
<p>如今 Growth 已经有了过万的用户,每天活跃的用户数也接近 300 了。第一步看上去很成功,但是下一步怎么走呢?</p>
|
||
<h3 id="下一个开源项目">下一个开源项目</h3>
|
||
<p>后来我开始在思索一个问题,创建一个开源框架是必须的吗?</p>
|
||
<p>在编写 Growth 电子书的时候,我发现一个好的软件工程实践远远比一个易上手的框架重要多了。框架本身是易变的东西,过去你在用 Backbone,现在你在用 React.js;过去你在用 Angular.js,现在你在用 Vue。会不会使用某个框架,并不是区分你是不是一个有经验的开发者的标准。</p>
|
||
<p>一直将焦点关注于<strong>学习不同的框架的使用</strong>是有问题的,一个在校生可以轻松地比你了解某个框架的原理——你白天在工作,而他整天在学习。这时你很容易就失去竞争力了,你需要从框架之外了解更深层次的东西。<strong>一个好的框架并不能让你写出一段好的代码</strong>。</p>
|
||
<blockquote>
|
||
<p>如果中国人的思想不觉悟,即使治好了他们的病,也只是做毫无意义。</p>
|
||
</blockquote>
|
||
<p>这算是我为自己在 GitHub 下的 Markdown 的自辩吧——谁让我一直有写作的冲动呢。</p>
|
||
<p>不过我仍然还有一些想法,只是还没有抽出足够的时间去思考这样的事。</p>
|
||
<p><strong>GNU/Linux 的桌面</strong>。这是几年前的一个想法了,当时 GNU/Linux 的那些操作系统上都没有一个好玩的桌面,不过感觉这个坑太深了,就没有进行了。</p>
|
||
<p><strong>家居智能中心</strong>。我仍然对于大学学的知识有点念念不忘,虽然已经写了一本书,但是硬件还是相当的刺激。唯一的问题是:连房子都没有,怎么做智能家居。</p>
|
||
<p><strong>图形框架</strong>。这是我之前在做一个图形界面的时候,发生没有一个合适的框架可以满足我的要求。然后我就在想,还是自己做一个吧。</p>
|
||
<p>不过,最好的开源项目就是自己平时用的。于是,我开始将写各种工作来提自己使用——如现在在用的这篇微信编辑工具:<a href="https://github.com/phodal/mdpub">mdpub</a>。</p>
|
||
<p>最后,我做了一个简单的 HTML 5 动画来记录这一时刻,作为这一个里程碑的记念:</p>
|
||
<p><a href="https://phodal.github.io/20k/" class="uri">https://phodal.github.io/20k/</a></p>
|
||
<section class="footnotes">
|
||
<hr />
|
||
<ol>
|
||
<li id="fn1"><p>https://zh.wikipedia.org/wiki/%E7%90%86%E6%9F%A5%E5%BE%B7%C2%B7%E6%96%AF%E6%89%98%E6%9B%BC<a href="#fnref1">↩</a></p></li>
|
||
<li id="fn2"><p>https://www.gnu.org/philosophy/open-source-misses-the-point.zh-cn.html<a href="#fnref2">↩</a></p></li>
|
||
<li id="fn3"><p>https://zh.wikipedia.org/wiki/WTFPL<a href="#fnref3">↩</a></p></li>
|
||
</ol>
|
||
</section>
|
||
</body>
|
||
</html>
|