• 我的房间很小,我就把窗户开的很大。
  • 我的感情很重,我就把诺言许的很轻。
  • 我的往昔很空,我就把今天填的很满。
  • 我的喜悦很少,我就把笑容积的很多。

Mac下的输入法总是各种怪异,明明输入中文还是会被Vim截获,现在基本都在Mac下开发,加上这段时间都有比较稳定的学习时间,为了避免浪费更多时间在切换语言状态上,我决定换回Emacs。

然后受Apple4us毒害,我也开始用上传说中的神级输入法RIME,虽然没有跨平台共享词库,但是超快的响应的确让我有深刻的印象。项目本身是开源的,或许我可以花点业余时间研读一下代码,把这个共享词库的功能做一下。

“第一条:不要借钱去炒股,也即不要把股票抵押出去再去炒;第二条:股票不要做空,因为股市总体来讲一直上升,做空是投机行为,只能一时赚钱。这两条跟投资行为有关,第三条跟在座各位都有关系:不要做自己不擅长的事,要专注于自己非常懂的事。做事要成功,一定是原则放中间,利益放两边,反过来就麻烦了。”

Tesseract的交叉编译和Pocket-OCR

Tesseract-OCR是一个开源的OCR引擎,具体信息可以在这里找到,在iPhone跑这玩意还真是个很蛋疼的事情,木有办法,让我找到了Pocket-OCR

然后就开始交叉编译,作者在其博客上提供了详细的解释,在Xcode 4.3和iOS5.1的SDK里总是有点小麻烦,所以改了一下作者的脚本:

也没太多东西,就是把cpp的编译器改成llvm的,然后把cxx的注释掉让llvm选择-E选项,避开一个编译器校验错误,然后最后把ccutilccmain里的头文件拷贝进去$GLOBAL_OUTDIR/include即可。

Xcode在build的时候检查一下Library跟Header的查找路径,确认是$GLOBAL_OUTDIR/lib$GLOBAL_OUTDIR/include,没用的删掉。

最后一个地方是OCRDisplayViewController.m里在#import "baseapi.h"补上using namespace tesseract;

这样应该就可以跑起来Pocket-OCR了,如果要识别中文,还需要在这里下载训练好的文件,放在工程的testdata-svn目录,如果你已经安装过一次,记得先删除App的Documents/testdata目录再重新安装。

然后修改OCRDisplayViewController.m第77行的初始化参数,改engchi_sim,这个字符串就是训练数据的名字(不含后缀)。

稍微尝试了一下,中文识别在手机上基本要几分钟,目前基本只能识别系统截图(因为字体也调用了系统字体去识别);英文因为字形不算复杂,效果还可以。Pocket-OCR只做了基本的压缩和转码,没有做降噪和中值滤波,也没有做水平,倾斜矫正,所以要很准确的方向对着才能有比较好的识别效果…也可以提供。话又说回来,这玩意还是放在集群上持续做学习比较靠谱…放到手机上还是很蛋疼的一件事情……

compile itunnel on mac

itunnel 实现了通过USB连接就可以建立一个本地隧道,然后就可以通过ssh直接访问iOS设备(前提是越狱并安装了openssh)。

iTools Windows 版本就有这个功能,可是Mac版本好像没跟上,所以就打算自己编译个。

在Mac下编译itunnel不是很顺利,遇到几个问题:

  1. libusb需要安装兼容版本,brew install libusb-compat 解决
  2. clock_gettime在Mac下需要替换为另外一个函数:gettimeofday 写法也有点不一样,参考这个
  3. MakefileLDFLAGS也要去掉-lrt参数

索性做了个兼容版本: https://github.com/yyfrankyy/itunnel

目前在Lion和几个常见的Linux发行版下编译通过。

前端计算规则联动引擎

这个方案是之前交易平台重构的时候重新设计的,用于解决目前购物车确认下单里,复杂交易金额计算规则带来的维护问题。

