从新手到专家——技能成长模型

专家的特征

2009年1月15日,Sullenberger (也叫萨利) 在全美航空1549号班担任机长,飞机从纽约拉瓜迪亚机场起飞。五分钟后,在爬升过程中遭到飞鸟撞击,两架发动机全部熄火。此时只能就近寻求迫降,但高度不够,在没有动力的情况下,很难抵达附近的机场,如果处置不当,也很容易引起地面的巨大伤亡。危急时刻,萨利决定在哈德逊河上迫降,155人 全数生还

aircraft crash

从起飞到迫降,短短六分钟。萨利凭借自己的经验、技术和准确的判断,拯救了机上所有人的生命。电影《萨利机长》还原了整个过程。在感慨紧张的英雄事迹之余,我们有幸看到一位真正的飞行专家是怎样行事的。

1

失去动力之后,仪表显示发动机损坏一个,另一个处于正常状态。然而萨利根据声音、气味等细节,确定是鸟袭,并且断定两个引擎均已损坏。这个判断非常关键,直接关系到是否能及时返航。如果还有动力,就可以回到拉瓜迪亚机场了。 专家重视细节,不盲从二手信息。

2

确定动力失灵后,萨利立即下意识地打开了辅助动力系统 (APU) ,然后让副机长杰夫拿出飞行手册,按手册指引操作。依据飞行手册,出现飞鸟撞击事故时,其中第15步是打开 APU,然而萨利却直接跳过了前面的步骤,直接打开 APU。如果没有 启动 APU,飞机将不能顺利滑翔。没有这一举动,迫降就不可能成功。此外,手册有三页长,到迫降时也只进行了一页半。如果完全遵循手册逐条进行,恐怕结局已经是机毁人亡了。专家重视情境,不盲从流程。流程甚至会阻碍专家行事。

3

与塔台沟通后,飞机掉头返航。塔台清除了机场的跑道提供迫降,但萨利回复道「unable」,因为此时飞机动力不足,是无法抵达出发机场的。随后塔台安排了附近另一个机场。返航过程中,萨利意识到仍然无法抵达动,只能迫降在水面上了,于是当机立断,迫降到哈德逊河上。不了解航空史的人可能不知道,水面迫降是几乎不可能成功的。在此之前,还没有大型客机水面迫降成功的案例。萨利机长在事后接受采访时,提到自己有非常大的把握能控制飞机,确信自己能做到水面降落。这样的自信,需要丰富的知识、经验。这离不开萨利机长的成长经历。

萨利 5 岁就知道自己想做一个飞行员,11 岁就读了所有能找到的跟飞行有关的书。16岁,萨利就跟着一个开农用飞机的驾驶员库克学习。之后萨利在美国空军学院学习了驾驶滑翔机,并在空军服役。退役后,他加入了太平洋西南航空(后并入美国航空)担任民航飞行员。在2009年的时候,他已经差不多有30年的民航驾驶经验了。滑翔机的学习经验非常重要,飞机在高空失去动力,也就相当于一架十几吨重的滑翔机。无独有偶,史上最成功的几次迫降事件,驾驶员都有滑翔机驾驶经验。 专家有丰富的知识积累。

4

萨利本身也是 NTSB (美国航空安全委员会)的调查员,在 2009 年之前就协助 NTSB 调查航空事故,访问空难家属。同时,他还会收集各种飞行事故的调查报告。这些研究对这次的迫降非常重要。飞机要在水上迫降成功,需要几个要点:

  • 水上迫降时应收起起落架
  • 飞机近水时的速度越小越好
  • 机翼的襟翼应在水下位置接水更有利

这些知识,正是从之前的空难调查报告中总结出来的。在这次事件中,帮助萨利做了正确的选择。专家会不断反思,从(自己或他人的)不足中总结。

5

我必须压抑本能的激动反应,来挽救机上的每一条人命。我很快思考各种可能性,然后选择唯一的可行方案。—— 萨利

