Info

幽音待清景,唯是我心知

需求是个复杂的问题。我们的生活逐渐被各种各样的app包围,每天都有很多新鲜而热辣的点子出现在你的手机/平板/电脑上。搞得自己写程序的时候也总是跃跃欲试,希望能够与时代保持同步。总之一句话,说起需求总是不淡定。于是,我又开始看漫画,来寻找inner peace。

《篮球飞人》到最后的山王战时,湘北已经落后非常多了,这时候带着主角光环的樱木开始拯救球队。这个时候,他很自然地想到的是:我能做什么?他自己深信他与生俱来的天才就是灌篮。但是湘北需要的并不是他自认为最擅长的得分/灌篮能力,球队需要他做的是抢篮板(是冥想哦,“件”字科的来看)!!

如果考虑能做什么,需求多的跟苍蝇一样,但是哪些真的是客户需要的呢?估计并不没有想的那么多吧!所以说,任何脱离实际客户价值的需求都是耍流氓。对吧,小熊同学

btw,这次神奈川的高中生不知道,还是要靠白发魔的啊~~!

虽然在在《篮球飞人》中有很多leader,比如藤真,比如津深,但是完整的leadership转型故事只有两个:第一个是鱼住,而后是赤木。

鱼住始终保持着对赤木的对抗精神,也一直以战胜赤木做为目标。然而在对湘北的一战中,深处逆境的陵南让鱼住的leadership觉醒了。或许是因为这是他唯一能战胜赤木的方式,他选择了让出舞台,不再以主角的身份拼杀在战场上,而是选择了篮板、防守等方式支撑他的队友完成这场比赛。

而赤木的leadership转型则要晚得多,但境况是类似的。作为湘北的绝对核心,赤木从来没有碰到过比他更强的中锋,因此他更加深信自己的发挥能决定球队的胜败。直到湘北的最后一战中,赤木碰到了更为全能和强大的河田雅史。被困扰的赤木认为如果无法战胜河田,那么湘北就会失败。直到鱼住提醒了他。

所谓领导力,一个重点就是把让别人变得更好放在首要位置,然后一边承认有人在你曾经最擅长的领域做得比你更好,一边去做以前想做而做不到的事情。你看,连神奈川的高中生都明白这个道理呢。

 

 

上周碰到一个问题,有一个1.2G的subversion代码库,大概2600多个revision,准备在小组内切到git。但是不知道是网络问题还是代码库太大了,每次git-svn clone都不能完成,总是在200多revision的时候就停了。这个时候我有一个问题,倒底怎么才能让这个clone到10%的代码库继续下去?当然,在这个google成才的年代里,这并不是什么难题。答案是git svn fetch。让我惊奇的是,这到不是什么新命令,但是没想到还能让clone到一半的代码库进行下去。

那么我怎么才能让别人也知道这个知识呢?我可以像上面一样啰啰唆唆地写一段话,然后含羞带臊地告诉你,git svn fetch可以搞定。我也可以把它描述成problem/solution的形式:

Q: How to resume git svn clone, if it stopped?

A: git svn fetch

为什么要这么做?我表达的并不仅仅是知识,而是能力。知识和能力是有差别的,如果我努力,我可以背诵整个git-svn的manual。可是这些背下来的东西有什么用呢?不好说(万一2012的上船考试是背git手册呢?谁知道呢)。可是当我把它写成problem/solution的时候,它就具体了。不光是它是啥,还有它能干什么。有人可能会说,我看也没什么嘛。那么我们可以再看一个例子:

Q: In git, how to update the source code from the original subversion repository?

A: git svn fetch

答案一样,但是解决的问题不同,也就是我拿同一段信息,解决了两个问题。或者说,同一段信息为我提供了两个能力。学习能力真正的差别就在这里,同样是一段信息,你能用它解决多少个问题,学习能力是把知识转化为behavior change/action的能力。那些所谓一通百通的人,也不过是可以很快发现多个问题而已。