目前淘宝的购物车确认下单金额计算包含了各个单品的金额总和,运费平台,优惠平台,商城积分等几个大部分,而其中每个部分内部又有自身独立的运算。比如运费还包含快递,邮政和COD(货到付款),快递的计算规则由各个卖家设定的运费模版决定,不同的商品有不同的运费模版,这样总运费就不是简单的商品*快递费了;COD里除了快递的运费模版之外,还需要对最终运费进行取整,这个取整规则又根据不同的价格区间而不同,根据不同的COD运费模版而不同。再比如优惠平台,包括单品优惠,店铺优惠和跨店优惠(活动促销)等,这些优惠信息都跟卖家相关,统一由优惠系统集中提供,每次修改数量,收货地址的时候影响了原始总价格,然后就需要重新计算优惠金额,从而影响了最终实际价格。

上面仅仅是最简单的列举了目前购物车确认下单里的计算逻辑。前端在页面需要实时的根据用户的修改作出计算,告诉用户当前的实际付款金额。这样复杂的金额计算规则,是整个购物系统经过好几年一步一步发展过来的。而以往的前端代码里,根据业务的变化,修改具体的计算代码,一步一步地变脏,变乱。代码的强烈耦合,导致有些前端无从了解具体业务,出现了直接根据DOM结构来计算金额的窘境。

我们经常讲代码是反映业务逻辑的,而不需要太多的注释来解释。从这个角度出发,要求我们更进一步的从代码的角度理解和抽象业务逻辑。从前端的角度,我们将每个具体的计算逻辑抽象为以下几点:

  1. 计算公式(计算逻辑)
  2. 公式所需要的变量
  3. 公式代入具体变量后的值(计算结果)

这样讲似乎看不出跟具体业务的关系,我们拿商品数量这个简单的例子来解释下:

  1. 计算公式:商品价格*商品数量
  2. 公式所需要的变量:商品价格:页面某个Tag;商品数量:输入框
  3. 公式代入具体变量后的值:算出来后传递出去。

具体到代码层面,还需要再进一步提出问题:

一、计算公式用什么方式描述?

可以采用原生的JS代码来描述具体的逻辑,因为维护这段代码的都是前端,能够理解。

二、变量通过什么方式传递?变量的类型是什么?

另外,因为我们计算的过程都是数字,所以为了简化,我们假设所有的变量都是数字。

三、计算结果通过什么方式传出?通过什么方式接收?

我们都知道观察者模式(自定义事件)最适合拿来做逻辑隔离(解耦),这样满足了我们不希望每个业务混杂在一起的初衷,每个业务规则可以只关心自己的计算,然后通过自定义事件接收变量;另外一方面,有些计算数据可能是等待一个异步结果,比如优惠部分的结果都需要等待Ajax的返回,那可以把这部分trigger的代码放到异步回调里。伪代码如下:

quantityPrice.bind('inputChange', function(e) {
  this.trigger('quantityPriceChange', price * e.quantity);
})

计算完成,通过trigger将结果抛出,抛出之后就等待其他监听方来接收了,自己也再不用关心自己被谁依赖了。

通过这三个问题,我们确定了一个基本的计算规则怎么和其他规则进行交互,以及交互过程中怎么传递数据。接下来,我们再进一步。我们知道所有规则都是通过自定义事件进行接收参数和抛出计算结果,那为何不将这一步再封装一下,从业务的角度重新设计一下API。

这里,我们就可以将这个计算规则独立抽象为一个类,这个类包装了特定规则的计算方法,也声明了该规则依赖了哪些变量等。我们将初始化参数设计为:

new Rule('quantityPrice', function(quantity, price) {
  return quantity * price
}, { parents: ['quantity', 'price'] })

这个初始化过程就封装了上述自定义事件绑定的过程。然后也声明了该规则依赖了商品价格和数量这两个变量。所有的计算规则都可以通过这个简单的API来描述。

进一步,我们追溯到quantityPrice的上游price,因为pricequantity这两个已经没有上游规则,这里我们再独立一个入口,提供给最顶的变量使用:

new Rule('price', function(e) {
  return parseInt(e.price, 10)
}, { vars: {price: $('#price').text() } })

有些时候,我们还遇到一些会动态变化的变量,比如商品数量,是在用户操作后才变化的,这时候,我们还需要一个手动触发更新的接口:

var quantityRule = new Rule('quantity', function(e) {
  return parseInt(e.quantity, 10)
})