除了大量的知识储备,危急关头的冷静和直觉,让萨利得以快速反应,在短时间内做出正确选择。 专家依赖直觉。

飞行当然是一种技能。但技能在我们的生活、工作中无处不在。 我们的生活中需要各种各样的技能,例如做饭,与人沟通,写程序,养育小孩等等。这些事情都需要我们具备一定的知识,并有运用这些知识的技巧。以编程为例,作为一个程序员,首先要学会产品经理的语言,能读懂需求文档,知道常见的一些需求实现方案,并将已知的方案组合,实现产品的需求;其次要学会计算机的语言,将方案转化成计算机程序,并且要留意工程性,写出代码还需要关注可维护性和性能。

三百六十行,行行出状元。总有一些大师、专家涌现在我们的视野中。即便没有那么遥远,我们的身边也不乏一些更会做菜的朋友、更会和宝宝交流的爸爸妈妈。他们是如何成长的?他们的成长经历了哪些阶段,不同的阶段又是如何跨越的呢?

技能提升的五个阶段

1980年,德雷弗斯兄弟(Dreyfus Stuart E, Dreyfus, Hubert L) 发表了一篇论文《A Five-Stage Model of the Mental Activities Involved in Directed Skill Acquisition》 (直译:直接技能的获取相关的五阶段精神活动模型),提出了技能成长的五个重要阶段。我在《程序员的思维修炼》这本书中第一次接触到这个模型,感觉茅塞顿开。这个模型套用到其他领域,也非常适用,很好地指导了我在各方面的成长,也让我在新接触新领域的时候,能知道如何更快提升自己。所以我决定把这个模型分享给更多的人。

好了,我们先来看看这个模型。

I. 新手

新手需要指令才能工作。这个指令需要明确地指导他,遇到情况 A 如何应对,遇到情况 B 又如何应对。比如煎荷包蛋时,加多少油,什么时候放鸡蛋。

新手和专家之间沟通比较困难。新手描述事情的方式往往简单,缺乏必要的信息,专家不敢轻易下结论;而专家给的建议往往是考虑全面,涉及了不同的情境,并且会给出更底层的原则,而这种原则容易让新手忽略情境盲目遵从,或者无法在需要的时候想起来。

II. 高级新手

不愿全盘思考。统计资料显示,多数人落在这个层级;当管理阶层分配工作给高级新手,他们认为每项工作一样重要,不明了优先层度,意味着他们无法认知每件工作的相关性。因此管理者认清,工作需给高级新手时,必须排列优先级。

高级新手已经脱离手册的帮助,能够独立完成项目了。但还缺乏全局观,表现为:不知道为什么要这样做,背后的原因是什么(知识广度和深度都不支持自己得出答案);也不知道这件事在全局中的意义,如这个项目在公司的战略意义,对用户又能提供什么价值。

另外,高级新手还只能解决已经解决过的问题,对于自己没有遇到过的问题,他们还是一筹莫展,通常他们会询问专家。

III. 胜任者

能解决问题,不管是已经解决过的,还是首次遇到的。他们知道在哪里能得到需要的信息。

IV. 精通者

  • 技能上:反思 — 能认知自己的技能与他人差异,能透过观察别人去认知自己的错误,形成比新手更快的学习速度。
  • 职位上:全局观 — 能明确知道自己的职位在整体系统上的位。

反思的特征让人的技能提升开始加速。所以精通者更接近初级专家,而不是高级胜任者。

V. 专家

专家凭直觉工作。专家是各个领域知识和信息的主要来源。他们总是不断地寻找更好的方法和方式去做事。他们有丰富的经验,可以在恰当的情境中选取和应用这些经验。他们著书、写文章、做巡回演讲。他们是当代的巫师。

总结