这种方法对于什么的知识有效呢?我个人觉得对于algorithm类的知识很有效。首先是可共享。比如我看完了pro git,我有可能能把这本书转化为100个这样的problem/solution。而另一个人可能看看我这些problem/solution对于git的使用就能掌握个大概了,不经意间就省了不少寿命筋力。而我也可以从别人那里得到一样的好处。你可能已经发现了吧?没错,很像cheat sheet。大多数cheat都是省力取巧的,对吧!

其次是时间利用。我现在年纪大了,记忆力倒也平常了,也开始要记笔记了和靠calendar过活了,没有ide和google写代码也慢了。把这些algorithm整理成problem/solution之后呢,我可以在ankiweb上建个deck,然后在手机上安个ankidroid flashcards。三上之时拿出来翻翻,倒也不错。

最后呢,对于祖师爷也是个交待。最近痴迷心学,跟突然狂追心学名著《海贼王》也有点关系(当然做为热爱学习的好学生,我也复习了其它名著,比如团队管理名著《篮球飞人》啊,刻意练习名著《棋魂》啊之类的)。心学大家都知道要知行合一,啥叫知行合一,把knowledge变成action就是知行合一。

时间有限信息太多,我们总是希望可以更有效地利用别人的智慧。胡凯同学最近做了一个有趣的应用来收集ThoughtWorkers读过的书,他跟我说这样可以更好地利用别人的大脑,通过自己信任的人来帮助过滤不值得一读的书。我觉得这个很好,但是并不总是work。比如之前我看到《Talent is overrated》这本书,我感觉写得一般新意不多(因为同类的书我已经看过几本),而熊桑却觉得很好,并且很敏锐地发现了其中可以借鉴的部分,有些甚至成为我们能力建设的指导方针和一些新的service offering的开端。那么如果熊桑在这件事上信任了我的判断,那么我们将错失很多。于是我想问的问题是,我到底可以相信哪些智慧?

为了回答这个问题,我需要借用一下Rodger Martin提出的知识漏斗(knowledge funnel)。它代表了我们处理知识的三个阶段:mystery, heuristic和algorithm。

mystery的知识属于未知领域,我们甚至无法了解这个问题本身是什么问题。这个领域有时候也被称作wicked problem。典型的问题比如“如何成得到最好的设计”,“怎么获得美满的婚姻”,甚至一些看上去不那么难回答的问题实际上也属于这类问题,比如“下一个客户从哪来”,“如何让业务增长十倍”。这类问题中的每个问题都是独特的问题,就好像并不是所有企业都能做大十倍,而且做大过程中的方式也不一定相同。再比如对于某个项目有效的手法,对别的项目不一定适用,都是一个道理,有时候又被称作艺术。

algorithm的知识属于完全已知领域,在这个领域没有复杂问题,所有的复杂性都被解决掉了。无论在何种环境下,按照algorithm来作,总能得到相同或者是可预期的结果。这类知识往往是像“如何列出磁盘目录”,“房贷手续是怎样的”,“在东直门哪里能买到钱包”之类的问题。对于这类知识,你知道就是知道,不知道就是不知道。较容易通过训练获得,其终极形式就是写成程序,完全不用人来作。

从mystery转向algorithm就需要heuristic,我如何把一个未知的问题转化为已知的algorithm,或者我是能够去除掉mystery中的复杂性用简单的algorithm来解决它。或是说,你能否把艺术工程化或者流程化,去掉其中的文艺范。推理,发散,类比等等的都是这个阶段需要的技能。大凡革命性的飞跃,都源自把mystery变成algorithm(目前看来design thinking是个不错的heuristic方法)。

