<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
  <channel>
    <title>ajoo</title>
    <description>你们狭隘粗野视听能力和表达能力都有严重障碍差不多都不可理喻无法无天精神世界几乎没有容量只能认知眼前的一丁点儿人和事所有行动近乎简单的条件反射一句话我认不出你们是谁看你们的帖子我没有产生任何有关人人群的联想莫非发帖子就可以这么乱来?</description>
    <link>http://ajoo.javaeye.com</link>
    <language>UTF-8</language>
    <copyright>Copyright 2003-2008, JavaEye.com</copyright>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>JavaEye - 做最棒的软件开发交流社区</generator>
      <item>
        <title>抄课文，重复输入相同密码，测试</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/197804" style="color:red;">http://ajoo.javaeye.com/blog/197804</a>&nbsp;
          发表时间: 2008年05月28日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          记得以前曾经和一个tw出来的老兄一起共事过一小段时间。当时问他们组的测试情况，据说都是100%的覆盖率。说实话，心里挺惊讶的。<br /><br />我不是一个懒于写测试的人。实际上，通过是否便于测试来判断一段代码的设计优劣已经几乎是本能了。可是，我发现连九成的覆盖率对我来说都是几乎难以企及的目标。<br /><br />你的代码不管怎么重构，总有那么一些角落要连数据库，写文件，从信用卡里面划钱这些恶心吧唧的东西吧？这些应该都可以集成测试，但是单元测试我基本上就是绕过了。<br /><br />这也罢了。那些java bean的getter/setter你难道也要测？一行的throw new UnsupportedOperationException()难道也要测？<br /><br />最近和同事有一个有趣的讨论。原因是这么一个简单的函数：<br /><pre name="code" class="java">
void copy(Source source, Dest dest) {
  dest.setFoo(source.foo());
  dest.setBar(source.baz());
  // 不拷贝baz，baz的处理在别的地方做。
  // 还有其他的乱七八糟的拷贝
}
</pre><br /><br />我以为这么一个简单地几乎代码本身就相当于需求描述的东西没必要费劲写单元测试了。但是发现很多同事不是这么看，不管从tdd的角度还是纯朴地希望测试所有能测的东西的愿望都促使大家异口同声地说：测！<br /><br />那么，怎么测呢？这里面除了要测试foo和bar拷贝了，还要测试baz没有拷贝。大概是这么写吧：<br /><pre name="code" class="java">
Source source = new Source();
source.setFoo("foo");
source.setBar("bar");
source.setBaz("baz");
Dest dest = new Dest();
copy(source, dest);
assertEquals("foo", dest.getFoo());
assertEquals("bar", dest.bar());
assertNull(dest.baz());
</pre><br /><br />可是那么简单的原始代码就要用这么一堆杂乱的测试么？怎么想怎么不值啊。<br /><br /><br />我自己有这么一个小理论：<br /><div class="quote_title">引用</div><div class="quote_div"><br />所谓“测试”，就是给定一个生产系统P，我们制造一个测试系统T，T的复杂度小于P，而T和P的关系满足以下式子：<br /><br />T passes + T is correct => P is correct<br /><br />根据“bug永远存在定律”，我们永远无法宣称一个给定系统是完全正确的。但是，我们却可以猜测，一个简单的系统会比一个复杂的系统更容易正确。<br /><br />而T passes是自动运行的，所以我们就把保证P的正确性这个难题转化为保证T的正确性这个相对简单的问题了。<br /></div><br /><br />我这个小理论的前提是，T比P简单。所以对getter/setter这种，因为我无法写出比生产代码更简单地测试代码了，所以测试的有效范围到此结束；而对一些仍然比较复杂的测试代码，这个迭代会继续下去，写额外的测试代码来测试这个测试代码，直到我可以把P递减到T1, T2, ..., Tn，最后无法写出一个更简单的T来测试Tn，循环结束。<br /><br />用这个理论来衡量，上面的那个测试代码明显比生产代码复杂，可读性也差。所以在我看来属于不经济的。<br /><br />当然，测试什么“没有作”目前最好的方式其实不是这种state-based testing，而是interaction-based，比如用EasyMock。当然，EasyMock的expectation语法写出来会是象咳嗽一样地：<br /><pre name="code" class="java">
expect(source.getFoo()).andReturn("foo");
dest.setFoo("foo");
expect(source.getBar()).andReturn("bar");
dest.setBar("bar");
// 不用管baz了，所有我们没说的一概不允许发生！
</pre><br />还是不如生产代码看着舒服，其复杂度大致是原来的两倍上下。<br /><br />好吧，这只是EasyMock个人的问题。让我们来乐观一下，假设EasyMock可以被改进，支持直接用<br /><pre name="code" class="java">
dest.setFoo(source.getFoo());
</pre><br />这种更直观的方式来写expectation，那么上面的测试代码就可以简化许多了，理想情况下，我们就可以把它写成：<br /><br /><pre name="code" class="java">
dest.setFoo(source.getFoo());
dest.setBar(source.getBar());
// do not copy baz as it is taken care of separately
// other set(get())
</pre><br /><br />嗯。漂亮许多。不再那么唧唧歪歪了。可是，你发现了什么没有？这个测试代码可以直接从生产代码拷贝过来！<br /><br />也就是说，最最好的情况下，我们不过是在重复小时候淘气被老师惩罚的惨无人道的抄课文！<br /><br />记得我是一个老实孩子，作弊都不会，老师让抄五遍的，我偷了妈妈的复写纸还是老老实实地写了三遍<img src="/images/smiles/icon_redface.gif"/><br /><br />不过现在不同了，我们都是聪明的程序员，既然可以直接copy-paste的，那也就没什么麻烦的了。不管多少字，都是两下按键潇洒搞定。可是，这样一来，测试就没有意义了呀，搞什么搞嘛。<br /><br />你也许会说：你用复写纸当然起不到作用了，老师是让你用人肉来写的，这样才能锻炼书法，记忆力，毅力，恒心，专心，责任心，爱心等等的嘛。<br /><br />或者比如说你登录一个帐号，要输入密码的时候，不是要同样的密码用人肉敲两遍，而不是拷贝的么？<br /><br />是哈，看来EasyMock不做得象复写纸那么方便还是有道理的，头悬梁椎刺骨，吃得苦中苦，方为人上人，设计的真是人性化啊。<br /><br />不过，我还是总觉得有那么一点别扭。能（假设能）拷贝的非要装作不能，拿手去重新写一遍总是觉得有点象苏哥，一边说自己是救世主，一边人家随便一弄就搞死了那么的不通。要说多写一边能帮助避免错误，那为啥不写5遍？（恩，也许NASA这种关键部门的关键程序就是让程序员默写50遍的──这些程序员肯定是小学留级10年以上的问题学生来的）<br /><br />今天，又遇到一个问题，想了想，觉得我要是提出来，肯定还是要被大家批判得体无完肤的。生产代码里面需要调用某个Service，需要传递一个固定的map进去：<br /><br /><pre name="code" class="java">
Map&lt;String, String> params(int id) {
  return ImmutableMap.of(
    "m", "Get",
    "id", id
  );
}

void doFoo(int id) {
  service.call(params(id));
}
</pre><br /><br />然后在mock测试doFoo的时候，我就想，我是这样呢？<br /><pre name="code" class="java">
service.call(ImmutableMap.of(
    "m", "Get",
    "id", id
  ));

replay();

doFoo(id);
</pre><br /><br />还是这样呢？<br /><br /><pre name="code" class="java">
service.call(Foo.params(id));

replay();

doFoo(id);
</pre><br /><br />后者如果Foo.params()写错了，测试也就错了。<br /><br />又或者，我应该写一个Foo.params()的测试？这样？<br /><pre name="code" class="java">
assertEquals(ImmutableMap.of(
    "m", "Get",
    "id", id
  ), Foo.params(id));
</pre><br /><br />也许，通过抄课文，重复输入密码这些高科技手段，我的程序质量会不知不觉地提高？或者至少下次别人维护我的程序的时候，不小心改错一个地方，测试会报错。而且，当他看到我的测试代码和生产代码长得一模一样，就会发出会心的微笑：根据ajoo的人格，他肯定不是用复写纸作弊地。看来他的目的确实是这个了，写了两遍都是一模一样的。闹得闹得。
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/197804#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 28 May 2008 12:11:38 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/197804</link>
        <guid>http://ajoo.javaeye.com/blog/197804</guid>
      </item>
      <item>
        <title>动态properties转换</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/190440" style="color:red;">http://ajoo.javaeye.com/blog/190440</a>&nbsp;
          发表时间: 2008年05月07日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          今天同事和我讨论他遇到的一个问题。具体要求是这样的，在运行时，我们会从模块G得到一个Map，这个Map里面都是一些字符串对，你可以理解成一个字典，有字符串的key和字符串的value。简短节说，就是<br /><pre name="code" class="java">Map&lt;String, String></pre><br />非常非常复杂深奥。<br /><br />好，现在我们事先知道要从这个map里读取一些数据点，比如：id, name, sex等等。<br /><br />对id，我们知道读出来的是int；对name，是string；对sex，应该对应一个叫Gender的enum类型。<br /><br />这就涉及一个自动类型转换的问题。我们希望不用对每个数据点做手工类型转换。<br /><br />另外一个需求，一些数据点是有缺省值的。比如name我们可以缺省为空字符串。<br />这样，如果map里面没有某个值，我们就看缺省值，如果有，就用这个缺省值，如果没有，就抛异常。<br /><br />手工做的话，大概是这样：<br /><pre name="code" class="java">
String idValue = map.get("id");
if (idValue == null) {
  throw ...;
}
int id = Integer.parseInt(idValue);

String name = map.get("name");
if (name == null) {
  name = "";
}

String sexValue = map.get("sex");
if (sexValue == null) {
  throw ...;
}
Gender sex = Gender.valueOf(sexValue);
...
</pre><br /><br />比较痛苦。于是做了一个动态代理：<br /><pre name="code" class="java">
public final class PropertyConverter&lt;T> {
  private final Class&lt;T> targetType;
  
  private PropertyConverter(Class&lt;T> targetType) {...}

  public static &lt;T> PropertyConverter&lt;T> to(Class&lt;T> targetType) {
    return new PropertyConverter&lt;T>(targetType);
  }

  public T from(final Map&lt;String, String> map) {
    return Proxy.newProxyInstance(
      new Class[]{targetType}, targetType.getClassLoader(), new InvocationHandler() {
        public Object invoke(Object proxy, Method method, Object[] args) {
          String value = map.get(method.getName());
          if (value == null) {
            Object defaultValue = method.getDefaultValue();
            if (defaultValue == null) {
              throw ...;
            }
            return defaultValue;
          }
          return convert(value, method.getReturnType());
        }
    });
  }
}
</pre><br /><br />convert()函数是调用apache的ConvertUtilsBean做的，没什么说的。<br /><br />那么，用法呢？<br /><br /><pre name="code" class="java">
@interface Foo {
  int id();
  String name() default "";
  Gender sex();
}

Map&lt;String, String> map = ...;
Foo foo = PropertyConverter.to(Foo.class).from(map);
foo.id();
foo.name();
</pre><br /><br />这里面，对annotation的用法比较特别。不过不这么做，java也不提供一个简单并且类型安全的指定缺省值的方法。当然，如果你凑巧不需要缺省值，那么也不用annotation，直接用interface就好。
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/190440#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 07 May 2008 06:06:13 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/190440</link>
        <guid>http://ajoo.javaeye.com/blog/190440</guid>
      </item>
      <item>
        <title>怎样最有效地测试异常？</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/178998" style="color:red;">http://ajoo.javaeye.com/blog/178998</a>&nbsp;
          发表时间: 2008年04月02日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          工作中，和同事对测试异常的最佳方法产生了分歧。<br /><br />我是比较欣赏JUnit4的@Test(expected=FooException.class)的啦，觉得这样多清爽啊，多declarative啊，再不用写那么一大坨try-fail-catch了。<br /><br />不过同事（以下简称S）不这么认为。他觉得try-fail-catch挺好的，价格便宜，量又足，我们一直用它。而JUnit 4和TestNG提供的这个功能容易引诱程序员犯错误。<br /><br />S给提出了一个挑战：<br /><br /><pre name="code" class="java">
public void testDoSomethingBad() {
  initializeSomething();
  try {
    doSomethingBad();
    fail();
  } catch (FooException e) {}
}
</pre><br /><br />这里面initializeSomething()的作用是初始化到某一个状态，这个过程不应该出错，而到了这个状态之后，doSomethingBad()才会抛异常。<br /><br />然后他坚持认为这种情况是最普遍的情况。而用annotation虽然看上去很美，但是可能邪恶地诱惑程序员写出不准确的测试，造成false positive，比如，initializeSomething()抛了一个异常。<br /><br />当然，我们对这种情况的常见程度各执己见。也没什么说的。但是，后来我想，其实，这个测试换成自然语言表达是什么呢？大概是这样吧？<br /><ul><li> initializeSomething()不许出错</li><li> 在initializeSomething()之后doSomethingBad()要出错</li></ul><br /><br />那么，为什么不把这两个要求写成两个测试呢？<br /><pre name="code" class="java">
@Test
public void testInitializeSomething() {
  initializeSomething();
}