德雷弗斯技能模型指出了技能水平的不同阶段,不仅仅是知识的差异,更是思维模式的差异。对我来说,我知道了:

  • 当我新学习一个技能的时候,应该寻求情景无关的指令,快速入门;
  • 当我已经入门后,需要不断深挖,知道原理;
  • 当我需要进一步提升时,需要通过观察他人,反思自己;
  • 当我想成为专家时,我需要刻意练习,让自己形成直觉。

这里引出了 「刻意练习」 这个非常重要的概念,他是成为专家的唯一路径。下次我再写一偏,和大家交流刻意练习。

参考

Elixir 源码阅读笔记: Ex_doc

源码地址:https://github.com/elixir-lang/ex_doc ex_doc 是 elixir 代码生成文档的工具。

生成文档的方式有很多种:

  1. 利用语法解析分析源码;
  2. 利用语言提供的反射能力分析;

Elixir 提供的反射功能非常全面,利用 Code 模块提供的一些方法,可以获得目标模块的很多信息。

文档也是其中一种。例如我们有这样的一个模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
defmodule Example do

  @moduledoc """
  Example module for `ex_doc`.

  ## Examples

  ``\`
  iex> Example.hello
  :world
  ``\`
  """

  @doc """
  hello world!
  """
  def hello do
    :world
  end

end

我们可以通过 Code.get_docs/2 获得模块的文档。

1
2
3
4
5
iex> Code.get_docs Example, :all
[docs: [{ {:hello, 0}, 14, :def, [], "hello world!\n"}],
 moduledoc: {3,
  "Example module for `ex_doc`.\n\n## Examples\n\n```\niex> Example.hello\n:world\n```\n"},
 callback_docs: [], type_docs: []]

可以看到,Elixir 已经在模块的信息中保存了相关的文档信息。ExDoc 接下来做的是提取出来,并进行格式转换。其中用到非常多的反射的接口,大部分都在 Code 模块中提供了。

Daily Reading Note #1

这里记录了每天阅读代码记录的新知识。

List.keymember?/3

用来检查 Keyword List 中有没有对应的 key / value:

1
2
3
4
5
6
iex> List.keymember?([a: 1, b: 2], :a, 0)
true
iex> List.keymember?([a: 1, b: 2], 2, 1)
true
iex> List.keymember?([a: 1, b: 2], :c, 0)
false

:code.where_is_file/1

可以从 load_paths 中找到 beam 文件

1
2
iex> :code.where_is_file('Elixir.Kernel.beam')
'/usr/local/bin/../lib/elixir/ebin/Elixir.Kernel.beam'

Code.prepend_path/1

将一个路径加入 Elixir 的 load_paths

Code.eval_string/3

执行一段代码,并返回结果,以及结果 binding

1
2
iex> Code.eval_string "b = a + 1", [a: 1], file: __ENV__.file, line: __ENV__.line
{2, [a: 1, b: 2]}

Keyword.get_values/2

获取关键字列表中某个 key 对应的所有值,很适合用来获取参数

1
2
iex> Keyword.get_values [path: "a", path: "b/c"], :path
["a", "b/c"]

:binary.last/1String.last/1

获取字符串的最后一个字符

Kernel.struct/2

适合留下 struct 合法的 key,丢弃不合法的 key

1
2
3
4
5
iex> defmodule User do
...>  defstruct name: nil
...> end
...> struct %User{name: "qhwa"}, %{name: "Billy", age: 10}
%User{name: "Billy"}

Code.ensure_loaded?/1

可以判断一个 Module 是否已经加载进来了

Path.wildcard/1

等于 Ruby 的 Dir.glob

Path.basename/2

和 Ruby 一样,可以用来去掉扩展名

Kernel.function_exported?/3

判断一个模块是否暴露了方法名

1
2
iex> function_exported? Kernel, :struct, 2
true

Code.get_docs/2

如果编译时有 --docs 选项(默认是有的),可以从模块中得到文档。返回 {line, text}