那么在之前的情况下,到底熊桑做了什么呢?我觉得是他做了个很好的heuristic。这里的mystery是如何保持professional service firm的竞争力。那么对于professional service firm,什么是真正的differentiator ——人的能力;那么对于人的能力的管理,或者说有多快地可以提供人的能力,可能会是psf的核心竞争力。那么我们怎么才可以提高人的能力?那本书给了个algorithm,对于能力的培养可以通过刻意练习实现。于是一个mystery某种程度上说被揭开了:原来可以通过刻意练习,保持professional service firm的竞争力。我想他当时一定是激动地发抖吧,如果是我一定会的。

这里我们看到了heuristic的强大,产生的尽是些观念上的变革。但是它却是难以直接利用的,原因很简单啊,因为它自己就是个mystery。如果有一天我们把它也algorithm化了,我估计人类就走到尽头了吧,matrix就该成真的了,想想就可怕啊,所以我还是老老实实地想想怎么利用别人总结的algorithm好了。

 

很多人以为演奏古典吉他是我的业余爱好——没错它的确是我最大的爱好——但确忽略了这样一个事实,我的整个学琴生涯接受的都是专业训练。对于圈外人而言,陈派教学法不过是一种教学方法的名字而已,而对于圈内人这个方法意味着高效、准确和成功。我接受陈派教学法训练三年——我当年的老师现在是某专业院校的教师,他可以在六个月内教出全国冠军——除了技巧上的提高,对于练习本身我也有了更深的体会。这也是为什么当我第一次读到《哪里来天才》时,我对这本书根本不屑一顾。因为它里面所讲的一切,对于器乐练习者而言,都是基础得不能再基础的知识。而里面我不能接受的是,所谓刻意练习应该严格可重复。

我仍然以器乐练习为例,因为这个我感触最深。器乐演奏训练一般都分为两部分,音乐性训练(视唱练耳,读谱,作品分析,和声结构等等的)和肌肉控制训练(演奏,已经跟演奏有关的肌肉训练)。这种划分其实很有意思,一种是严格的可重复训练,很多时候需要使用节拍器在不同的速度下反复练习,比如我这周的练习册上写着:悲歌第2段,速度126,138,152,160,176,注意a指音色。意味着我需要在5个速度下演奏同一个段落,最慢的速度每分钟演奏126个音,而最快的要演奏176个。另一种则是启发性的训练(heuristic practice, 我发明的名词),这类训练的特点是单纯的重复并不会让你变得更好,比如音色练习,我把一个音弹100遍并不会让我把它弹得更好听,而很可能是我把错误重复了100遍而已。乌拉圭吉他教育家卡雷巴洛把这个问题总结为:练琴不光是练手指肌肉,而是在锻炼大脑的意识。最近一次看到这句话时让我深受震撼,没错,这正是脑力工作练习的秘密——我们需要启发式训练而不仅仅是重复性的。也许重复性练习对于肌肉训练必不可少,但是对于头脑而言,单纯的重复不一定会让它变得更好。

对于启发式练习而言,每一次练习都是新的挑战,之前的练习可能会有帮助,但是那不是决定性的。我举个例子。很多ThoughtWorker都觉得我主持的对象训练营对他们很有帮助,很多人认为每一期对象训练营都是相同的,但却不然,因为每一期参加的受众是不同的,也就是说,我在每一期的时候都会碰到不同的观点和想法。而我所需要做的事情,就是把我对于面向对象的认识,TDD和重构的技法与这些人沟通,每次都使用同样的讨论并不会让我成功,我需要理解每个听众的背景和关注点,然后用他们可以接受的方式把我的观点告诉他们,必要的时候可以耍一些手段。我不能保证我每次都成功,我也不能保证我每次都能比上一次做得好。面向对象训练营是我的一种启发式练习方法。通过它,我会根据不同的观点重新梳理我对于面向对象的认识以及TDD和重构的技法(参加过的同学应该明白我为什么每次都用你们的代码而不是预先写好代码了吧!没错这也是我练习的一部分),这也是为什么我如此痴迷于它的原因。