我们通过update方法来传递特定的变量,同时手动触发计算:

quantityRule.update({ quantity: $('#quantity').val() })

这个方法就放在具体的input.onChange事件绑定即可。合并这两步,我们实际上还可以将quantityPrice这个规则简化为:

var quantity = new Rule('quantity', function(price, e) {
  return e.quantity * price
}, { parents: ['price'] })

input.bind('change', function() {
  quantity.update({quantity: $(this).val() })
})

在这个定义里,我们在声明parents: ['price']的时候,自动帮quantity这个对象监听了price的变动,update之后,又自动做了一步trigger('quantity'),即相当于:

Rule.bind('price', function(e) {
  quantity.caculate(e.result, { quantity: this.vars.quantity })
  Rule.trigger('quantity', {result: this.result})
})

这里出现了一个Rule执行ontrigger。传统的观察者模式里,下游规则要监听上游规则,需要把下游规则的代码写到上游规则的绑定代码里,这样又违背了我们解耦的初衷,所以采用事件中心的概念,集中一个对象来执行监听和广播。

将这些独立的节点联合起来之后,我们可以看到一个完整的运算网络:

这里我们已经完成了大部分工作,实现了具体业务的隔离,也重新调整了业务代码之间交互的方式,从而达到了业务解耦的目的。解耦之后,我们可以对每个规则独立调优,分配给不同的人员负责,独立文档,独立单元测试,等等。

前面的规则,都是设计为单个规则,单个计算的,而遇到同样的规则使用多次的时候,就乱套了。即,以上设计无法用于多实例的场景。我们知道,只要拿到了实例,那是很容易进行绑定和通信的,但是拿到实例意味着监听方不仅需要知道上游规则的名称,还需要得到上游规则的对象实例,意味着两个规则已经形成强耦合,又违背了我们不希望两个规则耦合的初衷。

在事件中心的设计里,事件监听和触发都是一个对象,我们采用另外的方式来实现实例管理:对事件进行分组,用事件名来描述实例,即触发和监听的时候,采用这种事件名:

Rule.trigger('{groupName}:{ruleName}', result)

这样,监听方就需要对’groupName’进行管理,维护上游传下来的分组信息,形成一个自定义的变量表,从而达到一个对象管理多个实例的目的。

Rule.bind('{groupName}:{ruleName}', function(e) {
    self.vars[e.groupName] = e.result;
})

而且,在计算的时候,也需要选择正确的分组变量,进行计算。

self.vars[e.groupName] * price

整个规则对象可以整理为这样:

每个规则就像一个多入口多出口的管道一样,自由组装,从而形成一个复杂的多实例联动网络。

这样的设计,对于整个计算过程而言是一个整体,我们可以统一对整个过程做集中优化,比如运算缓存。

在上游的计算结果通知下游的过程中,每次计算都会被触发,这样对于修改数量这个很顶级的手动操作而言,可能会导致相当多的下游重复计算。这时候我们可以在引擎通知下游的时候,做一下运算缓存,如果当前计算结果跟上一次是一样的,那么就没有必要重复通知了,因为对于下一个计算规则,所有变量都是一样的,再算一次没有任何意义。(这个思路来源于JavaScript Memorization)

if (this.cache[e.targetGroup] !== this.result)
  Rule.trigger('{groupName}:{ruleName}', this.result)

这样优化之后,整体重复运算过程少了至少1/3。

总的来说,这个框架的具体实现可能只适用于电子商务相关的金额计算过程抽象,在重构迁移完成之后对每个计算过程做优化,还是很简单的事情,而且单元测试也很容易保证。

比如COD的运算,基本上就是把运算方法迁移过来,然后直接写多一个规则调用这个方法,调整一下传参的方式即可,原来的单元测试代码全部可以保留。后期在优化的时候,对每个规则都写测试用例,规则分散个各个业务模块里,只要页面初始化就会加入,分工也简单了很多,每个人负责好自己的业务模块,写好自己的规则代码和测试代码,规则之间联动的事情,交给联动引擎就ok了。