1
2
3
iex> Code.get_docs Code, :moduledoc
{2,
 "Utilities for managing code compilation, code evaluation and code loading.\n\nThis module complements Erlang's [`:code` module](http://www.erlang.org/doc/man/code.html)\nto add behaviour which is specific to Elixir. Almost all of the functions in this module\nhave global side effects on the behaviour of Elixir.\n"}

Enum.at/1

module.info(:compile)

能获取一些编译信息

1
2
3
iex> Code.__info__(:compile)
[options: [:debug_info], version: '6.0.1', time: {2017, 1, 5, 10, 33, 52},
 source: '/Users/jose/OSS/elixir/lib/elixir/lib/code.ex']

Kernel.Typespec.beam_specs/1

可以获取一个模块的类型信息

1
2
iex> Kernel.Typespec.beam_specs(Code)
 ...

如何在管道中用 ||

1
a |> func() |> Kernel.||([]) |> IO.inspect

也谈谈月饼事件

mooncake

中秋前夕,阿里的月饼事件引爆了圈内网络(事件回顾)。事情从当事人双方的角度来说都有道理:

  • 程序员:只是利用技术替代人工抢,和正常点击按钮抢并没有差别。没想到服务器端有程序 bug,可以不停抢。发现问题后没有付款,赶快联系了行政要退还。
  • HR: 5个人抢了120多盒月饼,过分了。安全部门的职责就是防范「黑灰产」的,怎么能「监守自盗」!(淘宝上有阿里的中秋月饼在售卖)

我相信阿里有自己的立场和原则,不会为外界质疑所动,甚至他们 4 小时的复盘结果也是毫无愧谦之意。也有人颇为赞同这种「捍卫价值观」的做法。

但阿里在程序员的心目中一落千丈已经是不争的事实,外界技术人员对阿里的看法更清醒了,阿里不是一家技术驱动的公司,这里没有工程师文化的土壤。甚至连内部的程序员也对事件非常失望。在我印象里,上市之后,阿里对技术「开源」的态度急转而下。阿里已经成为一个竭尽全力避免风险的公司。这无可厚非,几乎没有一家公司像阿里这样拥有巨大的成功而又充满争议。

toy

然而懂点程序的人都能看得出问题所在。

  • 假设这5个人,每人只抢了一盒月饼,从性质上来说,还是不是一样的?显然不是,因为明显可以排除拿去售卖 的可能。但从阿里的官方说辞里面,显然是一样的性质:「技术压制」造成不公平,以及利用系统漏洞获利。那这种情况,是不是也开除?
  • 对比一下以下情况,你觉得性质上是否有差别:

    • 抢到 100 盒月饼,每盒都付款;
    • 抢到 100 盒月饼,每盒都付款,然后进行高价售卖;
    • 抢到 100 盒月饼,但是只买 1 盒,其余的主动退掉。

我再举一个真实的发生在我身上的案例。有一次,妹纸和她的小伙伴们想要去一个博物馆看一个很不错的展览,这个展览在她们的行业里是很火爆的,需要在博物馆的网站上抢票,虽然票是免费发放的,但是只能在一个时间后才开放。她们担心到时候人多抢不到票,于是让我帮忙看看。我试了一下,发现时间限制只是在网页前端做的,完全可以绕开。就用几条命令帮她们提前申请到票了。我没有觉得有愧于心,因为:

  • 我有机会领到更多的票,高价出售给别人,但我没有;
  • 我可以炫耀给别人,让博物馆因为这个漏洞遭受损失,但我没有;

可见,我是有机会成为所谓的「黑灰产」的,但我没有。如果因为我将来可能会犯错,而将我提前关起来,这个逻辑是不合理的。

以我 7 年多阿里生涯来看,真没看出来抢月饼是不符合哪条价值观了。以未曾发生的事情(安全部成了自我黑灰产)为处理依据,臆断别人的用意;为了达到杀鸡儆猴的效果,做出令人啼笑皆非的处理。这件事情拿不出哪条条例,连价值观的说法也难以服众。还说不是处罚太重?这恐怕是本年度程序员最被误解的事情了吧?