@Test(expected=FooException.class)
public void testDoSomethingBadAfterInitializeSomething() {
  initializeSomething();
  doSomethingBad();
}
</pre><br /><br />只要我们写测试的时候不要总想着“聪明”地实现，而是直白地用代码表示需求，不就没问题了么？<br /><br />再说一说我为什么这么讨厌这个try-fail-catch。它有几个我深恶痛绝的毛病。<br /><ul><li> 它等于代码里的逻辑分支。如果没有抛异常，它执行fail()，而如果抛了异常，它进入catch()。而测试里的逻辑分支味道很坏。它让你的代码容易出错（比如，你忘了fail怎么办？测试一样是绿的，但是你的bug还躲在那）。而且，它让测试代码不能达到100%的分支覆盖率。本来如果用annotation的话，如果出现了initializeSomething()抛出异常的情况，覆盖率马上不是100%了，你可以很容易地发现问题。</li><li> 冗长烦琐。测试写的不象spec，而象过程形代码。</li><li> 这个try-fail-catch只在你检查Exception的时候成立。如果万一你要检查一个Error甚至是JUnit的AssertionFailedError，完了，你连fail()抛的异常也给截获了。</li></ul><br /><br />今天早晨，忽然灵机一动，其实，还有一个方法的。比如，在你自己的BaseTest的基类里面，你可以实现一个expectException()的函数，然后这么用：<br /><pre name="code" class="java">
public void testDoSomethingBad() {
  initializeSomething();
  expectException(FooException.class);
  doSomethingBad();
}
</pre><br /><br />这样，在runTest()结束前，可以检查是否存在一个exception expectation，如果有，就catch住抛出来的异常，然后进行检查。而如果没出现异常，直接就报错。这样，不就没问题了？还可以进一步抽象，弄个ExceptionExpectation的接口，这样客户代码可以灵活地登记并且重用任何的异常期待，不仅仅局限于检查异常类型和错误信息了。<br /><br />当然，这是在JUnit 3.8。
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/178998#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 02 Apr 2008 21:56:30 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/178998</link>
        <guid>http://ajoo.javaeye.com/blog/178998</guid>
      </item>
      <item>
        <title>为中国的未来担忧，我，上过大学，学过计算机，英语六级，当过DBA，架构师，软件工程师，还得自己扫雪！</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/174970" style="color:red;">http://ajoo.javaeye.com/blog/174970</a>&nbsp;
          发表时间: 2008年03月23日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          哎，一到下雪我就头疼。又得推着扫雪机吭哧吭哧扫雪。就说是三月学雷锋树新风，也不能总这样啊。妈的，还是住condo舒服，啥都不用干。住大房子？奉劝诸位还是等有钱养保姆，丫鬟，家丁了再说。
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/174970#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sun, 23 Mar 2008 03:06:15 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/174970</link>
        <guid>http://ajoo.javaeye.com/blog/174970</guid>
      </item>
      <item>
        <title>祝贺Guice拿了Jolt Award</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/169766" style="color:red;">http://ajoo.javaeye.com/blog/169766</a>&nbsp;
          发表时间: 2008年03月10日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          我对计算机界的八卦一向都不太敏感，也对很多所谓的“名人”不感兴趣。你说那章子仪，Britney好歹还能拿脸蛋，衣服，和衣服里面的东西来娱乐大众，一帮不知道哪来的猥琐男就写了点谁都能写的程序，又不是证明了歌德拔河，也不是火箭科学，有什么好追星的？<br /><br />所以对Guice拿了Jolt也是后知后觉。不过在知道Guice是打败了Spring拿到的奖的，还是颇觉得欣慰了一下。<br /><br />要说Guice也不是多完美，很多功能的缺失还是对使用造成了些障碍的。所谓的“EDSL”，或者马丁嚼了嚼前人吃剩的东西再吐出来给大众继续啃的“fluent interface”，给测试，扩展和阅读稳当都造成了很大的麻烦。（我的jparsec, rparsec系列也可以自称是fluent interface，比如你可以说foo.many.map {...}之类的，但是它的每一步本身都具有完整的语义，而且都统一地是一个Parser对象，而不是额外引入乱七八糟古里古怪的中间类型。）<br /><br />不过，你看跟什么比吧。这Spring，号称open-source，但是奉劝你还是闭着眼睛用，千万别好奇心起，往里面看什么代码。那设计，那代码，叫一个乱，真是让人不忍心看下去。<br /><br />作者也是一根筋，遇着要客户扩展功能的，必然是弄个什么MyChitterlingAware接口，然后instanceof到处用，然后跑到文挡里一丝不苟孜孜不倦地加上一章“如果实现了MyChitterlingAware接口，那么你就会自动得到我的原始的带着消化系统鲜活气息的大肠一段”；然后整个系统中谁也不知道到底有多少XYZAware的special case。<br /><br />遇着不同的目标场景，必然是弄一个子类，什么"ChitterlingApplicationContext extends ClassPathApplicationContext", "SpicyChitterlingApplicationContext extends ChitterlingApplicationContext"，而且如果某个MyChitterlingAware对象需要从appcontext得到东西，必然是要实现一个BeanFactoryAware，然后再判断"instanceof ChitterlingApplicationContext"。最后弄了一个非常sophisticated的类继承图，显得好专业呀；<br /><br />遇着逻辑分支，比如singleton与否，必然是一个"if (isSingleton()) {...}"；<br /><br />就算眼馋人家用java写配制可以重构，也还是脑子不带转弯地拿xml那套写JavaConfig，代码看上去还是xmlish，一点也不pojo；<br /><br />所有OO初学者能干的蠢事，似乎都一五一十一板一眼地干了一遍。<br /><br />那个，你要说了，那为什么人家那么成功？“一站式”啊，不管啥玩艺儿，一股脑都给你塞进来，这个主意还是很有市场的；“vendor lock-in”啊。这么多应用，这么多框架，都依赖于ChitterlingAware，你说你能想不干就不干了？“竞争对手实在不争气”啊，那个什么ejb，吓跑了多少“我其实就想写点程序呀”的程序员？<br /><br />现在好了，终于有一个decent的DI framework并且能够不被劣币给驱逐了。无疑这个成功相当程度上得力于Google这个名字，谢谢，crazybob，谢谢，Google。<img src="/images/smiles/icon_smile.gif"/>
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/169766#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 10 Mar 2008 12:21:20 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/169766</link>
        <guid>http://ajoo.javaeye.com/blog/169766</guid>
      </item>
      <item>
        <title>rparsec在《Pracical Ruby Projects》中</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/167129" style="color:red;">http://ajoo.javaeye.com/blog/167129</a>&nbsp;
          发表时间: 2008年03月04日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          最近发现《Practical Ruby Projects》用rparsec来讲解怎么在Ruby里构建lisp。于是假公济私地用公司的账户订购了一本，也算给自己捧场。<br /><br />书拿到，打开一看，好么，被批评了<img src="/images/smiles/icon_sad.gif"/><br /><br /><div class="quote_title">引用</div><div class="quote_div">Caution: For such an excellent library, rparsec plays very fast and loose with namespaces. Its parser classes are installed directly into root namespace. As a library author, try not to do this.</div><br /><br />赶紧自己把代码下载下来一看，可不是么，所有的类都在顶层namespace。汗啊！<br /><br />还是太习惯于java的方式了，潜意识中总是觉得在什么地方放一个"package org.codehaus.rparsec"就好了。结果ruby没这东西。<br /><br />作为我的第一个（也是迄今为止唯一一个）ruby项目，很多惯用法都不知道。记得当时写完代码，到处问别人：我这些源文件应该怎么组织啊？好像不是跟java似的，有"package"的概念？有classpath？用org/codehaus/rparsec这种方式？旁边的人都是rails的用户，基本上人家告诉说有个rails的咒语怎么念就跟着怎么念的那种，自己都没写过库，所以也都含糊，反正后来凑合着给整上了，到现在也不知道整得对不对。<br /><br />这个namespace好像一度曾经想过，但是当时觉得“以后问别人怎么弄吧，先写代码去也”，后来也就忘记了。<br /><br />不过到底应该怎么搞namespace呢？这样？<br /><br /><pre name="code" class="java">module org
  module codehaus
    module rparsec
      class Parser
        ...
      end
    end
  end
end</pre><br /><br />牙磕!<br /><br />这样？<br /><pre name="code" class="java">module RParsec
  class Parser
    ...
  end
end</pre><br /><br />好点，但是还是不喜欢每个源文件上来就全缩进两格。就没有java的"package RParsec"这类咚咚？Ruby语法这么灵活，应该会有吧？<br /><br />哎，下一版本如果要改namespace，就要破坏向后兼容性了。好在还没有太多用户呢。<br /><br />对了，如果你读过这本书，作者对rparsec的使用有一点点偏差，他用"alt(parser1, parser2)" 这个组合子，但是实际上这个alt是一个高级选项，应该用"parser1 | parser2"这个更直观，更不容易出错的组合子，这样书里面提到的一个陷阱就不存在了。
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/167129#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 04 Mar 2008 02:37:04 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/167129</link>
        <guid>http://ajoo.javaeye.com/blog/167129</guid>
      </item>
      <item>
        <title>中了annotation的毒了</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/160631" style="color:red;">http://ajoo.javaeye.com/blog/160631</a>&nbsp;
          发表时间: 2008年01月31日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          在1.4上，我最喜欢玩的是dynamic proxy。以前的那个Sum的“奇技淫巧”就人人喊打来着。自打不用担心对1.3, 1.4的兼容问题后，我发现我越来越爱拿着annotation的锤子到处乱砸了，而且是用reflection而不是apt。<br /><br />给Guice加了一个@Provide的外壳：<br /><pre name="code" class="java">
class MyModule extends AbstractModule {
  @Provide @Singleton
  public Foo foo(FooImpl impl) {
    return impl;
  }

  @Provide @LittleStrong
  public Bar bar(A a, B b) {
    return new BarImpl(a, b);
  }
}

// 用来代替
bind(Foo.class).to(FooImpl.class).in(Singleton.class);
bind(Bar.class).annotatedWith(LittleStrong.class).toProvider(new Provider&lt;Bar>() {
  @Inject A a;
  @Inject B b;
  public Bar get() {
    return new BarImpl(a, b);
  }
});
</pre><br /><br />又给EasyMock加了@Mock的外壳：<br /><pre name="code" class="java">
public class FooTest extends PorkTest {
  @Mock private Foo foo;
  @Mock private Bar bar;
}
</pre><br /><br />当考虑怎么样生成一个url字符串时，也是想用annotation:<br /><pre name="code" class="java">
public class User {
  @Param("username") String name;
  @Param("id") int id;
}
String queryString = QueryStringBuilder.build(new User("ajoo", 1));
</pre><br /><br />绳啊，救～救我吧，一把年纪啦，该稳重一点，“企业”一点啦。我本来是讨厌@UglyRetardedWackyEwEwEwAnnotation来的。
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/160631#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 31 Jan 2008 08:27:11 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/160631</link>
        <guid>http://ajoo.javaeye.com/blog/160631</guid>
      </item>
      <item>
        <title>俺摸，俺摸，俺默默摸 （2）</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/156769" style="color:red;">http://ajoo.javaeye.com/blog/156769</a>&nbsp;
          发表时间: 2008年01月17日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          那啥？刚才说到哪了？“如云朵般的呵护”？下面谁说的？给我拉出去好好“呵护呵护”！<br /><br />人家没那么说啦！讨厌！是刚才广告里说的嘛！我说刚才我们“俺摸”系列说到哪了？<br />对了，说到我们可以这么用PorkMockTest：<br /><br /><pre name="code" class="java">
public class LionHeadTest extends PorkMockTest {
  public void testHuoHou() {
    LionHead head = mock(LionHead.class);
    head.bite();
    replay();
    cook(head);
    // 忘记吧，忘记吧。忘记是一种幸福。能忘记的人才能快乐地吃狮子头啊！
  }
}
</pre><br /><br />那要是俺老猪比较馋（好像不需要“要是”了？），就好狮子头这口，连写了八个test都要mock(LionHead.class)怎么办？俺以前都这么写：<br /><br /><pre name="code" class="java">
public class LionHeadTest extends PorkMockTest {
  private final LionHead head = mock(LionHead.class);
  