自动事件绑定和基于文本的实例管理,这两者还都不容易把握,整个框架开发过程中花了很大心思来加强这两部分的稳定性。相比具体的实现,这两个模型倒是可以借鉴用到其他地方去。

2011年总结

2011年,实现的最彻底的应该就是按时上下班这一条了。2011相比2010,最大的变化是开始注重日常生活,而不仅仅像2010年的总结那样只有工作。

生活上

11年走了不少地方,北京,上海,杭州,广州,苏州,乌镇,顺德,昆明,加德满都,博卡拉,萍乡。。却总觉得心里空空的,匆匆忙忙去,匆匆忙忙回,每个地方都找不到熟悉的感觉。除了惊叹,震撼之外,如果非要说,那就是多了一股义无反顾说走就走的勇气。

其实说到义无反顾,有两个小故事。

第一个是苏州的。那是7月份某个周五早上,起床正准备洗漱上班,接到正谊电话,说下午到苏州,让我周末计划下怎么过去玩。不知道哪里来的决定,拍了下大腿说那不如我现在就过去吧。完全忘记了今天是工作日,完全不管今天还有多少事情,然后简单带上两件衣服打包出门,打车到汽车北站,然后买了票,一点多就已经到了苏州。

到了苏州其实也不知道怎么玩,周六周日玩了几个所谓著名旅游景点,发现也没啥吸引力,然后我们又突然决定绕着苏州河走一圈,真正体会一下苏州人的生活。于是开始从寒山寺出发,走到苏州河西面,然后就开始沿着河流暴走。苏州河上很多桥,我们就按桥一个一个打街旁mark距离,遇到好看的风景就停留,拍照,然后又继续,不知不觉就走了五个多小时,整整绕了苏州河一圈。虽然公路徒步是很累的,加上我们也没啥这么长距离不休息暴走的经历,走到最后脚都起泡,人也都几乎虚脱了,但是却有种满满的成就感。

第二个是出国的。常之一直在计划川藏的,所以我就开始看,开始计划一条长线,到后来盯上了尼泊尔,这次“深思熟虑”了几天,然后就做了决定,去尼泊尔徒步,接下来就是订机票,办护照,办签证,找攻略,还有每周的例行环山拉练。

有趣的地方在于,当一个人的目标很明确的时候,什么问题都阻挡不了你。护照办理的时候,出入境的阿姨说最快要9.31号才能拿到,然后我就一周催一次,广州的也催,全程盯紧了整个流程,最后顺利在三周内就拿到了本来可能要一个多月才能拿到的护照,然后就是计划,出行前准备了两条线路,到最后一刻,我还是把路线图丢在家里,只带上保险单和每天晚上的露宿村落,然后就出发了。最后回来的时候,那种感觉跟直接看着LP走的人,我想,应该不太一样吧。

走完ABC,自己有两个变化,一个是坚持发呆的本事,一个是蹭饭的本事。前者可以延伸为耐力什么的,解释了11月份我啥都没练习,直接把13km的短马跑完了,大学的时候5k我都跑不动,然后开始对自己的耐力有了信心;后者延伸为出行靠朋友,计划的时候,把借助他人这点充分考虑进去,不像第一次那样做很多不必要的最坏假设(去尼泊尔之前做了相当多不必要的准备工作,但凡事总有试错,才能体会嘛),省了很多事情。

