Bu・log
Jekyll
2021-04-24T20:37:57+08:00
https://yaowenjie.github.io/
Wenjie Yao
https://yaowenjie.github.io/
wsywj61@gmail.com
https://yaowenjie.github.io/life%7C%E7%94%9F%E6%B4%BB/missing-bicycle-in-my-memory
https://yaowenjie.github.io/life%7C%E7%94%9F%E6%B4%BB/missing-bicycle-in-my-memory
2018-07-26T15:08:00+08:00
2018-07-26T15:08:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<p> 最近总会想到老家…</p>
<!--more-->
<p> 总会想到一些记忆模糊,但又偶然闪现脑际的东西。比如,在异国他乡穿行时,随处可见的<strong>自行车</strong>。</p>
<p> 我已经不记得具体是什么时候学会骑自行车的了。我想肯定是初中某个时刻,因为学会骑自行车的那个时刻,我总觉得自己晚了别人好久。一旦有了这个主题,在我脑海里徘徊的就只剩下我家那座老房子和那一条修到一半荒废了路。老房子其实算是那个小镇上比较早的一批楼房,当时我家在3楼,301,旁边邻居都还不错,妈妈总是没事就去旁边几家唠叨聊聊家常里短,有时候他们也会跑到我家来唠嗑。荒废的路坐落在一个那时候还算蛮阔气的商场后面,旁边蛮多杂草,下雨后,杂草混着水洼变成了水草。但路终究是水泥路,还基本没啥人,所以那时候我经常在那儿“练车”。老房子好像还在,不过两年前看过一眼,那时候已经看起来满目疮痍老态龙钟。而那条路,被另一个市场占据了,再也不见原来模样。</p>
<p> 等等,关于自行车最初的记忆,我却越想越不对劲,导致脑壳有几分疼痛。我当时的自行车是我自己买的?还是从哪个邻居那借来的?它到底是什么模样?最后是怎么被我抛弃的?这几个问题我却无法从我的脑子里找到答案,关于他本体的记忆因子完全消失在我的万千思绪下。或许哪一天当我再次想起,父母又在身边,他们会给我正确的答案吧。是的,我想总有人帮我记着某些细节吧,可我自己却不能记得那辆自行车,我想如果它也有感情,估计难免也会有几分忧伤吧。</p>
<p> 可能我只是个念旧的人罢了,但是有些东西要像这自行车一样,该有多好。</p>
<p><a href="https://yaowenjie.github.io/life%7C%E7%94%9F%E6%B4%BB/missing-bicycle-in-my-memory">消失的自行车</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on July 26, 2018.</p>
https://yaowenjie.github.io/life%7C%E7%94%9F%E6%B4%BB/highlights-when-travel-in-europe
https://yaowenjie.github.io/life%7C%E7%94%9F%E6%B4%BB/highlights-when-travel-in-europe
2018-06-08T18:00:00+08:00
2018-06-08T18:00:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<p> 在德村待了两个多月,上上周(五月中下旬)终于出去溜达了一圈,分别去了三个国家的三个城市,其中文化、风景、食物的感受都不尽相同。</p>
<!--more-->
<p> 这三个城市分别是:</p>
<ul>
<li>水上小城,一个和丝绸之路、马可波罗紧密相联的著名城市 - <strong>威尼斯</strong>(🇮🇹)</li>
<li>浪漫的海滨城市,一座建筑独特、体育闻名的现代化都市 - <strong>巴塞罗那</strong>(🇪🇸)</li>
<li>欧盟总部所在地,欧洲各区域文化汇聚代表地,巧克力之城 - <strong>布鲁塞尔</strong>(🇧🇪)</li>
</ul>
<figure class="third">
<a href="https://yaowenjie.github.io/images/2018/europe/venice1.jpg"><img src="https://yaowenjie.github.io/images/2018/europe/venice1.jpg" /></a>
<a href="https://yaowenjie.github.io/images/2018/europe/venice2.jpg"><img src="https://yaowenjie.github.io/images/2018/europe/venice2.jpg" /></a>
<a href="https://yaowenjie.github.io/images/2018/europe/venice3.jpg"><img src="https://yaowenjie.github.io/images/2018/europe/venice3.jpg" /></a>
</figure>
<figure class="half">
<a href="https://yaowenjie.github.io/images/2018/europe/venice4.jpg"><img src="https://yaowenjie.github.io/images/2018/europe/venice4.jpg" /></a>
<a href="https://yaowenjie.github.io/images/wj/20.jpg"><img src="https://yaowenjie.github.io/images/wj/20.jpg" /></a>
</figure>
<figure>
<a href="https://yaowenjie.github.io/images/2018/europe/venice5.jpg"><img src="https://yaowenjie.github.io/images/2018/europe/venice5.jpg" /></a>
</figure>
<figcaption>威尼斯・Venice</figcaption>
<p> “威尼斯是世界闻名的水上城市,河道纵横交叉,小艇成了主要的交通工具,等于大街上的汽车”,这是小时候课本里的描述,而威尼斯确实如其所述。不过因为它太过于闻名,小城里的游客真心多。不过我们也去了一些偏僻的小巷,那里更能够感受到本地人的生活气息。他们没有停车场,但几乎每家都有停船的地方。大运河上或许还有曾经纵横四海的商人的影子,但现在它更多是游客观光游赏的船道了吧。</p>
<p><br /></p>
<figure class="third">
<a href="https://yaowenjie.github.io/images/wj/19.jpg"><img src="https://yaowenjie.github.io/images/wj/19.jpg" /></a>
<a href="https://yaowenjie.github.io/images/2018/europe/barce2.jpg"><img src="https://yaowenjie.github.io/images/2018/europe/barce2.jpg" /></a>
<a href="https://yaowenjie.github.io/images/2018/europe/barce3.jpg"><img src="https://yaowenjie.github.io/images/2018/europe/barce3.jpg" /></a>
</figure>
<figure class="half">
<a href="https://yaowenjie.github.io/images/wj/1.jpg"><img src="https://yaowenjie.github.io/images/wj/1.jpg" /></a>
<a href="https://yaowenjie.github.io/images/2018/europe/barce4.jpg"><img src="https://yaowenjie.github.io/images/2018/europe/barce4.jpg" /></a>
</figure>
<figure>
<a href="https://yaowenjie.github.io/images/2018/europe/barce1.jpg"><img src="https://yaowenjie.github.io/images/2018/europe/barce1.jpg" /></a>
</figure>
<figcaption>巴塞罗那・Barcelona</figcaption>
<p> 巴塞罗那之前给我的印象是体育胜地,可能是因为足球俱乐部的影响力导致的吧。不过在这里走着,也确实能感受到人民对于运动的热爱(我看见,清晨八九点左右篮球场上就很多男孩女孩在那玩耍)。</p>
<p> 巴塞罗那有很多奇特、别致的建筑,很多都是建筑师高迪的杰作,但是撇开建筑来说,我还是更喜欢他的生活气息和海滩。和威尼斯相比,巴塞的物价真心便宜,物资也要丰富很多,在超市里我居然看到很多兔肉(对此,之后和德村人民聊过,他们表示很无语,怎么能够吃兔兔?!)。在很多地方,你可以俯瞰这座城市,建筑密集而又有序,海紧邻着这座城。说实话,三座城市中最爱它了。</p>
<p><br /></p>
<figure class="third">
<a href="https://yaowenjie.github.io/images/wj/16.jpg"><img src="https://yaowenjie.github.io/images/wj/16.jpg" /></a>
<a href="https://yaowenjie.github.io/images/2018/europe/bruss2.jpg"><img src="https://yaowenjie.github.io/images/2018/europe/bruss2.jpg" /></a>
<a href="https://yaowenjie.github.io/images/2018/europe/bruss4.jpg"><img src="https://yaowenjie.github.io/images/2018/europe/bruss4.jpg" /></a>
</figure>
<figure>
<a href="https://yaowenjie.github.io/images/2018/europe/bruss3.jpg"><img src="https://yaowenjie.github.io/images/2018/europe/bruss3.jpg" /></a>
</figure>
<figcaption>布鲁塞尔・Brussels</figcaption>
<p> 布鲁塞尔之前对于我个人而言,记忆最深的莫过于“小英雄于连”,也就是那个撒尿小男孩,对小时候课本里的那副插画记忆犹新。可真的过去了,才发现这雕像居然这么小,连旁边巧克力店的复制品都比真实的雕像大(见上图)。另一个感受比较深的是,这里人民的多样性,因为他们的官方语言很多,路上好几个人主动找我们说话的人我都不知道说的是何国之言,每每我回复以“sorry, I can’t speak…”时,都不知道如何收场。因为这里文化差异性比较大,看到和接触到的人也很不相同(这多少给我一些危机感,因为不知道是好意还是坏意),不过这或许也是欧盟议会定址这里的原因之一吧。</p>
<p><a href="https://yaowenjie.github.io/life%7C%E7%94%9F%E6%B4%BB/highlights-when-travel-in-europe">滤静时光 - 欧洲三国游</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on June 08, 2018.</p>
https://yaowenjie.github.io/devops/development-branching-model-in-continuous-delivery
https://yaowenjie.github.io/devops/development-branching-model-in-continuous-delivery
2018-01-28T15:34:00+08:00
2018-01-28T15:34:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<p> 1月28号在<strong>ThoughtWorks武汉office</strong>做的一次演讲,题目为《持续交付下的开发分支模型》,分享一下slide:</p>
<!--more-->
<center><iframe src="https://yaowenjie.github.io/share/PDFs/branching-model.pdf" width="960" height="480" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:3px solid #666; margin-bottom:5px; max-width: 100%;" allowfullscreen=""> </iframe></center>
<p><strong>Reference:</strong></p>
<ul>
<li><a href="http://www.itdks.com/dakalive/detail/9937">持续集成下的开发分支模型 - IT大咖说</a></li>
</ul>
<p><a href="https://yaowenjie.github.io/devops/development-branching-model-in-continuous-delivery">DevOps Open day活动讲义 - 持续交付下的开发分支模型</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on January 28, 2018.</p>
https://yaowenjie.github.io/front-end/8-key-react-component-decisions
https://yaowenjie.github.io/front-end/8-key-react-component-decisions
2017-12-02T15:22:00+08:00
2017-12-02T15:22:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<h6 id="本文翻译自cory-house的博客文章">本文翻译自Cory House的<a href="https://medium.freecodecamp.org/8-key-react-component-decisions-cc965db11594">博客文章</a>。</h6>
<p> React自2013年开源以来,逐步进化演变。当你在网上搜索它时,你会偶尔发现一些讲述过时方法的旧文章。本文就是讲述了团队中编写React组件时需要采取的八项关键决策。</p>
<!--more-->
<h3 id="决策1开发环境">决策1:开发环境</h3>
<p> 在你编写第一个组件之前,你的团队往往需要在开发环境上达成共识。这里存在很多观点。。。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/react-1.png" alt="react.png" /></center>
<p> 当然,你可以<a href="https://www.pluralsight.com/courses/javascript-development-environment">从头构建一个JS的开发环境</a>。25%的React开发者这么做。我当前的团队使用了create-react-app的一个fork版本,不过添加了一些额外的功能,比如<a href="https://medium.freecodecamp.org/rapid-development-via-mock-apis-e559087be066">支持CRUD逼真的mock API</a>,<a href="https://www.pluralsight.com/courses/react-creating-reusable-components">可复用的组件库</a>,以及一些代码检查优化处理措施(我们也检查测试文件,而create-react-app是忽略的)。我喜欢create-react-app,但是<a href="https://www.andrewhfarmer.com/starter-project/">这个工具会帮助你比较众多具有竞争力的替代方案</a>。想在服务器端渲染?你可以看看<a href="http://gatsbyjs.org">Gatsby</a>和<a href="https://github.com/zeit/next.js/">Next.js</a>。你甚至可以考虑使用像<a href="https://codesandbox.io">CodeSandbox</a>这样的线上编辑器。</p>
<h3 id="决策2types">决策2:Types</h3>
<p> 你可以忽略类型,可以使用<a href="https://reactjs.org/docs/typechecking-with-proptypes.html">prop-types</a>,使用<a href="https://flow.org">Flow</a>,或者使用<a href="https://www.typescriptlang.org">TypeScript</a>。请注意prop-type在React 15.5中被抽成了一个<a href="https://www.npmjs.com/package/prop-types">独立的库</a>,所以一些旧文章告诉你的一些import已不再起作用了。</p>
<p> 在这个问题上,社区上仍然存在分歧:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/react-2.png" alt="react.png" /></center>
<p> 我更喜欢用prop-types,因为我发现它在组件内提供了足够好的类型安全性。使用Babel, <a href="https://facebook.github.io/jest/">Jest tests</a>, <a href="http://www.eslint.org">ESlint</a>, 以及prop-types的组合后,我很难看到运行时的类型错误了。</p>
<h3 id="决策3createclass还是es的class">决策3:createClass还是ES的Class</h3>
<p> React.createClass是最初的API,在15.5版本中,它就被弃用了。有些人会觉得<a href="https://medium.com/dailyjs/we-jumped-the-gun-moving-react-components-to-es2015-class-syntax-2b2bb6f35cb3">转战到ES的class上去有些为时过早</a>。但不管怎样,createClass风格已经从React的核心库中移出去了,取而代之的是React官方文档中<a href="https://reactjs.org/docs/react-without-es6.html">“React without ES6”</a>这一页内容。所以这就很清楚了:ES(ECMAScript)的class才是未来。你也可以使用<a href="https://github.com/reactjs/react-codemod">react-codemod</a>把createClass简单地转化成ES的class。</p>
<h3 id="决策4class还是function">决策4:Class还是Function</h3>
<p> 你可以使用class或者function来定义React组件。Class用在使用ref和生命周期方法的时候。这里是<a href="https://hackernoon.com/react-stateless-functional-components-nine-wins-you-might-have-overlooked-997b0d933dbc">在可能的情况下考虑使用function的9个理由</a>。不过,<a href="https://hackernoon.com/react-stateless-functional-components-nine-wins-you-might-have-overlooked-997b0d933dbc">function组件也有一些缺点</a>。</p>
<h3 id="决策5state管理">决策5:State管理</h3>
<p> 你可以使用自带的React组件state。它就足够了。<a href="http://www.css88.com/react/docs/lifting-state-up.html">状态提升</a>可以很好地运作。或者,你会喜欢Redux或MobX:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/react-3.png" alt="react.png" /></center>
<p> <a href="https://www.pluralsight.com/courses/react-redux-react-router-es6">我是Redux的粉丝</a>,但是我经常使用自带的React只因为他更加简单。在我当前的角色下,已经交付了十几个React应用了,其中只有两个值得使用Redux。我更喜欢在单个大型应用上发布许多个小型自治应用。</p>
<p> 顺便要提到的是,如果你对不可变状态感兴趣,至少有<a href="https://medium.com/@housecor/handling-state-in-react-four-immutable-approaches-to-consider-d1f5c00249d5">4种状态不可变的状态</a>。</p>
<h3 id="决策6-绑定binding">决策6: 绑定(Binding)</h3>
<p> 在React组件中,至少有<a href="https://medium.freecodecamp.org/react-binding-patterns-5-approaches-for-handling-this-92c651b5af56">六种处理绑定的方法</a>。这主要是因为现代JS语言提供了多种处理绑定的方式。你可以在构造方法(constuctor)里面做绑定,也可以在渲染时做绑定,在渲染方法中使用箭头方法做绑定,使用class属性做绑定,甚至使用修饰符做绑定。可以阅读<a href="https://medium.freecodecamp.org/react-binding-patterns-5-approaches-for-handling-this-92c651b5af56">这篇文章的评论</a>了解更多的选项!每种方法都有它自己的优点,但是假如你可以接受试验性的功能,<a href="https://medium.freecodecamp.org/react-binding-patterns-5-approaches-for-handling-this-92c651b5af56">我建议你使用默认的class属性(又叫做property initializers)</a>。
这个投票是从2016年8月份开始的。到目前为止,它显示了class属性使用人数的增长,以及createClass使用人数的减少。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/react-4.png" alt="react.png" /></center>
<p> <em>附注</em>: 一些人对于这个问题很疑惑:为什么render方法里面的箭头方法和数据绑定可以回存在潜在的问题。真实的原因是什么?因为<a href="https://medium.freecodecamp.org/why-arrow-functions-and-bind-in-reacts-render-are-problematic-f1c08b060e36">它会是shouldComponentUpdate和PureComponent行为变得古怪</a>。</p>
<h3 id="决策7样式">决策7:样式</h3>
<p> 在这一点上,选项之间的角逐就变得非常紧张了。有50+种的方式来定义组件的样式,其中包括React的内联样式,传统CSS,Ssss/Less,<a href="https://github.com/css-modules/css-modules">CSS模块</a>,以及<a href="https://github.com/MicheleBertoli/css-in-js">56个CSS-in-JS的选项</a>。不是开玩笑,在<a href="https://www.pluralsight.com/courses/react-creating-reusable-components">样式模块的这门课</a>里,我探索了React定义样式的几种方法,以下就是对应的总结:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/react-5.png" alt="react.png" style="margin-bottom: 0;" />
<p style="font-size: 12px; color: grey; text-align: center;">红色表示差,绿色表示好,灰色表示警告。</p></center>
<p> 那为什么在React的样式方案选项上有这么多的选择呢?因为没有明显的胜者。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/react-6.png" alt="react.png" /></center>
<p> 看上去CSS-in-JS正在增长,而CSS模块化正在逐渐失去增长动力。</p>
<p> 我当前的团队使用的是Sass,搭配着BEM,这已经蛮愉快了,但是我同样喜欢<a href="https://www.styled-components.com">styled-components</a>。</p>
<h3 id="决策8可复用逻辑">决策8:可复用逻辑</h3>
<p> React最初接受<a href="https://reactjs.org/docs/react-without-es6.html#mixins">mixin</a>作为组件间分享代码的一种机制。但是mixin导致了一些问题,并且现在<a href="https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html">被视为是有害的</a>。你不能在ES的class组件中使用mixin,因此现在人们使用<a href="https://reactjs.org/docs/higher-order-components.html">高阶组件</a>和<a href="https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce">render 属性</a>(又称function as child)来在组件间共享代码。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/react-7.png" alt="react.png" /></center>
<p> 高阶组件现在越来越流行,但是我更喜欢使用render 属性,因为它们往往更加易读易写。Michael Jackson最近给我推销了这个(译者注:youtube,翻不了的话请留言,我后期再换成国内源):</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/BcVAq3YFiuc" frameborder="0" allowfullscreen=""></iframe>
<h3 id="当然这还不是全部">当然,这还不是全部。。。</h3>
<p> 这里还有更多的决策需要考虑:</p>
<ul>
<li>用<a href="https://github.com/facebookincubator/create-react-app/issues/87#issuecomment-234627904">js还是jsx扩展名</a>?</li>
<li>是否把<a href="https://medium.com/styled-components/component-folder-pattern-ee42df37ec68">每个组件放在各自的目录下</a>?</li>
<li>是否是一个组件对应一个文件?要不要<a href="https://hackernoon.com/the-100-correct-way-to-structure-a-react-app-or-why-theres-no-such-thing-3ede534ef1ed">采用每个目录下放置一个index.js文件这样让人抓狂的形式</a>?</li>
<li>如果使用propTypes,那是在class自身内使用<a href="https://michalzalecki.com/react-components-and-class-properties/#static-fields">静态属性</a>,还是在底部声明它们?是否要将<a href="https://iamakulov.com/notes/deep-proptypes/?utm_content=buffer57abf&utm_medium=social&utm_source=twitter.com&utm_campaign=buffer">propType定义的足够深</a>?</li>
<li>是按传统的方式在constructor里面初始化state,还是利用<a href="http://stackoverflow.com/questions/35662932/react-constructor-es6-vs-es7">属性初始化器语法</a>?</li>
</ul>
<p> 因为React大多数情况下还是JavaScript,你同样需要考虑一份JS开发规则的常规决策清单,比如<a href="https://eslint.org/docs/rules/semi">分号</a>,<a href="https://eslint.org/docs/rules/comma-dangle">逗号</a>,<a href="https://github.com/prettier/prettier">格式</a>,以及<a href="https://jaketrent.com/post/naming-event-handlers-react/">事件处理器的命名</a>等等。</p>
<h3 id="选择一个标准然后自动化执行">选择一个标准,然后自动化执行</h3>
<p> 关于上面的这些内容,你在野蛮生长的现如今可能会看到几十种组合。</p>
<p> 那么,接下来几步就是关键了:</p>
<ol>
<li><strong>在团队内讨论这些决策,并且将标准定义成文档。</strong></li>
<li><strong>不要在代码审查(code review)时浪费时间人工检查这些。应该用像<a href="https://eslint.org">ESLint</a>, <a href="https://github.com/yannickcr/eslint-plugin-react">react-codemod</a>,<a href="https://github.com/prettier/prettier">prettier</a>这样的工具自动验证你的标准。</strong></li>
<li><strong>需要重建已有的React组件? 使用<a href="https://github.com/reactjs/react-codemod">react-codemod</a>来自动化这个过程。</strong></li>
</ol>
<p> 如果我还忽略了什么关键决策,请在下面的评论中指出。</p>
<p><a href="https://yaowenjie.github.io/front-end/8-key-react-component-decisions">[译]React组件的8项关键决策 - 标准化React开发</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on December 02, 2017.</p>
https://yaowenjie.github.io/%E7%BC%96%E7%A8%8B%E7%9B%B8%E5%85%B3/solving-bandwagonhost-disk-full-issue
https://yaowenjie.github.io/%E7%BC%96%E7%A8%8B%E7%9B%B8%E5%85%B3/solving-bandwagonhost-disk-full-issue
2017-10-15T16:21:00+08:00
2017-10-15T16:21:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<p> 已经很久没有关注自己的博客了,待回来细看时,发现文章下面自己写的评论服务已经挂了。虽然之前的大部分功能并没有完全完成,但作为一个有追求的developer,怎么能坐视不管呢。接下来就大致简单复现一下发现和解决问题的过程。</p>
<!--more-->
<h2 id="定位问题">定位问题</h2>
<p> 首先,这个评论服务是部署在<a href="https://bandwagonhost.com">搬瓦工</a>下的(咳咳,别问我为什么,自己闹着玩的东西,就用便宜的VPS搭建咯)。第一步,还是到搬瓦工对应的管理面板下查看一下机器的状态,貌似一切正常,除了这一抹红色:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/bandwagon_1.png" width="70%" alt="bandwagon_1.png" /></center>
<p> 对,10G磁盘都满了!这很有可能是导致评论服务挂掉的原因。所以,还是ssh登陆到服务器上去,看看到底是什么导致这10G内存(上次关注它的时候连10%都没用到)都用完了。接下来,就需要在命令行里查询具体是哪个目录占了很多资源了。通过下面的这样的指令,就可以发现是哪一块在我没关注的这一阶段默默膨胀了。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre>df -h
du -hs /*
du -hs /root/*
du -hs /var/log/*
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 果然,原来都是nginx的log捣的鬼(居然膨胀到了将近9G,那还得了)!</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/bandwagon_2.png" width="60%" alt="bandwagon_2.png" /></center>
<p> 再cd到对应的目录查看具体是哪个文件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>cd /var/log/nginx
du -hs *
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 罪魁祸首应该就是这Ngnix的access.log</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/bandwagon_3.png" width="60%" alt="bandwagon_3.png" /></center>
<h2 id="解决">解决</h2>
<p> 问题的原因找到了,解决起来就简单了。access.log记录了所有的nginx处理的请求记录,这肯定是随着时间的积累,信息量已经越来越大,以至于到了这个地步。不过这个log现在对于我而言没有太过价值,所以直接干脆<code class="language-plaintext highlighter-rouge"><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>rm -rf ./access.log
</pre></td></tr></tbody></table></code>删掉该文件。不过,当我开心地再次检查内存状况(<code class="language-plaintext highlighter-rouge"><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>df -h
</pre></td></tr></tbody></table></code>)时,发现内存占用和之前一样,依然有10G。这不科学?确实不科学,这个时候按经验来说,很多人可能会选择重启服务器(但我还是不希望为了这个重启云上的服务器)。网上大概了解下原因:可能存在一些运行进程在使用未链接(实际已经删除了)的文件。所以,检查一下是否这样的进程在运行(其实,这时候基本上知道是nginx了):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>ls -ld /proc/*/fd/* 2>&1 | fgrep '(deleted)'
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 果然,就是nginx。只要重启nginx服务就能够让<code class="language-plaintext highlighter-rouge"><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>df
</pre></td></tr></tbody></table></code>命令报出正式的内存状况。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>service nginx restart
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 毕,回到最初的问题上(是评论系统挂了),刷新一下博客页面,发现评论回来了。</p>
<h2 id="简要回顾原因">简要回顾原因</h2>
<p> 不难看出,评论系统挂了其实是因为其所在的服务器上nginx服务运行不正常。nginx服务运行不正常是因为它记录的log已经撑满了整个硬盘。看来后期得给nginx加上log压缩或者定期清理任务了。但是,还是等我先把这个评论系统的“评论”功能实现吧,啊哈哈哈[羞耻笑]。</p>
<h2 id="reference">Reference</h2>
<ul>
<li><a href="https://community.hortonworks.com/questions/21501/how-to-fix-host-disk-consumptions.html">How to fix host disk Consumptions</a></li>
<li><a href="https://www.linuxquestions.org/questions/linux-server-73/%60df-k%60-is-not-updating-the-right-space-status-without-reboot-600784/"><code class="language-plaintext highlighter-rouge"><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>df -k
</pre></td></tr></tbody></table></code> is not updating the right space status without reboot</a></li>
</ul>
<p><a href="https://yaowenjie.github.io/%E7%BC%96%E7%A8%8B%E7%9B%B8%E5%85%B3/solving-bandwagonhost-disk-full-issue">解决搬瓦工磁盘满载的问题</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on October 15, 2017.</p>
https://yaowenjie.github.io/life%7C%E7%94%9F%E6%B4%BB/start-posting-again
https://yaowenjie.github.io/life%7C%E7%94%9F%E6%B4%BB/start-posting-again
2017-10-15T10:29:00+08:00
2017-10-15T10:29:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<p>博客中断数月,于此期间一直在这么几件事,它们分别是:</p>
<!--more-->
<p>读这个:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/book_1.jpg" width="60%" alt="book_1.png" /></center>
<p>这个:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/book_2.jpg" width="60%" alt="book_2.png" /></center>
<p>以及这个:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/book_3.jpg" width="60%" alt="book_3.png" /></center>
<p>恍恍惚惚之间,发现读起来还是一知半解,想想这样的难题估计是迷惑半生之事,还是漫漫常日静静待之吧。</p>
<p>故,还是回来写点自己的文章和总结,重拾这种沉淀的价值!立此文为证!</p>
<p><a href="https://yaowenjie.github.io/life%7C%E7%94%9F%E6%B4%BB/start-posting-again">继续更博客!</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on October 15, 2017.</p>
https://yaowenjie.github.io/cloud/several-pit-on-aws-china
https://yaowenjie.github.io/cloud/several-pit-on-aws-china
2017-03-26T16:11:00+08:00
2017-03-26T16:11:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<p> 在没有使用AWS China(Beijing Region)之前,我还天真的认为它只是亚马逊云服务的一个中国区延伸,会延续AWS所有的服务。但是在这两天的使用之后,发现AWS China和AWS其他Region的服务差距还是挺大的,所以简单罗列一下,这两天使用它时发现的几个坑(后续也可能还会增加)。</p>
<!--more-->
<h3 id="1-缺少很多常用服务">1. 缺少很多常用服务。</h3>
<p> 如容器相关的ECS(EC2 Container Service)、ECR(EC2 Container Registry)等,部署相关的CodeDeploy,还有现在比较热门的API Gateway和Lambda等等,都不在AWS China的产品列表之内。如果你想在AWS China上面玩转docker,或者希望实现容器集群的自动化部署和管理,就变得很困难啦。</p>
<p> 以下为现阶段AWS China所拥有的所有服务:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/aws-china-services.png" width="80%" alt="aws-china-services.png" /></center>
<p> 而其他区域的服务却要丰富很多:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/aws-services.png" width="100%" alt="aws-services.png" /></center>
<h3 id="2-ec2的804438080端口受到限制需要icp备案才可以开放">2. EC2的80、443、8080端口受到限制,需要ICP备案才可以开放。</h3>
<p> 我的应用监听的端口是8080,所以就在对应EC2实例上新增了一个Security Group,用于开放了8080端口(允许TCP 8080端口anywhere的inbound),啥提示也没看到,然后就是死活curl不通它公共DNS的8080端口(Operation timed out),通过各种方法找原因,最后换了一个8888之类的端口(当然也同时修改了Security Group规则),居然成功了。兜兜转转之间发现国内的8080端口居然受到了限制,换句话说,80、443、8080这样端口都需要ICP备案才能开放。</p>
<h3 id="3-great-firewall导致的网络限制">3. Great Firewall导致的网络限制</h3>
<p> 众所周知的原因,既然是落在北京的机房,肯定过不了Great Firewall这一关。一些墙外的网站肯定访问不了,从Docker Hub上拉docker镜像肯定也是十分吃力的。依赖源如果来自国外可能就比较头疼了。</p>
<hr />
<p> PS. 我写这篇文章的时间是<strong>2017年3月</strong>,希望后续会逐渐改善吧。</p>
<h4 id="参考链接">参考链接</h4>
<ul>
<li><a href="https://fastretailing.github.io/blog/2015/09/29/AWS-ChinaBeijing-Region-Tips.html">AWS China(Beijing) Region Tips</a></li>
<li><a href="https://www.amazonaws.cn/products/?nc1=h_ls">AWS China 产品列表</a></li>
<li><a href="https://forums.aws.amazon.com/thread.jspa?threadID=200550">Re: ec2 80,443,8080 port blocked?</a></li>
<li><a href="https://forums.aws.amazon.com/thread.jspa?threadID=173724">Port 80 and 8080 can not be accessed</a></li>
</ul>
<p><a href="https://yaowenjie.github.io/cloud/several-pit-on-aws-china">使用 AWS China 的几个坑</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on March 26, 2017.</p>
https://yaowenjie.github.io/devops/thought-on-devopsdays-beijing
https://yaowenjie.github.io/devops/thought-on-devopsdays-beijing
2017-03-19T14:35:00+08:00
2017-03-19T14:35:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<p> 有幸参与了3月18号在北京举办的DevOpsDays活动,这也是DevOpsDays这个全球性的DevOps聚会第一次落地中国。官方给出的数据是,大会吸引了将近1200名参会者。在此期间,也有机会见到了被业界称为“DevOps之父”的Patrick,以及《持续交付》的中文译者乔梁。从火热程度、嘉宾阵容以及票价上来说,这样的技术活动可算是较为盛大了,但是从一个开发者的角度来说说,会议中的“干货”才是我最期望看到的内容。本文将会从个人角度来回顾这个会议中的一些内容,并针对其中DevOps相关内容给出一些自己的认识,如有不正之处,还望各位读者指正。</p>
<!--more-->
<h2 id="会议整体流程与内容">会议整体流程与内容</h2>
<p> 首先,活动的国际化程度还是很值得认可的,除了像Patrick、John Wills这样的国外演讲嘉宾出席之外,还是可以看到不少国外的参会者,并且还配备了专业的同声传译团队和设备。参与的企业也都是与DevOps强关联的技术企业,虽然在会议内外难免会有不少相关企业的软硬广,但是除了个别以自家工具为主题缺乏实质内容的演讲之外,总体感觉也不算过分。</p>
<p> 上午的前两个简短演讲来自活动的两位联合创始人。各十分钟的演讲除了讲述DevOpsDays来到中国的历程,感觉不出来太多DevOps真正技术相关的内容,中间还自带了些小广告,让人不得不对本次活动产生一些怀疑的想法。还好接下来的几个演讲还算有货,Patrick的slide内容足够吸引人;乔梁的内容让我略感失望(真的是冷饭,且没找到重点,PPT也是不够惊艳,也可能期望比较大吧);李俊提到的几个新的概念,可以看出他们的重点在于数据分析;John的内容主要集中在DevOps文化上。下午的三个分会场以及两个讨论会场的设置按主题区分同时进行,虽然节省了很多时间开支,但选择过多,让很多人不得不做出舍弃。另外,不知道组织方自己会不会针对活动做Retrospective,反正作为参与者,我是没有看到任何关于本次活动反馈的问卷调查(不知道是巧合还是什么原因,该文发布后一天,活动官方在微信群内发出了反馈问卷),虽然几乎每个演讲都提到了反馈文化的重要性。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/devopsdays.png" width="80%" alt="data-table.png" /></center>
<p> 而关于内容,个人觉得DevOps领域高屋建瓴的总结多于切实有效的实际案例,作为一个在TW工作两年多的家伙,DevOps是我进入TW的第一天就已经被“灌输”了的概念<a href="#ref-1">❶</a>,敏捷也可谓深入每天的工作,所以会议中提到的诸多概念、原则、结构等内容个人感觉不够惊艳,反而就像乔梁在演讲中说的那样 - 有种炒冷饭的感觉。不过,这不可谓大会中的内容没有价值,相反,这些内容都应该是各位结合实际提炼出来的精华,所以我还是打算用以下几个方面来再炒一碗“冷饭”,希望可以帮助大家进一步理解、或者作为简单的参考。而于我个人而言,自知这些内容不一定完全保证正确,毕竟一切方法论都需要因地制宜,但希望能够起到总结和督促的作用吧。</p>
<p> 在参加DevOpsDays之前,我阅读了伍斌伍道长 <a href="http://insights.thoughtworkers.org/instantiate-the-principles-of-devops/">实例化DevOps原则</a> 一文,深以为然,如果你还对DevOps知之甚少,推荐你阅读一下这篇文章。里面提到了两个相对主流的DevOps框架/原则:CALMS 和 The Three Way,之后在这次活动中的多个演讲中,都看到这两大框架的影子。本文我将不会围绕着这两个框架展开,而是以我自己的思路在4个维度叙述,当然,不可避免地,你会发现所有这些还是逃脱不了这两个框架/原则。</p>
<h2 id="1-人---组织---文化">1. 人 - 组织 - 文化</h2>
<p> CALMS原则第一字母C表示Culture(文化),它其实算是DevOps中的基本和核心,但对于很多企业和组织来说,也经常是最难实现的一部分。而文化的载体可以说是一个组织/团队,它的实现需要靠人,所以最基本的还是<strong>人</strong>。如何消除组织间的壁垒,促进人与人之间的沟通合作,是DevOps运动需要考虑的一大课题(当然,这往往也算是敏捷组织转型的企业需要考虑的)。DevOps强调合作,强调沟通,强调在组织/团队内建立信任,这其实也是敏捷所倡导的。这让我想到,曾经很多次,我们在公司内部关于敏捷的讨论中,我们都会提到一点“没有信任,就别谈啥敏捷”,是的,<strong>自组织</strong>、<strong>高效的合作和沟通</strong> 肯定不是建立在充满怀疑和问责的环境中。</p>
<p> 然而,“如何构建信任,如何高效合作”这不是一个简单的问题,也不会有一个普适的答案,因为这涉及企业文化类型、管理学等等相关内容。但是理想情况下,我们会追求建立全功能团队(实际真正的敏捷组织内几乎都是全功能团队),实现<strong>组织解耦</strong>。这里,我个人很赞同张乐在演讲中提到的,全功能团队应该具备的三个特点:</p>
<ul>
<li>Overall Goal</li>
<li>T-shaped</li>
<li>Co-located</li>
</ul>
<p> 这三点,第一次看到可能还不是特别容易弄明白。但是如果你一直工作在敏捷团队内,这三点便不难理解。<strong>Overall Goal</strong> 表明所有团队成员都有同一个目标 - 往往是保证某个软件/更改按质按量的准时交付。<strong>T-shaped</strong> 表示团队成员的技能应该呈现T字型,稍微形象点解释的话,就是你在专注某项技术或者某个内容的开发时(T字的那一横),也要关注团队整体技术或者产品整体业务(T字那一竖)。<strong>Co-located</strong> 是说团队成员的位置是随时变动的,这其实是期望团队成员多和团队内外不同的人交流,加深合作沟通的一种表现(就像在结对编程中,我们也经常switch pair)。这三个特点,其实可以实现资源效率到流动效率的转换<a href="#ref-2">❷</a>,关注的点在于产品交付完成,而不只是开发关注开发结果,运维关注运维内容,两者互相不满互相指责(其实全功能团队里没有绝对的Dev和Ops之分)。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/agile-team.png" width="50%" alt="agile team" /></center>
<p> 不过在这次活动的私下交流中,还是能感觉到很多人仍然觉得开发就是开发,运维就是运维,DevOps只是运维工程师,顿感路漫漫其修远兮。</p>
<h2 id="2-代码---工具---基础设施">2. 代码 - 工具 - 基础设施</h2>
<p> 说到代码,其实是一个软件产品的根本,如果把一个产品比较一栋建筑,代码就是它中间的砖块、钢筋、混泥土等建材。而保证建筑能够被顺利且高质量的建成,其实并不是把这些建材简单堆砌就可以实现的,需要设计,需要选材,需要度量。软件也是这样,需要考虑设计<a href="#ref-3">❸</a>,考虑技术选型,考虑度量。</p>
<p> 精益开发(CALMS中的L,lean)中<strong>内建质量</strong>的说法其实就是要求我们能够在开发的同时考虑,越早发现缺陷,修复它们的成本就越低,这其实也契合The Three Way原则中系统思考、不断试错的思想。其实敏捷开发中各个流派中的很多方法和理论都可以算是内建质量的一种体现 — TDD、测试金字塔,甚至结对编程。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/test-pyramid.png" width="60%" alt="test pyramid" /></center>
<p> 为了更好的开发编写代码,也为了让代码更好的工作,并了解、探析其运行状态,我们往往需要借助各种各样工具。大量的工具也伴随着DevOps运动的发展,顺势而来。版本化、测试、软件度量、可视化、监控、部署等等相关的工具<a href="#ref-4">❹</a>,让代码的编写、测试、维护、度量过程变得更加简单,也更加复杂。而这些工具,随着技术的发展,也走在了<strong>自动化</strong>的路上,以自动化配置管理工具为例,不管你是Ansible还是Chef、Puppet、SaltStack甚至是Shell脚本、PowerShell脚本,基本都是去除非自动化情况下的痛点,解决不同场景、不同平台下的基础设施管理问题。不过,我个人也赞成不能过分关注工具本身,毕竟所有这些都是为了解决特定的问题或者优化某些具体的过程,再好的工具也不能脱离软件本身而论。</p>
<p> 几乎所有的代码运行都需要一定的环境基础,用更加专业的词汇,那就是软件的基础设施。刚刚提到的自动化配置管理工具,其实也就是针对基础设施的管理,它们大多也是“<a href="https://yaowenjie.github.io/devops/infrastructure-as-code">基础设施即代码</a>”这一概念的实现手段。基础设施即代码,让我们用代码的形式管理和维护软件环境/基础设施,这不仅仅是针对现实机器上的基础设施,现在主流的工具/技术也让云平台<a href="#ref-5">❺</a>的基础设施管理变得简单。当然近年来,另一种“简化”基础设施管理的方式也越来越流行,那就是<strong>容器</strong>,让代码运行和基础设施配置都在容器中进行,环境的纯净性、隔离性得到巨大提升。而云平台、容器化的兴起,也衍生了很多其他工具,如容器的集群管理工具、服务注册与发现等等。</p>
<h2 id="3-分支---流水线---持续交付">3. 分支 - 流水线 - 持续交付</h2>
<p> 现代的开发团队,基本上都是多人的分布式团队,借助于版本控制工具,很多时候我们首先需要考虑的是如何设计项目的分支模型。我个人觉得,分支策略其实也是工作合作模式的一种代码层级体现。关于这方面,现阶段有很多成熟的模型体系可供参考,但不管你是用git flow、github flow、trunk based还是自定义的分支模型<a href="#ref-6">❻</a>,一个大部分人应该达成的共识是:除了主分支,我们应当尽量避免存活过长的其他分支的出现(这一点似乎在git flow里面经常被打破)。按照The Three Ways原则,我们应当尽早的试错,持续做试验,应用在分支模型上,就是需要大家及早地合并那些衍生分支,及早地发现可能存在问题(运行各层级的自动化测试),免去长时间积累下的合并之苦,这也就可以继续谈到持续集成、持续交付<a href="#ref-6">❻</a>了。另外,Pull Request也可以算是分支模型里面的一个高频词汇了,它在分支合并前加了一道步骤,可以看做成一种Code Review<a href="#ref-7">❼</a>,是一种更加安全和相对有益的合并举措。</p>
<p> 持续集成、持续交付恐怕是大家听到次数最多的关于DevOps的词汇了。不过每次在提到这些的时候,很多人包括这个DevOpsDays的嘉宾在内,都应该会说到一个词 – 构建流水线,也就是我们所说的pipeline。其实,流水线是自动化的多方体现,测试、构建、部署都应该在一个健全的CD流水线上自动实现。一次提交,只要顺利通过流水线的各个步骤,就是一个可以发布的版本。它提高的不只是发布的效率,更是利用机器实现对代码的频繁验证,当然,也不只是代码,同时还是对部署过程的验证(在发布之前会在多个环境中部署验证软件)。近年来,随着CI/CD工具的逐渐流行,人们也逐渐发现在维护和使用它们的过程中还是暗藏一些痛点,所以又多了一种实践/概念 — <strong>流水线即代码</strong>。这其实和基础设施即代码的思想类似,就是用代码来表示我的流水线结构和配置,并且放到版本控制中,这里我就不再赘述,感兴趣的读者以读读这篇文章 <a href="http://insights.thoughtworkers.org/pipeline-as-code/">流水线即代码</a>。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/ci-cd.png" width="80%" alt="CI CD" /></center>
<p> 其实说到分支合并、流水线、CI、CD,概括出一个核心词汇的话,那就肯定是<strong>反馈</strong>。Pull Request可以获取代码层级的反馈。及早合并分支、持续集成,就是希望及早地获取代码集成后的反馈。而持续交付涉及整个交付过程的反馈环,代码集成的反馈,部署到各个环境后的反馈,客户验收的反馈等等。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/feedback-loop.png" width="60%" alt="feedback-loop" /></center>
<h2 id="4-容器---微服务---新概念">4. 容器 - 微服务 - 新概念</h2>
<p> 刚刚提到的全功能团队,其实是为了实现<strong>组织解耦</strong>,代码上的设计和模式使用往往是为了<strong>代码解耦</strong>,而容器的出现,又导致另一种层级的解耦,那就是<strong>服务解耦</strong>。它让变化的环境变得可控,可随时创建销毁的特点让“一次构建,多次运行”、“配置一次,运行在多个环境”成为可能。服务的解耦很多时候似乎也可以让多团队之间解耦。微服务的架构似乎也应运而生,其实对于微服务的架构,大部分人都应该了解到它也不一定是完全完美的,也不一定全部适合所有场景<a href="#ref-8">❽</a>。但是随着组件化趋势的不断发展以及容器的大火,它势必为今后的一个重大课题。而关于容器的开发模式,DevOpsDays里面徐磊提到的一点:用容器开发容器,颇有道理。说简单点,就是利用容器的可以<strong>远程调试</strong>的特点,实现在真实的容器环境里面开发调试。</p>
<p> 当然,DevOpsDays上面还是接触到几个不是很主流的,相对“新”的概念。如Patrick和多个嘉宾都提到的ChatOps,它就是一种强调团队沟通协作的理念,通过协作缩短反馈环节。而刘俊提到的SRE(Site Reliability Engineering)其实最早是由Google提出的一套运维理念<a href="#ref-9">❾</a>,另一个概念AIOps偏重智能算法,也可能是未来的一种趋势。由于在这两个概念上未做过多研究,在这里就不做太多叙述啦。</p>
<p> 另外,本次大会中似乎没有提及(当然,也有可能是提及了但我错过了),一个比较不错也可能成为未来趋势的新概念:Serverless无服务器架构。它其实是依托类似AWS这样的云服务,尤其是在像Lambda这样的东西出来之后,它似乎让大家进一步忘记软件服务开发的过程中基础设施管理的成本了,这里我也不会赘述啦,感兴趣的读者依然可以阅读Martin Fowler的<a href="https://martinfowler.com/articles/serverless.html">Serverless</a>一文。</p>
<h2 id="最后如果用一句话">最后,如果用一句话…</h2>
<p> 说了这么多。最后,思索一二,如果让我用一句话总结下来,DevOps说到底,可以是:</p>
<p><strong> 为了更快、更有质量的交付可用软件,我们使用一些工具(容器、自动化工具、版本化工具、可视化工具等),遵循一些原则(基础设施即代码、流水线即代码等),打破职责间的壁垒,不断地验证软件的正确性(包括但不仅限于测试、构建和部署过程),且乐意并更快地收获反馈。</strong></p>
<hr />
<h3 id="注释">注释</h3>
<div style="font-size:14px;">
<div id="ref-1"><b>1:</b>虽然当时不一定真的理解,详情可以参考我14年底的文章 <a href="https://yaowenjie.github.io/life%7C%E7%94%9F%E6%B4%BB/first-met-with-thoughtworks">初涉ThoughtWorks</a>。</div>
<div id="ref-2"><b>2:</b>传统的组织结构更加关注各个团队角色所占有、产出的资源,多少会存在利益冲突,而全功能性的团队关注的是如何让产品生产线流动,享有一个共同的目标。</div>
<div id="ref-3"><b>3:</b>如代码的解耦,职责分离,其间,我们可能会用到一些解决经典问题的设计模式。</div>
<div id="ref-4"><b>4:</b>其中,就拿最基本的版本化工具来说,不管你是说git、svn甚至更老的CVS,其实从某种意义上说也是为了协作和沟通;度量工具,其实也为了能够获得各种层次的反馈。</div>
<div id="ref-5"><b>5:</b>如AWS、Azure、阿里云等等。</div>
<div id="ref-6"><b>6:</b>关于分支模型和CI/CD,不得不说两者之间还是存在着微妙的关系,这里不做过多延伸。不过,可以关注之前我之前写的一篇文章 <a href="https://yaowenjie.github.io//devops/thinking-in-two-kinds-of-ci-cd-strategies-and-git-branch-models">关于两种CI/CD策略以及git分支模型的思考</a></div>
<div id="ref-7"><b>7:</b>实际上,我们心目中一场好的Code Review应该像这样:<a href="http://insights.thoughtworkers.org/code-review/">Code Review: 超越“审、查、评”的代码回顾</a>。而不是PR中的review。</div>
<div id="ref-8"><b>8:</b>可以参考本文:<a href="http://www.infoq.com/cn/news/2014/06/microservices">微服务的优缺点</a></div>
<div id="ref-9"><b>9:</b>可以参考本文:<a href="http://blog.dataman-inc.com/shurenyun-sre-188/">SRE系列教程</a></div>
</div>
<p><a href="https://yaowenjie.github.io/devops/thought-on-devopsdays-beijing">DevOpsDays有感 - DevOps概谈</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on March 19, 2017.</p>
https://yaowenjie.github.io/coding/function-length
https://yaowenjie.github.io/coding/function-length
2017-02-21T11:01:00+08:00
2017-02-21T11:01:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<h6 id="本文翻译自老马martin-fowler的博客文章该译文现已被博客原文收录在其下方中文翻译处">本文翻译自老马(Martin Fowler)的<a href="https://martinfowler.com/bliki/FunctionLength.html">博客文章</a>,该译文现已被<a href="https://martinfowler.com/bliki/FunctionLength.html#footer">博客原文</a>收录在其下方中文翻译处。</h6>
<p> 在我的职业生涯期间,我曾听过很多关于一个方法(或者说函数,本文针对两者将不做区分)应当有多长的争论。这其实引申到另一个更加重要的问题上:我们应该在什么时候把代码封装在它自己的方法内?有些准则会基于方法的长度,比如方法的长度不应该超出屏幕可以容纳的范围<a href="#ref-1">❶</a>。有些会基于复用,即任何被使用超过两次的代码都应该抽出自己单独的方法,而只在一个地方使用过的代码就应当保留在行内。然而,于我而言,最合乎情理的还是这种论点:那就是<strong>意图和实现的分离</strong>。如果你不得不费点精力查看一段代码,才能弄清楚它具体做了什么,那你就需要把它抽出成一个方法,并且用“它具体做了什么”来为其命名。这样当你再次读到它的时候,这个方法的意图对你来说便一目了然,并且大多数时候你将不再需要关心这个方法是如何实现它的意图的(也就是这个方法的内容)。</p>
<!--more-->
<p> 一旦我接受这项原则,我就养成了编写(短)小方法的习惯(一般来说只有很少几行<a href="#ref-2">❷</a>)。任何超过六行代码的方法我都能察觉到,而且只有单行代码的方法对我来说也不是不寻常的了<a href="#ref-3">❸</a>。Kent Beck给我展示了一个来自最初Smalltalk系统的例子,使我明白了“方法的大小(长短)并不重要”这个事实。Smalltalk那时候运行在黑白系统上。如果你想强调/高亮某些文字或图像,就需要反向显示它们。Smalltalk的图像类有一个方法叫做<code class="language-plaintext highlighter-rouge"><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>highlight
</pre></td></tr></tbody></table></code>(高亮),它的实现就只有一个对<code class="language-plaintext highlighter-rouge"><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>reverse
</pre></td></tr></tbody></table></code>(反向)方法的调用<a href="#ref-4">❹</a>。这个方法的名字甚至比它的实现还要长,但这并没有什么关系,因为这段代码的意图和它的实现之间有一段比较大的距离。</p>
<p> 一些人对短方法存在顾虑,因为他们担心方法调用时的性能损耗。在我年轻的那个时代,这一点有时候确实是一个影响性能的因素,但是现在而言这种影响已经很少见了。优化的编译器通常可以和那些更容易缓存的短方法一起协同工作。和以往一样,<a href="https://martinfowler.com/ieeeSoftware/yetOptimization.pdf">性能优化的通用准则</a>很重要。有时候你需要做的是稍后再把方法内嵌进去,但是通常更小的方法建议你用其他的方式来加快速度。我记得人们总是拒绝为一个list编写<code class="language-plaintext highlighter-rouge"><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>isEmpty
</pre></td></tr></tbody></table></code>方法,而是往往使用<code class="language-plaintext highlighter-rouge"><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>aList.length == 0
</pre></td></tr></tbody></table></code>这样的惯用语法。
但是如果能够找到比定义长度更快的方法来判断一个collection是否为空,那在这里给方法取一个意图相关的名字或许能够提供更好的性能。</p>
<p> 像这样的短方法,只有在它命名合理的情况下才会起到作用,所以你需要好好留意命名。这是需要练习的,但是一旦你擅长于此,这种方法会让代码变得十分的文档化。更大型的方法可以读起来像篇故事,阅读者可以按照自身需求选择深入了解哪些方法的细节。</p>
<h3 id="致谢">致谢</h3>
<div style="font-size:16px;">
  感谢Brandon Byars, Karthik Krishnan, Kevin Yeung, Luciano Ramalho, Pat Kua, Rebecca Parsons, Serge Gebhardt, Srikanth Venugopalan, 以及 Steven Lowe在我们内部邮件组内参与讨论这篇文章的草稿版。
<br />
  Christian Pekeler提醒了我:嵌套的方法并不适用于我的这种观点。
</div>
<h3 id="注释">注释</h3>
<div style="font-size:14px;">
<div id="ref-1"><b>1:</b>或者,在我的第一份编程工作中是:两页打印纸 - 即大约130行 Fortran IV代码。</div>
<div id="ref-2"><b>2:</b>一些语言会允许你用方法来包含其他方法。这通常用于范围缩小机制,比如使用<a href="http://www.cs.uni.edu/~wallingf/patterns/envoy.pdf">函数即对象</a>模式来实现一个类。这样的方法自然会大很多。 </div>
<div id="ref-3">
<b>3: 我的方法长度</b> <br />
最近我对构建这个网站(martinfowler.com)的工具链内的方法长度很好奇。它主要是用Ruby编写的,并且运行到了将近15千行代码(KLOC)。以下就是它的方法体长度的累积频率图(横坐标:方法内代码行数;纵坐标:累积方法数):
<center><img class="center" src="https://yaowenjie.github.io/images/2017/my-method-counts.png" width="60%" alt="my-method-counts.png" /></center>
你可以观察到,这里有很多小方法 - 代码里面半数的方法只有两行甚至更少(这里的行数是指没有评论、空格,并且不包括def和end的行数)。
以面用一个粗略的表格形式来体现数据:
<center><img class="center" src="https://yaowenjie.github.io/images/2017/data-table.png" width="60%" alt="data-table.png" /></center>
</div>
<div id="ref-4">
<b>4:</b>这个例子就是Kent这本优秀著作<a href="https://www.amazon.com/gp/product/013476904X?ie=UTF8&tag=martinfowlerc-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=013476904X">《Smalltalk最佳实践模式》</a>中提到的意图相关的内容。
</div>
</div>
<p><a href="https://yaowenjie.github.io/coding/function-length">[译]方法的长度</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on February 21, 2017.</p>
https://yaowenjie.github.io/life%7C%E7%94%9F%E6%B4%BB/summary-of-2016
https://yaowenjie.github.io/life%7C%E7%94%9F%E6%B4%BB/summary-of-2016
2017-01-15T17:06:00+08:00
2017-01-15T17:06:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<p> 2016过去已有半月,总想像大家一样,给自己的这一年做一些总结回顾。斟酌一二,便不再想花时间过分煽情,还不如列出一些数据,画一些图表,来反映我的<strong>生活及工作</strong>和这个<strong>博客</strong>在过去一年的发展,顺便也憧憬下崭新的2017年。</p>
<!--more-->
<script type="text/javascript" src="https://yaowenjie.github.io/assets/js/plugins/canvasjs.min.js"></script>
<script type="text/javascript" src="https://yaowenjie.github.io/assets/js/chart/2017-01-09.js"></script>
<h2 id="生活及生活">生活及生活</h2>
<p> 今年由于工作的原因,生活足迹主要停留在两座城市,上半年蓉城 - <strong>成都</strong>,下半年江城 - <strong>武汉</strong>。其他时间,除去春节在家乡小城古铜都 - <strong>铜陵</strong> 待过一个春节小长假外,就是一些旅行和外出探访的机会。全年仅仅<strong>2</strong>次旅行,都集中在上半年,包括2月份的<strong>西岭雪山</strong>和4月份的<a href="http://www.mafengwo.cn/i/5464134.html">泸沽湖之行</a>。</p>
<div id="cities-pie" style="height: 400px; width: 100%;"></div>
<p><br />
2016年,完全是托🐙童鞋的福,算是正式学会了<strong>做饭</strong>。做的最多的菜是<strong>青红椒炒肉丝</strong>,其次应该是<strong>青红椒炒鸡蛋</strong>和<strong>手撕包菜</strong>,哈哈,还是单调了点。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/rose.jpg" width="40%" alt="rose.jpg" /></center>
<p> 2016年,总共对外做过<strong>2</strong>次<strong>技术演讲</strong>,其中一次在成都的DevOps社区线下交流活动,另外一次是针对华中科技大学的学生们。作为coach参与了公司对外的<a href="https://www.nowcoder.com/activity/thoughtworks-pair">“结对编程最佳体验之<strong>男神女神</strong>”活动</a>。作为志愿者参与了一次公司组织的公益活动 - “一场为爱出发的旅程——北川香泉小学”,在那里见证了孩子们的单纯与可爱,也有机会亲眼看到了北川震后旧城区的凄怆和新城区的活力与悠闲。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/beichuang.jpg" width="80%" alt="beichuang.jpg" /></center>
<p> 2016年,基本上没怎么单纯的跑步,运动主要集中在<strong>篮球</strong>和<strong>羽毛球</strong>上,不过16年打篮球的次数应该不会太高,但至少有<strong>20+</strong> 。3月份计划的5周<strong>2000</strong>个<strong>俯卧撑</strong>计划最后也是搁浅,不过如果没有记错的话,应该至少有练到<strong>1400+</strong> 个。</p>
<p> 2016年在工作之外,主要写了<strong>两</strong>样<strong>开源</strong>的东西,一个是<a href="https://github.com/Yaowenjie/yaowenjie.github.io">博客本身</a>,这个下文就会介绍,另一个是<a href="https://github.com/Yaowenjie/travis-github-chrome-extension">针对travis-ci和github的chrome插件</a>。</p>
<h2 id="博客">博客</h2>
<p> 2016年,从<strong>4</strong>月份开始,由于网络以及希望自定义博客的原因,决定将博客从<a href="http://yaowenjie.logdown.com">logdown</a>迁移到github上。自<strong>4月6号</strong>第一次提交以来,博客逐步添加、完善了一些小功能,发布了<strong>21</strong>篇新的博客文章,其中<strong>16</strong>篇为技术相关的文章。并且于4月份引入<a href="https://www.afsanalytics.com/">AFS Analytics</a>以来,我就可以关注博客的访问量以及各项数据了。以下,我便罗列了一些关于该博客2016年有趣的数据。</p>
<h3 id="访问量">访问量</h3>
<p> 由于是4月份开始引入AFS,但是真实开始宣传自己的博客文章却是7月份,所以可以看出来,<strong>7</strong> 月份才是访问量飞速增长的一个关键时期,并且页面访问量也达到了全年最高点(之后由于项目及环境变动,逐渐放缓了分享的力度,尤其是10月份)。</p>
<div id="page-views-chart" style="height: 400px; width: 100%;"></div>
<p><br /></p>
<p> 关于<strong>访问来源</strong>,由于我有在某些技术社区分享,所以虽然AFS给出的数据依然是大部分是直接访问,但是我相信里面很大一部分是从其他网站引流过来的,只是它并没有检测到。搜索引擎能够达到<strong>8%</strong> ,个人已经很满意了,毕竟某度贡献度几乎为<strong>0</strong>.</p>
<div id="sources-pie" style="height: 400px; width: 100%;"></div>
<p><br /></p>
<p> 至于访问的平台,可以看得出来,<strong>桌面</strong> 依然是主流,如我所期望,虽然该博客做了responsive,我个人感觉还是桌面上的体验更好一点。</p>
<div id="platforms-pie" style="height: 400px; width: 100%;"></div>
<p><br /></p>
<h3 id="瞎看数据">瞎看数据</h3>
<p> 这里懒得画图啦,直接截图算了。首先,回顾一下,访问量最多的那些页面/文章。看来关注前端的人还是最多,因为出去主页之外,<a href="http://yaowenjie.github.io/front-end/using-webpack-dashboard">《使用webpack命令行工具:webpack-dashboard》</a>这篇文章获得最多的访问量。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/articles.jpg" width="80%" alt="articles.jpg" /></center>
<p> 访问量其实大部分时间需要看那些<strong>引流的网站</strong>,其中某些社区确实做出了很大的帮助。具体的数据如下,不过有些网站(如开发者头条和Segmentfault)的redirect并没有被AFS计入其中,比较遗憾。稍仔细看的话,Google和Bing这两大搜索引擎还是比较良心的,通过这个我甚至发现了一个俄语搜索引擎,就是没有最大的中文搜索引擎某度,毕竟挣钱比搞技术重要(蒙住双眼状)。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/ref.jpg" width="80%" alt="ref.jpg" /></center>
<p> 再来看看访问源的<strong>城市</strong>分布,基本就能反映中国IT从业人员分布排行啦,哈哈哈。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/cites.jpg" width="80%" alt="cites.jpg" /></center>
<p> 以及访问源的<strong>国家</strong>分布,咳咳,这个多少能反映一点国内IT人士翻*****(对不起,这里哔哔和谐,坏笑脸)。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2017/countries.jpg" width="80%" alt="countries.jpg" /></center>
<h2 id="关于2017">关于2017</h2>
<p> 17年该怎么样过,不想在这里过于严肃、过度具体化讨论,只希望来年回首,能够觉得说的没有多大错,总结一下吧:</p>
<ul>
<li>热爱<strong>生活</strong></li>
<li>热爱工作和技术</li>
<li>继续博文文章</li>
</ul>
<p> 好的,就这样,来吧,2017!</p>
<p><a href="https://yaowenjie.github.io/life%7C%E7%94%9F%E6%B4%BB/summary-of-2016">数说我的2016</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on January 15, 2017.</p>
https://yaowenjie.github.io/%E7%BC%96%E7%A8%8B%E7%9B%B8%E5%85%B3/automatic-testing
https://yaowenjie.github.io/%E7%BC%96%E7%A8%8B%E7%9B%B8%E5%85%B3/automatic-testing
2016-12-18T14:31:00+08:00
2016-12-18T14:31:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<p> 12月20号在<strong>华中科技大学</strong>做的一次演讲,题目为《自动化测试,从入门到放弃》,分享一下slide:</p>
<!--more-->
<center><iframe src="https://yaowenjie.github.io/share/PDFs/automatic-testing.pdf" width="960" height="480" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:3px solid #666; margin-bottom:5px; max-width: 100%;" allowfullscreen=""> </iframe></center>
<p><a href="https://yaowenjie.github.io/%E7%BC%96%E7%A8%8B%E7%9B%B8%E5%85%B3/automatic-testing">华科校园活动讲义 - 自动化测试,从入门到放弃</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on December 18, 2016.</p>
https://yaowenjie.github.io/devops/thinking-in-two-kinds-of-ci-cd-strategies-and-git-branch-models
https://yaowenjie.github.io/devops/thinking-in-two-kinds-of-ci-cd-strategies-and-git-branch-models
2016-11-26T15:00:00+08:00
2016-11-26T15:00:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<p> 近两个月由于个人处于新环境、新项目的适应阶段,没怎么提笔写些文章。中间有好几个想法想记录下来分享,但受限于没有很好的时间段供自己总结思考(也可以总结为间歇性懒癌和剧癌发作),便啥也没有更新。借这个周末闲适的下午和明媚的阳光,决定把近来项目上的<strong>CI/CD(持续集成/持续交付)策略</strong>以及<strong>git分支模型</strong>和以前的项目做一下分析比较,希望对各位有所帮助,也能有所思考,尤其是那些期望<strong>搭建项目部署流水线</strong>或者想<strong>了解git分支模型</strong>的开发、运维人员。</p>
<!--more-->
<h2 id="背景">背景</h2>
<p> 废话不多说,由于近期做了N次release,所以对自己目前所处的新项目的部署方式有了一定的了解。为了方便,本文就叫该项目为A项目吧。发现A项目的部署方式和我之前接触的TW“传统”CI/CD策略差异比较大(在<a href="https://thoughtworks.com">TW</a>,几乎每个项目都有持续集成/持续交付流水线,如果你对它们的概念还不是很清楚,建议阅读<a href="https://book.douban.com/subject/6862062/">持续交付</a>这本书,将对你梳理整个交付流程帮助巨大)。</p>
<p> 关于A项目的背景,受客户保密协议的限制,我只能透露几点。A项目所属公司为国外某大型电信运营商,主要内容为用户账户自服务平台。该平台涉及诸多内外部服务,如认证、订单跟踪、短信认证等等,数量总数在三十多个左右,而每个服务都是一个独立的子系统,有独立的代码库、独立的机器实例(AWS EC2 实例)用于运行,以及一套独立的jenkins job用于自动化构建和部署(即我们接下来谈的内容)。当然,这也是为什么A项目想往微服务架构迁移的主要目的。</p>
<p> 接下来,让我剥去诸多项目的其他内容,仅仅讨论一下它的CI/CD策略,也可以说是它的构建、部署方式。</p>
<h2 id="a项目的cicd策略">A项目的CI/CD策略</h2>
<p> 千言万语还是不及一张图(作者小学美术数学老师教的,望见谅):</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2016/cd-1.jpg" alt="cd.jpg" /></center>
<p> 上图,为一个独立子项目(如背景中所说的某个服务)在其<a href="https://jenkins.io/index.html">jenkins</a>里面的任务(job)结构图,主要有两种自动化任务,build和deploy:</p>
<ul>
<li><strong>build</strong> - 即构建任务。developer在代码仓库(这里是github上某个私有仓库)某个分支上提交了代码后,自动或者手动地被触发。它会根据对应的分支,如develop、一些feature分支或release分支上,而在其对应的任务上构建、运行各层测试以及生成对应的<a href="http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html">AMI</a>镜像。</li>
<li><strong>deployment</strong> - 即部署任务。该任务需要人工手动点击触发,因为很多时候需要改动一些部署配置,比如说选择刚刚build任务生成的哪个分支的那个AMI文件以及更改一些endpoint的值。它会根据你需要部署的环境,利用自动化部署工具chef,基于对应的AMI镜像生成对应的EC2实例、ELB等等资源,让我们的服务在对应的环境中正式地运行起来(当然也伴随着销毁旧的资源的过程)。这个过程如果目标环境是prod的话,其实就是真实的发布了。</li>
</ul>
<p> 这用在该项目组中几乎所有的以服务为单位的子系统之上,也就是说,我们有将近三十套左右类似这样的jenkins任务。</p>
<p> 需要说明的是,上图中的黑色圆圈、黑色圆圈加横线和黑色空心圆圈分别代表<strong>完全自动化</strong>、<strong>需要手动更改配置后点击触发</strong> 和 <strong>需要手动点击出发</strong> 三种情况,即如下图所示:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2016/cd-5.jpg" width="35%" alt="cd.jpg" /></center>
<p> 这样的方式有如下几个特点:</p>
<ol>
<li><strong>以分支和环境为中心</strong> 这种策略在构建时以<strong>分支(branch)</strong> 来区分构建的产物,如果你的工作模式是在各个不同的分支上开发且测试的话,你可以基于分支的相互独立的进行对应的部署和测试。举个栗子,如果你基于feature1分支构建生成了一个AMI镜像,然后你基于该镜像部署它到qa-1环境中,然后同样的将feature2分支部署到qa-2环境中,然后测试人员就可以同时在两个环境测试不同的功能。</li>
<li><strong>保持了CI/CD中的自动化</strong> 构建和部署实际上还是自动化的,不过需要在运行自动化脚本之前,手动更新一些配置,比如该使用哪个AMI镜像等。</li>
<li><strong>自动化测试时间不会特别长</strong> 这里所说的特别长其实不容易定义,具体多长时间为长,都是相对而论。个人感觉,只要你觉得不用给各层测试做独立的jenkins任务(全都放在build中),仍然可以清晰的知道什么时候运行什么测试,什么测试出现了问题,即可。</li>
<li><strong>环境之间的递进关系不明显</strong> 这种策略下,由于是手动选择和触发部署过程,所以一次代码更改可能不会被部署到所有环境中,可能只会被部署到某一个测试环境中用于测试。所以环境之间的递进(如下文中越来越接近产品环境的)只能体现它对应的部署任务里的一些配置参数上,比如说preprod环境的部署job用的是真实数据库,而QA环境的部署job用的是mock的数据。</li>
</ol>
<h2 id="b项目的策略及比较">B项目的策略及比较</h2>
<p> 而我曾经接触过的一些项目,同样为了便于说明,这里我们统称它为B项目,不管它的CI/CD工具用的是jenkins还是<a href="http://go.cd">go.cd</a>,它们都会是一种流水线(pipeline)的形式,如下图所示(没错,请叫我灵魂画师,<手动羞耻脸>):</手动羞耻脸></p>
<center><img class="center" src="https://yaowenjie.github.io/images/2016/cd-3.jpg" alt="cd.jpg" /></center>
<p> 如上图所示,相对A项目的策略来说,这些jenkins任务分的更加细,中间的各层测试视具体项目而言可能包含单元测试、集成测试、回归测试、集成测试等等,然后就是将其部署到Dev环境(开发人员手动测试、验证的环境)。毋庸置疑,这里从开发人员提交代码到部署至Dev环境,包括测试的运行在内,都是自动化的。这意味着如果你的代码没有问题,你不需要做任何事,除了提交代码和看一下这个pipeline的状态。之后的几个环境,由于越来越接近产品环境,而且会提供给不同的人用于测试或者演示(showcase),所以很多时候需要对应的人手动的触发对应的部署/发布。当然,这样的部署/发布过程也是自动化,所以说在发布到产品环境之前,类似的部署/发布方式其实已经被验证过很多次了,而且是一次更改必须强制性地必须经过各个环境的测试和验证。</p>
<p> 结合《持续交付》一书中提到的部署流水线的三个目标而言,我们来比较一下A项目和B项目用的这两种部署策略优缺点:</p>
<ol>
<li><strong>可视化</strong> - 让软件的构建、部署、测试和发布过程对所有人可见,这一点对于合作至关重要。A项目这种分离的任务形式,其实不够直观,也不太能够让开发人员之外的业务人员、管理人员等直观地明白我们在哪里出现了问题,任务的划分也相对简单。B项目的这种策略,任务划分相对直观明了,任何人只要关注这条流水线,就大概知道应该是什么流程出现了问题。</li>
<li><strong>及时反馈</strong> - 持续交付的最大好处其实就是及时反馈了。而这一点在A和B上都有体现,任务的成功与失败都可以给出对应的反馈,告诉我们是否哪儿出了问题。不过A相对来说,反馈方式(可视化程度)更弱一点,反馈周期(集成周期较长)更长一点。</li>
<li><strong>自动化</strong> - 很明显,从上面两个图可以看出,B的自动化程度肯定是高于A的,无论是构建还是部署,A都需要去手动更改配置和手动触发。不过两种策略中间的实现毋庸置疑都是自动化的。</li>
</ol>
<p> 如果只从上面看,其实B项目的策略理应优于A项目的策略的。但是,很显然,“没那麼简单 就能去爱 别的全不看”。还记得我们说过A项目服务众多吗,A的采用这种策略很大一部分原因,个人猜测(还未经验实),一是重视任务之间的<strong>隔离性</strong>,二是为了便于<strong>管理各个服务之间依赖</strong>。比如,在A项目中,我想把之前feature1的某个测试环境里面的某个服务改为另外一个合适的版本,我只需要在部署时,将部署任务执行前的某个参数改为对应的endpoint就行,这在B项目策略中虽然也是可行的,但A项目的方式相当于在每次部署前都会提醒你这些参数的值,你可以决定是否修改。</p>
<p> 当然,我个人觉得这与它们的git分支模型也不无几分关系。接下来就让我们来看看它们分别使用什么样的git分支模型。</p>
<h2 id="a项目的git分支模型">A项目的git分支模型</h2>
<p> A项目使用的git分支模型 - git flow(如果你还不了解这个概念,请阅读<a href="http://nvie.com/posts/a-successful-git-branching-model/">A successful Git branching model</a>):</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2016/git-model.png" width="60%" alt="git-model.png" /></center>
<p> 简单介绍一下的各种分支:</p>
<ul>
<li><strong>master</strong> - 与产品环境代码保持一致的分支,也就是每次发布完成之后发布的功能分支就要合并于此,以保持master更新。</li>
<li><strong>develop</strong> - 开发的主分支,feature和release分支会基于此分支。</li>
<li><strong>feature</strong> - 具体要开发的功能的分支,完成后合并到develop。</li>
<li><strong>release</strong> - 用于发布新版本的分支,完成后合并到develop和master。</li>
<li><strong>hotfix</strong> - 用于紧急修复已发布的产品问题的分支,完成后合并到develop和master。</li>
</ul>
<p> 这种模型的话,理论上来说相对安全。但是一般feature分支都是需要用于开发一个较大的功能才做的分支,在此之上,我们还要建对应的故事卡(敏捷中,一个不可/不宜划分的需求单位)的分支,如下所示:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2016/cd-2.jpg" width="45%" alt="cd.jpg" /></center>
<p> 这么做的好处有:</p>
<ol>
<li><strong>隔离性比较好,更加安全</strong> 所有的功能都会有对应的分支,开发和测试工作不会互相干扰,发布进程也不会受其他未开发完的功能干扰。</li>
<li><strong>分支职责明确</strong> 对应的分支做对应的事情,职责明确。</li>
</ol>
<p> 但是缺点也比较明显:</p>
<ol>
<li><strong>集成的周期太长</strong> 如果同时有几个大的功能在各自的分支上开发,每个功能的开发周期都不短的话,那之后他们之间的合并、集成工作将会十分痛苦。如果以《持续集成》这一本书中观点来看,这甚至算不上持续集成。</li>
<li><strong>会有比较多的重复测试</strong> 完成分支的测试之后,在集成到主分支之后,还要重复一遍测试。自动化测试重复还可以接受,重复地手动的测试就比较烦人了。</li>
<li><strong>结构相对复杂</strong> 分支较多,且存在层级关系(比如故事卡分支出自feature分支,feature分支出自develop分支)。</li>
</ol>
<h2 id="b项目的git分支模型">B项目的git分支模型</h2>
<p> 对应地,B项目,存在分支的话(我这么说,是因为也有不使用分支的真实项目),以我之前的某个离岸海外项目为例,会像如下图所示:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2016/cd-4.jpg" width="45%" alt="cd.jpg" /></center>
<p> 明显地,这种结构看起来简单很多。所有分支都是基于develop或者叫master这样的主分支。基于故事卡建分支,合并分支。</p>
<p> 这么做有如下好处:</p>
<ol>
<li><strong>结构相对简单</strong> 所有分支的都是以故事卡为单位,结构简单。全部围绕一条主分支。</li>
<li><strong>符合小步提交、持续集成思想</strong> 以一张故事卡为集成的最小单位,相对来说集成的周期短,反馈的速度也快,能够及早的遇到问题,从而及早的解决问题。</li>
</ol>
<p> 但是,金无足赤,它有时候也可能会有一些缺点:</p>
<ol>
<li><strong>feature toggle的引入与测试</strong> 这种模型下,为了不让某些没有完成的功能影响已经完成的功能发布进程。在软件的设计初级以及后期测试,都需要把对应的feature toggle加入进来。也就是说,需要确保在各个环境中那些没有完成的功能应该处于disable状态。这无疑增加一部分工作量,也会带来一点风险。不过,这种工作量和风险大部分团队都会承担,毕竟如果计划分析的合理,发生的几率还是挺小的。</li>
<li><strong>隔离性较差</strong> 引入feature toggle的很大一部分原因就是为了弥补隔离性上的缺陷。但是如果你主张:所有的分支终究是要合并到一个分支、发布成一个产品的,那这一缺点其实并不重要。</li>
</ol>
<h2 id="总结">总结</h2>
<p> 当然,还有很多其他的策略和分支模型(或者没有分支的模型),我这里不再探讨过多。其实就我目前提到的AB两种,甚至可以交叉使用(比如A项目情况用B项目的策略),具体如何采用以及何时适合采用,这个问题可以留给有心的读者自己思考。</p>
<p> 最后我想说,这几种方式虽然各有优缺点,但相比更加传统的缺乏自动化的方式而言,已然进步太多。</p>
<p><a href="https://yaowenjie.github.io/devops/thinking-in-two-kinds-of-ci-cd-strategies-and-git-branch-models">关于两种CI/CD策略以及git分支模型的思考</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on November 26, 2016.</p>
https://yaowenjie.github.io/front-end/powershell-cheatsheet
https://yaowenjie.github.io/front-end/powershell-cheatsheet
2016-09-11T20:44:00+08:00
2016-09-11T20:44:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<p> 前段时间,微软终于开源了其广受关注的命令行 — <a href="https://github.com/PowerShell/PowerShell">PowerShell</a>,并推出了支持多个平台的版本。之前,由于工作原因,整理了一份PowerShell的Cheatsheet,它就像一个字典一样,方便使用过程中的一些基本内容查阅。如果你在使用PowerShell或者打算使用PowerShell,这不免为一份不错的简洁查阅手册。最近我重新整理了一下这份Cheatsheet,并把它分享到这里。</p>
<!--more-->
<p> 我将这份Cheatsheet转换成了<strong>网页版</strong>,请戳<strong><a href="https://yaowenjie.github.io/share/ps-cheatsheet-chn">这里</a>。</strong></p>
<p> 当然,你也可以通过下方的文件预览框预览及下载对应的PDF文件。</p>
<center><iframe src="https://yaowenjie.github.io/share/PDFs/PowerShell-CheatSheet.pdf" width="960" height="480" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:3px solid #666; margin-bottom:5px; max-width: 100%;" allowfullscreen=""> </iframe></center>
<ul>
<li>如果你发现问题,或者有任何疑问或建议,请在本文下方留言,或者直接在<a href="https://github.com/Yaowenjie/yaowenjie.github.io/issues">该博客github仓库</a>中提出你的issue。感谢你的关注!</li>
</ul>
<p><a href="https://yaowenjie.github.io/front-end/powershell-cheatsheet">PowerShell CheatSheet</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on September 11, 2016.</p>
https://yaowenjie.github.io/front-end/using-webpack-dashboard
https://yaowenjie.github.io/front-end/using-webpack-dashboard
2016-08-28T22:55:00+08:00
2016-08-28T22:55:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<p> webpack-dashboard是用于改善开发人员使用<a href="http://webpack.github.io/">webpack</a>时控制台用户体验的一款工具。它摒弃了webpack(尤其是使用dev server时)在命令行内诸多杂乱的信息结构,为webpack在命令行上构建了一个一目了然的仪表盘(dashboard),其中包括<strong>构建过程</strong>和<strong>状态</strong>、<strong>日志</strong>以及涉及的<strong>模块列表</strong>。有了它,你就可以更加优雅的使用webpack来构建你的代码。</p>
<p> 另外,它自开源以来短短半个月,就已经在github上收获了6000多枚star,足见人们对于提升开发工具的用户体验有着巨大的需求。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2016/wd1.png" alt="star.png" /></center>
<!--more-->
<h2 id="它是什么">它是什么</h2>
<p> 简单地说,<a href="https://github.com/FormidableLabs/webpack-dashboard">webpack-dashboard</a>就是把原先你使用webpack时(特别是使用webpack dev server时)命令行控制台打印的日志:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2016/wd2.png" alt="console.png" /></center>
<p> 转换成了这样:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2016/wd3.png" alt="dashboard.png" /></center>
<p> 看到这里,是不是觉得整个人生都变美好了呢。仔细看,这个dashboard里面按日志(Log)、状态(Status)、运行(Operation)、过程(Progess)、模块(Modules)、产出(Assets)这6个部分将信息区分开来。用官方的话,这将会给你一种在NASA工作的即使感,哈哈。</p>
<h2 id="如何使用">如何使用</h2>
<p> 其实安装和使用webpack-dashboard的过程非常简单,首先使用npm本地安装它,到你基于webpack的前端项目上:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nx">npm</span> <span class="nx">install</span> <span class="nx">webpack</span><span class="o">-</span><span class="nx">dashboard</span> <span class="o">--</span><span class="nx">save</span><span class="o">-</span><span class="nx">dev</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 如果你利用webpack-dev-server启动了一个server,而不是express的话,可以直接在<strong>webpack.config.js</strong>里面初始化dashboard。</p>
<p> 首先,导入dashboard和其对应的插件,并创建一个dashboard的实例:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="kd">var</span> <span class="nx">Dashboard</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">webpack-dashboard</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">DashboardPlugin</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">webpack-dashboard/plugin</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">dashboard</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Dashboard</span><span class="p">();</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 然后,在对应的<strong>plugins</strong>里面添加DashboardPlugin:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="nx">plugins</span><span class="p">:</span> <span class="p">[</span>
<span class="k">new</span> <span class="nx">DashboardPlugin</span><span class="p">(</span><span class="nx">dashboard</span><span class="p">.</span><span class="nx">setData</span><span class="p">)</span>
<span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 最后,你需要让dev server在静默的状态中启动(主要是为了去掉多余的日志),要实现这一点,你可以像官方的做法那样,在WebpackDevServer的构造函数里添加 <strong>quiet: true</strong>。</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="k">new</span> <span class="nx">WebpackDevServer</span><span class="p">(</span>
<span class="nx">Webpack</span><span class="p">(</span><span class="nx">settings</span><span class="p">),</span>
<span class="p">{</span>
<span class="na">publicPath</span><span class="p">:</span> <span class="nx">settings</span><span class="p">.</span><span class="nx">output</span><span class="p">.</span><span class="nx">publicPath</span><span class="p">,</span>
<span class="na">hot</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">quiet</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// lets WebpackDashboard do its thing</span>
<span class="na">historyApiFallback</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">).</span><span class="nx">listen</span><span class="p">(</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 当然,你也可以在npm的script里面启动dev server时添加<strong>quiet</strong>选项(我在尝试的时候选择这种简单的方式)。</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="dl">"</span><span class="s2">scripts</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">start</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">webpack-dev-server --quiet</span><span class="dl">"</span>
<span class="p">},</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 这样,你就可以运行诸如<strong>npm start</strong>这样的命令启动你的server。然后,你就可以休息一下,泡杯咖啡,假装自己就是一位宇航员,静静地看着你的dashboard。</p>
<p> 如下图所示,为笔者尝试时的截图:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2016/wd4.png" alt="dashboard.png" /></center>
<h2 id="最后">最后</h2>
<p> 本文只介绍了基于webpack-dev-server的这一种使用情况,其他启动server的方式(比如express)或者其他情况可以参考<a href="https://github.com/FormidableLabs/webpack-dashboard">webpack-dashboard github官方仓库</a>。</p>
<p> webpack-dashboard目前还处于初期阶段,所以必然还有一些值得注意或者值得改进的地方。如果你使用的是OS X自带的终端(Terminal),需要确认“View → Allow Mouse Reporting”是使能(Enable)状态,如果你的终端没有这个功能的话,你或许可以尝试一下<a href="https://www.iterm2.com/index.html">iTerm2</a>。另外,如果你忘记使用quiet模式或者你的某句日志或者名字过长,可能会导致显示的字符串“越界”,跑到另一个区域,看起来没有那么直接美观了。</p>
<p> 最后,如果你想简单的看一下webpack-dashboard启动起来的效果,你可以参考使用<a href="https://github.com/Yaowenjie/React-learning/tree/master/lesson1">本文示例代码</a>。</p>
<p><a href="https://yaowenjie.github.io/front-end/using-webpack-dashboard">使用webpack命令行工具:webpack-dashboard</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on August 28, 2016.</p>
https://yaowenjie.github.io/front-end/jest-how-do-you-debug-it
https://yaowenjie.github.io/front-end/jest-how-do-you-debug-it
2016-08-24T11:26:00+08:00
2016-08-24T11:26:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<h5 id="本文翻译自liusy182">本文翻译自<a href="https://liusy182.wordpress.com/2015/03/12/jest-how-do-you-debug-it/">liusy182</a></h5>
<p> <a href="https://facebook.github.io/jest/">Jest框架</a>是facebook旗下一款单元测试框架,我个人十分喜欢它,因为它自动mock这一点十分强大。然而,当它遇到问题的时候,就会经常抛出一些模糊的调用栈信息。我在网上搜索尝试找到如何debug Jest测试的方法,却很难找到有用的信息。总之,它仍然还是一个比较新的测试框架。</p>
<p> Jest使用虚拟DOM来运行测试。这一点不同于Karma和Jasmine(它们是利用浏览器来运行测试的)。我觉得这就会给它带来一个很大的缺点:不能使用浏览器上的调试工具来调试Jest的测试。因此,我们需要借助于Node/V8引擎自带的调试器。<a href="https://nodejs.org/api/debugger.html">Node默认的调试器</a>是完全基于命令行形式的,类似于GDB - 虽然我从来就不是一个命令行调试的拥簇,但先还是解决这个问题吧。</p>
<!--more-->
<h2 id="首次尝试">首次尝试</h2>
<p> 要调试(debug)一个Jest测试,比如说“myView-test.js”,我们需要使用如下的node指令来实现:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nx">node</span> <span class="nx">debug</span> <span class="o">--</span><span class="nx">harmony</span> <span class="p">.</span><span class="err">\</span><span class="nx">node_modules</span><span class="err">\</span><span class="nx">jest</span><span class="o">-</span><span class="nx">cli</span><span class="err">\</span><span class="nx">bin</span><span class="err">\</span><span class="nx">jest</span><span class="p">.</span><span class="nx">js</span> <span class="o">--</span><span class="nx">runInBand</span> <span class="nx">myView</span><span class="o">-</span><span class="nx">test</span><span class="p">.</span><span class="nx">js</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 接下来让我来解释一下我们这里到底做了什么:</p>
<ul>
<li>“node debug”将会启动node自带的调试器。“debug”会调用一个V8引擎调试器的wrapper。这个wrapper提供了一系列的指令,用于在代码中跳进跳出和跟踪,而这些指令都不会出现在V8的“node -debug”中。(我承认它们看起来很相似,一个“-”符号之差)</li>
<li>“-harmony”标志是为了让Jest正确地运行。更多关于harmony的信息可以戳<a href="http://stackoverflow.com/questions/13351965/what-does-node-harmony-do">这里</a>。</li>
<li>“.\node_modules\jest-cli\bin\jest.js”就是Jest的入口。这个文件会在我调用“\node_modules.bin”里的“Jest”时被调用。</li>
<li>“-runInBand”告诉Jest在当前的进程中运行所有测试,而不是再启动一个进程。Jest默认就会启动多个进程并行的运行测试。如下为源码中关于这个选项的描述的片段(在.\node_modules\jest-cli\bin\jest.js中):<br /></li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>'Run all tests serially in the current process (rather than creating a worker pool of child processes that run tests). This is sometimes useful for debugging, but such use cases are pretty rare.'
---
'在当前的进程中顺序地运行所有测试(而不是创建一个包含诸多子进程的进程池来运行测试)。这便于调试,但是使用它的场景确实挺少的。'
</pre></td></tr></tbody></table></code></pre></div></div>
<ul>
<li>“myView-test.js”就是我们想要debug的测试文件。像这样使用相对路径是没有问题的,因为Jest会把它转换为一段正则表达式。</li>
</ul>
<p> 一旦启动了调试器,我们就可以看到调试器会在jest.cs的第一行就暂停了。可以使用Node调试器支持的API来跟踪代码。或者也可以在测试中添加断点:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nx">debug</span><span class="o">></span> <span class="nx">sb</span><span class="p">(</span><span class="dl">'</span><span class="s1">C:</span><span class="se">\\</span><span class="s1">example</span><span class="se">\\</span><span class="s1">reactApp</span><span class="se">\\</span><span class="s1">__tests__</span><span class="se">\\</span><span class="s1">myView-test.js</span><span class="dl">'</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p> Node调试器提供了一系列的有用指令:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
</pre></td><td class="rouge-code"><pre>cont, c - 继续执行
next, n - 跳到下一步
step, s - 跳进
out, o - 跳出
pause - 暂停执行的代码 <span class="o">(</span>就像开发工具中的暂停按钮<span class="o">)</span>
setBreakpoint<span class="o">()</span>, sb<span class="o">()</span> - 在当前行设置断点
setBreakpoint<span class="o">(</span>line<span class="o">)</span>, sb<span class="o">(</span>line<span class="o">)</span> - 特定行设置断点
setBreakpoint<span class="o">(</span><span class="s1">'fn()'</span><span class="o">)</span>, sb<span class="o">(</span>...<span class="o">)</span> - 在函数体内的第一段声明中设置断点
setBreakpoint<span class="o">(</span><span class="s1">'script.js'</span>, 1<span class="o">)</span>, sb<span class="o">(</span>...<span class="o">)</span> - 在script.js的第一行设置断点
clearBreakpoint, cb<span class="o">(</span>...<span class="o">)</span> - 清楚断点
backtrace, bt - 打印当前执行帧的回溯
list<span class="o">(</span>5<span class="o">)</span> - 列出脚本源代码的5行内容(前面和后面5行)
watch<span class="o">(</span><span class="nb">expr</span><span class="o">)</span> - 向观察列表中添加表达式
unwatch<span class="o">(</span><span class="nb">expr</span><span class="o">)</span> - 去掉观察列表中的表达式
watchers - 列出所有的watcher以及它们的值(在每段断点处自动列出)
repl - 在调试脚本的内容时打开调试器的repl用于评估
run - 运行脚本(在调试开始时自动运行)
restart - 重启脚本
<span class="nb">kill</span> - 结束脚本
</pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="使用webstorm-9">使用WebStorm 9</h2>
<p> 因为我从来都不是命令行debugger的拥簇,所以使用Node的调试器我很快就会迷失了。我开始思考在怎么把Jest的调试工作集成到像<a href="https://www.jetbrains.com/webstorm/">WebStorm 9</a>这样强大的IDE中。</p>
<p> 就从这里开始,使用WebStorm我需要应付两个问题:</p>
<ol>
<li>没办法直接使用“node debug”.</li>
<li>WebStorm并没有对Jest的直接支持(尽管它支持Karma和Mocha…)</li>
</ol>
<p> 我最终还是解决了这个问题,那就是搭建一个针对Node的调试配置项,但是把它用作于Jest测试调试。WebStorm会在它尝试调试Node的时候自动加上V8的标志”-debug”。我们只需要配置好它,就可以调试我们想要调试的JS测试。</p>
<p> 下面就是具体的步骤:</p>
<ol>
<li>点击菜单“Run – Edit Configurations…”</li>
<li>添加一个新的“Node.js”配置并且像下面这样填写对应的配置项:<center><img class="center" src="https://yaowenjie.github.io/images/2016/jest-1.png" alt="jest.png" /></center></li>
<li>保存并关闭对话窗口。在编辑器内里面任意地方添加断点<center><img class="center" src="https://yaowenjie.github.io/images/2016/jest-2.png" alt="jest.png" /></center></li>
<li>点击菜单中“Run – Debug…”并且选择我们刚刚保存的配置。这会启动一个调试的shell来运行这些测试。并且它最终会停到我们设置的断点处。</li>
<li>现在,我们可以使用WebStorm的调试器(出现在IDE的底部)来调试Jest测试啦!</li>
</ol>
<p><a href="https://yaowenjie.github.io/front-end/jest-how-do-you-debug-it">[译]如何调试JEST测试?</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on August 24, 2016.</p>
https://yaowenjie.github.io/front-end/using-gulp-with-babel
https://yaowenjie.github.io/front-end/using-gulp-with-babel
2016-08-14T11:00:00+08:00
2016-08-14T11:00:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<h5 id="本文翻译自macrae">本文翻译自<a href="http://macr.ae/article/gulp-and-babel.html">macr.ae</a></h5>
<p> <a href="http://babeljs.io/">Babel</a>是一个JavaScript转换编译器,它可以将ES6(下一代JavaScript规范,添加了一些新的特性和语法)转换成ES5(可以在浏览器中运行的代码)。这就意味你可以在一些暂时还不支持某些ES6特性的浏览器引擎中,使用ES6的这些特性 - 比如说,class和箭头方法。本文,我将围绕<a href="http://www.gulpjs.com.cn/">gulp</a>和babel,介绍如何使用它们。</p>
<p> “使用基于Babel的gulp”其实可以有两种理解:一是使用Babel编写ES6语法的gulpfile;二是使用gulp来运行babel,让ES6编写的JavaScript代码转化成浏览器可以理解的JavaScript代码。这两种情况接下来我将一一介绍。</p>
<!--more-->
<h2 id="用es6编写gulpfile">用ES6编写gulpfile</h2>
<p> Gulp自3.9版本以来,就添加了针对Babel这样的转换编译器的支持,这样你就可以使用ES6来编写gulpfile了。比如说,如果你正用着Node 0.12,你就可以使用ES6中的箭头方法了。首先,你需要使用gulp构建的项目中的npm来安装<strong>babel</strong>这个安装包。然后,你需要把gulpfile命名为<strong>gulpfile.babel.js</strong>,从而告诉gulp需要找到babel。</p>
<p> 在使用<strong>npm install babel-preset-es2015</strong>安装好babel-preset-es2015这个插件之后,需要在<strong>.babelrc</strong>文件内添加它(在Babel 6.0中,默认不包括任何插件):</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="p">{</span>
<span class="dl">"</span><span class="s2">presets</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">es2015</span><span class="dl">"</span><span class="p">]</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>(参考<a href="https://babeljs.io/docs/plugins/preset-es2015/">the Babel website</a>)</p>
<p> 此后,你就可以在gulpfile里面使用ES6的语法。举个栗子:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="k">import</span> <span class="nx">gulp</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">gulp</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">gulp</span><span class="p">.</span><span class="nx">task</span><span class="p">(</span><span class="dl">'</span><span class="s1">default</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Default task called</span><span class="dl">'</span><span class="p">));</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 这些很容易理解。你可以像往常一样调用gulp,并且得到你期望的运行结果。</p>
<p> 如果你想指定babel的一些选项,最好的方式就是使用<strong>.babelrc</strong>。但如果这行不通的话(比如,当你想指定某个函数需要使用一个Babel选项的时候),你就不能用我上面说的方法了。所以,此时你需要创建一个名为<strong>gulpfile.js</strong>的文件,并包含如下内容:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">babel/register</span><span class="dl">'</span><span class="p">)({</span>
<span class="na">nonStandard</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">ALLOW_JSX</span>
<span class="p">});</span>
<span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">./gulpfile.babel.js</span><span class="dl">'</span><span class="p">);</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 然后再使用上面提到的gulpfile.babel.js。</p>
<h2 id="使用gulp构建es6语法的文件">使用Gulp构建ES6语法的文件</h2>
<p> 只是使用babel把ES6转换为ES5是相当简单的。使用如下的<a href="https://www.npmjs.com/package/gulp-babel">gulp-babel</a>插件:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="kd">var</span> <span class="nx">gulp</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">gulp</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">babel</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">gulp-babel</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">gulp</span><span class="p">.</span><span class="nx">task</span><span class="p">(</span><span class="dl">'</span><span class="s1">default</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">gulp</span><span class="p">.</span><span class="nx">src</span><span class="p">(</span><span class="dl">'</span><span class="s1">src/app.js</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">babel</span><span class="p">())</span>
<span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">gulp</span><span class="p">.</span><span class="nx">dest</span><span class="p">(</span><span class="dl">'</span><span class="s1">dist</span><span class="dl">'</span><span class="p">));</span>
<span class="p">});</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 这里的输出将会是经过babel处理的代码,它们可以用于那些暂时还不是完全支持ES6特性的浏览器中。一旦调用了gulp-babel插件,你就可以调用像uglify这样的常用插件。</p>
<p> gulp-babel插件也支持<a href="https://www.npmjs.com/package/gulp-sourcemaps">gulp-sourcemaps</a>,它可以用于简单的浏览器调试。</p>
<p> 这种方式下差不多唯一没有使能的特性就是ES6的模块化了。为此,我推荐使用browserify。</p>
<h3 id="在基于broswerify的gulp里调用babel">在基于Broswerify的gulp里调用Babel</h3>
<p>(查询所有这些库的名字的大小写花了我一会儿工夫)</p>
<p> 如果你想学习ES6的模块(modules,它让你能够<strong>import</strong>工程中的其他文件),你可以结合使用<a href="http://browserify.org/">Broswerify</a>和babel。</p>
<p> 或许你还没有听说过Broswerify,其实它让你可以使用Node.js风格的require来编写代码:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="kd">var</span> <span class="nx">$</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">jquery</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">body</span><span class="dl">'</span><span class="p">).</span><span class="nx">css</span><span class="p">(</span><span class="dl">'</span><span class="s1">background-color</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">orange</span><span class="dl">'</span><span class="p">);</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p> Browserify支持“转换(Transforms)”,它们主要是一些高效的辅助插件 - 就像gulp里面有很多插件可以处理很多文件相关的事情一样,也有很多Browserify的转换,它们可以让你在脚本编译的机器上完成很多支持环境变量的事情,或者编译<a href="https://facebook.github.io/react/">React</a>的JSX文件。</p>
<p> 其中一个转换插件叫做<a href="https://github.com/babel/babelify">babelify</a>,它为Browserify添加了babel的支持。除了让你使用ES6和<strong>require()</strong>之外,它还可以把<strong>import</strong>声明转换为<strong>require()</strong>,此时你就可以在你的代码里使用ES6的模块:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="k">import</span> <span class="nx">$</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">jquery</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">body</span><span class="dl">'</span><span class="p">).</span><span class="nx">css</span><span class="p">(</span><span class="dl">'</span><span class="s1">background-color</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">red</span><span class="dl">'</span><span class="p">);</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 你可以在下面的代码里一起使用Browserify和babelify:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="kd">var</span> <span class="nx">fs</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">fs</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">babelify</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">babelify</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">browserify</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">browserify</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">bundler</span> <span class="o">=</span> <span class="nx">browserify</span><span class="p">(</span><span class="dl">'</span><span class="s1">src/app.js</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">bundler</span><span class="p">.</span><span class="nx">transform</span><span class="p">(</span><span class="nx">babelify</span><span class="p">);</span>
<span class="nx">bundler</span><span class="p">.</span><span class="nx">bundle</span><span class="p">()</span>
<span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">error</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span> <span class="p">})</span>
<span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">fs</span><span class="p">.</span><span class="nx">createWriteStream</span><span class="p">(</span><span class="dl">'</span><span class="s1">bundle.js</span><span class="dl">'</span><span class="p">));</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p> bundler.bundle()方法返回了一段包含处理过的代码的可读流。我们可以使用vinyl-source-stream和vinyl-buffer,来把这个转化为可以被送入其他gulp插件和<strong>gulp.dest()</strong> 的内容:尽管之前的代码完全可以很好工作了,但最佳实践还是要避免在gulpfile里面使用fs模块。</p>
<p> 在gulpfile里面,之前的代码可以写成这样:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="kd">var</span> <span class="nx">babelify</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">babelify</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">browserify</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">browserify</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">buffer</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">vinyl-buffer</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">source</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">vinyl-source-stream</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">uglify</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">gulp-uglify</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">gulp</span><span class="p">.</span><span class="nx">task</span><span class="p">(</span><span class="dl">'</span><span class="s1">default</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">bundler</span> <span class="o">=</span> <span class="nx">browserify</span><span class="p">(</span><span class="dl">'</span><span class="s1">src/app.js</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">bundler</span><span class="p">.</span><span class="nx">transform</span><span class="p">(</span><span class="nx">babelify</span><span class="p">);</span>
<span class="nx">bundler</span><span class="p">.</span><span class="nx">bundle</span><span class="p">()</span>
<span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">error</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span> <span class="p">})</span>
<span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">source</span><span class="p">(</span><span class="dl">'</span><span class="s1">app.js</span><span class="dl">'</span><span class="p">))</span>
<span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">buffer</span><span class="p">())</span>
<span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">uglify</span><span class="p">())</span> <span class="c1">// Use any gulp plugins you want now</span>
<span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">gulp</span><span class="p">.</span><span class="nx">dest</span><span class="p">(</span><span class="dl">'</span><span class="s1">dist</span><span class="dl">'</span><span class="p">));</span>
<span class="p">});</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 最后,加分题!给下面的代码添加对source maps的支持。</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
</pre></td><td class="rouge-code"><pre><span class="kd">var</span> <span class="nx">babelify</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">babelify</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">browserify</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">browserify</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">buffer</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">vinyl-buffer</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">source</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">vinyl-source-stream</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">sourcemaps</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">gulp-sourcemaps</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">uglify</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">gulp-uglify</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">gulp</span><span class="p">.</span><span class="nx">task</span><span class="p">(</span><span class="dl">'</span><span class="s1">default</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">bundler</span> <span class="o">=</span> <span class="nx">browserify</span><span class="p">({</span>
<span class="na">entries</span><span class="p">:</span> <span class="dl">'</span><span class="s1">src/app.js</span><span class="dl">'</span><span class="p">,</span>
<span class="na">debug</span><span class="p">:</span> <span class="kc">true</span>
<span class="p">});</span>
<span class="nx">bundler</span><span class="p">.</span><span class="nx">transform</span><span class="p">(</span><span class="nx">babelify</span><span class="p">);</span>
<span class="nx">bundler</span><span class="p">.</span><span class="nx">bundle</span><span class="p">()</span>
<span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">error</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span> <span class="p">})</span>
<span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">source</span><span class="p">(</span><span class="dl">'</span><span class="s1">app.js</span><span class="dl">'</span><span class="p">))</span>
<span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">buffer</span><span class="p">())</span>
<span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">sourcemaps</span><span class="p">.</span><span class="nx">init</span><span class="p">({</span> <span class="na">loadMaps</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}))</span>
<span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">uglify</span><span class="p">())</span> <span class="c1">// Use any gulp plugins you want now</span>
<span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">sourcemaps</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="dl">'</span><span class="s1">./</span><span class="dl">'</span><span class="p">))</span>
<span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">gulp</span><span class="p">.</span><span class="nx">dest</span><span class="p">(</span><span class="dl">'</span><span class="s1">dist</span><span class="dl">'</span><span class="p">));</span>
<span class="p">});</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 棒!(它是不是有点长了,或许有必要把它拆成一个独立的文件?)</p>
<hr />
<p> 你现在应该知道如何在你的gulpfile中使用ES6了 - 通过安装<strong>babel</strong>和重命名gulpfile为<strong>gulpfile.babel.js</strong>; 以及如何使用gulp把ES6的代码转换成ES5 - 使用<a href="https://www.npmjs.com/package/gulp-babel">gulp-babel</a>或者结合使用<a href="https://www.npmjs.com/package/gulp-babel">babelify</a>和Browserify。</p>
<p> 如果你在升级到Babel 6之后遇到了什么奇怪的error,先确认你是否使用了<a href="https://babeljs.io/docs/plugins/preset-es2015/">es2015 present</a>!</p>
<p><a href="https://yaowenjie.github.io/front-end/using-gulp-with-babel">[译]使用基于Babel的gulp</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on August 14, 2016.</p>
https://yaowenjie.github.io/life%7C%E7%94%9F%E6%B4%BB/chengdu
https://yaowenjie.github.io/life%7C%E7%94%9F%E6%B4%BB/chengdu
2016-08-01T16:33:00+08:00
2016-08-01T16:33:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<p> 成都,在<a href="http://music.163.com/#/song?id=30260031">赵雷的歌</a>里,是浓浓的忧伤、不舍、思恋… 片语转旋间,是深秋的垂柳,阴雨的小城,成都的街头,玉林路的尽头,小酒馆的门口…</p>
<!--more-->
<p> 在我的记忆里,那本是一座被雾圈住的城,安静,不喧嚣,偶尔阴雨小作。</p>
<figure class="third">
<a href="https://yaowenjie.github.io/images/wj/42.jpg"><img src="https://yaowenjie.github.io/images/wj/42.jpg" /></a>
<a href="https://yaowenjie.github.io/images/wj/39.jpg"><img src="https://yaowenjie.github.io/images/wj/39.jpg" /></a>
<a href="https://yaowenjie.github.io/images/wj/40.jpg"><img src="https://yaowenjie.github.io/images/wj/40.jpg" /></a>
</figure>
<p> 它也不时明艳,一洗往日的灰暗。</p>
<figure class="half">
<a href="https://yaowenjie.github.io/images/wj/36.jpg"><img src="https://yaowenjie.github.io/images/wj/36.jpg" /></a>
<a href="https://yaowenjie.github.io/images/wj/34.jpg"><img src="https://yaowenjie.github.io/images/wj/34.jpg" /></a>
</figure>
<p> 可最爱还是,余晖依存,路灯点亮的那一刻。</p>
<figure class="third">
<a href="https://yaowenjie.github.io/images/wj/29.jpg"><img src="https://yaowenjie.github.io/images/wj/29.jpg" /></a>
<a href="https://yaowenjie.github.io/images/wj/31.jpg"><img src="https://yaowenjie.github.io/images/wj/31.jpg" /></a>
<a href="https://yaowenjie.github.io/images/wj/33.jpg"><img src="https://yaowenjie.github.io/images/wj/33.jpg" /></a>
</figure>
<p> 这点记忆和印象都被容纳在了那方圆几千步的范围内。它不足以代表成都,但于我,它足够真切,也足够细微。抛去这些,还有宽敞马路上憋坏了等待绿灯的新人,街边小巷随处可见油光满面的面,公园亭苑间清茶飘香中的机麻声…</p>
<p> 这里生活可以巴适,人儿可以很乖。不妨结识一拨好友,好吃,好喝,好耍。</p>
<figure>
<a href="https://yaowenjie.github.io/images/wj/35.jpg"><img src="https://yaowenjie.github.io/images/wj/35.jpg" /></a>
</figure>
<p><a href="https://yaowenjie.github.io/life%7C%E7%94%9F%E6%B4%BB/chengdu">成都</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on August 01, 2016.</p>
https://yaowenjie.github.io/devops/code-analyzing-practice-with-sonar
https://yaowenjie.github.io/devops/code-analyzing-practice-with-sonar
2016-07-19T16:56:00+08:00
2016-07-19T16:56:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<p> <a href="http://www.sonarqube.org/">SonarQube(Sonar)</a>是一个用于管理代码质量的开源平台。SonarQube目前已支持超过20种主流编程语言,它管理的代码质量主要涉及7个维度:架构与设计、重复、单元测试、复杂度、潜在的bug、代码标准、注释。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2016/7axes.png" alt="7axes.png" /></center>
<p> 本文,笔者将围绕搭建SonarQube这样的代码质量管理平台这个主题展开,结合java代码实例一步步讲述具体的过程,其中涉及Sonar的下载安装、创建对应Mysql数据库以及运行和管理,并对实践过程中出现的一些问题进行了分析和解决。</p>
<!--more-->
<p> <em>注:本文中所有的实践都是在Ubuntu虚拟机(系统具体版本为Ubuntu 12.04 LTS)下进行,但目测同样适用于各个平台。</em></p>
<h3 id="1-安装sonar">1. 安装Sonar</h3>
<p> 从<a href="http://www.sonarqube.org/">SonarQube官方网站</a>下载对应的安装包<a href="http://www.sonarqube.org/downloads/">http://www.sonarqube.org/downloads/</a>,下载并解压至任意目录。</p>
<h3 id="2-安装mysql创建sonar的用户和数据库">2. 安装MySQL,创建sonar的用户和数据库</h3>
<p> Sonar支持SQL Server、MySQL、Oracle、Postgres等多种数据库,这里我们以MySQL为例,如果你没有安装MySQL的话,可以使用诸如以下的指令(Ubuntu下)安装:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="nb">sudo </span>apt-get <span class="nb">install </span>mysql-server
<span class="nb">sudo </span>mysql_secure_installation
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 接着创建一个用于创建对应sonar用户和sonar的数据表的SQL文件,名为 <strong>create_database.sql</strong>:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="k">CREATE</span> <span class="k">DATABASE</span> <span class="n">sonar</span> <span class="nb">CHARACTER</span> <span class="k">SET</span> <span class="n">utf8</span> <span class="k">COLLATE</span> <span class="n">utf8_general_ci</span><span class="p">;</span>
<span class="k">CREATE</span> <span class="k">USER</span> <span class="s1">'sonar'</span> <span class="n">IDENTIFIED</span> <span class="k">BY</span> <span class="s1">'sonar'</span><span class="p">;</span>
<span class="k">GRANT</span> <span class="k">ALL</span> <span class="k">ON</span> <span class="n">sonar</span><span class="p">.</span><span class="o">*</span> <span class="k">TO</span> <span class="s1">'sonar'</span><span class="o">@</span><span class="s1">'%'</span> <span class="n">IDENTIFIED</span> <span class="k">BY</span> <span class="s1">'sonar'</span><span class="p">;</span>
<span class="k">GRANT</span> <span class="k">ALL</span> <span class="k">ON</span> <span class="n">sonar</span><span class="p">.</span><span class="o">*</span> <span class="k">TO</span> <span class="s1">'sonar'</span><span class="o">@</span><span class="s1">'localhost'</span> <span class="n">IDENTIFIED</span> <span class="k">BY</span> <span class="s1">'sonar'</span><span class="p">;</span>
<span class="n">FLUSH</span> <span class="k">PRIVILEGES</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 运行:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>mysql <span class="nt">-u</span> root <span class="nt">-p</span> < create_database.sql
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 正常情况下,你便在MySQL中创建sonar的用户和数据库。你可以使用sonar用户登录查看是否成功创建了一个名为sonar的数据库:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>mysql <span class="nt">-u</span> sonar <span class="nt">-p</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h4 id="21-创建数据库时出现错误第二步成功请无视这一步">2.1 创建数据库时出现错误(第二步成功请无视这一步)</h4>
<p> 笔者在创建sonar数据库的时候由于“手残”等诸多原因,导致数据库创建失败/中断,这时候需要我们手动的Drop掉(注意一定是Drop掉,不能只是删除)对应的数据库和用户,并重新执行第二步创建数据库的操作:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="n">mysql</span><span class="o">></span> <span class="k">DROP</span> <span class="k">DATABASE</span> <span class="n">sonar</span><span class="p">;</span>
<span class="n">mysql</span><span class="o">></span> <span class="k">DROP</span> <span class="k">USER</span> <span class="s1">'sonar'</span><span class="o">@</span><span class="s1">'%'</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="3-修改sonar配置并启动">3 修改Sonar配置并启动</h3>
<p> 再启动之前,需要修改第一步解压的安装包下<strong>conf/sonar.properties</strong>文件,去掉这两行前面的注释符号,可能需要填充具体的username和password(前文创建数据库时用到的username和password):</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="n">sonar</span><span class="p">.</span><span class="n">jdbc</span><span class="p">.</span><span class="n">username</span><span class="o">=</span><span class="n">sonar</span>
<span class="n">sonar</span><span class="p">.</span><span class="n">jdbc</span><span class="p">.</span><span class="n">password</span><span class="o">=</span><span class="n">sonar</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 再去掉这行前面的注释符号:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="n">sonar</span><span class="p">.</span><span class="n">jdbc</span><span class="p">.</span><span class="n">url</span><span class="o">=</span><span class="n">jdbc</span><span class="p">:</span><span class="n">mysql</span><span class="p">:</span><span class="o">//</span><span class="n">localhost</span><span class="p">:</span><span class="mi">3306</span><span class="o">/</span><span class="n">sonar</span><span class="o">?</span><span class="n">useUnicode</span><span class="o">=</span><span class="k">true</span><span class="o">&</span><span class="n">characterEncoding</span><span class="o">=</span><span class="n">utf8</span><span class="o">&</span><span class="n">rewriteBatchedStatements</span><span class="o">=</span><span class="k">true</span><span class="o">&</span><span class="n">useConfigs</span><span class="o">=</span><span class="n">maxPerformance</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 然后运行如下指令启动Sonar:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>{Your_Sonar_Path}/bin/linux-x86-64/sonar.sh start
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 如果系统为32位的,你需要将上方路径改为“bin/linux-x86-32/sonar.sh”,否决启动将会失败。当然,把该路径加入环境变量也不失为一种方便的举措。<br />
启动成功后,在浏览器中访问:<strong>http://localhost:9000</strong>,你将看到类似这样的SonarQube的Home页面(首次Project应该是空的):</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2016/sonar-home.png" alt="sonar.png" /></center>
<h4 id="31-sonar启动后异常停止">3.1 Sonar启动后异常停止</h4>
<p> 笔者在正常启动Sonar后,遇到过两种异常停止的情况,由于控制台看不到具体的log信息,可以在sonar的解压包路径下的<strong>logs/sonar.log</strong>里寻找到具体信息。</p>
<h5 id="sonar与mysql版本不匹配"><strong>Sonar与MySQL版本不匹配</strong></h5>
<p> 这种情况下,可以在log里面看到类似如下这样的内容:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre>2016.05.18 15:17:37 INFO web[o.a.c.h.Http11NioProtocol] Starting ProtocolHandler ["http-nio-0.0.0.0-9000"]
2016.05.18 15:17:37 INFO web[o.s.s.a.TomcatAccessLog] Web server is started
2016.05.18 15:17:37 INFO web[o.s.s.a.EmbeddedTomcat] HTTP connector enabled on port 9000
2016.05.18 15:17:37 WARN web[o.s.p.ProcessEntryPoint] Fail to start web
java.lang.IllegalStateException: Webapp did not start
at org.sonar.server.app.EmbeddedTomcat.isUp(EmbeddedTomcat.java:84) ~[sonar-server-5.5.jar:na]
at org.sonar.server.app.WebServer.isUp(WebServer.java:48) [sonar-server-5.5.jar:na]
at org.sonar.process.ProcessEntryPoint.launch(ProcessEntryPoint.java:105) ~[sonar-process-5.5.jar:na]
at org.sonar.server.app.WebServer.main(WebServer.java:69) [sonar-server-5.5.jar:na]
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 这里没有明显的错误,但是<a href="http://stackoverflow.com/questions/37296804/java-lang-illegalstateexception-webapp-did-not-start-at-sonarqube">Google之</a>才发现与版本有关,笔者一开始使用的SonarQube 5.6并不支持MySQL 5.5。所以需要将SonarQube降到5.4,当然也可以升级MySQL,笔者选择了前者。</p>
<h5 id="虚拟机内存不够"><strong>虚拟机内存不够</strong></h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre>2016.07.18 22:58:26 ERROR web[o.a.c.c.StandardContext] One or more listeners failed to start. Full details will be found in the appropriate container log file
2016.07.18 22:58:26 ERROR web[o.a.c.c.StandardContext] Context [] startup failed due to previous errors
2016.07.18 22:58:26 WARN web[o.a.c.l.WebappClassLoaderBase] The web application [ROOT] appears to have started a thread named [Abandoned connection cleanup thread] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
java.lang.Object.wait(Native Method)
java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
com.mysql.jdbc.AbandonedConnectionCleanupThread.run(AbandonedConnectionCleanupThread.java:43)
2016.07.18 22:58:26 WARN web[o.a.c.l.WebappClassLoaderBase] The web application [ROOT] appears to have started a thread named [Timer-0] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
java.lang.Object.wait(Native Method)
java.util.TimerThread.mainLoop(Timer.java:552)
java.util.TimerThread.run(Timer.java:505)
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 如果出现这样的log信息,那是因为SonarQube运行需要的内存不够的原因,缺啥补啥,笔者便将使用的虚拟机运存从512MB增加到1024MB,问题便消失了。</p>
<h3 id="4-使用sonarqube-scanner扫描分析具体代码">4. 使用SonarQube-Scanner扫描分析具体代码</h3>
<p> Sonar正常运行后,就需要添加/扫描/分析具体的代码了,SonarQube提供了支持多种工具的扫描器(SonarQube Scanner),其中包括针对MSBuild、Ant、Maven、Gradle这样构建工具以及Jenkins这样CI工具的插件支持之外,还有一个可以直接运行的独立Scanner。这里就以一个<a href="https://github.com/Yaowenjie/Cucumber-Demo">简单的基于Gradle构建的Java项目</a>为例,通过添加对应的gradle插件,实现对该项目代码的代码分析。</p>
<p> 首先,从github上clone/下载这个工程:<a href="https://github.com/Yaowenjie/Cucumber-Demo">https://github.com/Yaowenjie/Cucumber-Demo</a>,然后在<strong>build.gradle</strong>中添加sonarqube插件(这种方式要求gradle的版本为2.1+):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>plugins {
id "org.sonarqube" version "2.0.1"
}
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 接着,运行如下:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>./gradlew sonar
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 如果你运行test的时候报错了的话,请在build.gradle内的test里排除掉BaseFlowTest:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>exclude '**/BaseFlowTest*'
</pre></td></tr></tbody></table></code></pre></div></div>
<p> 成功执行后,在浏览器中访问<strong>http://localhost:9000</strong>,会发现新增了一个名为Cucumber-Demo的Project,点击进入可以看到详细的代码分析数据和图表。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2016/sonar1.png" alt="sonar.png" /></center>
<p>技术债细节:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2016/sonar2.png" alt="sonar.png" /></center>
<p>项目结构信息:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2016/sonar3.png" alt="sonar.png" /></center>
<p> 至此,整个过程便完成了。当然,Sonar可以展示和管理的内容远远不止这些,这里只是一个简单但暂且还算全面的Demo,更多内容请访问<a href="http://www.sonarqube.org/">SonarQube官网</a>。</p>
<p><a href="https://yaowenjie.github.io/devops/code-analyzing-practice-with-sonar">利用SonarQube实现代码静态扫描及问题总结</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on July 19, 2016.</p>
https://yaowenjie.github.io/%E7%BC%96%E7%A8%8B%E7%9B%B8%E5%85%B3/travis-github-chrome-extension
https://yaowenjie.github.io/%E7%BC%96%E7%A8%8B%E7%9B%B8%E5%85%B3/travis-github-chrome-extension
2016-07-06T17:47:00+08:00
2016-07-06T17:47:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<h3 id="太长不读版">太长不读版</h3>
<p> 最近自己写了一个Chrome浏览器扩展/插件(<a href="https://github.com/Yaowenjie/travis-github-chrome-extension">github repo</a>),并将其发布到了谷歌官方商店(<a href="https://chrome.google.com/webstore/detail/github%2Btravis-v2/ekkfhiophiaakmeppcnkblpbbjlnlnmh">chrome web store</a>),该插件为github上的仓库提供travis-ci运行状态和运行时间的 <strong>图表</strong>,你可以直观地了解到哪些仓库(自己或者别人的)开通了travis-ci的build,还可以看到特定repo近十次build的 <strong>时间和状态变化</strong>,鼠标置于具体build上,还可查看具体build时间和message,点击还可以进入特定的travis-ci build页面。<br /></p>
<!--more-->
<hr />
<h2 id="背景">背景</h2>
<p> 前段时间开始用<a href="http://travis-ci.org">travis-ci</a>来自动化运行<a href="https://github.com/Yaowenjie/yaowenjie.github.io/tree/master/cucumber-test">一个简单的功能测试</a>,感觉travis-ci简洁易用的界面还是比较适合CI入门,但是用起来发现几点不是特别方便之处:</p>
<ul>
<li>每次都需要到travis-ci的页面才能知道build最新的状态</li>
<li>想看前几次的build状态,还要点击”build history”到对应的页面查看,并且近几次的build信息个人感觉并不直观。</li>
<li>又一次更改CDN之后,build(跑测试)的时间变化反而增长了,如果不盯着“build history”页面上的build time,很难直接知道构建时间的变化。</li>
<li>我会经常访问github,但不是每次提交都上travis看状态,或者坐等failed邮件发到我的邮箱。</li>
</ul>
<p> 所以就萌发了做一个chrome插件的想法,我期望这个chrome插件可以做到以下几件事情:</p>
<ul>
<li>在github上显示开通travis-ci服务的repo(仓库)当前的状态,不管是自己的还是别人的repo。</li>
<li>在每个repo内显示最近10次的build的状态变化。</li>
<li>在每个repo内显示最近10次的build的时间变化。</li>
<li>当我想查看具体build的具体信息时,能够点击进入对应的travis-ci页面。</li>
</ul>
<p> 经过两三天零碎时间的摸索,借鉴别人的插件思路,便整出来了这个:<a href="https://chrome.google.com/webstore/detail/github-travis-stat/ekkfhiophiaakmeppcnkblpbbjlnlnmh">Github Travis Stat</a>,并将其发布在chrome的官方商店,欢迎大家免费使用哈。</p>
<h2 id="基本简介">基本简介</h2>
<p> 该插件为github上的仓库提供travis-ci运行状态和运行时间的 <strong>图表</strong>,你可以看到特定repo近十次build的 <strong>时间和状态变化</strong>,鼠标置于具体build上,还可查看具体build时间和message,点击还可以进入特定的travis-ci build页面。如下图所示:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2016/travis0.png" alt="travis.png" /></center>
<p> 还可以直观地了解到哪些public仓库(自己或者别人的)开通了travis-ci的build,以及它们的最新build状态(现已适配github最新布局),点击build按钮还可以进入对应的travis页面:</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2016/travis-new.png" alt="travis.png" /></center>
<h2 id="安装">安装</h2>
<p> 安装该插件,你可以直接点击<a href="https://chrome.google.com/webstore/detail/github-travis-stat/ekkfhiophiaakmeppcnkblpbbjlnlnmh">该链接</a>,或者访问chrome web store,并搜索“<strong>github travis stat</strong>”关键字,然后添加你的chrome浏览器即可。<br />
如果你无法访问chrome web store,请下载<a href="https://github.com/Yaowenjie/travis-github-chrome-extension/releases">github repo最新的release版本</a>,解压后,在Chrome浏览器开发者模式下手动添加该插件(如下图所示)。</p>
<center><img class="center" src="https://yaowenjie.github.io/images/2016/travis2.png" alt="travis.png" /></center>
<h2 id="最后">最后</h2>
<ul>
<li>欢迎大家star/fork该<a href="https://github.com/Yaowenjie/travis-github-chrome-extension">github repo</a>,也接受有益的Pull Request。</li>
<li>欢迎在<a href="https://github.com/Yaowenjie/travis-github-chrome-extension/issues">github issue</a>提出你的问题和建议。</li>
</ul>
<p><a href="https://yaowenjie.github.io/%E7%BC%96%E7%A8%8B%E7%9B%B8%E5%85%B3/travis-github-chrome-extension">【Chrome插件】Github Travis Stat</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on July 06, 2016.</p>
https://yaowenjie.github.io/devops/automatic-confirguration-in-windows
https://yaowenjie.github.io/devops/automatic-confirguration-in-windows
2016-06-24T23:22:00+08:00
2016-06-24T23:22:00+08:00
Wenjie Yao
https://yaowenjie.github.io
wsywj61@gmail.com
<h3 id="非官方vi版">非官方VI版</h3>
<p> 分享一下最近在一个对外活动上做的Presentation非官方版(网页版),详细请戳<a href="https://yaowenjie.github.io/share/dsc-slide">该链接</a>。</p>
<h3 id="官方vi版">官方VI版</h3>
<p> 官方VI版本的Slide PDF版下载,请戳 <strong><a href="https://yaowenjie.github.io/share/PDFs/Windows-Automatic-Configuration.pdf">这里</a></strong> 。</p>
<!--more-->
<h3 id="两个slide略缩展示">两个Slide略缩展示:</h3>
<center><iframe src="https://yaowenjie.github.io/share/dsc-slide" width="840" height="440" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:3px solid #666; margin-bottom:5px; max-width: 100%;" allowfullscreen=""> </iframe></center>
<center><iframe src="https://yaowenjie.github.io/share/PDFs/Windows-Automatic-Configuration.pdf" width="960" height="480" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:3px solid #666; margin-bottom:5px; max-width: 100%;" allowfullscreen=""> </iframe></center>
<p><a href="https://yaowenjie.github.io/devops/automatic-confirguration-in-windows">Windows下自动化配置管理实践讲义</a> was originally published by Wenjie Yao at <a href="https://yaowenjie.github.io">Bu・log</a> on June 24, 2016.</p>