  public void testEat() {...}
  public void testEatAgain() {...}
  public void testOneMorePlease() {...}
  ...
}
</pre><br /><br />不过后来在喜欢作JUnit专家状的沙师弟的唠叨下，已经不敢再明目张胆地这么做了。据丫说吧：“JUnit 3.8下，所有TestCase的构造函数都是在TestSuite创建的时候调用的，所以不要在构造函数里做任何可能比较慢或者可能抛exception的动作，否则，一旦发生异常，错误信息是惨不忍睹地不知所云！！！”。在Google上搜索出来的结果也似乎站在这个喜欢冒充高手的家伙那边，让我真想打他的脸啊，打他的脸。<br /><br />哎，我真想揪住某个洋洋得意写了《Test Driven Development》的人的领口，啐他一脸。凭啥非要恶心俺老猪？招你啦？<br /><br />于是，俺只好捏着鼻子在这个讨厌的家伙的监督押送下返工：<br /><pre name="code" class="java">class LionHeadTest extends PorkMockTest {
  private LionHead head;

  @Override protected void setUp() throws Exception {
    super.setUp();
    head = mock(LionHead.class);
  }

  ...
}
</pre><br /><br />靠！这是一砣什么屎？于是，除非实在饿的不行了，俺老猪宁可在每个test里面调用一遍mock(LionHead.class)也懒得搞这么一砣东西出来。<br /><br />这不？昨天听师傅讲楞加经（楞要加塞儿经？），迷迷糊糊又睡着了，等猴哥拎着耳朵（当然是他自己的耳朵，敢拎我的？哼哼，现在俺这肉可贵了，掂量着自己赔的起先！）把俺叫醒，正好听见师傅不小心把经文夹缝里的字儿给念出来几个。然后俺就觉得小腹一股热气冲上丹田，瞬间增加了几个甲子的功力。然后体力，智力，根骨，悟性都增加5点。任督二脉无师自通。这个讨厌的问题也终于找到了一个解决方案，哈哈。<br /><br />长话短说，我想到了用annotation的方法。只要在PorkMockTest里面加上一点反射的咚咚，我就可以这么写了：<br /><pre name="code" class="java">
public class LionHeadTest extends PorkMockTest {
 @Mock  private LionHead head;
  
  public void testEat() {...}
  public void testEatAgain() {...}
  public void testOneMorePlease() {...}
  ...
}
</pre><br />比我原来的那个版本还爽，现在我都不用车轱辘话把LionHead说两遍了！那么PorkMockTest怎么搞的呢？南无阿弥陀佛：<br /><br /><pre name="code" class="java">
@Target(FIELD)
@Retention(RUNTME)
protected @interface Mock {}

@Override public void runBare() {
  mockFields(getClass(), this);
  super.runBare();
}

private void mockFields(Class cls, Object obj) {
  Field[] fields = cls.getDecalredFields();
  for (Field field : fields) {
    if (field.getAnnotation(Mock.class) != null) {
      field.setAccessible(true);
      field.set(obj, mock(field.getType());
    }
  }
  Class superclass = cls.getSuperclass();
  if (superclass != null) {
    mockFields(superclass, obj);
  }
}
</pre><br /><br />几个可能的问题回答一下：<br />1。为什么用runBare()而不是runTest()？因为我希望所有的@Mock fields在setUp()之前就初始化好，这样子类在setUp()里面就可以为所欲为了。<br />2。这样的速度是不是比较慢？毕竟每个test都要跑一下runBare()，也就都要跑一下这些反射代码？是的。真实代码是用了一个cache来缓存每个class的所有@Mock field。不过这种技术细节我就不展开了。<br /><br />现在的PorkMockClass不要太好用，尤其是需要用到很多mock的类。老猪俺想吃啥就mock啥。什么龙虾，鲍鱼，燕窝，象拔蚌，都不在话下。<br /><pre name="code" class="java">
public class FeastTest extends PorkMockTest {
  @Mock private Lobster lobster;
  @Mock private Oyster oyster;
  @Mock private BirdNest nest;
  @Mock private ElephantNoseClam clam;

  ...
}
</pre><br /><br />俺摸，俺摸，俺使劲儿摸！哈哈。
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/156769#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 17 Jan 2008 00:31:29 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/156769</link>
        <guid>http://ajoo.javaeye.com/blog/156769</guid>
      </item>
      <item>
        <title>俺摸，俺摸，俺默默摸</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/156764" style="color:red;">http://ajoo.javaeye.com/blog/156764</a>&nbsp;
          发表时间: 2008年01月16日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          猪肉炖粉：“那啥，猪哥啊，这两天你身价大涨，发了吧？忙啥呢？”<br />一只猪：“可不是！到哪儿都被采访，这名猪也有隐私权滴！最近特想过普通猪的生活。这不，洗尽铅华，老老实实地躲圈里整Java呢”<br /><br />话说这Java的一级摸客（EasyMock），老猪最近用的满多的。在Java 4里面不忍卒睹的代码在java 5里面骤然变得性感许多。不过用的多了，也发现有些不方便的地方。最不爽的就是：俺老猪不是粗心吗？有时候不小心就会忘了EasyMock.verify()或者IMocksControl#verify()。<br /><br />这一忘了不打紧，有些bug就藏在那，测试也过了，但是bug也没找出来。<br />有很多淫说这还不容易？用IMocksControl，然后在tearDown()里写control.verify()，就没事了。（不好，被你发现老猪还在用回立牌JUnit 3.8）<br /><br />嘿嘿，老猪针对这种左倾机会主义思潮做出了最坚决的斗争。<br /><br />为啥？且听老猪我慢慢到来。在tearDown()里面写verify()有三个很致命的毛病：<br />1。 JUnit 3.8的tearDown()相当于try-finally里面的finally。也就是说，即使你的test出现了异常，它也会被执行。因此如果你的测试代码还没等到verify()的时候就因为某种原因歇菜了（比如，某个assertEquals()失败了），这个异常会被吞噬掉，你得到的将会是一个毫无意义的EasyMock的verify()异常。<br /><br />2。tearDown()很有可能被子类重载滴。万一子类忘记了调用super.tearDown()怎么办？”子类不应该忘“？呵呵，要我说你还不如干脆就”不应该“忘记调用verify()呢。这不是前门驱虎，后门进狼么？<br /><br />3。不能不分3＊7＝21就verify呀同志！咱假如说，俺老猪懒，为了跑到绿草如茵的山坡睡个暖洋洋的懒觉，写了一个啥也不干的testStone()，反正回头就说这山叫石头山，山上有个石头洞，洞里有俩和尚在讲故事...（说走嘴了），迷迷糊糊闭着眼睛哼着小曲点击了一下my precious testStone()。“啦啦啦啦啦，啦啦啦啦，天空出彩霞呀，.哦？.哦.bug出来啦呀<img src="/images/smiles/icon_sad.gif"/>”。啥？居妍说俺的testEmpty错了？俺老猪是无辜地呀。因为俺啥也没干啊！是猴哥。没错，肯定是这弼马温！说啥不能还没replay()呢就verify()？废话，我也没让你verify呀。这个故事告诉我们，不能没事就verify()，你至少得知道人家replay()了没有先。<br /><br /><br />老猪的解决办法是做一个PorkMockTest类，然后在TestCase#runTest()上做文章：<br /><pre name="code" class="java">
public class PorkMockTest extends TestCase {
  private IMocksControl control = null;
  private boolean replayed = false;
  private boolean verified = false;

  @Override protected void runTest() throws Throwable {
    super.runTest();
    if (replayed && !verified) {
      verify();
    }
  }
  private IMocksControl control() {
    if (control == null) {
      control = EasyMock.createMockControl();
    }
  }

  public &lt;T> T mock(Class&lt;T> type) {
    return control.createMock(type);
  }

  protected void replay() {
    control().replay();
    replayed = true;
  }

  protected void verify() {
    verified = true;
    control().verify();
  }
}
</pre><br /><br /><br />这个代码还比较简陋，还不处理strict和nice mock。不过呢，基本上就剩照猫画虎了——当然，我没跟你说就copy&paste啊，你怎么也得refactor一下才好意思见人吧？<br /><br />好了，现在只要你继承我这个PorkMockTest，你就可以一直往前走，不用往两边看了。你可以理直气壮地忘掉verify()——要求我们懒惰如猪的程序员记住计算机可以搞定的东西是犯罪呀，对人民赤果果地犯罪！<br /><br />好，现在我可以这么写了：<br /><br /><pre name="code" class="java">
public class LionHeadTest extends PorkMockTest {
  public void testHuoHou() {
    LionHead head = mock(LionHead.class);
    head.bite();
    replay();
    cook(head);
    // 忘记吧，忘记吧。忘记是一种幸福。能忘记的人才能快乐地吃狮子头啊！
  }
}
</pre><br /><br />欢乐的时光总是过得快，又到时间说白白。不要走开，广告之后请继续收看俺摸（mock），俺摸，俺默默摸！
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/156764#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 16 Jan 2008 23:50:27 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/156764</link>
        <guid>http://ajoo.javaeye.com/blog/156764</guid>
      </item>
      <item>
        <title>Not Convinced about JavaConfig</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/139638" style="color:red;">http://ajoo.javaeye.com/blog/139638</a>&nbsp;
          发表时间: 2007年11月10日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          印象中，Spring就象上个世纪的产品。不论使用方便性和代码设计都相当20世纪。有了Guice，Spring似乎应该逐渐退隐，被遗忘于江湖了。我最近这个项目就是在从Spring往Guice移植。大家都很高兴终于不用在这个即将失事的火车上挤着了。一个哥们在白板上用红笔大字写上：“用xml写配制半点好处也木有！”。<br /><br />然后就看到了Spring JavaConfig。和这个火药贴：<br />http://www.jroller.com/habuma/entry/guice_vs_spring_javaconfig_a<br /><br />本来都对Spring失去兴趣了，不过看这么多人为之正名，于是就去看了一下。总的来说呢，眼睛一亮，然后比较失望。<br /><br />JavaConfig从语法上说比Guice 1.0又进了一步，使用了最简单直观的method，然后用一些annotation来增加功能。这个和Guice 2.0比较象。谁抄谁我不管，技术上就这么点东西，这年头，民工都快能造原子弹了。<br /><br />简单的东西两者差不多。文章里比的是Guice 1.0, 但是我觉得拿快出来的Guice 2.0比较更加有意义一些。比如他那个KnightConfig的例子，用Guice 2.0写，大致是这样：<br /><br /><pre name="code" class="java">public class KnightModule {
  @Provide
  public Knight knight() {
    ...
  }

  @Provide
  public Quest quest() {
    ...
  }

  ...
}</pre><br /><br />JavaConfig是这样：<br /><pre name="code" class="java">@Configuration
public class KnightConfig {
  @Bean public Knight knight() {
    KnightOfTheRoundTable knight = new KnightOfTheRoundTable("Bedivere");
    knight.setQuest(quest());
    return knight;
  }

  @Bean public Quest quest() {
     return new HolyGrailQuest();
  }

  @Bean @SpringAdvice("execution(* *.embarkOnQuest(..))")
  public MinstrelInterceptor minstelAdvice() {
     return new MinstrelInterceptor();
  }
}</pre><br /><br />除了annotation不同，都一样。JavaConfig基本上就是把xml的manual wiring换到Java里面了（一件非常积德的事情）。Walls喋喋不休地强调的Spring的非侵入性其实没半点意义。要是真不想用Guice的@Inject，你完全可以也写成这种manual wiring的形式（就象上面的例子一样），根本不会有侵入。Guice只不过是通过@Inject让你可以以少许的侵入换取编程的方便罢了。<br /><br />我认为比较重要的，是两者如何解决“依赖”。这个骑士的例子不存在依赖，每个bean都不需要引用其它的bean。其实这种简单的例子根本就没有意义。你说你能看出来JavaConfig比直接用裸Java配制优势在哪？就在于你最后可以用：<br /><pre name="code" class="java">Knight knight = (Knight) ctxt.getBean("knight");</pre><br />而不是简单的：<br /><pre name="code" class="java">Knight knight = new KnightModule().knight();</pre><br />显得比较sophisticated？今天我“春”了？<br /><br />然后我就去找了JavaConfig的教程，看了一下它怎么解决依赖，下面是例子：<br /><pre name="code" class="java">@Bean(scope = DefaultScopes.SINGLETON)
public Person rod() {
  return new Person("Rod Johnson");
}

