也谈谈月饼事件

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

石潭村游记

石潭村

石潭村在安徽省黄山市徽州区歙县霞坑镇,距离黄山市50多公里,离杭州余杭150公里多点。我们在杭州,所以过去很近,非常适合清明小长假自驾游。从杭州往西,一路有天目山、青山湖这样的景区,即便是在高速公路上,也能看到路边的小风景。

杭徽高速西行大约120km,之后是省道。经过一段蜿蜒刺激的盘山公路,又穿过几个热闹的小镇子,就来到了车子能开到的尽头——石潭村。啰嗦几句,实际上还能往前开,只是政府封了路,里面的路外地车进不去,只能乘坐当地的小商务车,一辆车可以坐八个人。网上也有老的攻略说石潭村里面还有十几个村子,进去需要徒步。其实我们发现已经修了不少水泥路进去,车子可以一直开到5公里深的大龙湾。一路依山傍水,满目都是油菜花,黑砖白墙的村落点缀其间,风景很美。

路边

我们是当天下午到的石潭村,在村口打尖吃饭,人还挺少的。暗自庆幸来对了地方,是个清净的地方。吃完饭再往前走了1公里路,正式进了村子,才发现实际情况稍微糟糕了一点。人虽然没有比肩继踵,但是已经把小村子挤得满满的了,很困难地找了车位,然后随便选了一家名字看上去比较正规的旅店住下,放了行李。

这家旅店的名字有好几部分组成,我们只记得第一部分“游侠客”,后面那部分所有人都没记住,因为看到“游侠客”三个字,有人就兴奋地说认识游侠客的创始人,我们就决定入住这家亲切的旅店了。

有了地方住,心里就踏实了。放下行李,我们一行人就兴致勃勃地出发,往村里头扑去。

石潭村

村子依山傍水而建。山并不高,一条小溪从山间穿过,将石潭村一分为二。溪水不算多,但是很清澈。河滩上有许多青色的鹅卵石。

玩水

我们在小溪边玩了一会,然后就开始随便行走。

村边菜地里面油菜花还开着一些,但是也有许多已经谢了,结了果实。还有葱,一些蚕豆,长得都很粗壮。村子里面很安静。房子都很老,但历史并不长。清一色的白墙,绘了深色的花边,古色古香。有几间木结构的老屋子,看上去很精致。

村子

村子

村子

村子

走着走着,就出了村子,来到了后山。看上去有路往里面走,据说是到另外的村子去的。这时候太阳已经快下山了,我们就决定不继续往前了。但是往回走又心有不甘,才没出来多久呢!边上的小山上,菜地里面中了很多油菜花,好像可以上去的样子,可是又不见有路。这时鸽子mm 咻咻地爬了上去,大家也跟着,顺着隐隐约约的小坎,爬了上去。

上去之后,穿过齐头高的油菜田,来到另一边山头,美景就这么突然映入眼帘。山下的村子,小溪,对面的林子、夕阳,和清新的空气一起,像画一样。

山坡

夕阳

放下野餐毯,大家坐在一起,吃吃喝喝,好不快活!

慢慢地太阳从对面的山头下去了。我们也下山,继续沿着小溪散步。然后就回到旅店稍作休息,便开始找吃晚饭的地方。

晚饭在另外一家本地的食宿一体的旅店吃的。菜很好吃,加上大家有点累,都觉得很美味。回到杭州后鸽子还认真地在大众点评上给了4星好评,这是后话了。

吃完饭已经不早,大家稍微讨论了一下第二天的行程,小朋友不喜欢坐船,所以坐船的方案就被否定了。但去哪里还是没什么定论。累了一天,大家纷纷洗澡睡了。

大龙湾

天刚蒙蒙亮,村子就已经苏醒了。出门务农的人,开张的早餐店,准备上山的摄友……外面逐渐嘈杂起来。

我6点时候醒了,躺在床上看了一下地图,觉得可以沿着小溪往下游走。看到小溪在一个叫做“大龙湾”的地方拐了大弯,有山有水有桥,觉得可以去看看。

其他同伴们还在睡觉中,我决定独自起来看看。

吃过白粥配咸菜的早餐,买了根刚炸出来的油条,在村外随便散步起来了。

早晨的景色果然更美。山中雾气缭绕,空气有点清凉,提神。远处的树林是一层层的,像水彩画一般。透过片片的油菜花,此时的村子最像水墨画了。

早上出门没有带相机,只有一个效果很差、不能自动对焦的手机。。。你们勉强感受一下好了。。

早晨

早晨

早晨

早晨

早晨

信步所至,都是美景。去哪应该都不错。

散步回来,大家已经陆续起床了。然后一起退房、出门,吃早饭,散步。简单商量了一下,就拦了一辆车往大龙湾去了。

沿途都是都是油菜花、小桥流水人家,大家都啧啧赞叹。

路上车、人都很少。开车的师傅说,我们是他见过的第一批去大龙湾的游客,上车的时候,他还以为我们是家在那边的呢!

大约10分钟不到,车子就飞驰到了目的地。

下车的地方,是村子外的桥头。清澈的山泉水沿着村民搭的竹筒子流在路上,又流进小溪。桥下的溪水很清,很多小鱼在不停嬉戏。

大龙湾

穿过桥就到了村子里面。

村子里静悄悄的,偶尔才看到骑自行车的小朋友从我们身边路过。后面还跟着一只小黄狗,好奇地嗅嗅我们,然后迅速追向主人了。

村里人少,但是很热情。我们在一位大爷家门口的长石板凳上坐下休息,和大爷聊天。

吃了点零食,稍微填了一点肚子。然后继续探索。

水边有一颗上了年纪的樟树,落下的叶子盖满了竹林间的小路。

樟树竹林

中饭本来想在水边烧烤的,但是走的时候没有带吃的东西,于是问一位采茶归来的老奶奶买了一些粽子、年糕,准备烧烤。不过老奶奶听说我们要生火,很是担心安全。。最后我们在老奶奶家借了灶,生火做饭。菜都是问老奶奶买的。

人们开始大秀厨艺。。

做中饭

吃了一顿饱饱的中饭,时间也是下午两点了,刚好适合回杭州。打电话叫上午送我们过来的师傅来接我们回去,不过他有事,另外找了一个师傅来接我们。

这是我们在等车:

等车

回到石潭村,我们就心满意足地开车回杭州啦!

Chinese_number: 一个解析汉字数字的 ~rubygem

ruby-china 上有人问到怎么优雅地解析汉字数字,比如 二十五 解析成 25 。我第一反应是应该查一下有没有这样的 gem,因为是一个很普遍的需求,说不定已经有人实现了呢!

不过很可惜,不知道是不是我没有查到,还是真的没有,总之没有找到。所以我就写了一个这样的 gem — chinese_number

用法很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
require 'chinese_number'

ChineseNumber.trans '今天二十万'
#=> "今天200000"

ChineseNumber.find "一年有十二个月三百六十五天"
#=> [{"一"=>1}, {"十二"=>12}, {"三百六十五"=>365}]

ChineseNumber.extract "今天二十晚"
#=> [20]

ChineseNumber::Parser.new.parse '二零一四'
#=> 2014

ChineseNumber::Parser.new.parse '一万三千'
#=> 13000