• 在《管窥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。

  • 敏捷的核心:消除浪费,走向精益

    谈到敏捷,你会想起什么呢?测试驱动开发?结对编程?我刚到ThoughtWorks不久,gigix就和我提到过,关于敏捷消除浪费的观点,说实话,我一直不太理解这个观点。不过,随着在ThoughtWorks体会的点滴逐渐增多,我慢慢的体会到这句话的含义。

    举个简单的例子,为什么要做测试驱动开发呢?从字面上看,将“测试”放在前面,提高了测试的重要性,告诉人们应该注意这一重要但经常被忽略的事实,这一点对于保证软件质量来说,确实是应该的,再有单元测试的存在让自动化回归测试变得容易了许多,还有写测试的价值会让人从不同的角度考虑设计,也许会让设计的耦合性大大降低。人们通常想不通的一点是,为什么要先写测试。如果只是单纯的交换编码和测试的位置,实际上并不会带来太多的好处。那该从怎样的角度理解测试驱动开发呢?测试代表着什么呢?需求。软件开发的目的就是为了满足需求,如果能够把需求确定了,编写代码满足它就相对容易多了。从需求出发,按照需求编写满足需求的最简单代码,而不去做许多额外的工作,这就是测试驱动开发不同于传统编写方式的地方。按照传统的方式先写代码,后写测试的话,我们的测试代码需要做的工作尽可能覆盖代码的路径,而为了覆盖面尽可能大,我们就需要花费许多额外的努力,这就是浪费。

    敏捷的思想不仅仅体现在测试上,在这两个月左右的时间里,我对设计的理解也开始发生了一些转变。曾经我对设计的理解是设计一个能够应对未来变化的美好结构,但事实上,这种竭尽全力设计出来的“好”设计总是轻而易举的被现实问题打败。为什么呢?因为这个设计是想出来的,并不是基于实际的需求逐步演化出来的。在ThoughtWorks的招聘流程中,会有一个编写程序的过程。在这个过程中,我们总会听到应聘者说他的设计是为了应对将来的变化。但事实上,这样的变化是否存在呢?不一定,多半是没有的。所以,许多看起来比较华丽的设计完全经不起推敲。其中的一个例子是,为了面向接口编程,设计一个接口,却只有一个对应的实现类,本应枝繁叶茂的继承树显得十分单薄。当然,这里并不是说面向接口编程就不好,但当只有一种情况时,就做这种抽象显然是一种过度设计,过度设计就意味着浪费,在这个例子里面,至少需要多付出编写一个接口的代价。也许你会说,未来如果真的出现第二种或者更多的情况,那该怎么办。很简单,等真正出现了再做也来得及。

    当然,问题并不绝对。一个例子是在数据库的设计通常我们会提前做出。基于前面的讨论,也许有人会认为这么做不太敏捷,恰恰相反,因为随着项目的进行,数据库修改的成本会越来越大,所以,我们宁肯在项目的前期多做些功课,以保证后面尽可能不去或少去修改它,以便减少浪费,所以,这种选择也是敏捷指导下的选择。

    也许,这种讨论会给人一种人嘴两张皮的印象,其实,这和做许多事一样,就是一个度的把握。所以,很多人都看了一些敏捷的书,但用到实际中,却总觉得不是那么回事。

    如果开头的那篇文章是写给各种各样的管理者的,那我这篇算是写给程序员的吧!

  • 要让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中,它的实际工作要略多一些,如果这个对象的类没有加载,就会加载相应的类。
  • CSDN的专访()(

    被CSDN的人抓住完成了一个采访,大有一种被揭老底的感觉。不过,还是很高兴能和别人分享一下自己对一些问题的理解。出于媒体的需要,我的回答在某些部分被修改,稍微有些不同于我的原意,比如这么煽情的标题就是从我的原文中截取出来的,而意思就是因为少了几个字而变得有些不同,不过,对比于徐X那篇(不过现在已经不是最开始那个极其吸引眼球的标题了),我还是觉得访问我的那个编辑还是不错的。下面是当时的问题和我的回答,有兴趣的朋友可以读一下。因为这里的修改权在我手里,我也把问题修改到让我可以接受的程度。:)

    C:CSDN,D:dreamhead

    C: 用精辟的语言做一个自我介绍
    D: 我,一个热爱编程的程序员。

    C:您的软件开发生涯分了哪几个部分?
    D:我的程序员之路迄今为止经历了三个阶段:
    * 大学毕业之前,初学阶段:大学之前,我对计算机的了解几乎是0。经过四年的学习,我找到了自己真正喜欢的事情:编程,因为用程序解决问题让我很有成就感。于是毕业的时候,我放弃了继续读书的想法,我知道我需要的是更多的实践。
    * 大学毕业后的前三年,实践阶段:投身到实际的项目中,我对软件开发有了真实的体验。起步的时候,我遇到了几个良师益友,让我走上我认为的正确道路。我曾做过一个长达两年的项目,这让我对软件开发的生命周期有了一个相对完整的认识,个人能力也在这个过程中得到了极大的提升:从最开始的负责某个模块的编写,到后来负责整个系统的设计,直到最后基于此设计出一个框架,拿了公司Java大赛的冠军。经过这段时间的实践,我逐渐开始对软件开发有了一些自己的看法,也终于可以写出自己满意的代码。此外,我的blog之路也始于这段时间,并一直坚持下来,不断进行自我总结的同时,也结交了许多朋友。
    * 随后两年时间,提高阶段:我在公司内部做了一次调转,工作内容的转换,让我有机会接触到更多的东西:从图像识别到并行计算,再到多核程序优化,我的技术视野不断开阔,同时,利用业余时间完善着自己的知识体系,将原本零散的知识串在了一起。实际上,我认为自己这段时间做得并不出色,不过,恰恰是这段时间,我开始反思一些东西,逐渐的掌握了一些做事的基本原则,懂得了“踏踏实实做事,也不忘抬头看路“的道理。也正是在这个阶段,我接触到了XRuby,找到了自己的乐趣所在。
    我希望自己在加入ThoughtWorks之后,进入到第四个阶段,贡献阶段,用自己所学为别人带来更大的价值。软件开发不应该仅仅是自娱自乐,更应该是做一些真正对别人有价值的事情。

    C:你的经历对你加入这个项目有怎样的影响?您成长的环境对这个项目又有怎样的影响呢?
    D:我之前的经历对我参与XRuby这个项目并没有什么直接的影响,但正是多年的积累,让我有了足够的知识和能力参与到XRuby中:对编程的热爱,对新技术的关注、软件开发的功底和对计算机技术的理解等等。

    C:平时除了写程序之外还有怎样的爱好?这些爱好对你在软件方面的造诣有何影响?
    D:我的个人爱好是“读书、写字、编代码”。读书,让我学到知识,开阔视野;写字,也就是写一些东西,让我可以不停下思考的步伐;编代码,是我的工作,也是我的爱好,对我而言,分不清,也不需要分清。

    C:现在一起合作的伙伴都是对软件开发有热情的程序员,觉得哪些宝贵的经验可以与大家分享?
    D:我相信,每个人都希望自己能够同高水平的人一起工作,我也一样。能同一些高水平的人一起合作,我感到很兴奋,因为有机会和他们一起合作,才有机会近距离观察他们思考问题,处理问题的方式。代码可以轻松复制,但是代码背后隐藏的思想却不像代码本身那么显而易见。如果不是同这些高水平的人一起工作,很难发现背后隐藏的点点滴滴,而正是这些点点滴滴才让这些人与众不同。
    我接触到的这些许多人眼中的高手,其实都是一些普通人,只是他们对自己所做的事情都很有激情,也能脚踏实地的把事情一点一点地做好。他们通常十分谦虚,不会过分强调个人的工作,这使得大家的交流变得容易了许多,也有益于让事情向着最恰当的方式发展。这些人做事通常都有一定的美感,我很喜欢编程是一种艺术的说法,好的程序员不会允许自己做的事情向着丑陋的方向发展,这是他们做事质量的一个重要保证,这种美感是在日常学习工作生活中不断的思考逐渐养成的。这些人通常思路会比较开放,他们会非常包容的看待一些问题,吸纳别人的优点,不会将一些非技术的界限看得非常清楚。我曾经看到过不少拿“国内“、”国人“来说事的项目,其实,这种提法本身就是对项目没有信心的表现,我自己作为技术人员很清楚,促使我们做出选择的更多是因为技术上的原因。开源项目是没有国界的,它的生命力是由它自身的优点和其参与者的热情决定的。

    C:XRuby现在进行到什么阶段了?对于XRuby的发展规划和目标是怎样的?现在XRuby发展的最大问题是什么?
    D:XRuby目前最新的发布版本是0.2.0,这个版本是我们的一个里程碑版本,已经可以运行Ruby的单元测试框架。有了单元测试框架作为基础,我们就可以大大提高开发效率,我们下一个里程碑版本0.3.0的目标就是通过所有Ruby自带的单元测试,而XRuby 1.0的目标是运行Ruby on Rails。XRuby所做的事情是整合Ruby和Java平台,这样,我们便可以将Ruby的开发效率和Java的丰富资源结合起来。从这个角度来说,我们和JRuby团队拥有着共同的目标,所以,许多人认为两个团队是互相竞争的。事实上,两个团队是在互相帮助。我们会给JRuby团队一些建议,甚至贡献一些代码让JRuby更好的运行,而JRuby团队也会的与我们进行讨论,把他们的一些经验分享给我们。当然,这点在我和Ola Bini之间体现得尤为明显,因为我们都是ThoughtWorker,经常在一起交流。XRuby现在遇到的最大的问题就是就是资源有限:团队规模有限,而且大多数人没有很多时间可以投入,所以,很难快速发展。

    C:当时刚刚加入XRuby时是在一个什么样的背景下?当时这个项目最吸引你的是什么?
    D:加入XRuby时,我刚好在业余时间完成了《Ruby Hacking Guide》几个章节的翻译,对Ruby的内部实现有了一些了解。看到XRuby这个项目时,我感到特别兴奋:自己熟悉的Java、自己喜欢的Ruby、自己想要了解编译器技术。所以,我毫不犹豫的加入了这个项目。

    这些年的工作中,我见到过很多的开源项目,只有XRuby是一个让我真正动心,并投入大量时间去参与其中的项目。抛开个人喜好,我依然觉得XRuby是一个非常不错的项目。首先,当时看来,Ruby on Rails已经让Ruby赢得了更多的关注,只要有足够的关注,多半会有一个不错的发展,现在诸多公司的动作再次证实大家对Ruby的看好。再有,整合 Ruby和Java是一个非常棒的主意,因为程序设计语言的发展历程中,优秀的后来者都提供了比较好的方式利用原有者的优势,而不是直接推翻重来,比如 C/C++提供了整合汇编的方式,Java提供了JNI的方式去整合C/C++的资源,所以,Ruby已经有了整合C的方式,再有一个整合Java的方式就更完美了。当然,这两点并不足以让XRuby与JRuby区别开来,真正让XRuby具有优势的是它的编译方式。谈到编译和解释,人们通常会想到性能上的差异,也就是说采用编译的方式会让程序跑得更快。事实上,JRuby让很多诟病的主要原因就是性能。面对许多新技术,人们首先会质疑的就是性能,当年的 C/C++和Java都曾经历过这样的过程,但是事实证明,只要性能差异在一个可接受的范围之内,就可以消除人们对于性能的质疑。编译会成什么样子呢?这是我感兴趣的,也是XRuby主要的价值所在,事实证明,XRuby可以让Ruby代码在JVM平台上跑得像C Ruby一样快,甚至更快。当然,C Ruby还有很多可以提升的空间。比起当时的JRuby,这就是一个巨大的进步。所以,我相信,作为第一个做JVM上Ruby编译器的项目,XRuby这个项目对整个软件开发社区的发展是有好处的,这也是我愿意参与其中的重要原因。

    很多人对于参与开源项目,尤其是像XRuby这样的编译器项目,第一个反应是我不行,因为我不会这个,不懂那个。实事求是的说,参与XRuby之初,我也只是对语言的实现有兴趣,对编译器的了解也仅限于大学所学过的那点编译原理的知识,没有什么实战的经验。但我还是加入到XRuby中,因为我相信我可以在这个过程中学会这个项目所需要的知识。直到现在,我也不敢说自己精通编译器。随着项目发展,我不断补充着自己的知识,对编译器实现的理解也是越来越深:从最开始的只是贡献一些builtin的代码,到后来重写了这个runtime的实现,再到后来修改编译器的实现。希望我走过的这条路可以那些希望进入 XRuby项目或是其他开源项目的人借鉴。

    C:您觉得开发编译器最大的乐趣是什么?要开发一个编译器应该从什么地方开始?最需要注意的是哪些问题?
    D:从大学时代开始,软件开发中有三个领域一直我所希望有机会从事的,分别是操作系统、编译器和游戏。想把任何一个做好,都需要对计算机有着深刻的理解,而我从小就特别愿意弄清楚事情的来龙去脉。作为一个程序员,像操作系统和编译器的这些知识虽然很少能够直接应用到工作中,但懂得了它们的基本原理,可以让人写起程序来如虎添翼。我对编译器尤其有好感,作为一个程序员,理解程序设计语言背后的东西,有助于我们写出更好的程序,避免因为无知造成的损失。

    说到编译器,大家会想到词法分析、语法分析、代码生成等等这些令人望而却步的词汇。其实,做一个简单的编译器并不难,很多人熟知的龙书的第二章实现一个简单的计算器,涉及到了这本书介绍的几个大的方面。不过,从参与XRuby的经验来看,编译器只是整个开发中的一环,后面的Runtime和数量庞大的 builtin占了相当大的比重,它们是让语言更具生命力的部分。

    同普通的软件一样,编译器的开发首先要弄清楚需求。很多人谈到编译器,就会想到设计一种新的程序设计语言,其实,这个世界最不需要的就是一种新的程序设计语言,当然,这并不能阻挡很多人去设计新的语言。我看到过一些号称很有热情的软件开发者,设计了一些声称全新的语言,实际上,却没有很强的针对性,所以,这些语言注定没有很强的生命力。从需求的角度来说,XRuby既不是第一个在做JVM上做编译器的动态语言,也不是第一个在JVM上做Ruby的,但它做了JVM上的Ruby编译器,这是之前没有人做过的,独一无二的,也是它的价值所在。

    C:现在XRuby的开发团队是怎样的情况?团队是否还会继续扩大?准备达到一个什么样的规模?
    D:XRuby团队现在有大约十几个人的规模,经常性提交代码的只有五六个人。开源项目不同于公司的项目,我们无法预测团队规模如何发展,加入到这个团队完全是依赖于个人对于开源的热情和对技术的热爱。我们希望有更多的人加入这个项目中来,在这个过程中,可以体会到乐趣,还会有许多收获。这个团队欢迎任何贡献,鼓励任何尝试。我们一向欢迎任何对XRuby有兴趣的人加入其中,和我们一起体会开发的乐趣。

    C:Ruby语言最吸引您的地方在哪里?将来的发展趋势又如何呢?
    D:Ruby语言最吸引我的地方是它的优美。对于很多用惯了像C/C++、Java这种静态语言的人来说,初涉Ruby会有一种震撼的感觉,它会让那些我们习以为常的繁琐操作变得异常简单,这也是开发效率大幅度提升的根本,这是许多动态语言的共同特征。随着计算机硬件的不断进步,机器的价值越来越低,人的价值则越来越高,所以,提高开发效率对于从事软件开发的人来或组织来说,显得更加重要。再有,我们知道,程序设计语言是架设在问题领域和解领域之间的桥梁,主流程序设计语言的进步就体现在越来越靠近问题领域,从汇编语言到C,再到C++,再到Java,无不如此。相比于很多静态语言,Ruby更接近问题领域,所以,我们可以更多把自己的精力放在要解决的问题上。UML也是一种向问题领域靠近的方式,不过,在我看来,相对于这种使用于模型的方式,Ruby之类动态语言更容易为程序员们所接受。还有很重要的一点,Ruby语言具有很强的元编程能力,这也是越来越多人关注的DSL(Domain Specific Language)的基础,通过适当的定制,可以让语言更加靠近问题领域,提升抽象层次。从实现角度来说,相对我所了解到其它动态语言来说,Ruby有一个设计良好的对象模型,只要理解了这个对象模型,就很容易对Ruby语言进行整体上的把握。

    从最近一段时间一些公司的动作来看,大家都特别看好Ruby的未来。我相信,Ruby不仅仅可以用在日常脚本或是Web应用上,它会得到越来越广泛的应用,会在越来越多程序员的日常开发中扮演越来越重要的角色。事实上,ThoughtWorks正尝试着让Ruby运用于企业开发,并且取得了不错的效果。另外,公司的重头产品Mingle——一个敏捷项目管理工具——也是用Ruby开发的。我期待着,在Ruby社区和众多软件公司的共同努力下,Ruby会得到更好的发展。
  • 程序员最熟悉的是源代码,但是要让程序真正的发挥功效,少不了编译器的帮助。javac的作用就是将Java代码编译为JVM指令。由于Java语言和JVM同出一门,所以,稍微熟悉一下,我们便不难发现,二者几乎是直接对应的。当然,为了简化代码的编写,javac除了直接翻译之外,还暗地里帮我们做了不少工作,我们从最简单的情况看起。

    public class Test {
    }

    我们用javac编译这段代码,javap可以帮助我们反编译生成的类文件。
        javap -c Test

    下面就是反编译的结果。
    public class Test extends java.lang.Object{
    public Test();
      Code:
       0:   aload_0
       1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
       4:   return
    }

    抛开指令具体的内容,上面反编译的结果清清楚楚的告诉我们,我们编写的这个空类一点都不空,因为其中还有一个构造函数。这就是javac替我们做的工作。没错,这是javac做的,但未必是JVM要求的。其实,JVM上运行的类,完全可以没有构造函数。不过,前面的例子已经明明白白的告诉我们,因为javac的作用,直接用Java语言是无法构造出真正的空类。那我们就不妨直接从字节码入手,借助ObjectWeb ASM构造真正的“空“类。

    public class NoCtorGenerator {
        public static void main(String[] args) throws Exception {
            String className = "NoCtor";
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
            cw.visit(Opcodes.V1_2, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", null);
            cw.visitEnd();
            
            try {
                os = new FileOutputStream(className + ".class");
                os.write(cw.toByteArray());
            } finally {
                if (os != null) {
                    os.close();
                }
            }
        }
    }

    借助javap,我们可以看到生成的结果,确实没有构造函数。
    public class NoCtor extends java.lang.Object{
    }

    不过,因为没有构造函数存在,我们并不能用这个类创建对象,但是,下面的代码证明了这个类生成的类确实可用。
    public class NoCtorMain {
        public static void main(String[] args) {
            System.out.println(NoCtor.class);
        }
    }

    运行这段代码,我们可以得到下面的输出:
    class NoCtor

    关于Java虚拟机的指令,可以参考《深入Java虚拟机》,而ObjectWeb ASM的入门,可以参考我的《Hello, ASM——代码生成》。