@Bean(scope = DefaultScopes.PROTOTYPE)
public Book book() {
  Book book = new Book("Expert One-on-One J2EE Design and Development");
  book.setAuthor(rod());  // rod() method is actually a bean reference !
  return book;
}
</pre><br /><br />然后我就怅然太息。这就是Spring的想象力呀。还是xml那一套，不过是从：<br /><pre name="code" class="java">
&lt;property name="author" ref="rod"/>
</pre><br />变成了<br /><pre name="code" class="java">
book.setAuthor(rod());
</pre><br /><br />这到也罢了。但是看到人家自豪的宣称：“你以为rod()是个函数调用吗？哈，答错啦！扣10分。这其实是一个bean reference, 它不直接调用你简单的大脑自以为调用的rod()函数，也不要以为你在电脑前作弊，用F3/F4热键带你过去的就是正确的选项。正确的答案是这里通过Spring的魔法得到了一个singleton！强吧？”，我就吐了。<br /><br />这里的魔术，不用说，又是用了cglib，给偷偷弄了一个子类给override了——虽然我看不见这个子类。<br />但是，用了cglib，也就意味着下面几个限制：<br />1. 必须有public default constructor。<br />2. 必须不能final<br />也就是说我这个Config类不能带状态，不能给它注射依赖。这些函数虽然不能是static（否则spring的魔法就失效了）但是作用跟static method一样。白马非马吗？<br /><br />确实强悍啊。看来以后可以象oracle认证一样，弄个spring认证来骗钱好了。<br /><br />算了，不够聪明的我还是看看Guice 2.0的吧：<br /><br /><pre name="code" class="java">
@Provide @Singleton
public Person bob() {
  return new Person("crazybob");
}

@Provide
public Book book(Person author) {
  Book book = new Book("Guice!");
  book.setAuthor(author);
  return book;
}
</pre><br /><br />恩。我发现我虽然比较笨，看这个代码似乎还没有什么困难。不过是两个一点花头没有的函数，我眼睛里看见的它做了什么，它就必然做了什么，所有经典物理定律完全适用，没有任何不符合常识的东西。而且这似乎更符合"don't call us, we'll call you"的精神。也不需要担心那么多限制。比如，我可以快乐地把book title当成参数注射到这个Module里面：<br /><pre name="code" class="java">
class BookModule {
  private final String title;

  BookModule(String title) {
    this.title = title;
  }

  @Provide @Singleton
  public Person bob() {
    return new Person("crazybob");
  }

  @Provide
  public Book book(Person author) {
    Book book = new Book(title);
    book.setAuthor(author);
    return book;
  }
}
</pre><br /><br />想怎么搞就怎么搞，因为BookModule自己也不过就是一个没什么限制的Pojo。
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/139638#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sat, 10 Nov 2007 12:06:27 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/139638</link>
        <guid>http://ajoo.javaeye.com/blog/139638</guid>
      </item>
      <item>
        <title>摹客测试蛛丝程序中的绑匪</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/130982" style="color:red;">http://ajoo.javaeye.com/blog/130982</a>&nbsp;
          发表时间: 2007年10月11日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          为了响应伟大的“抵制中英文夹杂的资本主义不良思潮”运动，本文将全部用中文书写（代码暂时例外）。<br /><br />最近工作需要，发现要写代码扩展“蛛丝”来实现一个春天遗留系统的移植。基本上就是围绕着春天的“应用上下文”和蛛丝的绑匪／模块来上下其手。<br /><br />在测试的的时候，习惯性地选择用“简单摹客”来测试行为。摹客春天的应用上下文和豆子工厂真是简单啊：<br /><pre name="code" class="java">
ListableBeanFactory beanFactory = mock(ListableBeanFactory.class);
expect(beanFactory.getBeanDefinitions()).andReturn(beanNames);
replay();
...
verify();
</pre><br /><br />但是在摹客蛛丝的绑匪的时候遇到了麻烦。假设我要纪录这么一段行为期待的话：<br /><pre name="code" class="java">
binder.bind(List.class).annotatedWith(Names.named("test")).to(ArrayList.class).in(Singleton.class);
</pre><br />知道摹客的期待代码会是什么样子吗？给大家瞧瞧：<br /><pre name="code" class="java">
Binder binder = mock(Binder.class);
AnnotatedBindingBuilder annotatedBuilder = mock(AnnotatedBindingBuilder.class);
LinkedBindingBuilder linkedBuilder = mock(LikedBindingBuilder.class);
ScopedBindingBuilder scopedBuilder = mock(ScopedBindingBuilder.class);
expect(binder.bind(List.class)).andReturn(annotatedBuilder);
expect(annotatedBuilder.annotatedWith(Names.named("test"))).andReturn(linkedBuilder);
expect(linkedBuilder.to(ArayList.class)).andReturn(scopedBuilder);
scopedBuilder.in(Singleton.class);
</pre><br />哈哈，爽到了吧？这还是就对应一句蛛丝文档所谓的“嵌入式领域特殊语言”。要是多来那么几句，那才叫真爽啊。<br /><br />为了避免爽到高潮，我就写了一个小动态代理来包装简单摹客。用了这个包装之后，期待代码就变成了简单的：<br /><pre name="code" class="java">
Binder binder = mockBinder();
binder.bind(List.class).annotatedWith(Names.named("test")).to(ArrayList.class).in(Singleton.class);
</pre><br /><br />这个代理代码写起来不是很难。因为代码属于公司产权，所以就不贴了。不过，是不是只有我遇到过这种需要摹客蛛丝的绑匪的需求呢？<img src="/images/smiles/icon_wink.gif"/>
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/130982#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 11 Oct 2007 10:45:45 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/130982</link>
        <guid>http://ajoo.javaeye.com/blog/130982</guid>
      </item>
      <item>
        <title>虎父无犬女</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/111638" style="color:red;">http://ajoo.javaeye.com/blog/111638</a>&nbsp;
          发表时间: 2007年08月13日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          最近一些朋友纷纷给下一代开了blog，孩子她妈看着眼馋，急扯白脸地也给我家蓓蓓开了一个。博客刚开张，急需人捧场，孩子他爸只好风餐露宿，提心吊胆地满城找电线杆子，站牌儿贴小广告。<br /><br />某一日再次被东城城管打得满头包，被撵得惶惶如得冠军的刘翔，气喘吁吁之余忽然想起家爱这儿似乎民主得很，小报不怕乱贴，标题党不怕乱做，挖坑只怕不深，盖楼不怕违章。虽然这样一来有可能暴露俺一只猪名花有主的身份，对以后和广大文学女青年的交流合作产生不必要的障碍和误解，但是考虑到家爱大概还有很多无聊的人可以拉来帮我贴小广告，还有老庄之流树大招风，大可以树下乘乘凉，当当挡箭牌，关键时刻大义灭亲，舍卒保车。嘿嘿嘿，于是，一个邪恶的计划悄悄展开了...<br /><br /><a href="http://sophie-castle.blog.sohu.com/" target="_blank">http://sophie-castle.blog.sohu.com/</a><br /><br />喂！我说楼上楼下的兄弟们见客啦——————<br /><br />捧场捧场，大家多多捧场啊。免费茶水供应，免费摇奖。幸运者会有机会和一只猪帅猪当众深情对视。（好吧，我演对穿肠， 你扮唐伯虎，行了吧？）
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/111638#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 13 Aug 2007 05:56:51 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/111638</link>
        <guid>http://ajoo.javaeye.com/blog/111638</guid>
      </item>
      <item>
        <title>JUnit Sucks</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/109106" style="color:red;">http://ajoo.javaeye.com/blog/109106</a>&nbsp;
          发表时间: 2007年08月05日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          好象貌似有本书是用写一个JUnit作为例子来讲解TDD的。要说TDD绝对是个好东西，不过TDD本身并不能保证搞出好软件。这不，Junit就是个活生生的例子呀。<br /><br />一直以来，我写Junit+Easymock测试都是这么来的：<br /><pre name="code" class="java">
public class SomeTest extends TestCase {
   private final IMocksControl control = EasyMock.createStrictControl();
   private final Connection connection = control.createMock(Connection.class);
   @Override protected void tearDown() {
     control.verify();
     // other cleanups.
   }
   public void test1() {
      expect(connection.createStatement()).andReturn(null);
      ...
      control.replay();
      ...
   }
}
</pre><br /><br />不要细抠具体的mock用法，我这都是临时编的。但是这个测试类有两个肥肠严重的问题。说起来我一直都是这么做的，真是愧对付给我工资的那些老板们啊！<br /><br />第一个问题：<br /><br /> JUnit里面不应该用field initializer的——或者说，不能用构造函数来初始化test fixture的。当调用new TestSuite(SomeTest.class)的时候，JUnit系统会对每一个testSomething()函数调用一次构造函数，生成一个独立的test instance. 这样做，保证了每个测试的独立性，最大程度地避免了某个test case影响其它test case的出现。<br /><br />不过，“等等”，你说。这不是正好就对了么？每个test case都会调用一遍这些field initializer，没有任何问题了呀？<br /><br />是呀，所以我才这么多年一直理直气壮，厚颜无耻地用这个idiom呀，用这个idiom。直到最近，同事中的java专家告诉我，这是不安全地，不道德地，是低级趣味地！<br />因为JUnit在没有执行test case，只是构造test suite的时候，就调用了这些构造函数，这就造成了几个额外问题：<br />1。这造成TestSuite的创建不够安全，有可能出现异常——我们希望异常只出现在test case运行的时候。并且TestSuite的创建速度也有可能受到影响。<br />2。我们无法保证一个TestCase只被运行一次。比如说RepeatedTest就会多次运行一个TestCase。所以保证TestCase至少看上去是immutable的就比较重要了。<br /><br />第二个问题：<br />不应该在tearDown()里面调用verify()。如果test case失败了，首先这个verify()就没有调用的必要了。不应该用tearDown()来强制调用它。其次，如果verify()也失败（相当有可能地），那么后一个exception会冲掉前面的那个（JUnit 4里面修好了这个bug），而前面那个exception才是你真正需要的呀。<br /><br />而且，如果verify()失败了，那么后面的other cleanups也被跳过去了，而它们才是最最重要的呀。<br /><br />你说可气不可气？我还以为JUnit这么简单的东西，肯定不会用错的呢。<br /><br />但是，让我不许定义final field，让我把所有的初始化都放在setUp里面，让我在tearDown里面一个接一个地套try-finally，这，这不是代码难看的问题，这是太没人性了！<br /><br />我想啊想，终于顿悟了。这不是人民内部矛盾，而是不可调和的阶级冲突啊。<br /><br /><br />JUnit不同于TestNG, 它极度强调测试的隔离性，为此不惜禁止我们在一个测试类中共享一些有用的信息（比如，一个parse好的xml数据之类），每个test method都将使用一个单独的instance。可是，如此代价换来的居然还是一个不干不嘎的局面：我们还是不能假设一个instance只被用一次。它肯定不会被两个不同的test method使用地，——但是它可以被一个test method重复使用啊。哈，哈，哈。没想到吧？<br /><br />到了JUnit 4, tearDown的问题解决了。我可以在每个@After函数中释放各自的资源，框架会保证它们都被执行。不过，我们还是不能在@After的函数中调用verify(), 还是因为对于verify()我们并不希望在测试本身失败的时候还调用它。<br /><br />至于构造函数问题，没有任何改善，没有。同样，JUnit 4也没有正视广大人民群众对共享数据的呼声。不管TestSetup还是@BeforeClass都是要求你用evil static field。<br /><br />忍无可忍之下，我又怒了。于是自己写了一个AjooTestSuite，偷偷从TestNG搞来了几个我一直非常眼馋的annotation: @BeforeTest, @AfterTest, @BeforeSuite, @AfterSuite, @ExceptionExpected。又自己填了两个@Verify和@Shared。前者用来在测试没出问题的情况下做一个公用的verify，比如verify mock object；后者用来在test case之间共享数据。一个使用AjooTestSuite的测试类可以这么写：<br /><br /><pre name="code" class="java">
public class SomeTest extends TestCase {
  public static Test suite() {
    return new AjooTestSuite(SomeTest.class);
  }
  
  // 哈哈。终于可以用final和initializer了！
  private final IMocksControl control = EasyMock.createStrictControl();
  private final Connection conn = control.createMock(Connection.class);

  @Verify
  public void verifyMocks() {
    control.verify();
  }

  @Shared
  public static XmlObject provideXml() {
    // ... read xml file and parse.
    return xmlObj;
  }
  
  private final XmlObj;

  // the shared xmlObj will be injected for each test case.
  public SomeTest(String name, XmlObject obj) {
    super(name);
    this.xmlOj = obj;
  }

