• 你应该更新的Java知识之常用程序库(一)
    你应该更新的Java知识之常用程序库(二)
    你应该更新的Java知识之构建工具
    你应该更新的Java知识之Observer

    在Java里,如何初始化一个List呢?

    我知道,你可以毫不费力地写出这样的代码:

    List<String> names = new ArrayList<String>();
    names.add("dreamhead");

    这几乎是在Java 5/6风格的代码中随处可见的,但不知道你怎么想,反正这样的代码让我觉得很不爽。

    首先,变量声明里存在重复,明明我已经知道它是一个String的List,还要再后面再说一次。如果你尝试过一些具有类型推演功能的语言,你就会知道,这种重复完全是可以由编译器处理掉的。Java 7也认为这样的代码不好,所以,它给我们提供了一个新的语法:

    List<String> names = new ArrayList<>();

    其实,即便是Java 5/6,我们也是有办法写出不那么冗余的代码。Guava已经为我们提供了一个解决方案,所以,同样的代码如果用Guava来写,它会是这样:

    List<String> names = newArrayList();

    这里,它利用了编译器的类型推演功能保证了类型的正确性。从代码上看,它甚至要比Java 7提供的解决方案键入的字符还少。

    这只是第一步,再有我还要在List里面放入内容,让我们继续来看在Guava里面怎么做。

    List<String> names = newArrayList("dreamhead");

    一行代码搞定,因为支持变参,所以,我们甚至可以一次放入很多元素。但是,这不是终点。

    在很多数情况下,使用List时,我们并不关心其具体的类型,只是把它当做一组数据来用,而并不是为了动态添加一些东西。这个时候,我们只要有一个不变的List就可以了。Guava里的ImmutableList就可以很好地把我们的意图表现出来。一方面,它实现了List接口,你可以把它当做一个List来用,另一方面,如果试图改变其中的内容,它会抛出异常。

    就我们这里讨论的内容而言,它的初始化方法更为简单:

    List<String> names = of("dreamhead");

    这里讨论的只是List,但大多数内容同样适用于Set和Map,另外两种我们最常用的数据结构。

    需要额外加以解释的是,of其实是为少量元素初始化打开的方便之门。所以,如果查看of的实现,你会发现List最多支持12个元素的处理,而Set只有6个,只是因为可变参数的存在,它可以支持到很多。但是,Map的则不同,它只有5对,因为它的键值对必须成对出现。如果你需要元素很大的ImmutableMap,一个更好的解决方法是采用Builder,下面是一个例子:

    ImmutableMap<String, String> contentTypeMap = ImmutableMap.<String, String>builder()
               .put("png", "image/png")
               .put("gif", "image/gif")
               .put("jpg", "image/jpeg")
               .put("jpeg", "image/jpeg")
               .put("tiff", "image/tiff")
               .put("css", "text/css")
               .put("html", "text/html")
               .put("txt", "text/plain")
               .put("js", "application/x-javascript")
               .put("json", "application/json")
               .put("pdf", "application/pdf")
               .put("zip", "application/zip")
               .put("xml", "text/xml")
               .build();

    在程序设计领域,有一个经典的说法,语言设计就是程序库设计,程序库设计就是语言设计。像List、Map这样的集合类型,有些语言把它放进了语法,使得它用起来更简单。像Java这样做起事来一板一眼的程序设计语言,如果有了好的程序库支撑,也可以让它相对方便,Guava在这方面做了一个很好的尝试。

    对普通的Java程序员而言,是时候抛弃繁重的Java旧语法,拥抱新时代的Java了。

  • 2013-05-01

    Moco 0.7发布

    Tag:moco

    我很高兴地宣布,Moco第一个正式版本0.7发布了。

    Moco是什么?

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

    Moco的使用场景

    • 我是一个企业级软件开发人员,每次面对集成就是我头疼开始的时候,漫长集成拉锯战拖延了我们的进度。幸好有了Moco,几行配置就可以模拟一个服务,我再也不需要看集成服务团队的脸色了。
    • 我是一个移动开发人员,老板催得紧,可服务器端开发进度慢,我空有一个漂亮的iphone应用,发挥不出作用。幸好有了Moco,很快就可以搭建出一个模拟服务,我再也不用把生命浪费在无效的等待上了。
    • 我是一个前端开发人员,做Inception的时候,客户总想看到一个完整的应用演示,可哪有时间开发后端服务啊!幸好有了Moco,几下就可以弄出一个模拟服务,我做的页面一下就有了生命力。

    使用Moco

    Moco目前支持多种使用方式,最基本的方式是两种:API独立运行

    如果你编写的是个Java应用(或是以其它JVM上的语言编写的应用),你可以采用直接调用API;我们也可以独立运行的方式,在配置文件中,配置一个属于自己的服务器。

    如果你是Mac或是Linux用户,你还可以选择Shell的方式运行。事实上,它是最简单的方式,它会自动帮助你找到最新版本的Moco。

    如果你在用Maven,那么Moco的Maven插件你一定会喜欢。作为一个新时代的自动化用户,Gradle插件也是不可或缺的。

    特性

    此次发布的特性包括

    • 对HTTP的支持
      • 内容(文本,文件)
      • URI
      • 查询参数
      • HTTP方法
      • HTTP版本
      • HTTP头
      • 状态码
      • 重定向
    • 对Web的支持
      • cookie
      • form
    • 对集成的支持
      • XML
      • XPath
      • JSON
      • 访问其它URL
    • 杂项
      • 延迟
      • 缓存
      • 目录挂载

    具体的使用方式,可以看这里

    感谢

    感谢刘宇峰熊节赵俊伟Garrett Heel,为Moco贡献代码。

    感谢郭晨,为Moco实现了一个favicon。

    感谢何海洋,贡献了Shell的支持。

    感谢Garrett Heel,实现了Maven插件。

    感谢银大伟,实现了Gradle插件。

  • Moco在程序库设计包括两个方面,如何设置服务器和如何让服务器运行起来。

    先说简单的,如何让服务器运行。最简单的选择是让用户自己启动服务器,然后,在测试结束之后关闭服务器。这么一来,每个测试都大概会是这样:

    public void shouldWork() {
     ... setup server ...
     try {
       server.start();
       ... your test here ...
      } finally {
       server.stop();
     }
    }

    作为一个框架,留给客户使用的接口一定简单,把实现细节封装在框架以内。如果每个测试都这么写,用户很快就会骂娘了。现在的Moco的做法是使用了running方法,把Server启停的细节封装了起来。

    running(server, new Runnable() {
     @Override
     public void run() throws Exception {
       assertThat(helper.get(root()), is("foo"));
     }
    });

    我知道你不喜欢匿名内部类,我也不喜欢,但是,这是Java的局限。等到Java 8驾临,相信一切会有所改观。

    设置服务器,也就是在什么样的请求,给什么样的应答。Moco有一个很关键的起点,那便是DSL,要知道,之前的一段时间我刚刚完成了《领域特定语言》的翻译。于是,API的可读性成了一件非常重要的事。

    说起来很简单,但是,要知道匹配请求的条件有很多,而且还可能任意组合。如果这些条件是彼此独立的(“或”),我可以用变参来解决,但有时,这些条件是相关的(“与”),那该怎么办呢?拜函数式编程思路所赐,我想到了函数组合的方式。

    我知道,Java没有一等公民的函数,但是,Java里有一等公民的对象,我们可以用对象模拟函数,事实上,这也是很多面向对象程序设计语言解决函数组合的一种方式,而这种对象称为函数对象,也叫functor。

    把需要的条件封装成一个个函数对象,然后通过几个简单的运算符就可以将它们组合起来。目前Moco里提供了and和or两个运算符用于组合。

    server.request(and(by(uri("/foo")), by(method("put")))).response("bar");
    server.request(or(by("foo"), by(uri("/foo")))).response("bar");

    随着面向对象思路的普及,函数组合的写法会越来越普遍的,它会成为程序员工具箱中的必备品。

    DSL是一个重要的考量,所以,这里没有直接new对象,而是用函数(比如uri、method)做了一层封装。或许你会说,那为了可读性,我可以把类名设计成一个可读的样子,但new一个对象的问题在哪呢?就在new上。一方面,new是为了创建一个对象,这是实现细节,与我们要表达的内容不在一个层次,另一方面,你会发现多个new会让这句话显得不连贯。这句话?是的,我们的写法就像是用一句话声明一个东西一样,而我们期望自己讲的内容有一定的连贯性,而这才是DSL。

    一旦设计成DSL,单个函数的文档也就失去了意义,更重要的是一个用法的文档。所以,在Moco里,我写了文档,却没有写JavaDoc。

    在Moco里,请求和应答里可能有同样的东西,比如file,而request和response的参数类型是不一样的。如果file能够返回不同类型是最好的,可惜在Java里面,我们不可能根据返回类型做重载。一种解决方案是为request和response分别设计一个函数,比如requestFile和responseFile,但显然,这种做法会把同样的逻辑分散到两个类实现里,而且这样需要共享的东西不只是file,还有其它一些东西。

    在计算机问题里,加上一个中间层永远是一个重要的解决方案。这也是by的目的所在。这样一来,这些共享的东西就可以做成一个抽象(Resource),对请求来说,只要有一个by,就可以适配成Matcher。

    在Moco设计中,还涉及到了一个框架设计中很重要的点:类型。Java是静态类型程序设计语言,利用好类型,就可以在编译期把错误报出来,而不会留到运行时,fail fast是很重要的一个程序设计实践。

    Moco目前支持一些Content Type检测。如果把这个Content Type放到Resource里面,你会发现不是所有Resource都需要这个,比如method,我们当然可以在Resource接口里支持Content Type,在不需要的地方抛出异常。

    在Moco里,我的做法是引入了一个ContentResource,支持Content Type,它继承于Resource,这样依赖,需要Content Type的接口(比如content),直接支持ContentResource,而其它部分继续支持Resource,这样,如果一不小心用错的话,编译就会报错。

    再有一点是关于Publish接口,在Moco的实现里有一个Setting,还有一个BaseSetting,如果观察实现,在设置Request会创建出一个BaseSetting,但其返回的接口是Setting。这么做的一个原因是,因为Setting出现在用户可以使用的接口上,而BaseSetting是留给内部实现的,它提供了一些用于内部实现的方法,而用户是不应该使用这些方法的,所以,将二者做了一个分离,这样一来,保证了用户不会误操作。同样处理的还有HttpServer和ActualHttpServer。

    小结一番,简化用户接口,设计DSL,利用好类型,区分Publish接口。

  • 作为一个Java程序员,不熟悉设计模式简直不好意思和人打招呼,而Observer模式可以说是位列最常用的设计模式之列,虽然有时候在具体代码里,它不一定叫这个名字,比如改头换面叫个Listener,但模式就是这个模式。

    手工实现一个Observer也不是多复杂的一件事,只是因为这个设计模式实在太常用了,Java就把它放到了JDK里面:ObservableObserver,从JDK 1.0里,它们就一直在那里。从某种程度上说,它简化了Observer模式的开发,至少我们不用再手工维护自己的Observer列表了。

    不过,如前所述,JDK里的Observer从1.0就在那里了,直到Java 7,它都没有什么改变,就连通知的参数还是Object类型。要知道,Java 5就已经泛型了。Java 5是一次大规模的语法调整,许多程序库从那开始重新设计了API,使其更简洁易用。当然,那些不做应对的程序库,多半也就过时了。这也就是这里要讨论知识更新的原因所在。

    今天,对于普通的应用,如果要使用Observer模式该如何做呢?答案是GuavaEventBus。如你所见,它的名字并没有直接告诉你它是一个Observer,但这有什么关系呢,Listener不也是这样。

    首先,我们声明一个Observer:

    public class EventObserver {
     @Subscribe public void onMessage(Message message) {
       ...
     }
    }

    你会发现,这个类并没有继承任何接口,只是在用来响应通知的方法上声明了一个@Subscribe。

    使用EventBus很简单,先声明一个

      EventBus eventBus = new EventBus();

    然后,把我们写好的Observer注册进去:

      eventBus.register(new EventObserver());

    当要通知Observer时,我们只要这样即可:

      eventBus.post(message);

    这里,我们并没有告诉EventBus,我们要处理的是一个Message类型,只是在EventObserver的onMessage方法的接口声明上使用了这个类型而已。但是,当我们把消息发送出去的时候,它会根据类型进行匹配,保证我们的消息正确地发送到对应的地方。

    相比于JDK原有的实现,这个实现会更简单。EventObserver不再需要存在一个继承体系中,而继承总是一种枷锁,把我们套牢在一个体系之中:

    • 我们不必遵循一个特定的名字,比如Observer的update,而这里的名字onMessage是我们自己起的。
    • 我们不必遵循特定的类型,比如update方法中作为被观察对象Observable和作为参数的Object,而是根据我们自己的需求选择的类型。

    这种变换让静态类型的Java语言,有了一些动态类型的特质,也让程序更加灵活。这种灵活性多半要归功于Annotation,它在很大程度上影响了Java的程序设计风格。

    除了标准的EventBus,Guava还提供了另外一个AsyncEventBus,从名字就可以看出,这是一个异步的EventBus,也就是说,消息扔给它之后,会立即返回,至于Observer什么时候处理,那就是它的事情了。

    如果你想更多地了解EventBus,那Guava的官方文档是个不错的去处。

  • 最近经常被不同的人问到同一个主题,时间管理。他们很好奇,除了每天工作,还在翻译书,还写开源项目,我哪来的那么多时间。在自己眼中,我一直是时间管理做得很糟糕的。如果说我是时间管理做得很好,我应该已经做出了更多的东西,而不是最近才产出。

    之所以最近才开始有产出,一个最大的转变是生活方式的转变:早睡早起。其实,这个话题我很早就说过。只是那时,我还没有意识到这种方式对于自己时间管理的转变。

    在《聊聊早起》里,我提到过,现在我的工作大多是在早上完成。晚上回家大多数时间,我甚至连电脑都不开,而是陪着家人一起聊聊天,或是看看书。把更多的时间给了家人,有助于促进工作生活平衡。有不少工作优秀的人把大量的时间给了工作,结果影响了自己的生活。这样的失衡无论如何是不值得羡慕的。

    早起之后,我通常会有两个小时左右的时间,而且一般因为刚刚睡醒,注意力会非常集中,周边非常安静,无论是写代码还是写文章,都是很有好处的,我的大多数个人产出都是在这个阶段完成的。现在,只要有人问我时间管理,我第一个推荐的便是早睡早起,至少我知道的,有几个人已经开始这么做了,并且全部都反馈良好。

    除了早睡早起,还有一点,算是教训。有一个同事问我最近要做的事太多,怎么办。我的回复只有一个字“砍”。

    初入ThoughtWorks,大多数人会像刘姥姥进了大观园,但很多人也会迷失:太多各种各样的技术牛人,太多各种各样的技术,太多各种各样的活动,无论想学东西,还是想贡献,这里最不缺的就是机会。所以,如果你什么样的活动都想冒个头,结果就是被太多活动拖累了。

    我也对iOS开发感兴趣,我也想花时间写几个漂亮的界面,但你知道,我还想花时间写一个游戏,还想花时间实现自己的语言,但是,但是,我的时间只有这些,我能做的事情是有限的,唯一能做的就是把一些感兴趣的事情暂且放到一边,集中精力做好优先级最高的几件事,这个“几”越少越好。

    不用担心别人在那个方向已经跑了很远,这个世界总有人跑在你前面,而且事实上,没有在乎一只菜鸟的小跑。十万八千个“Hello,world”也换不回做成一件事得到的欣赏。

    写程序的时候,我们都知道要分离关注点,只是到做事的时候,就糊涂了。

    关于时间管理,还有一条经验:小步前进。在《新ThoughtWorker开发的头两件事》里,我提到了一个非常重要的工作习惯:任务分解。

    我知道很多人,包括我自己以前也是,通常都是事一来,猛烈地冲刺,不把问题解决完誓不罢休,但这种做法通常会让整个人很疲惫。我现在的做法是把要做的事情先分解,如果不是特别着急,就慢慢来做。比如翻译书,我只要要求每天能够翻译两页,写开源只要求每天能有提交。冲刺的做法虽然单位时间产出大,但却很难坚持,小步前进虽然一天的量不大,但时间累积却也威力无穷。

    每天早起,过一下自己的任务列表,选取一项,动手做就好了。有时任务真的很简单,就是简单升级一下依赖。很多事情便这样坚持下来的,有人在微博上对我的github连续提交产生了兴趣,这就是我个人在尝试的坚持。

    程序员不应该是青春饭,而可以成为一个终身职业,那么,我们要做的是可持续发展,我们在比拼不是一个时间点的产出,而是持续的产出。“一鼓作气,再而衰,三而竭”,那不是我追求的。