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

SSL Notes With Ruby on Rails

上周我给小兔盒加上了https,比想象中容易许多。记录一下

免费的CA:

之前一直以为 CA 是需要每年付一笔不菲的费用的,但其实也有免费的,例如:

按照网站指示,可以获得由其颁发的证书 有两个文件(certification & key) 是后面工作的基础。

在开发环境中使用 https (无需nginx)

1
thin start --ssl --ssl-key-file YOUR_SSL_KEY_FILE --ssl-cert-file YOUR_CERT_FILE

一些 tips

url helpers

不管当前 request 是 https 还是 http,rails 的 url_helper 都使用 http

1
2
3
4
5
6
# file: config/routes:
root to: 'home#index'

# in views or helpers:
root_url                    #=> http://YOUR_SITE/
root_url protocol: 'https'  #=> https://YOUR_SITE/

使用 protocol 无关的 assets 地址

1
2
3
4
5
6
# file: config/environments/production.rb
Rails.application.configure do
  ...
  config.action_controller.asset_host = '//YOUR-CDN.com'
  ...
end

用工具检查

有很多工具可以检查 https 部署情况,例如这个: https://www.sslshopper.com/ssl-checker.html