  @BeforeSuite
  public static void initialize() {
    // some suite level initialization. No more TestSetup!
  }
  @AfterSuite
  public static void deinitialize() {
    // suite level deinitialization.
  }
  @BeforeTest
  public void setupMocks() {
    expect(conn.createStatement()).andReturn(null);
  }
  @ExceptionExpected(NullPointerException.class)
  public void test1() {
    throw new NullPointerException();
  }
}
</pre><br /><br /><br />写完之后，我这个得意呀。终于不用忍受JUnit屎一样的限制了。构造函数只有在测试运行的时候才调用，换句话说，这回test case是绝对immutable的了，绝对线程安全。耶！<br /><br />除此之外，还加上了一些流行的annotation。@BeforeTest, @AfterTest, @BeforeSuite, @AfterSuite，@ExceptionExpected不用说，都跟TestNG一样的语义。@Verify用来搞类似verify mock之类的事情；@Shared标注的静态函数在每次test suite执行的时候会被调用一次，返回的数据会被保存并被注射给每一个test case instance。<br /><br />完美呀，完美。这样一来，也不用转移到TestNG了，也不用忍受一些工具集成的问题升级到JUnit4了。就是一个新的TestSuite而已，100％向后兼容。<br /><br />可是，事实证明，我说的太早了。我的java专家同事又给我泼了一瓢凉水：<br /><br />现在，你在Eclipse/Intellij里面点运行，没问题，它会知道去寻找suite()函数，然后用你的自定义test suite。可是，如果你然后点某一个单独的test case, 比如test1, 然后说"run"。会发生什么哩？嘿嘿，它不再去找你的suite()函数啦，啦，啦，啦，啦（回声逐渐消失）。它直接跑到你的类里面去找这个函数来调用了。Surprise!<br /><br />天啊。该死的Eclipse, 它难道不会调用suite(), 然后在suite()里面找么？<br /><br />可仔细想想，又不是IDE的错。即使它调用suite()又如何？没有一个TestSuite.getTest(String name)的API供它调用啊。实际上，JUnit的TestCase也并不强制保证每个Test都有一个id的。<br /><br />于是，我三天的工作白费了。呜呜呜！<br /><br />总而言之，言而总之，千言万语，咬牙切齿汇成一句话：JUnit Sucks!
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/109106#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sun, 05 Aug 2007 10:16:10 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/109106</link>
        <guid>http://ajoo.javaeye.com/blog/109106</guid>
      </item>
      <item>
        <title>什么时候使用assumption？</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/54689" style="color:red;">http://ajoo.javaeye.com/blog/54689</a>&nbsp;
          发表时间: 2007年02月16日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          问题背景，定义三个概念先：<br />1。percent。就是百分比。<br />2。weight。如果percent是10，weight就是0.1，weight=percent/100.<br />3。amount。如果percent是10，总数是1000，那么amount就是100. amount=total*weight.<br /><br />在程序中，我们很多时候需要在amount, weight, percent之间来回转换。根据不同模块的需要，把同一个数据点转换成percent或者weight或者amount。<br /><br />目标是设计一个类Portion，它的接口如下：<br /><pre name="code" class="java">
public class Portion {
  public double getPercent();
  public double getWeight();
  public double getAmount();
  public double getTotal();
  public static Portion fromPercent(double total, double percent);
  public static Portion fromWeight(double total, double weight);
  public static Portion fromAmount(double total, double amount);
}
</pre><br />另外，程序中还使用这样一个习惯：如果total为0，那么从amount计算percent和weight的时候也是0。（以避免divide by 0）<br /><br /><br />我的pair想这样实现：<br /><pre name="code" class="java">

public class Portion {
  private final double total;
  private final double amount;
  Portion(double total, double amount) {
    this.total = total;
    this.amount = amount;
  }
  public double getPercent() {
    return getWeight()*100;
  }
  public double getWeight() {
    return total==0?0:amount/total;
  }
  public double getAmount() {
    return amount;
  }
  public double getTotal() {
    return total;
  }
  public static Portion fromPercent(double total, double percent) {
    return new Portion(total, total*percent/100);
  }
  public static Portion fromWeight(double total, double weight) {
    return new Portion(total, total*weight);
  }
  public static Portion fromAmount(double total, double amount) {
    return new Portion(total, amount);
  }
}
</pre><br /><br />我对这个设计是不满意的。我提出了几点问题：<br />1。假设在某一点total不可知，那么怎样用Portion来封装唯一可知的percent/weight？Portion.fromWeight(0, 0.1).getPercent()在这个设计中不会返回我期待的10，而是0。<br />2。double是有误差的。如果客户写一个<br /><pre name="code" class="java">
assertEquals(weight, Portion.fromWeight(777, weight).getWeight(), 0)</pre><br />居然也要失败，不免尴尬。<br /><br />所以我提议这样做：<br /><pre name="code" class="java">
public class Portion {
  private final double total;
  private final double amount;
  private final double percent;
  private final double weight;
  Portion(double total, double amount, double percent, double weight) {
    this.total = total;
    this.amount = amount;
    this.percent = percent;
    this.weight = weight;
  }
  public double getPercent() {
    return percent;
  }
  public double getWeight() {
    return weight;
  }
  public double getAmount() {
    return amount;
  }
  public double getTotal() {
    return total;
  }
  public static Portion fromPercent(double total, double percent) {
    return new Portion(total, total*percent/100, percent, percent/100);
  }
  public static Portion fromWeight(double total, double weight) {
    return new Portion(total, total*weight, weight*100, weight);
  }
  public static Portion fromAmount(double total, double amount) {
    double weight = total==0?0:amount/total;
    return new Portion(total, amount, weight*100, weight);
  }
}
</pre><br />这样，虽然double计算有误差，我至少可以保证Portion.fromWeight(total, weight).getWeight()永远等于weight。<br /><br />但是，pair对这种设计感觉不舒服，他不喜欢把逻辑放在静态工厂方法中。<br />而且，pair对我提出的几个他的方案的缺陷如此回应：<br />1。total为0的情况本身就特殊。根据整个系统目前的情况，getWeight()和getPercent()都返回0可以接受。<br />2。我不care是否有误差，反正我们现在比较double的时候都是用一个tolerance的。<br /><br /><br />我的pair对整个系统了解的比我多，我相信他说的话。只不过，要我做一个单独的小模块，却总要依赖于外部系统的“对特殊情况A，我们可以容忍；对情况B，我们不在乎”这些assumption，让我深藏于心底的洁癖非常不舒服。<br /><br />我并不是绝对排斥使用适当的assumption来简化实现复杂度。但是当两个方案的复杂度相近时，我很反感把外界的一些assumption引进来。<br /><br />那么，你怎么想？
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/54689#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 16 Feb 2007 19:20:49 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/54689</link>
        <guid>http://ajoo.javaeye.com/blog/54689</guid>
      </item>
      <item>
        <title>DRY与简单性的平衡</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/54686" style="color:red;">http://ajoo.javaeye.com/blog/54686</a>&nbsp;
          发表时间: 2007年02月16日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          这个事例说起来相当简单。不过小中见大，它大致体现了我和pair在DRY vs. 简单性上的差别，和那个“这样代码重用”里面的例子体现了同样的分歧。<br /><br />目标是重构下面的测试代码：<br /><pre name="code" class="java">
public void test1() {
  Account acct = new Account();
  acct.setName("test");
  acct.setType(TypeEnum.Type1);
  acct.setActive(true);
  Result result = runSomeApi(acct);
  assertEquals("test", result.getName());
  assertTrue(result.isActive());
  ResultType[] arr = result.getTypes();
  assertEquals(1, arr.length);
  assertEquals("type1", arr[0].getName());
}

public void test2() {
  Account acct = new Account();
  acct.setName("test");
  acct.setType(TypeEnum.Type2);
  acct.setActive(true);
  Result result = runSomeApi(acct);
  assertEquals("test", result.getName());
  assertTrue(result.isActive());
  ResultType[] arr = result.getTypes();
  assertEquals(1, arr.length);
  assertEquals("type2", arr[0].getName());
}
public void testNull(){
  Account acct = new Account();
  acct.setName("test");
  acct.setType(null);
  acct.setActive(true);
  Result result = runSomeApi(acct);
  assertEquals("test", result.getName());
  assertTrue(result.isActive());
  ResultType[] arr = result.getTypes();
  assertEquals(2, arr.length);
  assertEquals("type1", arr[0].getName());
  assertEquals("type2", arr[1].getName());
}
</pre><br /><br />首先通过发现重复，我和pair都同意下面的重构：<br /><pre name="code" class="java">
private String name = "test";
private getResult(TypeEnum type) {
  Account acct = new Account();
  acct.setName(name);
  acct.setType(type);
  acct.setActive(true);
  return runSomeApi(acct);
}
public void test1() {
  Result result = getResult(TypeEnum.Type1);
  assertEquals(name, result.getName());
  assertTrue(result.isActive());
  ResultType[] arr = result.getTypes();
  assertEquals(1, arr.length);
  assertEquals("type1", arr[0].getName());
}

public void test2() {
  Result result = getResult(TypeEnum.Type2);
  assertEquals("test", result.getName());
  assertTrue(result.isActive());
  ResultType[] arr = result.getTypes();
  assertEquals(1, arr.length);
  assertEquals("type2", arr[0].getName());
}
public void testNull(){
  Result result = getResult(null);
  assertEquals(name, result.getName());
  assertTrue(result.isActive());
  ResultType[] arr = result.getTypes();
  assertEquals(2, arr.length);
  assertEquals("type1", arr[0].getName());
  assertEquals("type2", arr[1].getName());
}
</pre><br /><br /><br />分歧来自于下一步，我认为重构到下面这样就够了：<br /><pre name="code" class="java">
private String name = "test";
private getResult(TypeEnum type) {
  Account acct = new Account();
  acct.setName(name);
  acct.setType(type);
  acct.setActive(true);
  return runSomeApi(acct);
}
private void assertInvariants(Result result) {
  assertEquals("test", result.getName());
  assertTrue(result.isActive());
}
private void verifyWhenTypeIsNotNull(String expectedType, TypeEnum type){
  Result result = getResult(type);
  assertInvariants(result);
  ResultType[] arr = result.getTypes();
  assertEquals(1, arr.length);
  assertEquals(expectedType, arr[0].getName());

}
public void test1() {
  verifyWhenTypeIsNotNull("type1" TypeEnum.Type1);
}

public void test2() {
  verifyWhenTypeIsNotNull("type2", TypeEnum.Type2);
}
public void testNull(){
  Result result = getResult(null);
  assertInvariants(result);
  ResultType[] arr = result.getTypes();
  assertEquals(2, arr.length);
  assertEquals("type1", arr[0].getName());
  assertEquals("type2", arr[1].getName());
}
</pre><br />而pair重构到这一步后，仍然觉得不够DRY，坚决推进下一步的重构：<br /><pre name="code" class="java">
private String name = "test";
private getResult(TypeEnum type) {
  Account acct = new Account();
  acct.setName(name);
  acct.setType(type);
  acct.setActive(true);
  return runSomeApi(acct);
}
private void verify(String[] expectedTypes, TypeEnum type){
  Result result = getResult(type);
  assertEquals("test", result.getName());
  assertTrue(result.isActive());
  ResultType[] arr = result.getTypes();
  assertEquals(expectedTypes.length, arr.length);
  for(int i=0; i&lt;expectedTypes.length; i++) {
    assertEquals(expectedTypes[i], arr[i]);
  }
}
public void test1() {
  verify(new String[]{"type1"} TypeEnum.Type1);
}

public void test2() {
  verify(new String[]{"type2"}, TypeEnum.Type2);
}
public void testNull(){
  verify(new String[]{"type1", "type2"}, null);
}
</pre><br /><br />我的观点：<br />最后这一步的重构，是以简单性换取非常细微的DRY，并不划算。最好的情况，也就是和重构前半斤八两，所以不值得投入时间来做。而重构前的代码更能灵活适应变化。<br /><br />比如，经过分析，我们完全可以仅assertEquals(2, arr.length)而不用分别对每个元素进行assert了（同样的逻辑在别的测试中已经覆盖了，而且，测试代码决定"type1", "type2"的顺序也加大了测试和被测试代码的耦合，这个顺序本来是无所谓的。）这个变化在重构前很容易，只要从testNull()里面删掉两行assertEquals就可。而重构后的代码则需要更复杂的逻辑控制才能达到这个目标。<br /><br /><br /><br />那么，你怎么看？
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/54686#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 16 Feb 2007 18:45:57 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/54686</link>
        <guid>http://ajoo.javaeye.com/blog/54686</guid>
      </item>
      <item>
        <title>Web AOP?</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/54657" style="color:red;">http://ajoo.javaeye.com/blog/54657</a>&nbsp;
          发表时间: 2007年02月16日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          今天这个其实不是争论。<br /><br />这是我接手的一个web系统的一个设计。我觉得很不爽，但是一时又没有好的解决方法。<br /><br />情况是这样的。<br /><br />我们的web app是一个传统的jsp+controller+dao的设计（Controller用的是我们元老自己设计的一个框架）。<br /><br />这个app我们叫做product。<br /><br />除此之外，我们还有一个定制版本的app。这个定制版本是给某个客户定制的。功能和product大同小异。但是有些小的地方的业务逻辑或者web页面会有些区别。（比如说某个提示信息不同，或者多出或者少一个text box之类的）<br /><br /><br />大家知道jsp的复用不是很容易的。而这个定制版本和product的区别完全都是这个特定客户决定的，并没有可以实现预测的比较系统的规则。<br /><br /><br /><br />现在的解决方法，是在product里面增加一个叫做HtmlManipulator的接口：<br /><pre name="code" class="java">