从阿里离职以来,本着价值观「随时随地维护公司形象」,我从未说过阿里什么坏话,但这件事情让我太看不下去了,看不下去程序员被如此误解,如此对待。

只想表达一个观点:这件事情的处理是不懂程序员的阿里管理层犯下的一个错误,但会有什么影响,历史会告诉我们。

Minifying-with-webpack

webpack bundling

When coding in a modern modular way with Webpack is done, it enters the deploy phrase. We minify assets to reduce the network transfer cost. While bundling is not necessary nowadays thanks to HTTP/2, minifying is still an important work, because in many cases we can reduce the size to transfer between servers and clients:

  • unreachable codes can be removed
  • comments code can be removed
  • images can be optimised losslessly
  • variable names can be shortened in Javascript
  • HTML and SVGs can be optimised

Here in Helijia, we use webpack to build mobile apps. Here are some notes from us on minifying with Webpack.

Minifying Javascript / CSS

Webpack is shipped with some builtin plugins which can do the job well. * Dedupe Plugin searches for similar files and deduplicated them in the output. * UglyfiJS Plugin uses the famous UglifyJS to minify JS and CSS assets.

Tip: Use webpack stats to review your modules

Webpack can generate packing stats in json format while packing. We use Webpack visualizer to visualize it to find which part of our works can be optimized. It helps a lot by providing an detail view inside the bundled file.

webpack viz

Minifying HTML

HTML webpack plugin can help minifying HTML. Here is an example config:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
new HtmlWebpackPlugin({
  template : path.join(__dirname, '../src/template.html.ejs'),
  filename : 'index.html',
  title    : '河狸家',
  inject   : 'body',
  minify   : {
    html5                          : true,
    collapseWhitespace             : true,
    minifyCSS                      : true,
    minifyJS                       : true,
    minifyURLs                     : false,
    removeAttributeQuotes          : true,
    removeComments                 : true,
    removeEmptyAttributes          : true,
    removeOptionalTags             : true,
    removeRedundantAttributes      : true,
    removeScriptTypeAttributes     : true,
    removeStyleLinkTypeAttributese : true,
    useShortDoctype                : true
  }
}),

Minifying SVG

we are currently using svgo-loader to keep SVGs clean.

before:

1
2
3
4
5
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 10 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
    <path id="Shape-Copy" d="M9.157,0.136L0.157,8.636C-0.052,8.834 -0.052,9.166 0.157,9.364L9.157,17.864C9.357,18.053 9.674,18.044 9.864,17.843C10.053,17.643 10.044,17.326 9.843,17.136L1.222,8.994L9.843,0.864C10.044,0.674 10.053,0.357 9.864,0.157C9.674,-0.044 9.357,-0.053 9.157,0.136Z" style="fill:rgb(189,157,98);"/>
</svg>

after:

1
<svg class="SVGInline-svg icon-svg" style="width: 1em;height: 1em;" viewBox="0 0 10 18" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M9.157.136l-9 8.5a.5.5 0 0 0 0 .728l9 8.5a.5.5 0 0 0 .686-.728l-8.62-8.142 8.62-8.13a.5.5 0 0 0-.686-.728z" fill="#bd9d62"></path></svg>

Minifying bitmap Images

Image-webpack-loader can save a lot bandwidth.

Conclusion

Webpack provides a powerful way of web developing, we use some plugins to automating the deployment. and there are more out of our view. So let’s keep exploring and make web development more enjoyable.

Setting Up Your React Project Is Easier Than You Think

I have heard some ones complaining that setting up a Webpack + React project is not an easy job. It’s only partly true. Webpack is not well documented at this moment, and has many powerful features and plugins. This makes configuration on Webpack seem difficult to new developers. To make things even worse, there are tons of ‘starter kit’ projects over there, most of which are lack of maintainance.