重复对于启发式训练也是有用的,但我们需要知道每次重复的时候,我们其实面对的是不同的问题。比如design pattern,假如我教了你如何使用visitor,而下次你练习使用它的时候,代码环境要解决的问题可能都不一样,如果我们只追求严格可重复,我恐怕你永远都学不会visitor了。那么则么才可以有效地设计启发式训练呢?我有这样几个方法:识别模式,启发式重复和强制联想。不过今天恐怕没时间写下来了。

 

本文发表在InfoQ中国站,http://www.infoq.com/cn/articles/xh-four-color-modeling,是2011年ThoughtWorks文集中的一篇。

领域建模有很多种方法,对于同样的问题域使用不同的建模手段得到的模型可能也不尽相同。于是我经常听到这样一个问题:怎么才能保证建模的正确性?

这听起来是个合理的质疑,但实际上却不是那么有道理。首先我们需要明白建模的目的是什么?如果仅仅是为了描画问题,那么并没有什么对错之分——仅仅是立场和角度的差别;而如果是为了企业业务系统而进行建模,那么这个问题应该变为:如何保证模型能够支撑企业的运营?

我想用下面这个例子来简要的回答一下这个问题。

在开始分析和建模之前,我们需要知道企业业务系统的目的是什么;而企业业务系统的目的往往跟决策者或者管理的诉求相关。我们现在需要移情到一位管理者身上,看看他的诉求到底是什么。

现在假想你是一家在线电子书店的COO。突然有一天,有一位顾客向你投诉,说他订购的书少了一本,并且价钱算错了,他多给了钱。在你承诺理赔之前,你需要核对一下这位顾客说的是否属实。那么这个时候你需要知道什么样的信息才能做出准确的判断呢?

简单来说,你需要知道这位顾客订购了那些书籍,付了多少钱以及书店到底为这个顾客递送了那些书籍。不幸的是,由于科技不够发达,你无法直接驾驶时间机器回到从前去亲眼看看发生了那些事。但幸运的是,你并不需要这么做,你只需要看看这位顾客的订单,和网银的支付记录以及你们书店交给EMS的快递单存根,就应该知道这些信息了。

你找到了订单和EMS快递存根。发现这位顾客是在三天前订购的书,而你们在前天就已经将书邮寄出去了。并在订单上看到这位顾客一共订购了7本书,但是在EMS的快递存根上,并没有任何书籍的信息,只有地址,包裹号,邮费和重量什么的信息。这时候你觉得应该去询问一下配送部门,看看他们做了什么。

在配送部门你根据包裹号查到了那个包裹的信息,果然里面只有6本书。同时你在包裹部门发现了一张延期交货单。上面说明由于缺货,这位顾客另外一本书正在等待发货。

那么剩下的问题就是支付问题了,从网银的记录上看,客户不含邮费一共支付了132.5。订单上显示的价钱也是132.5,显然这位顾客并没有多付钱。

为了保证准确,你重新从网站上选了这7本书,想看看是否也会是这个价钱。但你却意外的发现,一共只需要128.3。仔细辨认后,你发现有一本图书现在是促销。那么现在的问题是,促销到底是什么时候开始的?

你到了市场部,市场部给了你一份近期促销计划。你发现那本书是昨天才开始促销的,也就是说在那位顾客在下订单的时候,促销还没有开始。

这个时候,你觉得应该给你的顾客打一个电话致歉,商讨如何后续邮寄的问题,并向他说明促销的事情。

你是否觉得这个COO当得有点累呢?这当然是虚构的。但是从这故事里面我们看到什么呢?

任何的业务事件都会以某种数据的形式留下足迹。我们对于事件的追溯可以通过对数据的追溯来完成。正如上面这个故事里,你无法回到从前去看看到底发生了什么,但是却可以在单据的基础上,一定程度的还原当时事情发生的场景。当我们把这些数据的足迹按照时间顺序排列起来,我们几乎可以清晰的推测出这个在过往的一段时间内到底发生了那些事情。

