• “default method”是Java 8引入的一个特性,其初衷是为了解决既有程序库扩展的问题。在之前的Java版本中,如果要给一个已有接口添加新方法,这会带来一些问题,因为新方法没有对应的实现,所以,实现这个接口的类就会编译不通过。而“default method”的引入给了方法一个实现,编译就可以通过了,从而我们可以在不改变已有代码的前提下,为程序库增加新的方法。

    但是,既然接口方法可以有实现,那它也给了我们另一种思路。

    在Java开发中,我们可能会经常面临一种情况。以Web开发为例,假设我们有一个领域对象Foo。

    class Foo {
      ...
    }

    我们有个需求,根据其某些属性决定是否在页面上隐藏它。你当然用一个类实现它,但在页面上是否隐藏它,显然不应该属于领域对象的一部分。所以,我们通常会用另外一个类封装它,比如HiddenableFoo。

    class HiddenableFoo extends Foo {
      boolean isHidden() {
        ...
      }
    }

    好,新需求来了,我们要在页面上决定是否要给它的名字加粗,于是,这个类就成了HiddenableBoldableFoo。

    class HiddenableBoldableFoo extends HiddenableFoo {
      boolean isBold() {
        ...
      }
    }

    这里其实有个问题,为什么不是先由BoldableFoo,然后,从它继承呢?我们暂且不关心这个细节。

    又有一个需求来了,在另一个页面,我们需要确定这个对象是否需要隐藏以及是否需要斜体:

    class HiddenableItalicableFoo extends HiddenableFoo {
      boolean isItablic() {
        ...
      }
    }

    又有一个页面,需要的判断一下某些地方是否要加粗,某些地方判断要斜体,那这个类要怎么做呢?

    class BoldableItalicableFoo {
      boolean isBold() {
        ...
      }

      boolean isItablic() {
        ...
      }
    }

    如果这里的isBold和isItablic与前面的实现是一样的,是不是重复代码就此出现了呢?这就是在Java 8之前,我们面对的问题,我们可以继承接口,但实现不成。

    Java 8来了,“default method”就给了我们一个机会,让我们可以继承实现。下面是一种实现:

    interface Fooable {
      ...
    }

    class Foo implments Fooable {
      ...
    }

    interface Hiddenable extends Fooable {
      default boolean isHidden() {
        ...
      }
    }

    interface Boldable extends Fooable {
      default boolean isBold() {
        ...
      }
    }

    interface Italicable extends Fooable {
      default boolean isItablic() {
        ...
      }
    }

    这里之所以引入一个Fooable接口,因为接口只能继承接口。有了这样的基础,我们就可以自由组合了,比如:

    class HiddenableBoldableItalicableFoo extends Foo implements Hiddenable, Boldable, Italicable {
    }

    如果你熟悉Ruby,这俨然就是Mixin,对于Scala粉丝来说,Trait已然呼之欲出,C++人则会看到多重继承的影子。是的,所有这些语言特性背后都有一个共同的理念:面向组合的编程。

    Trygve Reenskaug和James Coplien在2009年提出的DCI架构,它是一种很好的面向对象编程的视角,其基础就是这种面向组合编程的理念。DCI架构在Java社区里面一直没有很好的讨论,也是因为Java语言没有给力的支持。

    诚如前面所说,Java语言之前是不支持面向组合编程的,但是,在Java社区里,有人不断地做着这方面的探索,Rickard Oberg,这个前JBoss架构师,实现了一个Qi4J的框架。不过,现在Java语言本身也可以这么做了。

    当然,相比于其它的语言,Java在这方面的表现力是有限的,因为它的基础是接口,所以:

    • 它不能有字段
    • 所有方法只能是public的

    另外,组合只能是基于类来做,就像HiddenableBoldableItalicableFoo,虽然这个类除了声明什么都没有。相比于Ruby的Mixin这种可以在运行时扩展的特性,更是表现力要弱了许多。

    但与之前的版本相比,Java算是在这个方面迈了一步,至少我们可以不用再为同一份代码多处出现而纠结了。

  • 2012年时,我写过一篇blog,谈到了加入项目时,如何了解一个项目。

    当我加入项目时,我要了解什么

    最近在客户现场做咨询,有人跑过来,问了我同样的话题。经过简单沟通,我发现他真正的问题是,为什么我们作为一个外来人可以很快地上手他们的项目,而他们自己的新员工却很长时间才能上手。

    这是一个很有趣的问题。

    正如我在前面那篇文章里写到的,通常我加入一个项目,我首先要获得这个项目的大图景。作为一个ThoughtWorker,我一定会了解这个项目业务价值,它要解决的问题是什么。即便是技术,我也会尝试先从架构入手。等有了大图景,具体到一个问题时,我就知道它在整个项目中,是拼图的哪一块。

    对比而言,客户的很多新人加入一个项目时,常常是试图从一个具体的问题着手,在解决问题的过程中,有太多小障碍了,每次遇到的几乎都是一个全新的问题。所以,几乎就是一路磕磕绊绊。当然,这里不排除我比客户的大多数新人工作经验丰富,所以,对于大部分技术理解能力要比他们好。

    我看到的另外一点大的差别是,我了解项目时,我会按照自己的思路,通过问题,一步步将项目分解开来,也就是从业务到技术,从大框架到小模块。通常,只要与我交流的人对项目足够了解,基本上,我都可以把项目大致梳理出来。

    相较而言,一些新人的接收模式是,等着别人按照他们的思路来介绍。以我和许多客户的合作经历来看,大多数经年累月的项目往往是不足够清晰,甚至是相当混乱的,在这个项目上长时间工作的人也很难把它梳理得很清楚,所以,指望他们把项目介绍清楚只能祈求好运了。前面那篇文章我提到过,许多人是把业务和技术混在一起的,这是我经历过的实际情况。

    除了按照上面所说的方式理解项目,之所以会给人一个很快上手项目的感觉,可能还有一点:使用“行话”,也就是客户他们常用的术语。与人用“行话”交流,往往会更容易建立彼此的信任,也会让人认为你对交流的东西很懂行,这或许算是附赠的技巧吧!

  • 2014-07-02

    Moco 0.9.2发布

    Tag:moco

    前版信息:Moco 0.9.1发布

    我很高兴地宣布,Moco 0.9.2发布了。

    Moco是什么?

    Moco是一个可以轻松搭建测试服务器的框架/工具/程序库。

    变更

    本次发布最大的变更是加入了HTTPS的支持。

    HTTPS服务器的创建即不同于普通的HTTP服务器,它使用的是httpsServer方法,除了类似于HTTP服务器的参数之外,一个很重要的参数是certificate,这里需要给出相应文件以及对应keystore密码和certificate密码。

    final HttpsCertificate certificate = certificate(pathResource("cert.jks"), "mocohttps", "mocohttps");
    final HttpsServer server = httpsServer(12306, certificate);

    独立服务器用户也可以通过命令行生成一个HTTPS服务器:

    java -jar moco-runner-<version>-standalone.jar start -p 12306 -c foo.json --https /path/to/cert.jks --cert mocohttps --keystore mocohttps

    还有一个比较重要的调整,在JSON配置中,增加了直接对JSON的支持,比如,

    {
       "request": {
           "uri": "/json_response_shortcut"
       },
       "response": {
           "json": {
               "foo" : "bar"
           }
       }
    }

    这样,给出应答就会是一个JSON对象:

    {
       "foo" : "bar"
    }

    而原来的做法如果需要返回一个JSON对象,需要大量的转义字符或是存放到文件中。

    在API方面,也做了许多调整:

    • 在Java API中,增加了HTTP版本协议类,无需以字符串的方式制定HTTP版本。
    • 在匹配方面,增加了更多的运算符,比如startsWith、endsWith、contain、exist。
    • 对于RequestHit的验证,增加了between运算符,可以判断请求次数在某个区间内。
    • 增加了多个Request Monitor的接口,以便处理遗留代码的时候,可以同时进行验证和查看日志。
    • 在模板接口上,将模板变量的类型由Object接口改成了String,这样,API用户必须确定好模板变量具体的表现形式。

    更多的细节请参考ReleaseNotes

    感谢

    感谢Michal Svab,实现了HTTPS API部分。

  • 在单元测试中,与时间相关的测试总是让人很头疼。举个例子,我们希望做一个定期过期缓存,比如30分钟过期,这该怎么测试呢?等30分钟?那要是过期时间是3天,你打算把开发时间全部交给等待,然后向老板汇报,我在等测试。祝你有一个糊涂的老板!

    我们不妨分析一下,看看有没有什么不那么令人发指的解决方案。以上面的缓存为例,缓存怎么知道过了多长时间,它肯定是在哪取个时间?对于Java应用来说,很有可能最终就是调到System.currentTimeMillis()或者nanoTime()。但是,这两个方法都是静态方法,想用Mock的人可以休息了。

    那么直接调用操作系统的方式修改时间呢?可以,只是一旦与具体的OS相关,各种跨平台的问题就随之而来,总而言之,麻烦。

    许多计算机问题都是可以通过引入间接层来搞定的,这一次又到了“间接层”的表演时间了。正如前面所说,与时间相关的应用最终可能都要到最底层那两个方法。如果我们可以做一层隔离,让所有的调用都来到间接层,我们就可以在间接层上做手脚了。

    事实上,这是一个如此通用的问题,以致于我们甚至都不需要提供自己的解决方案。

    在Guava中,有一个类叫Ticker,它提供了一个方法叫做read(),他就是我们的间接层。缺省情况下,我们会直接使用Ticker.systemTicker(),顾名思义,它是系统提供的ticker,read方法最终会调用倒System.nanoTime()。如果测试需要,我们可以自己Mock出来一个Ticker,就像下面这样:

    Ticker ticker = mock(Ticker.class);
    long time = Ticker.systemTicker().read();
    when(ticker.read()).thenReturn(time, time + TimeUnit.SECONDS.toNanos(10));

    这段代码第一次调用时返回一个时间,第二次调用返回的时间就是第一次调用的10秒之后了。所以,如果你的代码依赖于Ticker,剩下的魔法你都可以自己完成了。

    不过,这还不是终点。

    在Java中,谈及时间和日期,Joda Time已经是一个必选项了。实际上,Joda Time也为自己的用户提供了一套解决时间测试的方案,它有一个DateTimeUtils.setCurrentMillisFixed方法,我们可以传入一个固定的时间,比如,10秒后。时间就固定在那了。当然,如果你需要让时间重新流动起来,需要调用一下DateTimeUtils.setCurrentMillisSystem。

    原理很简单,Joda Time底层最终会调用DateTimeUtils.currentTimeMillis方法,而我们调用的set方法就会让这个方法返回不同的值,当然,缺省的是系统时间。

    Ticker和DateTimeUtils.currentTimeMillis在单位上有个差别,Ticker用的是System.nanoTime(),而DateTimeUtils.currentTimeMillis则如名所示,使用的是System.currentTimeMillis()。

    好了,这就是测试“时间”的方法。

  • 数据,是软件处理的核心,我们写各种各样的应用都是为了处理数据。处理数据有一个非常重要的前提,数据是合法的。一个计算数字的应用如何面对一堆字符呢?为了保证应用可以正常运行,各种校验是必不可少的。

    问题来了,校验该在哪做呢?

    显然到处做校验不是一个好主意,那到底服务层、数据层,还是接口层,才是校验的藏身之所呢?

    为了不让校验四处进行,一个建议的做法是在数据的入口进行校验,保证所有进入系统的数据都是合法的,这样一来,所有逻辑处理的代码只要关心逻辑就好了,而不需要关心数据合法性。比如说,客户端请求就在请求到达的入口进行校验,而从数据库读出的内容,就在数据层进行校验,而如果是集成了其它的服务,则要在读回数据的地方立刻进行校验。数据一到系统,先进行校验,如果有错了,就会立即发现,而不是等到数据跑到了程序里面,出错了,我们再去定位数据的来源,这种做法符合我们常说的“Fail Fast”原则。

    有一个有趣的问题,作为一个对细节特别较真的程序员,如果我的数据是字符串,虽然你说你在接口部分校验了数据,万一你遗忘了,流到了我这里,我不放心啊!所以,我可能还要在我这里写一遍校验。

    对于这个问题,我只能说,谁让你把字符串到处传了?

    事实上,在开发初期,很多东西用字符串表示起来很简单,比如语言,比如Tag。这种不精心的实现就会给未来带来很多麻烦。正如前面这个问题,其实大多数时候,你需要的不是一个字符串,而是一个“东西”,它应该被封装起来。所以,与其纠结于这个字符串是否还要校验,请考虑封装。

    另外一个有趣的问题是,如果我这个数据允许为空怎么办?想想都头疼,到处判断一个对象是否为空。

    如果我们能分清能为空的对象和不能为空的对象,那就再好不过了。幸好我们有了Optional,它给我们提供了一个有意义的空。结合前面的内容,在接口部分,如果数据可能为空,我们就用Optional把它包装起来,而普通对象则直接传递。我们就在程序里有了一个约定:

    • Optional的对象可以为空,需要判断的时候,自己处理一下。
    • 普通对象都不能为空,为空就直接空指针异常,因为那是错的。

    关于Optional,我强烈建议每个Java程序员都去了解。在Java 8里,它已经成为了JDK的一部分,如果你的Java版本还早,Guava做了很好的支持。