interface HtmlManipulator {
  String manipulate(String html);
}
</pre><br /><br />然后通过一个ChainManipulator把一个数组里的HtmlManipulator串接起来。在product里面这个manipulator的数组是空的。而在定制版本里面，则是通过写各种HtmlManipulator来实现订制，比如下面的伪代码：<br /><pre name="code" class="java">
class ChangeUserMessageManipulator implements HtmlManipulator {
  public String manipulate(String html) {
    range = find range of element("user message");
    return replaceRangeWith(html, range, "this is the new user message");
  }
}
</pre><br /><br />也就是说，所有的定制都是通过拦截并且篡改product生成的html文本来实现的。<br />是不是看起来非常象基于web页面的aop？<br /><br />我很不喜欢现在这个方法。<br />效率是一方面。<br />这个方法感觉非常原始，而且很多这种manipulator工作都是依赖于html里面的某个特征字符串，理论上如果数据库里面储存了一个“user message”然后被product写入html，那么上面的代码就会失效。<br /><br />不过，至少在不彻底推翻现有框架的基础上，我想不出什么好方法。<br /><br />独立维护product/定制两套代码是不可接受的。<br /><br />感觉也许需要采用基于web组件的技术才行。（不熟悉tapestry，不知道是不是合适）。<br />这两天发现了一个StringTemplate，看来似乎也是一个可能的方向。<br /><br />聪明如你，有什么好的方法么？
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/54657#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 16 Feb 2007 11:12:00 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/54657</link>
        <guid>http://ajoo.javaeye.com/blog/54657</guid>
      </item>
      <item>
        <title>依赖是否可以作为一个独立的衡量软件质量的标准？</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/53874" style="color:red;">http://ajoo.javaeye.com/blog/53874</a>&nbsp;
          发表时间: 2007年02月12日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          这个争论的背景有点复杂。我就尽量简化了说。<br /><br />遗留系统有一个自制的service locator。是一个静态函数：<br /><pre name="code" class="java">
public static Object newObject(Class interfaceOrDefaultClass, Class[] parameterTypes, Object[] arguments);
</pre><br />使用起来是这样：<br /><pre name="code" class="java">
ImplFactory.newObject(MyInterface.class, new Class[]{int.class, String.class}, new Object[]{new Integer(1), "abc"});
</pre><br />这个函数会根据一个properties文件的配置来寻找一个带有制定构造函数的实现或者继承MyInterface的类。<br />比如，如果配置文件里面配置了<br /><pre name="code" class="java">
com.mycompany.MyInterface=com.mycompany.MyInterfaceImpl
</pre><br />而MyInterfaceImpl有这个构造函数：<br /><pre name="code" class="java">
public MyInterfaceImpl(int i, String s);
</pre><br />那么，MyInterfaceImpl就会被使用。<br /><br />而如果调用的时候用了一个缺省类，那么如果配置文件没有配置，就会使用这个缺省类，比如：<br /><pre name="code" class="java">
ImplFactory.newObject(DefaultMyInterfaceImpl.class, new Class[]{int.class, String.class}, new Object[]{new Integer(1), "abc"});
</pre><br /><br />这个自制的service locator无疑是非常原始，也是很难用的。为了准备向一个真正的ioc container过渡，我实现了一个dynamic proxy来封装ImplFactory。<br /><br />使用起来如下：<br /><pre name="code" class="java">
MyInterface = serviceFactory.getMyInterface(1, "abc");
</pre><br />使用者只需要把getMyInterface这个函数声明在ServiceFactory这个接口中，就可以直接使用getMyInterface()函数了。当然，这个serviceFactory是作为倚赖被注射进客户类的。以后，如果我们切换成了ioc容器，比如spring，只要增加一个dynamic proxy就行了，客户代码基本不用动。<br /><br />我的同事（ImplFactory就是他亲手做的），对这个ServiceFactory不是太感冒。他认为这样做明显的好处就是一个语法糖，语法漂亮一点而已。而因为ImplFactory本身已经是个抽象，ServiceFactory又是包在ImplFactory外面的抽象，那么"abstraction on top of abstraction"就显得多余，或者说过度设计。我对这样一个在我看来毫无争议的问题有点不知道怎么说，不过我跟他说这样以后可以轻易地转移到别的ioc container上面，才说服了他。<br /><br /><br />ImplFactoryProxy具体怎么实现的，只要懂dynamic proxy的都会，我就不赘述了。<br /><br /><br /><br />这个实现里面，有一个问题，就是怎么处理这个“缺省实现类”。在原来的代码里面，缺省实现类是硬编码在客户程序里的。而现在的用法里面，客户不能制定缺省实现类，所以这个信息需要额外提供给这个dynamic proxy。<br /><br />我的做法仍然是注射，通过注射一个java.util.Map对象到这个ImplFactoryProxy类，来给这个类提供缺省实现信息。签名如下：<br /><pre name="code" class="java">
public class ImplFactoryProxy {
  private final Map defaults;
  ImplFactoryProxy(Map defaults) {
    this.defaults = defaults;
  }
  ...
}
</pre><br /><br />写好了ImplFactoryProxy之后，我面临的下一个问题是怎么得到这个Map。我的做法是通过ClassLoader.loadResourceAsStream()来读入一个存在当前package里面的properties文件，然后调用Properties.load(inputStream)来得到Properties，这个Properties对象自然就可以注射进ImplFactoryProxy了。<br /><br />至此，希望你会说：没什么亚。大家都是这么做的。<br /><br /><br />不过，问题来了。<br />我的那个老资格的同事在review代码的时候看到这个ClassLoader.loadResourceAsStream，说：<strong>这个不行，我希望你改成用我们的PropFactory框架。</strong><br /><br />话说，这个遗留系统有一个相当强大（或者说复杂？）的读取property的框架，这个框架除了一般的按照key读取value，还支持树形的property，也就是说，一个key可能对应一个子property map。（当然，还有其他功能）<br /><br />用法是：<br /><pre name="code" class="java">
PropFactory.getInstance().getProperty("property file name").getPropertyValue("key");
</pre><br /><br />这样的代码充斥整个code base。<br /><br /><br />我对这个框架的态度是相当保留的。主要的原因是我认为大多的配置值应该通过注射，而不是主动地去找PropFactory框架要。这种PropFactory.getInstance()的代码使单元测试变得困难，而且加大了系统耦合，随便一个模块就要依赖于PropFactory。<br /><br />而我在我的dynamic proxy中不使用PropFactory，除了上面的原因，还有以下几点：<br />1。我要的就是一个简单的key-value map。根本不需要PropFactory提供的那么多功能。<br />2。Properties, ClassLoader.loadResourceAsStream都是标准jdk的东西，用起来也不难。而且我也做了一个IOUtils类来封装这部分代码：<br /><pre name="code" class="java">
IOUtils:
  Properties loadProperties(ClassLoader loader, String resourceName);
</pre><br />3。我这个dynamic proxy相当的self contained。它基本上和现有的遗留系统除了ImplFactory没有任何其它关联。我也不希望引入任何不必要的依赖。<br /><br />但是，显然，这些观点在同事那里是站不住脚的：<br />1。不管你需不需要额外的功能，你直接调用这个api就好了。又不需要额外写代码。<br />2。项目中大家都使用PropFactory来读取配置。如果大家你写一个ajoo way of reading property，他写一个bjoo way of reading property，那不是乱套了？这样做破坏了一致性，增大了团队协作的难度。<br />3。依赖就依赖了，有什么关系？这个几乎是公司内部的事实标准了。大家都这么用，还是头回听说有人对这个依赖有问题的。<br />4。如果不用PropFactory，谁能保证你的代码就在任何情况都工作？这个项目的build process, deploy process都很复杂，难以预测你这个代码在nightly build甚至在生产环境中也会工作。<br /><br /><br />对此，当然我是不同意的。我认为，ClassLoader.loadResourceAsStream几乎是工业界的标准，相比于一个公司自制的标准，我还是更倾向于相信被无数人证明工作的业界标准。<br />而且，试图让PropFactory包打一切也是不现实的。最起码，你用的commons logging, log4j等等开源库，都不可能依赖于你自己写的PropFactory，它们只能使用ClassLoader。所以这个一致性从一开始就不存在。<br />最后，我认为“一致性”在项目中被错误解读了。打个比方，项目中大家一致都是用jsp，但是我不认为当只需要一个servlet或者甚至一个静态的html的时候，我们也必须要为了一致性通过jsp来绕一圈生成这个servlet或者html.<br /><br /><br /><br />经过争论，我还是做了妥协，只要我可以注射Map，你非要用in house framework而不是业界标准来读文件，也罢。<br /><br />于是我的方案变成：<br /><pre name="code" class="java">
new ImplFactoryProxy(PropFactory.getInstance().getProperty("defaults.properties").toProperties())
</pre><br />这个toProperties()是PropFactory框架提供的一个函数，可以把我们inhouse的IProperties转换成java.util.Properties。<br /><br />最终面向用户的接口（在没有采用ioc容器的情况下，只好还是允许客户代码主动取得ServiceFactory实例。）是：<br /><pre name="code" class="java">
public class ServiceFactoryUtil {
  public static ServiceFactory getServiceFactory();
}
</pre><br />这个类负责调用PropFactory.getInstance()，并且提供singleton服务以避免重复读取properties文件。<br /><br /><br />这几乎是我可以接受的底线了。<br /><br /><br />但是，同事仍然不满意。他最喜欢的是我不去注射Map，而是在ImplFactoryProxy内部直接调用PropFactory。这个在我来说是不可考虑的。<br /><br />当然，他也退了一步，同意我的注射方式。但是认为我不应该注射java.util.Map，而应该注射IProperties。<br /><br />同事的理由：<br />1。这样可以避免一个toProperties()调用。别人读代码的时候，可以不会纳闷“为什么这里要这么做？”。<br />2。java.util.Map这个接口太肥大。我需要的其实就是一个get()，最多加上keySet()和containsKey()，用一个java.util.Map不合适。<br /><br /><br />而我反对使用IProperties，理由是：<br />1。IProperties不是标准接口，我宁愿以来jdk标准接口。毕竟熟悉java.util.Map的比IProperties多多了吧？即使IProperties是在公司内部被“一致”使用。<br />2。没人会纳闷为什么要调用toProperties()。如果这都要纳闷，那么整个遗留系统的那数万行的代码就没法读了。<br />3。如果说java.util.Map不是最小接口，IProperties也不是。它也有一些我不需要的函数。<br />4。整个公司从来没有人用注射的方式来使用IProperties。大家都是PropFactory.getInstance()这样从头调用的。难保注射IProperties不会出什么问题。（比如，同步问题？后来，虽然同步问题没法验证，我确实发现了IProperties不支持Serializable，致使我的ServiceFactory也不能Serializable。这样类似的问题，如果真是采用了IProperties，还不知道会不会陆续向外蹦呢）。<br /><br />最终，因为我是这个功能的开发者，还是以我的意见为主了。但是我并没有说服同事。在争论过程中，让我深感郁闷的是，我的“减小依赖”的论点根本不为同事接受，似乎在他们看来“依赖”并不能作为一个理由。而我也发现要把减小依赖和他们接受的DRY，unit test等原则直观地联系起来不太容易。<br /><br />那么，你是怎么看这个问题的呢？
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/53874#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 12 Feb 2007 05:03:50 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/53874</link>
        <guid>http://ajoo.javaeye.com/blog/53874</guid>
      </item>
      <item>
        <title>这样代码重用？</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/53872" style="color:red;">http://ajoo.javaeye.com/blog/53872</a>&nbsp;
          发表时间: 2007年02月12日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          这是一个工作中遇到的背景比较简单的争论。<br /><br />有这么一个persistent object，姑且叫它Plan吧。<br /><br />有这么两个函数：<br /><br /><pre name="code" class="java">