Yeoman

Indeed, it is super easy to setup a brand new Webpack + React project using Yeoman.

Yeoman is a scaffolding tool for web developers. With diffrent generators we can generate different kinds of web projects quickly by lines of command.

Here’s how it is done:

1
2
3
4
5
6
7
8
# install Yeoman command and generator
npm install -g yo
npm install -g generator-react-webpack

# generate project
mkdir test-project
cd test-project
yo react-webpack

Thanks to generator-react-webpack, after answering some questions from yo you will get your project setup in seconds.

Easy and quick, right? Here’s what we have got out of the box:

  • a project with Webpack and React configured;
  • a solution for diffrent environments both for runtime and webpack configuration;
  • a flux project structure;
  • an optional PostCSS setup

Also this generator can help generate React components via command line.

为什么写不出博客了

近几年以来,我写博客的数量每况愈下,从几个月一篇,到几个季度一篇,现在已经将近一年一篇了。。 (✖╭╮✖)

今天也是无意间提起,让我思考这个问题

为什么越来越不写博客了呢?

  1. 首先想到的是「懒」+「忙」

    但其实「没时间」是一个最不成立的理由,所谓的「没时间」往往是「优先级不高」的幌子罢了。很多时候不忙,心里知道有很多事情应该去做,却什么也不想做。阳明先生 说没有「知易行难」这件事,不「行」是由于「知」得还不彻底。是的,我没有在自己心里把写博客重视起来当一回事,当成一定非完成不可的的事情。

  2. 追求的事物已发生了转移?

    是以前对技术更感兴趣一些,现在不感兴趣了吗?似乎也不是,现在每天起来之后也会阅读技术文章。对技术反而比以前更执着了。

  3. 害怕写不好

    以前反而大胆一些,也不怕误导别人;现在反而一些就想写很多,想写一个系列,系统地传授一系列的知识,系统地引导别人掌握一个东西。想做的事情太大,又没有足够的精力去做,于是就这样了。

原因算是找到了。

Done better than perfect

写得差总比不写要好,其实我的 blog 访问量那么低,也误导不了几个人。何况我最近也想明白了,没有所谓的「误导」,每个人都需要有自己的判断力,看到别人的论点需要有甄别的能力。阅读是自己的权利,判断也是自己的义务。坚持推荐我认为「好」的事物,就算因此让别人走了弯路,相信这种弯路也会是一种积累的。

所以我决定,从这篇开始,要多写写一些短小的技术文章了。即便没有人看也没关系,回归当初写 blog 的心境,记录自己的成长。

为什么 Limits 不生效

最近遇到一个奇怪的问题,无法将服务器的最大文件打开数量提高。

服务器是 Ubuntu Server 14.04

1
2
3
4
5
6
$lsb_release -a
No LSB modules are available.
Distributor ID:   Ubuntu
Description:  Ubuntu 14.04.2 LTS
Release:  14.04
Codename: trusty

正常情况下, /etc/security/limits.conf 的改动,应该在下次访问时就生效才对。 但是总是没生效,查了很多资料,尝试了很多修改,终于成功了

记录一下填的坑:

1. 确保 pam 生效

/etc/pam.d/login 中,存在:

1
session required pam_limits.so

2. 确保 ssh 使用 pam

/etc/pam.d/sshd 中,存在:

1
session required pam_limits.so

/etc/ssh/ssd_config 中, 存在:

1
UsePAM yes

3. limits.conf 建议不要使用星号

官方 manual 以及网上的教程有很多都用了 * 符号,然而不是所有系统都认的,我最后发现问题就是在这里!

1
2
3
4
5
6
7
8
9
10
# 不兼容方式:
* soft nofile 51200
* hard nofild 51200

# 兼容方式
root soft nofile 51200
root hard nofile 51200

qhwa soft nofile 51200
qhwa hard nofile 51200

