-
2007-07-22
你的心情还好吗?
周末,跨越了大半个北京城和几个老朋友聚到了一起,由于有一段时间没有见到,自然一个个都变成了话痨。一个朋友的精神状态对比于之前见到的他来说,明显好了许多,生活显然也丰富多彩了一些。聊到原因,他从之前一个很糟糕的情况逐渐摆脱了出来,心情好了,自然而然人的状态也随之好转了。当然,朋友们也看到了我类似的变化。加入到ThoughtWorks后,我整个人的状态变得明显不同了,更活泼了。按照一个朋友的说法,明显是被释放的结果。
曾经和老妈聊天,我说找工作的标准是“钱多和心情好” 至少有一样。其实,虽然我也知道金钱的重要性,但我一直没有树立了一个良好的追逐金钱的观念,所以,我真正看重的是心情。工作最初的部门给我留下了一些很美好的回忆,其中一个重要的原因就是那里给了我一个良好的心情,周围的环境让我感到很舒服,所以,在那里我的表现也得到了大家的认可。虽然后来我离开了那里,但原因也与环境无关。反而,每次我回到沈阳,都会回到那个部门与一些老朋友叙旧。在我正式离职之前,我还曾经到那个部门与大家分享了一些Ruby的东西。
之后工作的那个部门,虽然我从中也学到了不少的东西,但自己整体评价我那两年的表现,很糟糕。自我分析的结果是,我一直没有找一个让自己心情愉快的理由。虽然大家也经常在一起玩,但那个部门的整体氛围一直不是我很喜欢的,人和人之间表现得也不是那么友善。正所谓祸不单行,有一段时间,我觉得自己很“背”,把该倒的霉在那一段时间都倒了。所以,在这种氛围中工作了一段时间之后,我感觉很压抑。我知道自己肯定会离开,只是不知道什么时候,下一个落脚点会在哪里,直到我找到了ThoughtWorks。
一个朋友看到我写的那篇《一月思想工作者》给出的评价是如鱼得水。经过长时间的压抑之后,我感觉自己一下子解脱了。我很快就融入了这样的环境中,以至于一些新来的同事误以为我已经在这里工作了很长的时间。ThoughtWorks给了我一个我喜欢的环境。我不会为了那些没有意义的东西,消磨自己锐气。
在ThoughtWorks的招聘流程中,有一条原则,如果应聘者让你感觉不舒服,你就完全有理由拒绝这个应聘者。这是一条看似不合理的原则,但对于维护一个良好的企业文化却至关重要。因为招进来的人是要和我们一起工作的。如果这个人在应聘过程中让人不舒服,那么在日后的工作过程中,他可能也同样会表现得让不舒服,让人不舒服的结果就会是影响工作效率,精力被浪费在一些无谓的地方。事实上,正是有这样一条原则,所以,几乎与所有ThoughtWorker在一起都会让人感觉很舒服。不管这个人来自哪里,即便是刚刚认识,也会很快就会熟悉起来。
你的工作环境给了你怎样的心情呢?
-
2007-07-15
在Agile Day讲Ruby on JVM
去年Agile Day的时候,我还在沈阳,没有近距离感受ThoughtWorks带来的敏捷盛宴。今年的Agile Day,我已经成为了一名ThoughtWorker。我不仅仅有机会到了现场,更是成为了一个演讲者。演讲的题目自然是我现在的工作重点:Ruby on JVM。公司对Ruby的关注从这次Agile Day的日程安排中可见一斑,在技术/工具分会场的四场演讲中,就有两场与Ruby相关:第一场我现在的老板Chad讲企业级Ruby和我的Ruby on JVM。
虽然讲的是自己熟悉的东西,但准备这次演讲的材料还是花了很大的力气。面对的观众不同,讲的内容就应该有所差别。从众多相关的内容选择出让观众有兴趣的内容,而且深浅适度,这是个问题。所以,每次有讲演的机会,我都对仔细考虑讲的内容。好在在ThoughtWorks总是可以找到人一起讨论,一旦确定了演讲的基调,就拥有了骨架,剩下的问题就是血肉丰满起来。在准备这次演讲的内容过程中,yawl和Ola Bini都给了我不少建议。
这次Agile Day的活动,公司允许任何人在中国的ThoughtWorker参加。本来周六的活动,人在西安的ThoughtWorker周五晚上出发。为了更好的准备这次的演讲,我提前一天到北京。对于自己所讲的内容,因为经过了自己的思考,我觉得还好,我最主要的担心就是怕自己会紧张。在北京办公室准备的过程中,我才发现,原来大家都一样,每个演讲者都是怕自己会紧张,所以,为了能在大会时有个良好的表现,大家都在一遍遍回顾着自己的所讲的内容,偶尔还要拉几个观众实战一下。我就给人当了演习观众。当然,我也会拉着别人来当观众。第一次讲的时候,我能清楚的感觉到自己在开始的时候,声音有一些微微的颤抖,语言的表达上有些凌乱,好在这是练习。Mingle产品经理Adam看我的讲稿之后,第一反应就是问我Mingle是否可以运行在XRuby上,这也增强了我对讲稿的信心,不过,我的答案还是令人遗憾的“暂时不行“。
Agile Day当天,ThoughtWorker们都会充当杂役,所以,早早就去现场干活。稍微得到一些清闲的时候,脑子里就一次次回顾着要讲的内容。确实这次大会人来了好多,要在这么多人面前讲东西,无论事先我做了多少准备,我还是会觉得紧张,尽管表面上还和同事们开着玩笑。其实,原本我希望在Agile Day多听几场演讲,但事实上,在别人演讲的时候,我都在休息室准备自己的演讲,根本没有心情去听。Chad结束演讲之后跟我说,我还在演讲中提到你了,我只能很抱歉的说,我没有听到。真正到自己站在台上开始演讲,我发现自己的声音没有了练习时的颤抖,这也给了自己不少信心。随着演讲的进行,我也就自己渐渐进入了状态,也就不太顾忌那些无关的东西了。至于表现得到底如何,我不知道,别人评价去吧!gigix之后就告诉我,我经常出现背向观众的情况。之后,精神一下子松懈下来,感觉好累,在外面的沙发上,躺了好长时间,所以,我后面一场的演讲也没怎么听。对我而言,当我是演讲者时,其实往往意味着失去一次很好的学习机会。
虽然大家都愿意去听各种各样的演讲,但实际上,一个演讲真正能让人记住的只有非常有限的内容,所以,我所希望的只是通过这次演讲,让更多的人知道我们在Ruby on JVM上的努力是有价值的。如果能够吸引更多的人加入到这个过程中来,那就是太完美了。 -
2007-07-08
生成方法的Wrapper
在《管窥Ruby——方法定义》中,我们曾经讨论过在Java中实现方法定义时,因为Java语言的限制,我们不得不为方法加上了一个Wrapper,让它可以满足接口定义的需要。事实上,XRuby一直就是这么做的,通常Wrapper放在com.xruby.runtime.builtin这个包中,而诸如Array、Hash、String之类的具体实现放在com.xruby.runtime.value这个包中。初涉XRuby的人,常常会被这两个包搞得晕头转向。到底方法的具体实现应该放在哪里,大多是根据感觉来定义的。
在那篇blog中,我还讨论了JRuby的实现,不过,那里面的讨论并不是特别的完善。事实上,除了利用reflection,其中还有一种方式,通过代码生成动态产生这个Wrapper。我将这个实现借鉴到了XRuby中。通过采用这种生成Wrapper的方式,我们就可以在实际编写的代码中,忽略掉Wrapper部分的实现,而将原来令人混淆的builtin和value包统一起来。
下面是Array的clear方法原来的实现:
class Array_clear extends RubyNoArgMethod {
protected RubyValue run(RubyValue receiver, RubyBlock block) {
RubyArray value = (RubyArray) receiver;
return value.clear();
}
}
在这里,其实只要将receiver转型,然后调用它的方法就好。实际上,生成的代码只是做这样简单的工作就好,当然,根据具体的方法还会略有些差别。关于如何使用ASM进行代码生成,我在几篇blog中都进行了介绍,这里就不介绍生成代码的实现了。下面是在代码中如何使用这个方法。
MethodFactory factory = MethodFactory.createMethodFactory(RubyArray.class);
c.defineMethod("clear", factory.getMethod("clear", 0));
在这个方法的实现过程中,还有一些比较有趣的点。首先,并不是每次都需要调用getMethod都要动态生成一个类,因为一个类一旦已经加载,就没有必要再次进行加载,即便强行加载,class loader也是会抱怨的。所以,在创建类之前,我们需要尝试加载一下这个,如果加载成功,便省去了再次生成的麻烦。再有,每次都去创建这个Wapper实际上也没有什么必要,一次生成之后,保存起来就可以了。如果我们把它放在class path中,那么我们尝试加载就会成功,所以,刚才提到的尝试加载还有这样一层含义。
作为builtin实现,我们还是希望这些生成类是可以放在我们最终发布的jar文件中,所以,我们通常的做法,是在打包之前,先用XRuby做一次最简单的执行,让所有的Wrapper生成出来,这样,打包的时候就可以将它们都加入其中。
具体的做法可以参考XRuby中MethodFactory和build.xml。 -
2007-07-04
消除浪费的敏捷
敏捷的核心:消除浪费,走向精益
谈到敏捷,你会想起什么呢?测试驱动开发?结对编程?我刚到ThoughtWorks不久,gigix就和我提到过,关于敏捷消除浪费的观点,说实话,我一直不太理解这个观点。不过,随着在ThoughtWorks体会的点滴逐渐增多,我慢慢的体会到这句话的含义。
举个简单的例子,为什么要做测试驱动开发呢?从字面上看,将“测试”放在前面,提高了测试的重要性,告诉人们应该注意这一重要但经常被忽略的事实,这一点对于保证软件质量来说,确实是应该的,再有单元测试的存在让自动化回归测试变得容易了许多,还有写测试的价值会让人从不同的角度考虑设计,也许会让设计的耦合性大大降低。人们通常想不通的一点是,为什么要先写测试。如果只是单纯的交换编码和测试的位置,实际上并不会带来太多的好处。那该从怎样的角度理解测试驱动开发呢?测试代表着什么呢?需求。软件开发的目的就是为了满足需求,如果能够把需求确定了,编写代码满足它就相对容易多了。从需求出发,按照需求编写满足需求的最简单代码,而不去做许多额外的工作,这就是测试驱动开发不同于传统编写方式的地方。按照传统的方式先写代码,后写测试的话,我们的测试代码需要做的工作尽可能覆盖代码的路径,而为了覆盖面尽可能大,我们就需要花费许多额外的努力,这就是浪费。
敏捷的思想不仅仅体现在测试上,在这两个月左右的时间里,我对设计的理解也开始发生了一些转变。曾经我对设计的理解是设计一个能够应对未来变化的美好结构,但事实上,这种竭尽全力设计出来的“好”设计总是轻而易举的被现实问题打败。为什么呢?因为这个设计是想出来的,并不是基于实际的需求逐步演化出来的。在ThoughtWorks的招聘流程中,会有一个编写程序的过程。在这个过程中,我们总会听到应聘者说他的设计是为了应对将来的变化。但事实上,这样的变化是否存在呢?不一定,多半是没有的。所以,许多看起来比较华丽的设计完全经不起推敲。其中的一个例子是,为了面向接口编程,设计一个接口,却只有一个对应的实现类,本应枝繁叶茂的继承树显得十分单薄。当然,这里并不是说面向接口编程就不好,但当只有一种情况时,就做这种抽象显然是一种过度设计,过度设计就意味着浪费,在这个例子里面,至少需要多付出编写一个接口的代价。也许你会说,未来如果真的出现第二种或者更多的情况,那该怎么办。很简单,等真正出现了再做也来得及。
当然,问题并不绝对。一个例子是在数据库的设计通常我们会提前做出。基于前面的讨论,也许有人会认为这么做不太敏捷,恰恰相反,因为随着项目的进行,数据库修改的成本会越来越大,所以,我们宁肯在项目的前期多做些功课,以保证后面尽可能不去或少去修改它,以便减少浪费,所以,这种选择也是敏捷指导下的选择。
也许,这种讨论会给人一种人嘴两张皮的印象,其实,这和做许多事一样,就是一个度的把握。所以,很多人都看了一些敏捷的书,但用到实际中,却总觉得不是那么回事。如果开头的那篇文章是写给各种各样的管理者的,那我这篇算是写给程序员的吧!
-
2007-06-30
Javac背后的故事——创建对象
要让Java这个面向“对象”的世界正常运作,创建对象就是一项不可或缺的操作。
public class NewMain {
public static void main(String[] args) {
new Object();
}
}
用javap反编译上面的代码,我们可以得到下面的指令,这里省去了javac暗中创建的构造函数。
public class NewMain extends java.lang.Object{
...
public static void main(java.lang.String[]);
Code:
0: new #3; //class java/lang/Object
3: invokespecial #8; //Method java/lang/Object."<init>":()V
6: return
}
从这段代码中,我们可以清晰的看出创建对象(new)和调用构造函数(invokespecial)两个过程。关于这个问题,我在《对象的生命》中曾经进行过讨论。
既然javac将一个new的动作被解释为两条指令,那在JVM的层面上,我们当然就可以将它们分开。下面是一段没什么实际用途的代码,只是证明这个观点可行性。
public class NewGenerator {
public static void main(String[] args) throws Exception {
String className = "New";
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(Opcodes.V1_2, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", null);
Method m = Method.getMethod("void main (String[])");
GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, m, null, null, cw);
mg.newInstance(Type.getType(Object.class));
Label label = mg.newLabel();
mg.ifNonNull(label);
mg.mark(label);
mg.getStatic(Type.getType(System.class), "out", Type.getType(PrintStream.class));
mg.push("new object is not null");
mg.invokeVirtual(Type.getType(PrintStream.class), Method.getMethod("void println(java.lang.String)"));
mg.pop();
mg.returnValue();
mg.endMethod();
cw.visitEnd();
OutputStream os = null;
try {
os = new FileOutputStream(className + ".class");
os.write(cw.toByteArray());
} finally {
if (os != null) {
os.close();
}
}
}
}
这段代码生成的类是能够运行的,有兴趣的可以自己试一下。这段代码的作用是new出一个对象之后,如果这个对象非空的话,就会产生一个输出:
new object is not null
当然,如果尝试用这个对象做一些其它的操作,会有错误等待着我们,因为这个对象并不是一个完整的对象。在JVM规范中有相关的解释:new指令并不能完整创建出一个新的对象,直到对未初始化的对象调用了实例初始化方法才会完成实例的创建。这段代码也正好符合《对象的生命》中的解释,它只是负责做出申请内存。当然,在JVM中,它的实际工作要略多一些,如果这个对象的类没有加载,就会加载相应的类。