其他方面

  • 开始重视体能,开始有目的训练自己的耐力和上肢体能
  • 学摄影
  • 开始读历史(《大秦帝国》还没看完=,=)
  • 了解天文学(ABC那晚被星河震到了,回来后开始学辨认星相
  • OST听得越来越多
  • 电影电视也看了不少,所以12年业余时间也会玩玩智能家居

虽然前面都是比较好的,但是,2011有个不太好的地方是,咖啡因和尼古丁摄入量上涨飞快了。。T_T

工作上

10年底如愿以偿跳级晋升,然后果断放弃了带团队的任务,离开了搜索,从心里还是愿意继续走纯技术路线。而10年的进步太快,导致了11年相比之下基本上没有太多明显的进步。

上半年设计上的思考多,下半年实施的东西多,但是总觉得中间还差点什么,结合在一起。上半年的工作缺乏总结和反思,下半年艰难的转岗后,开始融入开发团队,开始着手前端相关的运维工具开发,从NodeJS开始渗入后端。

最大的体会其实是充分利用自己技术面广的优势做点东西,同时在做这些事情的时候,一步一步深入。玉伯今年给我的评语也是技术全面,但是深入不足。纵观整一年的技术发展,迷茫还是第一位,或者因为前半年和下半年的变化太大,前后无法衔接。

在企图解决前端框架设计不足的问题上,我们今年从前端框架+平台化工具两个方向着手解决,上半年设计前端业务框架(不包括应用代码的View层)+重构,下半年从工具本身入手,企图将可自动化的任务做平台化处理。难题在于,前半年工作交接后,没有一个合适的人选,理解这个思路的人选继续这条路,从而基本上断了源头;而下半年开发工具的过程中,各种各样的原因,自身的,外部的,导致了整个项目进展缓慢。

然而这两步工作,都只能说是试点,一个复杂团队的变更,不是一两个人简单就能用技术解决的。从公司和团队的角度,这样的结果其实没有不必然的因素。没有一个长远和全面大胆的视野看待目前前端的问题,没有一个合适的契机,大部分工作都只能说极小的推动了发展,然而还不到质变的时候。

从个人发展看,下半年的路线作为一个职业发展路线上后端方面有效的扩充,但不可过于沉迷。2012年其实很容易在技术上找到落脚点。但是在哪个方向用多少力才能获得良性地发展,还是一个值得深思的,值得不断去试错的问题。

年底迎来一个不小的变化,或许这个变化,能让自己找到另外一份安定。

最后,今年最想感谢的人有两个:

玉伯
工作上,从上半年的交易平台设计初期的指导,到spm,到后续转岗,到RMS的支持;然后玉伯在我每次出去玩的时候不让我请假,让我直接走的这个问题上,我又欠了玉伯一个大大的人情了=,=
老妈
虽然感谢老妈理论上是每天必须做的事情,但是在今年发生了这么多变化之后,还是要谢谢老妈的理解和支持,没阻拦我不断地跑出去玩,以及从未像今年那样支持我每一次选择。

再见,2011。

2012元旦武功山

第一次跟磨房的人出去,2011年最后一天晚上,18个人赶在最后五分钟上了K759次火车,浩浩荡荡地奔向江西,元旦我们去看武功山

D1:

元旦一大早到了萍乡,整个萍乡都被浓浓的大雾笼罩着,百米不见人影,我们吃完早餐,采购荤菜后,一起包车到了山脚龙山村,然后就马上开走了。

雾气很重,65+10L的包装上14斤牛肉后变得无比沉重,今天负重近20kg。

虽然气温很低,但是没走多久就开始热了。今天的任务还算轻松,走到发云界,轻装跑得快的话2个小时,我们18个人,慢慢走了4个小时。因为开走的时候已经10点了,所以中午走了两个小时,在一片小平地路餐。

煮热水,冲面条,大家很快就忙开了。

其乐融融。

下午还有两个小时路,所以半个小时我们就都清理干净起身继续走了。两点多快到山顶了,乌云和大雾也都散去,懒懒的阳光开始照射在一大片枯黄的山头上。

随着海拔上升,又开始见到这种植被了。

最后一段路,终于到了山顶。懒懒的阳光,轻轻的风加上软软的枯草,吹的人很舒服。

马上云雾又开始涌了上来,前队开始赶过去扎营地,元旦走武功山的人相当多,赶紧抢个好位置。

傍晚的时候天气又转晴,山里的天气真是变换莫测,这时候就可以看到壮观的云海。

虽然第二天基本都在云海上行走,但初见云海的还是很兴奋。武功山的云海比黄山的要壮观,因为山顶基本都是光秃秃的,不像黄山有怪石做衬托,武功山的云海更加绵长,一个山头过一个山头,云海无穷无尽。

然后今天又遇到一个50出头的阿姨,两鬓斑白。给出赞许的目光,举起大拇指便擦肩而过,以武功山的难度,这么大年纪还是要点勇气的。

傍晚遍开始起锅吃饭,天黑气温下降了,就开始就着火锅聊天,取暖。

因为买不到木材生火,8点两个童鞋主动要求回去下午的丛林里捡木材回来烧,所以就有了我们的篝火晚会,一群人一边传着酒壶,递着烟,一边乱七八糟的K歌,好像回到了大学。

D2:

昨晚扎的营地还算安静,在-14的睡袋里被热醒一次,睡得相当舒服。早上起来吃完早餐,拖拖拉拉到9点才上路。昨晚大家干掉9斤牛肉,所以我今天又少背了9斤,负重15kg左右。

人越来越多,顺走的反走的,加上坡都挺抖的,行进速度并不快。中午我们前队比后队整整快了一个半小时。

紧凑的前队。

赶路的童鞋们。

一到了旺季,连上坡都要排队。。

大家快速搞定午餐,两点多开走,今天早上天气还不错,下午雾气又开始起来,又开始下小雨,大家都在雨中匆匆赶路。

到下午5点,前队先到了金顶,最后近500阶台阶走得大家都有点绝望,等最后一个童鞋到了,已经是7点了。大家已经开始准备锅底炉头开始火锅了,今晚是鸡汤火锅,今天强度有点大,大家都狼吞虎咽的,很快就干掉了三只鸡,还有剩下的牛肉。

因为雨很大,所以今晚大家都挤在旅馆的大厅里,8个睡袋,好不壮观。

D3:

今天因为减少了牛肉,基本上保留原包重量,负重12.5kg。前两天的强度有点大,几个人准备坐缆车下山。其他人继续前往沈子村。

今天的路比昨天要更加难走,因为气温低和小雨,路上的岩石基本都结了层薄薄的冰,上午大家都走的小心翼翼的,倒也不是特别辛苦,零下天气徒步,冻得眉毛睫毛和胡须都结冰了。。

不过一路可以看到漂亮的冰花,一边滑溜溜的躲来躲去的倒是也挺好玩。早上最后一段路是泥路,雨夹雪润滑过的泥土,加上薄冰润滑过的岩石,基本上没有个落脚点。只好要么不走大路穿丛林下去,要么滑下去,用登山杖减速。后来听说后面一童鞋,摔了8次。。。

中午在半山腰的一户守山的老人家里吃饭,我们冒昧就闯进去了。打扰了老人家一个多小时,吃饭,烘干衣服,山里人就是淳朴,还一边帮我们烘干衣服,一边聊天。我们最后把剩下的没开封的食物留给了两个老人。

最后一段路就是下山到沈子村,一个小时,戏剧性的下到山下,竟然下起了雪。。

总体而言,武功山的难度算中等,冬天有雪的话稍微危险一点。现在发云界和金顶都那么多旅馆,基本上两个多小时也有小卖部啥的,安全问题不是很大,如果有雪的话还是蛮值得一来的。

装备方面,路上基本还靠C3+神衣就足够了,零度完全没问题,不热不冷。不过零下5度的夜晚还是得棉衣或者羽绒。睡袋-14对这次来讲太热了,迟点还要搞个ED300的三季袋。防潮垫下次搞个充气垫,要保证所有装备塞得进我那个22L的小包,哈哈。另外这次的热水壶很有用,一路咖啡全靠他了。。

“一个不关注薪水的员工是可怕的,因为他想要更多。”
不记得啥时候听过这句话,想想以前离职的同事觉得特别应景。

Breaking Bad

很久没这么着迷过一部电视剧了,Breaking Bad,正式入驻我最喜爱的美剧第三名~~

片子里的黑色色彩让这部带着科学因子的片子变得不那么沉闷,感情和法律之间的冲突,师生之间矛盾的激发和融合,无一不贯穿始终,Bryan还包揽了08-10年三年的艾美奖剧情类最佳男主角,Alen也在10年拿到了最佳男配角,第四季是如此精彩,看来12年的艾美奖基本也没啥悬念了,可惜的是明年就是第五季终结季了。。

高中的化学还记得点,到大学放下来四年,再到考研的时候把南开那本有机化学大部头啃完,还算有点印象,可是现在高等化学相关的实验技巧还基本为零,真心怀念以前那段叱诧风云的日子。。