Plan getPlanByName(String userid, String planName);
Plan[] getPlans(String userid);
</pre><br /><br />getPlanByName内部执行的是：<br /><pre name="code" class="java">
select * from Plan where userid=#userid# and plan_name=#planName#
and status=1
order by order_num
</pre><br />getPlan的内部执行的是：<br /><pre name="code" class="java">
select * from Plan where userid=#userid# and status=1
</pre><br /><br />现在，我的pair认为这里面有DRY violation。因为两个select有些重复的东西。pair认为可以这样重构：<br /><pre name="code" class="java">
Plan getPlanByName(String userid, String planName){
  Plan[] plans = getPlans(userid);
  for(int i=0; i&lt;plans.length; i++) {
    Plan plan = plans[i];
    if(planName.equals(plan.getPlanName())) {
      return plan;
    }
  }
  return null;
}
</pre><br /><br />而我并不认为应该这样做。我的理由是：<br />1。原来的实现很简单直观。sql本来就是声明式语言。放着简洁的声明式不用而用复杂的命令式，有走回头路的嫌疑。<br />2。重构之后代码量更多，还要写更多的单元测试。<br />3。两个select只见的共同之处更像一种偶然的而不是概念上的重复。让getPlanByName依赖于getPlan感觉增大了耦合。<br />4。真要觉得select里面的东西有重复，不如创建一个view: v_plans这样这两个select就变成：<br /><pre name="code" class="java">
select * from v_plans where userid=#userid#
select * from v_plans where userid=#userid# and plan_name=#planName#
</pre><br />避免了status=1的重复。虽然还有"select *"之类的重复，但是这种重复就是语法上的，就像我们在java程序里面可能写无数次"static public void"，我们从来不认为这是一个值得改变的重复。<br />5。效率。<br />6。系统本来是工作的。即使重构后确实好一点，也不值得花这个工作量。<br /><br />而pair的主要观点是:<br />重构后维护上简单，不用维护两个select。<br /><br />后来和组里其他人沟通，发现持重构想法的不只一个人，相对来说我的观点是比较孤立的。<br /><br />那么你是怎么看这个问题？
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/53872#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 12 Feb 2007 03:09:37 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/53872</link>
        <guid>http://ajoo.javaeye.com/blog/53872</guid>
      </item>
      <item>
        <title>jdbc还是ibatis？</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/52448" style="color:red;">http://ajoo.javaeye.com/blog/52448</a>&nbsp;
          发表时间: 2007年02月06日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          公司的一个大系统的持久层一直是直接使用jdbc。在jdbc的基础上，又自制了一个简陋的cache。<br /><br />每个持久功能的实现都比较类似，大致相当于这样：<br /><pre name="code" class="java">
MyProprietaryConnection conn = ConnectionManager.checkOut(Database.DB_NAME);
try {
  PreparedStatement stmt = conn.getPreparedStatement("some statement id, identifying a sql statement in an xml file");
  stmt.setString(1, "param 1");
  stmt.setInt(2, param2);
  ...
  try {
    ResultSet resultSet = stmt.executeQuery();
    try{
      while(resultSet.next()) {
        ...
      }
    }
    finally {
      resultSet.close();
    }
  }
  finally {
    stmt.close();
  }
}
finally {
  ConnectionManager.checkIn(conn);
}
</pre><br />当然，各个功能的实现不完全一样，有的有事务，有的没有；有的忘了关闭statement，有的忘了checkIn connection；有的在出现Error的时候忘了rollback。等等等等。<br /><br />dao层的代码就是调用这些不同的jdbc代码，然后再包上一层HashMap做cache:<br /><pre name="code" class="java">
Object cacheKey = ...;
synchronized(cache) {
  Account acct = (Account)cache.get(cacheKey);
  if(acct == null) {
    acct = runJdbcForAccount(...);
    cache.put(cacheKey, acct);
  }
  return acct.cloneAccount();
}
</pre><br />当然，还要自己实现cloneAccount()。<br />所有对Account, Contribution, Plan之类的cache代码也类似。<br /><br />后来鉴于偶尔出现资源泄漏问题，一个程序员写了一个jdbc模板，长成这个样子：<br /><pre name="code" class="java">
abstract class PersisterCommand {
  protected abstract void populateStatement(PreparedStatement stmt);
  protected abstract Object processResult(ResultSet resultSet);
  protected abstract boolean isTransactional();
  protected abstract PreparedStatement getStatement();
  public Object run() {
    MyProprietaryConnection conn = ConnectionManager.checkOut(Database.DB_NAME);
    try {
      PreparedStatement stmt = getStatement();
      populateStatement(stmt);
      ...
      try {
        if(isTransactional()) {
          conn.startTransaction();
        }
        ResultSet resultSet = stmt.executeQuery();
        try{
          Object result = processResult(resultSet);
          if(isTransactional()) {
            conn.commitTransaction();
          }
          return result;
        }
        catch(Exception e){
          if(isTransactional()) conn.rollbackTransaction();
          throw e;
        }
        finally {
          resultSet.close();
        }
      }
      finally {
        stmt.close();
      }
    }
    finally {
      ConnectionManager.checkIn(conn);
    }
  }
}
</pre><br />然后上面的代码可以简化为仅仅重载这四个抽象函数：<br />getStatement负责取得某个特定的sql statement；populateStatement负责填充参数；processResult负责把ResultSet转换成domain object；isTransactional制定是否使用事务。<br /><br />介绍了这么多背景情况，希望你已经看到了，原来的直接jdbc方法是非常繁琐，容易出错，代码量大，而且重复很多。<br />这个PersisterCommand也有很多局限：<br />1。它只能处理一个connection一个statement，不能做batch。<br />2。它在Error出现的时候没有rollback。<br />3。子类仍然要针对jdbc api写一些有重复味道的代码。<br />4。代码不容易单元测试。因为ConnectionManager.checkOut()和ConnectionManager.checkIn()都是写死的。<br /><br /><br />另外，这个自制的cache也是一个重复代码的生产者。<br /><br />针对这种情况，我本来想继续重构，弄出一个CacheManager和更灵活的jdbc模板。但是后来一想，倒还不如直接用ibatis来得好。毕竟ibatis已经是被业界广泛使用的工具，总比自己制造轮子强。而且，相比于hibernate，ibatis也有更贴近我们现在的模型和使用习惯的优势。<br /><br /><br />我的一个同事（公司的元老），开始是对ibatis很感兴趣的。<br /><br />可惜的是，当我完成了ibatis的集成，他试用了一下之后就改变了主意。这个同事在项目组甚至整个公司说话都是很有分量的，不说服他，推广ibatis就面临夭折的可能。<br /><br />我的ibatis的集成长成这个样子：<br /><pre name="code" class="java">
public interface Action {
  Object run(SqlMapSession session);
}
public class IbatisPersistence {
  public SqlMapSession openSession();
  public Object queryForObject(String key);
  public List queryForList(String key);
  public int update(String key, boolean useTransaction);
  public int delete(String key, boolean useTransaction);
  public int update(String key);
  public int delete(String key);
  public Object run(Action action);
}
</pre><br /><br />这样，除非用户代码调用openSession()，其它的函数都自动处理了Session的关闭。事务处理用一个boolean参数来控制也相当简单。<br /><br />上面的那么多jdbc代码和cache代码最终就可以直接变成：<br /><pre name="code" class="java">
Accunt acct = persistence.queryForObject("getAccountById", accountId);
</pre><br /><br />那么同事对这个东西的意见在哪里呢？<br />1。他和另外一个同事为调试一个使用了ibatis的程序bug花了一天时间。后来把ibatis删掉，直接用jdbc就修好了。<br />当时我在休假，回来后一看，这个bug首先是一个stored proc的bug。他们花很多时间在ibatis里面找问题其实都是瞎耽误工夫；其次，在他们到处找ibatis的问题的时候，注释掉了两行关键代码，后来忘了放回来，所以才发生stored proc修好后，ibatis代码还是不工作，直到换了jdbc才修好。<br />虽然我解释了原因，同事坚持认为ibatis过于复杂。如果它花了他这么长时间来debug，别人也有可能因为种种原因花很多时间来debug别的问题。<br />2。ibatis只支持一个参数。这个我也解释了，你可以用java bean或者Map。可是同事认为这也是ibatis的学习曲线问题。如果采用，就要求大家都去学ibatis doc才行。<br />3。同事原来期待的是象Active Record那样的革命性的提高和简化。象ibatis这样还是要进行手工mapping的，对他来说就是没什么太大意义。他不觉得在java里面做这个mapping有什么不好。（我想，也许Hibernate对他更有吸引力。不过把这个系统转换为Hibernate这工作量可大多了）<br />4。当我说ibatis可以节省很多资源管理的重复代码时，同事说他可以用PersisterCommand。我说PersisterCommand的这些局限性的时候，他说，他不在乎。大不了直接写jdbc。<br />5。一致性问题。如果同时用jdbc和ibatis，大家就要学两个东西，造成混淆。而如果要把所有东西都换成ibatis，工作量是一个方面，所有的人都要学习ibatis这个代价也是很大的。<br />6。同事认为cache的那点重复代码无所谓。即使有一些降低cache hit ratio的bug也不是什么大不了的。<br /><br />最后无法达成统一意见。因为你说什么优点的时候，他只要一句“我不在乎”你就无话可说了。<br /><br /><br />在这些论点里面，我也认可ibatis的学习曲线和一致性问题。可是，总不能就永远任由这个持久层代码这么滥下去吧？在java这个领域里，我几乎完全相信不可能出现Active Record等价的东西的。而无论Hibernate还是jpa，只怕都是有不下于ibatis的学习曲线和更高的从遗留系统移植的代价吧？<br /><br />越来越感觉自己不是一个合格的architect。因为我缺乏说服人的能力。<br /><br />你怎么看这个问题呢？
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/52448#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 06 Feb 2007 03:59:08 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/52448</link>
        <guid>http://ajoo.javaeye.com/blog/52448</guid>
      </item>
      <item>
        <title>一致性和Use Right Tool For Right Job哪个重要？</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/52289" style="color:red;">http://ajoo.javaeye.com/blog/52289</a>&nbsp;
          发表时间: 2007年02月05日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          这个争执发生在测试的时候。<br />背景是这样的：<br />一个接口有很多乱七八糟的业务相关的方法，其中有这么四个方法：<br /><pre name="code" class="java">
interface TaxLawBuckets {
  double getRemaining401k();
  double getRemaining403g();
  void apply401k(double amount);
  void apply403g(double amount);
}
</pre><br />当然，这个设计有点不是很好。更好的也许是：<br /><pre name="code" class="java">
interface TaxLawBuckets {
  TaxLawBucket get401k();
  TaxLawBucket get403g();
}
interface TaxLawBucket {
  double getRemaining();
  void apply(double amount);
}
</pre><br />不过，因为种种原因，无法采用这个设计（主要是这么一改，改动会比较大，这个系统里面有一个wsdl代码生成器，这个生成器对接口长的样子有变态的要求）。所以就凑合着把。<br /><br /><br />现在要制作一个PerAccountTaxLawBuckets类，这个类会对401k和403g有额外的逻辑处理：<br /><pre name="code" class="java">
interface TaxLawBuckets {
  private double bucket_401k;
  private double bucket_403g;
  private TaxLawBuckets globalBuckets;

  public double getRemaining401k(){
    return Math.min(bucket_401k, globalBuckets.getRemaining401k());
  }
  public double getRemaining403g() {
    return Math.min(bucket_403g, globalBuckets.getRemaining403g());
  }
  public void apply401k(double amount) {
    bucket_401k -= amount;
    if(bucket_401k&lt;0) throw new SystemException(...);
    globalBuckets.apply401k(amount);
  }
  public void apply403g(double amount) {
    bucket_403g -= amount;
    if(bucket_403g&lt;0) throw new SystemException(...);
    globalBuckets.apply403g(amount);
  }
  //所有其他的函数都直接委托给globalBuckets。
}
</pre><br />恩。有些代码重复。主要还是因为这个接口设计的不好。好在重复不多，凑合吧。<br /><br />下面要写测试代码了。先看怎么写针对401k的代码。pair用的是jmock。<br /><pre name="code" class="java">
public class PerAccountTaxLawBucketsTest extends MockObjectTestCase {
  Mock bucketsMock = mock(TaxLawBuckets.class);
  TaxLawBuckets globalBuckets = bucketMock.proxy();
  PerAccountTaxLawBuckets perAccount = new PerAccountTaxLawBuckets(100, 100, globalBuckets);
  public void testGetRemaining401kReturnsTheMinimal() {
    bucketsMock.expects(once()).method("getRemaining401k").will(return(200));
    assertEquals(100, perAccount.getRemaining401k());
  }
  ...
}
</pre><br />其它的测试也都是通过jmock设置expectation，然后从perAccount对象调用对应的函数。<br /><br />下面注意到401k和403g的逻辑几乎一模一样，只有方法名不同。所以为了避免代码重复，我和pair决定用一个abstract class来抽出共性。然后用两个子类来代表不同之处。pair的代码是这样：<br /><br /><pre name="code" class="java">
public abstract class AbstractPerAccountTaxLawBucketsTest extends MockObjectTestCase {
  Mock bucketsMock = mock(TaxLawBuckets.class);
  TaxLawBuckets globalBuckets = bucketMock.proxy();
  PerAccountTaxLawBuckets perAccount = new PerAccountTaxLawBuckets(100, 100, globalBuckets);
  public void testGetRemainingReturnsTheMinimal() {
    bucketsMock.expects(once()).method(getRemainingName()).will(return(200));
    assertEquals(100, getRemaining(perAccount));
  }
  ...
  abstract String getRemainingName();
  abstract double getRemaining(TaxLawBuckets buckets);
  abstract String getApplyName();
  abstract void apply(TaxLawBuckets buckets, double amount);
  ...  
}