那么为什么这些数据形成的链条能够成帮助我们追溯业务的营运呢?

因为这些数据并不是随便挑选的。如果我们回顾一下你作为COO检查这个疏漏的过程,你首先选择了订单和EMS快递存根,换句话说,如果订单出现差错,或者EMS快递存根上说明你的确邮寄了7本书,那么这个疏漏的责任并不在你。所以这两个订单实际上这个你这个企业法律责任的起点和终点。

当你确定这个疏漏的责任在你之后,你选择审查一些流程执行的结果,比如包裹存根。从而验证一些主要的业务流程执行的结果是否正确。换句话讲,这些数据是支撑你运营体系的关键流程的执行结果

正是由于这些数据是流程执行的结果,它们才使我们可以在不了解流程细节的前提下,对某些突发事件进行追述和分析。

除了上面那个极端的例子(投诉),对于任何一笔正常的经济往来,我们都需要知道:

  1. 如果我付出一笔资金,那么我的权益是什么?
  2. 如果我收到一笔资金,那么我的义务是什么?

而这些问题都需要业务系统捕捉到相应的足迹才能够回答。所以企业的业务系统主要的目的之一,就是记录这些足迹,并将这些足迹形成一条有效的追溯链。

而作为业务分析师的你,则应该知道那些事件在运营上是需要追溯的,这些事件都留下了什么足迹。

这些足迹通常都具有一个有意思的特性,即它们都是时标性对象(moment-interval)。发现这些时标性对象就是建模的起点。对于这些时标性对象稍加整理,我们就得到了整个领域模型的骨干:

在得到骨干之后,我们需要丰富这个模型,使它可以更好的描述业务概念。这时候,我们需要补充一些实体对象。通常实体对象有三类:人,地点, 物(party/place/thing)。

在这个基础上,我们可以进一步抽象这些实体事如果参与到各种不同的流程中去的,这时候,我们就需要用到角色(role):

最后再把一些需要描述的信息放入描述对象(description)。

我们就得了应用四色建模方法(color modeling)建立的一套领域模型。

简要回顾一下上面的过程,不难发现我们建模的次序和重点:

  1. 首先以满足管理和运营的需要为前提,寻找需要追溯的事件。
  2. 根据这些需要追溯,寻找足迹以及相应的时标性对象。
  3. 寻找时标对象周围的人/事/物
  4. 从中抽象角色
  5. 把一些信息用描述对象补足。

由于在第一步中,我们就将管理和运营目标做为建模的出发点。因此,整套模型实际上是围绕这些“如何有效地追踪这些目标”而建立的,这样的模型可以保证模型支撑企业的运营。

附言

几位同事帮我审校这篇文章的时候,有人问了一个很有意思的问题:为什么你会以一个看上去像极端情况的例子来说明这个建模方法? 以我的经验来看,对于业务系统有两个东西是很重要的:可追溯性(traceability)和执行效率(efficiency)。这里的可追溯性是指责任的可追溯性(traceability of liability),而通常都是在一些不太好的事情发生之后,才需要对责任进行追溯。所以想一个相对负面的例子更容易帮助我们找到建模所需要解决的问题。

另外还有位同事说,你的四色方法与Peter Coad的四色法并不完全相同。是的,我所介绍的并不是Peter Coad的四色法, 我不敢说是发展, 仅仅是对于Peter Coad四色的一种变化吧。

曾有人总结,无论你在加入ThoughtWorks之前有什么爱好,加入ThoughtWorks之后,自动化测试就成大家共同的爱好。比如亮亮同学的EFT,比如蛋蛋同学的Test Load balancer,比如李晓同学的fireworks,甚至陶文同学的ViPrototype。凡事总会有原因,没有无缘无故的爱也没有无缘无故的恨。那么为啥ThoughtWorks永远会上演everyone loves test automation涅?就我个人感觉原因有二。