如何确认问题之所在

查看当前用户的 limits

1
ulimit -a

查看另外一个用户的 limits

1
2
# 注意 ulimit 不是命令,而是 shell 方法
sudo -u <USER> sh -c 'ulimit -a'

经过 ssh 查看用户的 limits

1
ssh example.com 'ulimit -a'

Capistrano-hostmenu: 只操作指定的服务器

想必大家都用过 capistrano 了,部署代码到多个服务器上的神器。 不过使用中有个不太方便的地方是,某些情况下只想部署到其中几台。

例如我们新增了一个功能,但只想找一台服务器先部署上去测试一下,成功后再部署到整个集群。

有好几种方式可以做到:

  1. 临时修改发布脚本(config/deploy.rbconfig/deploy/***.rb
  2. 使用 HOST 命令行环境变量,或者 --host 参数, 比如 HOST=example.com cap production deploy

第1种不好重用,第2种在 capistrano v3.3 以上版本已经失效了。

我把我们项目中用的 task 抽出来做成了一个 gem: capistrano-hostmenu 功能实在是太简单,没有什么好描述的。。

一图顶千言:

如何开发 Ruby 命令行工具

最近 @季子乌 的 花瓣账号 突然被封,苦心采集的很多图片一下子全部看不到了!

后来虽然联系客服重新开通了账号,但还是心有余悸,觉得还是 pinterest 靠谱一些,准备把图片全部迁移到 pinterest 上。由于图片数量比较多,我就用 ruby 写了一个工具,将花瓣上的图片下载下来。(不过 pinterest 不能批量上传,这些图片也只是备份到本地,这是后话了)

这个项目已开源,地址是: https://github.com/qhwa/huaban_exporter

这个工具下载后,可以用执行 huaban 命令

1
huaban export boards --of qhwa

执行效果:

preview

我喜欢用命令行工作,之前做的几个 gem(lfd, fdlint)也都提供了命令行。这次就趁这个项目总结了一下怎样用 ruby 开发友好的命令行工具。

I. 初期怎样提高开发效率?

在写了基础的一些逻辑 model 后,我写个简单的 rake 文件,初期用 rake 来作为入口,边开发边测试。

一开始我只建了这样的一个 rake 任务,来调试获取画板列表功能:

1
rake boards         # 列出一个用户的所有画板 (user=用户名 rake boards)

后来随着功能不断完成,逐渐增加了几个新的任务,最后是完整的任务:

1
2
3
4
rake boards         # 列出一个用户的所有画板 (user=用户名 rake boards)
rake export_board   # 导出一个画板的所有图片到本地 (board_id=画板id  rake export_board)
rake export_boards  # 导出用户所有的画板图片到本地 (user=用户名 rake export_boards)
rake pins           # 列出一个画板所有的采集 (board_id=画板id rake pins)

II. 项目后期功能稳定后,怎么做命令行入口

rakefile 很适合自己用,但是要分发别人用,用 rakefile 就不方便了。做成带命令行脚本的 gem 更加方便。

  • 把你的脚本放到 bin 目录下
  • 加上执行权限(chmod a+x)
  • 加上 shebang, 比如 #!/usr/bin/env ruby

这一步我以前是用下面提到的 gli 来自动进行,但后来改成手动做了。因为 gli 生成了一些额外的文件,和 bundle 有点冲突。

III. 怎样让命令变得友好?

有个超棒的 gem 叫做 gli,帮你很容易实现出类似 git 这样风格的脚本

IV. 项目完成后,怎么用做成一个 gem,分享给别人?

bundler 提供了生成 gem 的功能

  1. bundle gem <name> 生成目录结构
  2. 修改 gemspec
  3. rake install 先在本地安装这个 gem 进行测试
  4. rake build 以最新的文件重新打包成 gem
  5. gem push pkg/<版本>.gem 将gem上传到 https://rubygems.org