public class PerAccount401kTestCase extends AbstractPerAccountTaxLawBucketsTest {
  String getRemainingName() {
    return "getRemaining401k";
  }
  double getRemaining(TaxLawBuckets buckets) {
    return buckets.getRemaining401k();
  }
  String getApplyName() {
    return "apply401k";
  }
  void apply(TaxLawBuckets buckets, double amount) {
    buckets.apply401k(amount);
  }
}

public class PerAccount403gTestCase extends AbstractPerAccountTaxLawBucketsTest {
  String getRemainingName() {
    return "getRemaining403g";
  }
  double getRemaining(TaxLawBuckets buckets) {
    return buckets.getRemaining403g();
  }
  String getApplyName() {
    return "apply403g";
  }
  void apply(TaxLawBuckets buckets, double amount) {
    buckets.apply403g(amount);
  }
}
</pre><br />而我则不太喜欢getRemainingName()和getRemaining的重复。所以我建议用easymock这样写：<br /><pre name="code" class="java">

public abstract class AbstractPerAccountTaxLawBucketsTest extends TestCase {
  MockControl bucketsMock = MockControl.createControl(TaxLawBuckets.class);
  TaxLawBuckets globalBuckets = bucketMock.getMock();
  PerAccountTaxLawBuckets perAccount = new PerAccountTaxLawBuckets(100, 100, globalBuckets);
  public void testGetRemainingReturnsTheMinimal() {
    bucketsMock.expectsAndReturn(getRemaining(globalBuckets), 200);
    bucketsMock.replay();
    assertEquals(100, getRemaining(perAccount));
    bucketsMock.verify();
  }
  ...
  abstract double getRemaining(TaxLawBuckets buckets);
  abstract void apply(TaxLawBuckets buckets, double amount);
  ...  
}

public class PerAccount401kTestCase extends AbstractPerAccountTaxLawBucketsTest {
  double getRemaining(TaxLawBuckets buckets) {
    return buckets.getRemaining401k();
  }
  void apply(TaxLawBuckets buckets, double amount) {
    buckets.apply401k(amount);
  }
}

public class PerAccount403gTestCase extends AbstractPerAccountTaxLawBucketsTest {
  double getRemaining(TaxLawBuckets buckets) {
    return buckets.getRemaining403g();
  }
  void apply(TaxLawBuckets buckets, double amount) {
    buckets.apply403g(amount);
  }
}
</pre><br />这样，减少了重复，代码感觉更干净。<br /><br />其实，这个例子简化了问题。实际的那个程序，除了getRemaining(), apply()之外，还有restore()和其他几个方法，也是401k和403g除了方法名字不同其他都一样。<br /><br />但是，pair一听说用easymock直接就把这个方案枪毙了。pair的观点：<br />为了保持一致性，我们应该都用jmock。使用easymock会给公司里别的程序员带来理解上的困难。（我很心虚地想，我的几个test case都是用的easymock亚。）<br /><br /><br />对这个说法，我有点不知道如何说了。我一直以来的观点，都是use the right tool for the right job。<br />如果工具甲能够做功能1,2,3,4，工具乙能够做功能3,4,5,6。那么我不会为了保持一致性而强迫只用甲或者只用乙。而如果一个公司标准的ComplexTool能够做功能1,2,3,4,5,6，一个业界标准的SimpleTool（比如java.util.HashMap）能做功能1，而我又不需要功能2,3,4,5,6，那么我会选择SimpleTool。<br /><br />何况，在一个比较强调技术的公司，担心大家不会用easymock或者jmock真的必要么？毕竟不管jmock还是easymock应该都是半个小时就能掌握的冬冬把？<br /><br />其实，这种关于强调一致性的问题我已经和同事有若干次分歧了。另外一次是：公司里大多用一个内部做的MyPropertyFactory framework来读取class path里面的property文件内容。而我有一次独立开发的一个模块直接使用了ClassLoader.loadResourceAsInputStream()。于是，同事以一致性为理由要求我更改代码来使用MyPropertyFactory。具体细节在下一次disagree里面会解释。<br /><br /><br /><br />那么，一致性和use the right tool真的是矛盾的么？你怎么在两者之间取得平衡呢？
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/52289#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 05 Feb 2007 12:52:20 +0800</pubDate>
        <link>http://ajoo.javaeye.com/blog/52289</link>
        <guid>http://ajoo.javaeye.com/blog/52289</guid>
      </item>
      <item>
        <title>关于 Replace Temp With Query</title>
        <author>ajoo</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ajoo.javaeye.com">ajoo</a>&nbsp;
          链接：<a href="http://ajoo.javaeye.com/blog/52283" style="color:red;">http://ajoo.javaeye.com/blog/52283</a>&nbsp;
          发表时间: 2007年02月05日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          这个I disagree系列里面我准备把所有在工作中技术上的争执记录下来。也有立此存照的意思。也许再过几年，回头一看，会自己bs自己一把呢。<br /><br />今天要记录的，是一个关于martin的refactoring那本书里提到的"Replace Temp With Query"的重构技术。<br /><br />事情是这样的。在和同事pair的时候，对他频繁使用的这个重构不太同意。搞得同事很不爽。很不好意思的是，我并没有读过这本书，所以对这个重构模式事先是一无所知。这就更加让同事不爽。<br /><br />当他无奈地指出“我用的是Martin推荐的”的时候，我当时还真有点不敢相信。于是我马上把书拿了过来，读了一遍Replace Temp With Query。<br /><br />读过之后，我的感觉是，只能部分同意Martin，而对同事对这个重构的使用仍然是无法苟同。<br /><br />先说对Martin的保留意见：<br />马丁说这个技术对把大的函数切割成小函数很有作用。这个我是同意的。有时候，当使用eclipse的"extract method"的时候，IDE会给这个新函数提示出七八个参数。这个时候，如果某些参数可以用query代替，自然会减少参数的个数。<br />不能同意的有：<br />1。马丁说在作extract method之前，要尽量多地做replace temp with query。而我认为，有那么两三个参数没什么不好。用参数传递比用query来隐含地传递信息有灵活性和清晰性上的好处。通过参数传递相比于通过query传递，有点像dependency injection vs. service locator。<br />所以，我认为只有在发现某个temp真的影响了extract method，才去做replace temp比较好。<br />一个类里面如果到处充斥着各种各样的query函数，在我看来也是味道不好闻。<br /><br />2。马丁对这个重构的局限性和危险性只是一笔带过。实际上，我感觉这个重构的适用范围极小。在一个临时变量的值会变化的时候，或者后面会发生副作用的时候，当然不能直接用这个重构，这点martin也说了。但是，martin举的例子都是query返回一个原始类型。实际使用中更多的是一个query需要返回一个对象。<br />而java作为一个引用不透明的语言，任何对引用类型局部变量的replace with query动作，理论上都不是安全的。<br /><br />举个例子：<br /><pre name="code" class="java">
interface Profile {
  Account getPrimaryAccount();
}
interface Account {
  ...
  Contribution getPreTaxContribution();
}
interface Contribution {
  Balance getBalance();
}

void f(Profile profile, Service service) {
  Account acct = profile.getPrimaryAccount();
  Contribution contrib = acct.getPreTaxContribution();
  Balance balance = contrib.getBalance();
  ...
  if(contrib.isMandatory()){...}
  ...
  service.setContribution(..., contrib);
  ...
  if(balance.isGood()) { ...}
  ...
  service.transferBalance(..., balance);
  ...
}
</pre><br /><br />我们能够简简单单地把contrib和balance这两个temp变成下面的query么？<br /><pre name="code" class="java">
Contribution contribution(Profile profile) {
  return profile.getPrimaryAccount().getPreTaxContribution();
}
Balance balance(Profile profile) {
  return contribution(profile).getBalance();
}
void f(Profile profile, Service service) {
  ...
  if(contribution(profile).isMandatory()){...}
  ...
  service.setContribution(..., contribution(profile));
  ...
  if(balance(profile).isGood()) { ...}
  ...
  service.transferBalance(..., balance(profile));
  ...
}

</pre><br /><br />Contribution, Profile, Account这些东西都是接口。而在接口的javadoc上没有明确表示getContribution(),getAccount(), getBalance()这些方法都必然一直返回一个对象的引用的时候，我是不敢这么做的。<br /><br />要知道，我们要重构的是一个架构很凌乱的系统里面的几十个大函数（一百行以上）中的一个，这个系统凌乱到没有一个人清楚知道总体到底是怎么回事，跟踪查找一个bug可能要按F3或者"Reference - Hierarchy"二十多次。<br />更讨厌的是，要重构的函数没有很好的单元测试。（当然，要是有良好的单元测试，也不会写成这个德性了）<br /><br />对这种系统，任何不能从理论上证明等价的重构都是危险的。也许，这么重构了之后，不会马上发现问题，但是，它会在我幼小的心灵里面留下的“我那个重构没有问题吧？？？”的阴影的。<br /><br /><br />google了一下"Replace Temp With Query"，发现马丁有这么一个补充声明：<br /><br /><div class="quote_title">引用</div><div class="quote_div"><br />Paul Haahr pointed out that you can't do this refactoring if the code in between the the assignment to the temp and the use of the temp changes the value of the expression that calculates the temp. In these cases the code is using the temp to snapshot the value of the temp when it's assigned. The name of the temp should convey this fact (and you should change the name if it doesn't).<br /><br />He also pointed out that it is easy to forget that creating a reference object is a side effect, while creating a value object isn't. <br /></div><br />这段话虽然语焉不详，但是它还是呼应了我对这个重构的保留："create a reference object"也是一个side effect。而麻烦的是，对一个接口里面的getSomething()，我基本无法知道这里面有没有一个"create a reference object"。（我的同事对这点不是很同意我的，他会说，我们目前有的两个实现都没有create a reference object。而我在面对一个接口的时候，更倾向于不去看现有的实现类里面到底如何实现的，我只在乎接口的spec,除非spec说这里不允许create a new reference object，我是宁可不做任何假设的。做个proxy之类，把返回值封装一下再返回的这种技术对我来说不是很不可思议的。）<br /><br /><br /><br /><br />下面再说我对同事的对这个重构的使用方法的不同意见。<br />1。同事基本上就是上来就能replace的就replace。有一个函数居然最后被重构成：<br /><pre name="code" class="java">
private Account[] accts;
Account[] filteredAccounts(String type){
  ArrayList ret = new ArrayList();
  for(...) {
    if(type.equals(accts[i].getType())
      ret.add(accts[i]);
  }
  return ret.toArray(new Account[ret.size()]);
} 
void f(String type) {
  for(int i=0; i&lt;filteredAccounts(type).length; i++){
    if(filteredAccounts(type)[i].getBalance()&lt;0) {
      filteredAccounts(type)[i].setValid(false);
    }
  }
}
</pre><br />哎，就算我装作看不见循环里面重复的子循环，或者捏着鼻子念着“不要过早优化”的咒语，这代码阅读起来，调试起来，也不如下面这个简单明了吧？局部变量真的这么可怕？<br /><pre name="code" class="java">
void f(String type) {
  Account[] found = filteredAccounts(type);
  for(int i=0; i&lt;found.length; i++){
    if(found[i].getBalance()&lt;0) {
      found[i].setValid(false);
    }
  }
}
</pre><br />2。在我表达了我对效率和副作用的担心之后，同事很富有团队精神地声明了几个局部变量来避免这个问题<br /><pre name="code" class="java">
private Account acct;
private Contribution contrib;
private Balance balance;
private void cleanStates(){
  acct = null;
  contrib = null;
  balance = null;
}
Account account(Profile profile){
  if(acct==null) acct = profile.getAccount();
  return acct;
}
Contribution contribution(Profile profile) {
  if(contrib==null) contrib = account(profile).getPreTaxContribution();
  return contrib;
}
Balance balance(Profile profile) {
  if(balance==null) balance = contribution(profile).getBalance();
  return balance;
}
void f(Profile profile, Service service) {
  cleanStates();
  ...
}
</pre><br /><br />我很不好意思地跟同事说，相比于前一个，我更不喜欢这个方案。两点问题：<br />1。副作用。我很讨厌引入可变的对象状态。它带来更大的bug几率，还有同步问题。<br />2。复杂性。代码比原来更多，更复杂了。本来是局部变量的现在变成了全局变量。而我记得从开始学写程序开始，都是局部变量优先于全局变量的。<br /><br /><br />其实，归根结底，我想跟同事说的是：<br />replace with query还是小心使用为上。我们先看看有没有必要把这些东西变成query好不好？如果这几个temp真的影响了重构，再研究怎么处置不行么？我在自己的代码重构中似乎还真是很少发现需要使用replace with query的。<br /><br />你怎么看replace temp with query呢？
          <br/>
          <span style="color:red;">
            <a href="http://ajoo.javaeye.com/blog/52283#comments" style="color:red;">本文的讨论