先说个靠谱的,自动化测试是关乎迭代交付节奏关键实践。大家都知道,在敏捷开发方法中,每个迭代实际上是交付迭代,而不仅仅是开发迭代。作为交付迭代,力保每个迭代的产出物是deliverable的是至关重要的。而作为deliverable,仅仅开发完成是不够,仅仅测试完本迭代的功能也是不够的。还需要对以前的功能进行回归,以保证在原有功能不被破坏的前提下,有了新的进步。通常团队的开发速率(velocity)是保持在一个比较稳定的数值的,那么也就意味着,几乎每过一个迭代,QA的工作量都会增加很大一块。

增加人肯定是不现实的,沟通成本在那里摆着。只测本迭代需求,但要怎么保证每个迭代之后都是可交付的呢?如果不是可交付的,还是不是迭代交付?仅仅做迭代开发的话,能省的实践就不只这么一个了,很多实践都可以不做了。真正可行的方案其实只有自动化测试一条,尽量以廉价的机器来代替人的时间,通过自动化测试跟上迭代交付节奏。这就不难看出为啥ThoughtWorkers都爱自动化测试了。再说一句额外,很多ThoughtWorkers还爱cucumber,非常追求business readable的测试,这也是有原因的。且按下不表。

再说个不靠谱的,话说貌似很少有人知道所谓“瀑布模型”其实是一种反模式。当年Winston Royce在1970年讨论软件工程的时候,说瀑布模型是一种反模型。他老人家讲,如果想使得这个模型工作,其中有一个重要的先决条件。就是,每一个stage的结果都是下一个stage的输入,同时,下一个stage只能给上一个stage反馈。不能出现跨级反馈。

而测试这个环节,恰好就是跨级反馈的坏分子,完全无视组织纪律嘛。一会儿说开发有问题,一会儿说设计有问题,一会儿又说分析有问题。当年Royce老爷子也给了两个答案:一是严格限制测试的能力范围,只测开发的问题,不要反馈其他环节,换句话说,就是测实现不测方案。只测实现的好不好,而不管实现是不是有用,是不是解决了问题;二是所谓的Do it twice,从测试环节收集下来反馈,再开一个瀑布过程解决之。

于是,如何对待测试,成了整个软件工程界的分水岭。从70s以降,业界似乎都遵循了Royce老爷子的第一建议,玩命限制测试的职责范围。而直到敏捷初兴,大家才真正的把所谓的“Do it twice”又发展了出来,玩命发展测试的职责。从单独一个团队,合并成一整个team;从后验式变成前驱式;从手动测试变成自动测试;从只要dev来自动化测试到引入客户一起写。作为敏捷实践先驱的ThoughtWorkers们,自然对于测试也是格外上心,花样频出了。

再说个八卦,老爷子他儿子,在敏捷兴盛之后,说,我爹其实应该是敏捷之父,因为他老人家的论文包含了朴素的迭代思想。我想说,老爷子,您要是坚持了第二条,DSDM就没戏了,Rosenberg就喝凉水去了,可惜您给的那个建议,您自己字里行间也认为是下策,这能怨得了谁呢?

Currently I spent my spare time on OSGifying a WebSocket based continuous integration system. The project was created by a small group of my sponsees, and the aim of it is not replacing current leading products, such as Go, but assessing some interesting emerging technologies which appears on ThoughtWorks Technical Radar. And this project uses Scala, Neo4J, JAX-RS, Non-Blocking I/O server side and HTML5.

We had a working prototype 3 months ago. And the performance was pretty awesome.The server can easily support 500 agents with default JVM heap. And scale up to couple of thousands agents within 2G memory allocation.

In the way of OSGifying the WebSocket part, we found out the current implementation of Grizzly OSGi HttpService only supports Comet but not WebSockets. Well, to be fair, the code has not been touched since late 2009, and not until 1.9.19-beta2, Grizzly itself didn’t support WebSockets neither. So in a nut shell, we have to patch the code by ourselves.

