-
2013-05-25
你应该更新的Java知识之集合操作
你应该更新的Java知识之常用程序库(一)
你应该更新的Java知识之常用程序库(二)
你应该更新的Java知识之构建工具
你应该更新的Java知识之Observer
你应该更新的Java知识之集合初始化我们打算做要做这样一件事,给出所有有资格过五四青年节的人的名字。如果你不知道的话,有资格过五四青年节的人要年龄在14周岁到28周岁之间。
按照传统的思路,这段代码应该是这样写:
List names = new ArrayList();
for (Person person : people) {
int age = person.getAge();
if (age >= 14 && age <= 28) {
names.add(person.getName());
}
}是不是很熟悉?这样的代码在我们的代码库中几乎随处可见。单就这段代码而言,本身没什么问题。这里只是举了一个简单的例子,但现实情况往往比这复杂得多,于是,我们经常看到一个for循环里嵌套了大量的代码。
好,新风格代码时间。这次我们还是会用到Guava,先上代码后解释:
Iterable names = transform(filter(people, isAgeQualified()), toName());
这里先介绍两个重要的函数transform和filter,这两个函数都是对一个集合中的各个元素进行操作。
transform如名所示,它把集合中的一个值转换为另一个值的函数,至于具体怎么转换,则由后面的函数定义。我们来看看toName的定义:
Function<Person, String> toNames() {
return new Function<String, Integer>() {
@Override
public boolean apply(Person person) {
return person.getName();
}
};
}这个函数的返回值是一个Function,是的,它是一个“函数”。在Java里,函数不是一等公民,所谓一等公民指的是:
- 它可以按需创建
- 它可以在数据结构中存储
- 它可以作为实参传给一个函数
- 它可以当做一个函数的返回值
Java世界的一等公民是对象,但我们可以用对象包装函数,就像前面这样,这就是函数对象。如果你了解函数式编程,你会发现,transform接收一个函数作为它的参数,那它实际上就是一个高阶函数:
- 接收一个或多个函数做实参
- 以一个函数做返回值
在函数式编程里,最为强大的一点就是通过函数的组合,我们可以做到很多未曾想过的做法,程序的可扩展性也会出乎意料地好。
让我们继续把未介绍完的说完,接下来是filter,它表示用一个条件对集合的元素进行过滤,条件在哪?我们来看isAgeQualified的定义:
Predicate isAgeQualified() {
return new Predicate() {
@Override
public boolean apply(Person person) {
int age = person.getAge();
return age >= 14 && age <= 28;
}
};
}Predicate实际上也是一种形式的函数,只不过,它的返回值是boolean,有了上面的基础,它也就不那么难以理解了。
好,为了体现这段程序的扩展性,我们继续扩展一下我们的需求:我们要找出所有符合条件的男同胞。如果采用的是原来的循环,那么我们通常的做法是在if里面在添加一个条件,事实上,大量复杂的for循环就是这么一点一点扩充出来的。好,我们看看按照新的做法,我们会怎么做:
Iterable names = transform(filter(people, and(isAgeQualified(), isMale()), toName());
这里新增了isMale()函数,它的实现你也可以想到:
Predicate isMale() {
return new Predicate() {
@Override
public boolean apply(Person person) {
return person.isMale();
}
};
}这里还增加了一个and函数,它可以把多个Predicate组合起来。Guava本身给我们还提供许多这样的逻辑运算符,比如or、not等等。
一旦形成了一个函数,它最大的价值在于重用,比如,我们现在要超出一些男同胞,干些体力活,那这个函数就又可以发挥价值了。
如果你接触 Lisp 族的程序设计语言,这样的括号套括号的做法你会觉得眼熟。当我们有很多的操作要组合在一起的时候,这种写法就颇具 Lisp 风味了。之所以我们需要写成这样,很大程度是拜 Java 僵化的语法所赐,我们无法给既有类型添加方法。在一 些可以扩展类的语言里,这段代码会更流畅一些。
Guava还给我们提供了另外一种做法,既然我们不能扩展已有的类,就不妨提供一个新的类,它的名字叫做FluentIterable:
FluentIterable names = FluentIterable.from(people).filter(isAgeQualified()).transform(toName());
-
2013-05-08
六年思想工作者
六年,在ThoughtWorks六年了。
还记得六年前,我加入公司的时候,整个办公室也不过三十几个人,而今天,ThoughtWorks中国区已经发展成四个办公室,小三百号人了。这六年间人来来往往,按照加入公司时间排序的话,我已经是ThoughtWorks中国区的第18号员工了,当年的新员工,已经成了老鸟。当年屁颠屁颠地跟着别人一起写程序,到现在带着一些人写程序,并影响着更多的一些人写程序。
作为一个普通的程序员,我也有过很多普通程序员的困惑。我应该在怎样的公司工作,公司能给我带来什么。我也看到过这样的讨论,我应该进ThoughtWorks,还是进百度。
其实,所谓这些问题都不是真正的问题。真正的问题是我想做什么。
100%可以肯定的是,无论哪个公司,有在里面做得怡然自得的,也有在里面做的闷闷不乐的。在ThoughtWorks里面,我们可能接触到各种各样好玩的技术,也可能在一个遗留系统焦油坑里被折磨得死去活来;我们可能参加各种各样有趣的活动,也可能因为没有个“标准”告诉我该做什么而茫然失措;我们可能享受与牛人交流的快乐,也可能被不断袭来的某些反馈弄得心烦意乱。
所谓机会,哪个公司都有各种各样的机会,只是这样的机会多大程度上能砸到你的头上。不是进了百度,就都能去做搜索引擎,不是进了淘宝,就都能去支持光棍节。你的机会在哪里?在自己。
很多人通常都习惯于别人把工作安排到自己头上,自己按部就班做就好了,却很少有人想我要做什么,一下子就把自己未来的发展放到别人的手里。遗憾的是,别人没那么关心你,于是,你会很好奇地发现,为什么机会都到别人那里了,自己只有怨天尤人的份。答案除了上司眷顾之外,个人的积极主动占了很大一部分。
面试的时候,我经常会问候选人一个问题,你未来想做什么?答案当然是各种各样。接下来,我会问,你之前为此做了些什么,这个时候你就会发现,很多人什么都没做。所谓想做什么,真的只是想想而已。
说起来,公司是我们个人发展的平台。但是,只有在我们为自己的发展做了打算,并付诸努力时,公司才会成为我们的推动力,机会才会不经意间降临在我们的头上。
遗憾的是,很多人考虑公司考虑的只是报道中的公司,考虑的是自己按部就班能够得到什么。
回想自己六年ThoughtWorker生涯,公司给我最大的回报是机会。当我想做一些事情时,我只要争取通常都有不错的结果。当我想尝试Rails项目时,我可以;当我想尝试培养新人时,我可以;当我想回归Java项目时,我可以……当然,有时我不得不做一些不那么心甘情愿的事,比如,敏捷咨询,那段时间恰恰是我自己最没想法的阶段。
相比于很多公司,ThoughtWorks的扁平结构让我们经常有机会把自己的想法和“领导”交流。不知道你上一次和自己公司的大领导是什么时候,我前不久刚和我们的新CEO吃饭聊天来着。交流了,想法得到表达,机会自然也就有了。在ThoughtWorks里,交流是很重要的。
如果说ThoughtWorks有什么特别之处,在我看来,它让普通员工有了更多实现自己想法的机会。
最后,讲一个我们“领导”的故事。最近有一条新闻,ThoughtWorks宣布了新的CEO,他就是我们原来中国区的总经理郭晓。。事实上,直到今年年初,郭晓都没想过做ThoughtWorks的CEO。他只是在每次ThoughtWorks全球管理团队一起讨论各种各样的事情时,非常积极,主动承担了很多工作。当大家认为需要一个成为CEO的时候,几乎所有人脑子里面都想到了一个名字:郭晓。于是,他成了ThoughtWorks的CEO,如果我没记错的话,郭晓也是第一个成为跨国外企CEO的中国人。顺便说一下,十几年前郭晓加入ThoughtWorks时,他是一个程序员。
-
2013-05-02
你应该更新的Java知识之集合初始化
你应该更新的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发布
我很高兴地宣布,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插件。
-
2013-04-30
从Moco谈程序库设计
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接口。