The essence part of the patch is pretty straightforward, the only thing we need to do is add a WebSocketAsyncFilter to the AsyncHandler of GrizzlyWebServer instance:

SelectorThread st = ws.getSelectorThread();
st.setEnableAsyncExecution(true);
AsyncHandler asyncHandler = new DefaultAsyncHandler();
asyncHandler.addAsyncFilter(new WebSocketAsyncFilter());
st.setAsyncHandler(asyncHandler);

One more thing left is make it play nicely with the previous Comet support. After reading the code, I personally don’t think it make much sense to support both Comet and WebSocket at same time(unless for the legacy), so I decided to only support either Comet or WebSocket. Here are how the activator looks like

 private static final String COM_SUN_GRIZZLY_ASYNC_SUPPORT = "com.sun.grizzly.async.support";

    private enum AsyncSupport {
        COMET {
            @Override
            public AsyncFilter createFilter() {
                return new CometAsyncFilter();
            }
        },
        WEBSOCKETS {
            @Override
            public AsyncFilter createFilter() {
                return new WebSocketAsyncFilter();
            }
        };

        public abstract AsyncFilter createFilter();
    }

    public void start(final BundleContext bundleContext) throws Exception {
        logTracker = new ServiceTracker(bundleContext, LogService.class.getName(), null);
        logTracker.open();
        logger = new Logger(logTracker);
        logger.info("Starting Grizzly OSGi HttpService");

        int port = readProperty(bundleContext, ORG_OSGI_SERVICE_HTTP_PORT, 80);
        if (bundleContext.getProperty(ORG_OSGI_SERVICE_HTTPS_PORT) != null) {
            logger.warn("HTTPS not supported yet.");
        }
        startGrizzly(port, readAsyncSupport(bundleContext));
        serviceFactory = new HttpServiceFactory(ws, logger, bundleContext.getBundle());
        registration = bundleContext.registerService(
                HttpService.class.getName(), serviceFactory,
                new Properties());
    }

    private AsyncSupport readAsyncSupport(BundleContext bundleContext) {
        try {
            boolean cometEnabled = readProperty(bundleContext, COM_SUN_GRIZZLY_COMET_SUPPORT, false);
            String asyncSupport = readProperty(bundleContext, COM_SUN_GRIZZLY_ASYNC_SUPPORT, cometEnabled ? "COMET" : null);
            return (asyncSupport != null) ? AsyncSupport.valueOf(asyncSupport.toUpperCase()) : null;
        } catch (IllegalArgumentException e) {
            return null;
        }
    }

    private void startGrizzly(int port, AsyncSupport asyncSupport) throws IOException {
        ws = new GrizzlyWebServer(port);
        if (asyncSupport != null) {
            logger.info(new StringBuilder().append("Enabling ").append(asyncSupport.toString()).append(".").toString());
            SelectorThread st = ws.getSelectorThread();
            st.setEnableAsyncExecution(true);
            AsyncHandler asyncHandler = new DefaultAsyncHandler();
            asyncHandler.addAsyncFilter(asyncSupport.createFilter());
            st.setAsyncHandler(asyncHandler);
        }
        ws.setMaxThreads(5);
        ws.useAsynchronousWrite(true);
        ws.start();
    }

And the full patch could be found here.

阿瓜多的《华丽的回旋曲》是我最喜欢的古典吉他独奏曲之一。我曾立志,30岁前人生目标有三:《华丽的回旋曲》,一把Stenzel吉他和北京饭店的黃闷鱼翅。Stenzel我已经有了,黃闷鱼翅虽然不能说是piece of cake,但若真想倒也不难。唯有《华丽的回旋》,仅有几成把握。眼下离30还有不到3个月,也不得不加紧练习。目前最大的难点在第二插部的三连音段落,一大串快速换把和横按,实在是力有不殆。当然作为陈志老师再传弟子,我也深谙练习要诀:放慢,做好准备,放松,难点分解; 先把技术基础打牢,而后是表达,最后是速度。但是纵然如此,进来也是原地踏步,鲜有进益。

今天早上我练琴的时候,练到这个段落,又是无比难受。于是我把琴放一边(小心地),准备喘口气再跟他死磕的时候,我有看了看我正在彈的这个段落

突然一个从来没有想过的问题出现在我脑海里:在这个段落里我到底要彈什么?从技术上说,我要彈一组三连音,两个横按10把一个8把一个,还有一个跳把。而音乐上呢?我要表达什么?分解和旋?音阶?从始至终都要一个力度吗?我拿出笔随手标上了力度

我发现我并不需要全部都按一个力度演奏。为了表达动态的变化,我必须有意识控制我的力度,那么我的手的压力也会随着力度的变化而变化。于是我应该只在2,3个音的地方需要很紧张的张力,而其他时候并不需要产生那么多的压力。我拿起琴(同样小心地),第一次,第一次,我可以轻巧而流畅地演奏出这个段落(随后,在2档更高速度下,也同样的流畅)。

这件事情教会我:

1. 我有极大的可能会overestimate doing the right thing的成本
2. 当目标分解之后,有时候可能会迷失掉本来的目的
3. Nick Drew告诉我的:不要过早优化。right thing first,然后是optimization
4. 有的时候看上去是捷径的道路,根本就是歧途
5. 放松可以是随着音乐进行放松,不一定是纯技巧上的
6. 我在力度张力下,放松做的不好,后面得单练
7. 回旋有戏了,得考虑40岁人生目标了

最近在把一个Scala项目中的一些集成测试改成基于mock的单元测试,测试用的是scalatest,mock用的mockito,构建脚本是Buildr。根据builldr的文档,在项目中引入Mockito是很容易的一件事,只需要修改compile.with就可以了:

MOCKITO = 'org.mockito:mockito-all:jar:1.8.5'
#-- others omitted --
test.using :scalatest
test.with MOCKITO

这样使用mockito的test case就可以正常的被编译了,但是在执行测试的时候,我得到一个很古怪的异常

*** RUN ABORTED ***
  A needed class was not found. This could be due to an error in your runpath. Missing class: org/mockito/Mockito
  java.lang.NoClassDefFoundError: org/mockito/Mockito

检查test.depenencies,mockito的jar分明就在里面,但是似乎找不到。没办法,只好打开buildr的源代码,发现buildr里,scalatest有两个path

  ant.taskdef :name=>'scalatest', :classname=>'org.scalatest.tools.ScalaTestTask',
                     :classpath=>taskdef.join(File::PATH_SEPARATOR)
  ant.scalatest :runpath=>dependencies.join(File::PATH_SEPARATOR) do

而taskdef则被定义为

  taskdef = Buildr.artifacts(self.class.dependencies).each(&:invoke).map(&:to_s)

  in ScalaTest
  def dependencies
     ["org.scalatest:scalatest:jar:#{version}"] + Check.dependencies + JMock.dependencies + JUnit.dependencies
  end

这个貌似是个bug,Mockito作为scalatest支持的mock framework,并没有被加入scalatest的dependency。修改这个bug,有两个方式,一个是直接修改gem里的这个文件,但是需要在每一台机器上都做响应的修改才能保证任何人都能正确的执行构建脚本。另一种方式就是buildr的buildfile里利用ruby的动态特性,进行patch:

MOCKITO = 'org.mockito:mockito-all:jar:1.8.5'

class Buildr::ScalaTest
  class << self
    alias dependencies_old dependencies

    def dependencies
      dependencies_old + [MOCKITO]
    end
  end
end

总之,这是一个展示ruby优雅的丑陋bug。