破狼 Blog

Write less got more.

TW2015技术雷达中文版发布

tw技术雷达

今天thoughtworks 2015新版技术雷达pdf发布了,你可以从这里下载http://engage.thoughtworks.com/HQ0000Q0QOf5pE70nbD00GP,在这里你可以了解到我们都在用什么技术,那些技术是推荐的,那么技术是不再推荐的。

thoughtworks技术雷达

thoughtworks技术雷达是以独特的形式记录ThoughtWorks技术顾问委员会的讨论结果,为从首席信息官到开发人员在内的各路利益相关方提供价值。这些内容只是简要的总结,但建议您探究这些技术以了解更多细节。这个雷达是图形性质的,把各种技术项目归类为技术、工具、平台和语言及框架四个象限。

技术雷达还进一步将这些技术分为四个环以反映ThoughtWorks目前对其的态度。这四个环是:

  • 采用:强烈主张业界采用这些技术。
  • 试验:值得追求。必须理解如何建立此功能。企业应该在风险可控的计划中尝试此技术。
  • 评估:为了查明它将如何影响企业,值得作一番探究。
  • 暂缓:谨慎研究。

本期最新动态

DevOps领域的爆炸式增长

本版本的技术雷达中,我们花了大量篇幅来评估DevOps领域中浩如烟海的各项技术,并且这些技术仍在以爆炸式的速度增长和创新。容器化、云产品以及它们的各种排列组合,使得在这个领域中的创新简直改用疯狂二字来形容。微服务架构风格的普及和流行,进一步扩大了架构技术与DevOps之间的交集,所以,我们预计,在这个领域中的疯狂创新仍将持续下去。

下一代数据平台引入注目

大数据并不是新名词(事实上,这段时间我们强烈反对炒作此概念),但是,我们也开始关注相关技术并研究如何在企业中使用它们。像数据湖和Lambda架构这类技术是放眼“企业数据平台”的新方式,不管你是否真有“大”数据需要处理,它们都可以派上用场。

开发人员关注于于安全相关的工具

每周都有“数据泄露和滥用”的新“故事”发生,而社会公众对于“安全的、尊重隐私的系统”的需求一直在增长。本版的技术雷达中精选了很多工具,比如Blackbox、TOTP双重因数验证以及OpenID连接等,它们能帮助开发人员建立安全的系统以及基础设施

更多内容

更多内容您可以参见技术雷达和其中文版pdf.

另外本期thoughtworks技术雷达也基于大家的反馈加强了互动性:

  • 雷达A到Z:你现在可以在雷达上浏览任何曾经出现在雷达历史里的条目,你也可以直接搜索一项你感兴趣的技术。

  • 消退的条目:那些曾经出现在早期技术雷达但现在已经消失的条目,现在会被清晰地标注为消退状态。

希望技术雷达能够激励各位看官思考自己或者项目的技术选择,同时在可能的情况下提升您或者你项目团队的技术能力。 更希望您去”建立个人或者项目技术雷达“.

博主也会经常为自己更新自己的技术雷达,根据雷达与圆心的距离标注优先级来指导自己的技术学习。以及为了团队成员快速掌握项目技术栈,以及团队成员的技术成长,也有在项目初期建立项目的技术雷达指导团队成员的学习和分享,保证项目交付的技术支持。

Ngnice-国内ng学习网站

angular

今天给angular新手介绍一个国内开源的ng学习网站http://www.ngnice.com/这是由一批ng爱好者在雪狼大叔的带领下共同开发完成,致力于帮助更多的ng新人,他们分别是:

ckken,grahamle,NigelYao,asnowwolf,lightma,joeylin,FrankyYang,lrrluo, why520crazy,破狼,二当家, Ken, zxsoft, why520crazy, playing,天猪、jacobdong、以及一批后加入或审校未记名的社区爱好者功能完成的。[这里排名不分先后]

再ngnice还在逐步完善中,目前完成的重要模块主要分为3栏:ng文章、angular中文指南、ng案例展示。

ng文章:这里主要由一批国内早期的ng高手组成,在这里记录ng的坑、使用方式、以及ng原理之类的文章,方便更多人查看。地址为:http://www.ngnice.com/

ng文章

angular中文指南:这是在Angular.js中文社区群里相遇一群Angular的爱好者,在一次巧妙的交谈,大家对于Angular官方的Guide最新版本没有中文版本表示无助,所以为了诸君更好的了解学习Angularjs,大家临时组织了一个Angular 开发指南翻译团队。现已经完成:地址为http://www.ngnice.com/docs/guide

ng文章

ng案例展示:这也是ngnice中对大家最有帮助的一块,这里收集了大家日常开发中会遇见的很多案例,如:进度条、html5表单、ng报表、数据表格展示等更多的有用案例。ng案例展示的目的是在线展示angular的各种常见案例,并能方便大家直接copy到自己的项目中应用。地址为:http://www.ngnice.com/showcase/#/home/about

ng文章

目前由于进入年底等各种原因我们暂停了这些开发,我们希望在后面的时间能继续完善更多的案例和文章分享。同时也希望爱好分享、喜欢交流、也愿意帮助更多ng新人的你能加入我们的ngShowcase开发组(qq群:278252889,注:这里不会给你解决任何ng问题,只供开发组使用)。

Guava-Optional可空类型

接上篇Guava之Joiner和Splitter,本篇将介绍Guava的另外一个有用的对象Optional,这在Java中Google Guava首先给我们提出可空对象模型的。在其他语言如c#这是已经存在很久的模式,并包含在.net类库中Nullable(Int?也是一个可空类型)。

Null sucks

回到本文主题Optional。在我日常编程中NullPointerException是肯定是大家遇见最多的异常错误:

为此Doug Lea曾说过:

Null sucks.

Sir C. A. R. Hoare也曾说过:

I call it my billion-dollar mistake.

从上面我们能够足以看出NullPointerExceptiond的出现频率和可恨之处。因此在GOF的设计模式中我们也专门提出了空对象模式(或称特例模式)来应对这可恶的NullPointerExceptiond。空对象模式主要以返回一些无意义并不影响处理逻辑的特定对象来替代null对象,从而避免没必要的null对象的判断。 例如在计算一组员工的总共薪资的时候,对于返回的null对象则我们可以返回默认值为了0薪资的员工对象,那么我们就不需要做任何null的判断。

员工薪资问题

那么在Guava的Optional又该怎么解决呢?在讲解Optional之前,让我们仍然以计算一组员工的总共薪资为例用原生java代码将来看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Test
public void should_get_total_age_for_all_employees() throws Exception {
    final List<Employee> list = Lists.newArrayList(new Employee("em1", 30), new Employee("em2", 40), null, new Employee("em4", 18));
    int sum = 0;
    for (Employee employee : list) {
        if (employee != null) {
            sum += employee.getAge();
        }
    }
    System.out.println(sum);

}

private class Employee {
    private final String name;
    private final int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

如果换成Guava Optional将如何:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 @Test
public void should_get_total_age_for_all_employees() throws Exception {
    final List<Employee> list = Lists.newArrayList(new Employee("em1", 30),
        new Employee("em2", 40),
         null,
         new Employee("em4", 18));

    int sum = 0;
    for (Employee employee : list) {
        sum += Optional.fromNullable(employee).or(new Employee("dummy", 0)).getAge();
    }

    System.out.println(sum);
}

从上面可以清晰看出,我们不在担心对象对空了,利用Optional的fromNullable创建了一个可空对象,并将其or上一个dummy的员工信息,所以在这里我们不在担心NullPointerExceptiond。

也许你会说和利用三目运算 ( ?:_)没什么差别,在此例子中功能是的确是没多大差距,但是个人觉得Guava更有语义,更通用一些,而且满足很多空对象模式使用的场景。

Optional API

*. OptionalObject.isPresent(): 返回对象是否有值。

*. Optional.absent(): 返回一个空Optional对象,isPresent() 将会返回false

*. Optional.of(): 创Optional对象,输入参数不能为null

*. Optional.fromNullable(): 创Optional对象,输入可以为null

*. OptionalObject.asSet(): 和Optional对象值合并,如果为null则返回size为0的Set

*. OptionalObject.or(): 和Optional对象值合并,如果为null为空加则返回or参数作为默认值

*. OptionalObject.orNull(): 和Optional对象值合并,如果为null为空加则返回Null作为默认值

上面的api都是我们在使用Optional的时候最常用的方法属性方法,注意如果我们创建了Optional对象,但是没有判断isPresent()是否存在,就直接get这是会抛异常的,这属于乱用Optional情况,和直接用Null并没什么差别。

1
2
final Optional<Object> obj = Optional.fromNullable(null);
final Object o = obj.get();

同样Optional为空对象模式,可以添加默认值,null不会影响我们的处理,如果为null我们无法继续程序处理的情况,需要抛异常或者中断的的,还是需要抛异常、中断,利用Preconditions.checkNotNull等,而不是继续套一层Optional对象,这也属于乱用Optional之列。

Guava之Joiner 和 Splitter

最近在给客户准备一个Guava的分享,所以会陆续的更新关于Guava更多的细节分享。本文将记录Guava中得字符串处理Joiner(连接)和Splitter(分割)处理。

Joiner

首先我们来看看下面我们经常遇见的一个案例:

题目:
对于一个如下定义List

   List<String> list =of("1", "2", null, “3”);

按照’,’分割,并过滤掉null。

如果不用第三方库,如common-lange,Guava,用原生java,我们将怎么继续?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static String join(List stringList, String delimiter) {
    StringBuilder builder = new StringBuilder();

    for (Object item : stringList) {
        if (item != null) {
            builder
             .append(item)
             .append(delimiter);
         }
    }

   builder.setLength(builder.length() delimiter.length());

   return builder.toString();
}

是不是很简单,但是繁琐,而且这里还有个坑,我们使用append的方式,在每次for完成后,我们必须去修正remove最后的分隔符:builder.setLength(builder.length() delimiter.length());

Guava版本呢?

1
2
3
4
5
6
 public static String joinByGuava(List stringList, String delimiter) {
      return    Joiner
                 .on(delimiter)
                 .skipNulls()
                 .join(stringList);
  }

我们不在考虑更多的细节,并且很有语义的告诉代码的阅读者,用什么分隔符,需要过滤null值再join。

note:当然我们也可以用common-lange来很简单的完成:StringUtils.join(stringList, delimiter).但是个人推荐尽量使用Guava替代common-lange,因为Guava还有更多的有用方法,后续会陆续介绍,还有就是Guava的API相对更有语意一点。。

Splitter

MapJoinner和MapSplitter

对于MapJoinner和MapSplitter的最好案例就是url的param编码。

MapJoinner

题目:
生产一个查询id: 123,name: green的学生信息的url。

利用Guava的MapJoinner的代码如下:

1
 Joiner.on("&").withKeyValueSeparator("=").join(ImmutableMap.of("id", "123", "name", "green"));

这里采用了on传入map item之间分隔符,以及withKeyValueSeparator传递map项key/value之间的分隔符。所以能够很简单的实现,不用我们在去实现一个的for循环代码。

MapSplitter

题目:
对url中的查询字符串"id=123&name=green"进行分割

利用Guava的MapSplitter的代码如下:

1
final Map<String, String> join = Splitter.on("&").withKeyValueSeparator("=").split("id=123&name=green");

这里同样利用on传入字符串的第一分隔符,withKeyValueSeparator传入项的分隔符,产生map的key/value项,其结果是一个{id=123, name=green}的Map对象。

TDD随想录

2014年我一直从事在敏捷实践咨询项目,这也是我颇有收获的一年,特别是咨询项目的每一点改变,不管是代码质量的提高,还是自组织团队的建设,都能让我们感到欣慰。涉及人的问题都是复杂问题,改变人,改变一个组织是个更复杂问题,这里可能涉及很多的非技术,非能力问题。

在2014年12月我在某企业内部推行TDD(测试驱动开发)培训,一共分4个课时完成一个特定需求的例子,看着大家一步一步的加深对TDD的理解,直到2014-12-31,也是2014的最后一天下午培训完TDD课程,经过一系列的总结过后,某参与人员说道:“单元测试需要写更多的代码,但是从项目的总体来看,一个字‘值’.”。紧接着后来某参与人员发了一份其关于TDD培训感受,名叫《TDD随想录》也将是本文的主题,本文或许更好的说是转载此文,了解一个开发人员对TDD了解的心路历程,以及对TDD的看法。

注:原文发布与hxfirefox的https://github.com/hxfirefox/blog/blob/master/TDD/TDD%E9%9A%8F%E6%83%B3%E5%BD%95.md.

原文如下:

TDD随想录

谨以本文献给TDD的开创者与传播者

本文纯属个人经历,如有雷同纯属巧合

我从不觉得自己是一个好的程序员,甚至可能连合格都谈不上,不过在内心深处我却渴望着在编程这件事上获得成功。

可惜每次审视自己写的暂且称之为代码的东西,都会有挫折感,想重构却又感觉盘根错节,难以下手;想重写却又感觉自己好不容易写出来的,也花了不少心思,就这样丢弃心有不甘。

也曾思考过如何才能写好代码,有段时间觉得只有严格符合编程规范的代码才是好代码进而如同遵守戒律一样地字字斟酌,还有段时间觉得只有用上设计模式才能称之优秀代码进而非模式不用,一切套用模式。不过这些都没有让我走出开发的迷雾,永远是加不完的班,修不完的bug。

究竟是否有一种方法能够让我拨开开发迷雾,至少能够让我能够轻松地修剪代码,降低bug发生率,那么我觉得这种方法在我身上就是成功的。

初次接触到TDD是通过公司内部的“代码大全培训”,犹如十月革命中阿芙勒尔号的一声炮响,为我打开了软件开发的视野。先测试后开发,小步迭代,持续集成,这些新名词突然涌进了我的大脑,既新鲜又晦涩。犹如人的幼年容易犯幼稚病一样,初识这些新名词就以为了解了TDD的一切,结果却发现在实践过程中处处碰壁,举步维艰。对TDD中每个环节真正隐含的开发思想的囫囵吞枣,让这一次的培训只在我脑中留下TDD的一个模糊身影:为软件开发结下一张安全网。

虽然未领悟精髓,但培训后体验和直觉告诉我TDD是一条通往我向往的软件成功的道路,尽管自己摸索前行比较坎坷。很幸运的是团队获得了随队敏捷教练的支持,结对让我系统地了解到了TDD的思想。

测试先行,其实讲的是需求边界,测试不是漫无目的而是精确计算成本的一项活动。测试从何而来,从需求来,需求推演出测试,也规划出产品边界,不能反映需求的测试是一种浪费,因此引申出开发需要讲求适当。开发是一项功利性的活动,永远都在追求盈利,而测试就一条红线,一旦跨过就意味着亏损。

小步迭代,“让子弹飞”中有句话很经典:步子要一步一步迈,一步迈大了,咔,容易扯着蛋。代码堆叠的后遗症是复杂,复杂到没人愿意触碰,且不停地咒骂这代码有多烂,这是步子迈太大的真实写照。TDD讲求的小步迭代是写完一个测试再去写完一个实现,每个实现都是通过测试的,如此累加小胜为大胜,最后所有代码的收尾也不过是让最后一个测试通过而已,就是这样简单。

重构,这是我最喜欢的部分,为啥?因为这里面所有的活动都会要求你去思考,且看上去都像是让你的代码向着大师级代码前进。漂亮的代码并不是堆砌各种技巧,而是在正确的时间,正确的地点做正确的事,重构很容易实现这个目标。重构是一件让人一旦开始就会欲罢不能的事,会让开发者在整个开发阶段都能够不停地去思考、实践再思考,直到无法再添加或删除一个字母。

持续集成,你终究是需要交付产品的,产品就是客户需要的价值,就如同厨师终究会端出客人点的大餐一样,没有哪个厨师是把所有食材罗列着呈现给你的,而是混合在一起,蒸煮炖烧,有些食材需要先处理,这样吃起来才软硬适中,而有些则是最后下锅,这样吃起来才鲜嫩多汁,厨师就是这样一步步将食材集成起来,每一步的处理都是可用都是有价值的,都是为后续进行的铺垫。软件开发也一样,持续集成就要保证每一次的完成都是有价值都可以为后续提供支撑。

写到这里也许会有人问你如何知道TDD是真理,是康庄大道,它一定适合每个人吗?不,我并不知道,我所写的一切只是发生在我身上的一段经历。这段经历告诉我TDD迫使我去更多的思考,去切割我那些冗长且复杂又不切实际的胡思乱想,把它们碾碎成一个个小片段,提炼,过滤,不断累加,最终变成最接近交代价值的东西,而这最终的东西正是我一直在追求的那个成就感。如果想要知道TDD是不是适合自己,最好的办法就是去尝试,去亲身体验一下,无论好坏也许你能获得比我更多的体会。

总结

TDD并不是万能的,但是TDD也不是一无是处的,重要的是用方法论的人,引入某同事一句话:

站在教学的角度来讲,我还是很推崇TDD的,TDD是一个很好的思维框架,如果非要教人一个思维框架的话就得教TDD,
不然人会瞎碰,不思考,不总结,不结果导向,靠灵感编程,凭直觉设计,撞大运修bug。最糟糕的是因为没有好的习惯
会接二连三的发生灵异现象。同一道题,习惯不好的人做,总能做出无数种新问题来。而且问题套问题,给他解决要浪费
我半天时间,如果他学会了TDD出的错只在最近一个引入的变化里,就好纠正多了。甚至他自己都能纠正。

博主很是赞同该同事的看法,并且作者认为:

tdd重要的不是测试代码本身,是解决问题的思维,也许可以泛化,哪怕没测试,如果能够做到快速验证,反馈,价值的
稳定叠加,有足够信心,也未尝不可。也许你会说测试可以cover功能,那么如果只有这一点的话,我更喜欢BDD
(behavior-driven development),因为这具有用户最终的使用价值。如果你说快速定位bug,我们我更倾向于BDD
(bug-driven development,自创的,即时每天出现测试bug,在加一个单元测试cover,覆盖)。这写都是TDD的结果导致的好处所在,而价值反馈思维才是实现TDD背后原理。
TDD驱使我们以结果导向,使得我们简单设计(并不是无设计),日常重构我们的代码库,注重交付价值流稳定叠加。

世上并没有放之四海皆准的法则,TDD好坏在于你的判断,方法论的主体在于使用的人,本文并不会给你一个完美的答案,这需要你自己的发掘。

java轻量级IOC框架Guice

Guice是由Google大牛Bob lee开发的一款绝对轻量级的java IoC容器。其优势在于:

  1. 速度快,号称比spring快100倍。
  2. 无外部配置(如需要使用外部可以可以选用Guice的扩展包),完全基于annotation特性,支持重构,代码静态检查。
  3. 简单,快速,基本没有学习成本。

Guice和spring各有所长,Guice更适合与嵌入式或者高性能但项目简单方案,如OSGI容器,spring更适合大型项目组织。

注入方式

在我们谈到IOC框架,首先我们的话题将是构造,属性以及函数注入方式,Guice的实现只需要在构造函数,字段,或者注入函数上标注@Inject,如:

构造注入

1
2
3
4
5
6
7
8
9
10
11
12
public class OrderServiceImpl implements OrderService {
    private ItemService itemService;
    private PriceService priceService;

    @Inject
    public OrderServiceImpl(ItemService itemService, PriceService priceService) {
        this.itemService = itemService;
        this.priceService = priceService;
    }

    ...
}

属性注入

1
2
3
4
5
6
7
8
9
10
11
12
public class OrderServiceImpl implements OrderService {
    private ItemService itemService;
    private PriceService priceService;

    @Inject
    public void init(ItemService itemService, PriceService priceService) {
        this.itemService = itemService;
        this.priceService = priceService;
    }

    ...
}

函数(setter)注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class OrderServiceImpl implements OrderService {
    private ItemService itemService;
    private PriceService priceService;

    @Inject
    public void setItemService(ItemService itemService) {
        this.itemService = itemService;
    }

    @Inject
    public void setPriceService(PriceService priceService) {
        this.priceService = priceService;
    }

    ...
}

Module依赖注册

Guice提供依赖配置类,需要继承至AbstractModule,实现configure方法。在configure方法中我们可以用Binder配置依赖。

Binder利用链式形成一套独具语义的DSL,如:

  • 基本配置:binder.bind(serviceClass).to(implClass).in(Scopes.[SINGLETON | NO_SCOPE]);
  • 无base类、接口配置:binder.bind(implClass).in(Scopes.[SINGLETON | NO_SCOPE]);
  • service实例配置:binder.bind(serviceClass).toInstance(servieInstance).in(Scopes.[SINGLETON | NO_SCOPE]);
  • 多个实例按名注入:binder.bind(serviceClass).annotatedWith(Names.named(“name”)).to(implClass).in(Scopes.[SINGLETON | NO_SCOPE]);
  • 运行时注入:利用@Provides标注注入方法,相当于spring的@Bean。
  • @ImplementedBy:或者在实现接口之上标注@ImplementedBy指定其实现类。这种方式有点反OO设计,抽象不该知道其实现类。

对于上面的配置在注入的方式仅仅需要@Inject标注,但对于按名注入需要在参数前边加入@Named标注,如:

1
2
3
4
5
6
7
8
9
10
11
12
     public void configure() {
        final Binder binder = binder();

        //TODO: bind named instance;
        binder.bind(NamedService.class).annotatedWith(Names.named("impl1")).to(NamedServiceImpl1.class);
        binder.bind(NamedService.class).annotatedWith(Names.named("impl2")).to(NamedServiceImpl2.class);
    }

    @Inject
  public List<NamedService> getAllItemServices(@Named("impl1") NamedService nameService1,
                                                   @Named("impl2") NamedService nameService2) {
  }

Guice也可以利用@Provides标注注入方法来运行时注入:如

1
2
3
4
5
6
7
8
@Provides
public List<NamedService> getAllItemServices(@Named("impl1") NamedService nameService1,
                                             @Named("impl2") NamedService nameService2) {
    final ArrayList<NamedService> list = new ArrayList<NamedService>();
    list.add(nameService1);
    list.add(nameService2);
    return list;
}

Guice实例

下面是一个Guice module的实例代码:包含大部分常用依赖配置方式。更多代码参见github .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.github.greengerong.app;

/**
 * ***************************************
 * *
 * Auth: green gerong                     *
 * Date: 2014                             *
 * blog: http://greengerong.github.io/    *
 * github: https://github.com/greengerong *
 * *
 * ****************************************
 */
public class AppModule extends AbstractModule {
    private static final Logger LOGGER = LoggerFactory.getLogger(AppModule.class);
    private final BundleContext bundleContext;

    public AppModule(BundleContext bundleContext) {
        this.bundleContext = bundleContext;
        LOGGER.info(String.format("enter app module with: %s", bundleContext));
    }

    @Override
    public void configure() {
        final Binder binder = binder();
        //TODO: bind interface
        binder.bind(ItemService.class).to(ItemServiceImpl.class).in(SINGLETON);
        binder.bind(OrderService.class).to(OrderServiceImpl.class).in(SINGLETON);
        //TODO: bind self class(without interface or base class)
        binder.bind(PriceService.class).in(Scopes.SINGLETON);


        //TODO: bind instance not class.
        binder.bind(RuntimeService.class).toInstance(new RuntimeService());

        //TODO: bind named instance;
        binder.bind(NamedService.class).annotatedWith(Names.named("impl1")).to(NamedServiceImpl1.class);
        binder.bind(NamedService.class).annotatedWith(Names.named("impl2")).to(NamedServiceImpl2.class);
    }

    @Provides
    public List<NamedService> getAllItemServices(@Named("impl1") NamedService nameService1,
                                                 @Named("impl2") NamedService nameService2) {
        final ArrayList<NamedService> list = new ArrayList<NamedService>();
        list.add(nameService1);
        list.add(nameService2);
        return list;
    }
}

Guice的使用

对于Guice的使用则比较简单,利用利用Guice module初始化Guice创建其injector,如:

1
 Injector injector = Guice.createInjector(new AppModule(bundleContext));

这里可以传入多个module,我们可以利用module分离领域依赖。

Guice api方法:

“`java

public static Injector createInjector(Module... modules) 

public static Injector createInjector(Iterable<? extends Module> modules) 

public static Injector createInjector(Stage stage, Module... modules)

public static Injector createInjector(Stage stage, Iterable<? extends Module> modules) 

“`

Guice同时也支持不同Region配置,上面的State重载,state支持 TOOL,DEVELOPMENT,PRODUCTION选项;默认为DEVELOPMENT环境。

后续

本文Guice更全的demo代码请参见github .

Guice还有很多的扩展如AOP,同一个服务多个实例注入set,map,OSGI,UOW等扩展,请参见Guice wiki.

Guava - EventBus(事件总线)

Guava在guava-libraries中为我们提供了事件总线EventBus库,它是事件发布订阅模式的实现,让我们能在领域驱动设计(DDD)中以事件的弱引用本质对我们的模块和领域边界很好的解耦设计。

不再多的废话,直奔Guava EventBus主题。首先Guava为我们提供了同步事件EventBus和异步实现AsyncEventBus两个事件总线,他们都不是单例的,官方理由是并不想我们我们的使用方式。当然如果我们想其为单例,我们可以很容易封装它,一个单例模式保证只创建一个实例就对了。

下面将以EventBus为例,AsyncEventBus使用方式与其一致的。

订阅

首先EventBus为我们提供了register方法来订阅事件,Guava在这里的实现很友好,我们不需要实现任何的额外接口或者base类,只需要在订阅方法上标注上@Subscribe和保证只有一个输入参数的方法就可以搞定。这样对于简单的某些事件,我们甚至可以直接

1
2
3
4
5
6
7
new Object() {

    @Subscribe
    public void lister(Integer integer) {
        System.out.printf("%d from int%n", integer);
    }
}

Guava发布的事件默认不会处理线程安全的,但我们可以标注@AllowConcurrentEvents来保证其线程安全

发布

对于事件源,则可以通过post方法发布事件。 正在这里对于Guava对于事件的发布,是依据上例中订阅方法的方法参数类型决定的,换而言之就是post传入的类型和其基类类型可以收到此事件。例如下例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
final EventBus eventBus = new EventBus();
eventBus.register(new Object() {

    @Subscribe
    public void lister(Integer integer) {
        System.out.printf("%s from int%n", integer);
    }

    @Subscribe
    public void lister(Number integer) {
        System.out.printf("%s from Number%n", integer);
    }

    @Subscribe
    public void lister(Long integer) {
        System.out.printf("%s from long%n", integer);
    }
});

eventBus.post(1);
eventBus.post(1L);

在这里有 Integer,Long,与它们基类Number。我们发送一个整数数据的时候,或者Integer和Number的方法接收,而Long类型则Long类型和Number类型接受。

所以博主建议对于每类时间封装一个特定的事件类型是必要的。

DeadEvent

DeadEvent暂时不清楚怎么翻译更合意,它描述的是死亡事件,即没有没任何订阅者关心,没有被处理,以DeadEvent类型参数的方法表示.例如在上例中我们post一个Object类型,如下:

1
2
3
4
5
6
7
8
9
10
final EventBus eventBus = new EventBus();
eventBus.register(new Object() {

    @Subscribe
    public void lister(DeadEvent event) {
        System.out.printf("%s=%s from dead events%n", event.getSource().getClass(), event.getEvent());
    }
});

eventBus.post(new Object());

更多Guava博文:

  1. Guava – 并行编程Futures
  2. Guava – EventBus(事件总线)

Guava - 并行编程Futures

Guava为Java并行编程Future提供了很多有用扩展,其主要接口为ListenableFuture,并借助于Futures静态扩展。

继承至Future的ListenableFuture,允许我们添加回调函数在线程运算完成时返回值或者方法执行完成立即返回。

对ListenableFuture添加回调函数:

1
Futures.addCallback(ListenableFuture<V>, FutureCallback<V>, Executor)

其中 FutureCallback是一个包含onSuccess(V),onFailure(Throwable)的接口。

使用如:

1
2
3
4
5
6
7
8
9
10
Futures.addCallback(ListenableFuture, new FutureCallback<Object>() {

    public void onSuccess(Object result) {
        System.out.printf("onSuccess with: %s%n", result);
    }

    public void onFailure(Throwable thrown) {
        System.out.printf("onFailure %s%n", thrown.getMessage());
    }
});

同时Guava中Futures对于Future扩展还有:

  • transform:对于ListenableFuture的返回值进行转换。

  • allAsList:对多个ListenableFuture的合并,返回一个当所有Future成功时返回多个Future返回值组成的List对象。注:当其中一个Future失败或者取消的时候,将会进入失败或者取消。

  • successfulAsList:和allAsList相似,唯一差别是对于失败或取消的Future返回值用null代替。不会进入失败或者取消流程。

  • immediateFuture/immediateCancelledFuture: 立即返回一个待返回值的ListenableFuture。

  • makeChecked: 将ListenableFuture 转换成CheckedFuture。CheckedFuture 是一个ListenableFuture ,其中包含了多个版本的get 方法,方法声明抛出检查异常.这样使得创建一个在执行逻辑中可以抛出异常的Future更加容易

  • JdkFutureAdapters.listenInPoolThread(future): guava同时提供了将JDK Future转换为ListenableFuture的接口函数。

下边是一个对于Future的测试demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Test
public void should_test_furture() throws Exception {
    ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));

    ListenableFuture future1 = service.submit(new Callable<Integer>() {
        public Integer call() throws InterruptedException {
            Thread.sleep(1000);
            System.out.println("call future 1.");
            return 1;
        }
    });

    ListenableFuture future2 = service.submit(new Callable<Integer>() {
        public Integer call() throws InterruptedException {
            Thread.sleep(1000);
            System.out.println("call future 2.");
  //       throw new RuntimeException("----call future 2.");
            return 2;
        }
    });

    final ListenableFuture allFutures = Futures.allAsList(future1, future2);

    final ListenableFuture transform = Futures.transform(allFutures, new AsyncFunction<List<Integer>, Boolean>() {
        @Override
        public ListenableFuture apply(List<Integer> results) throws Exception {
            return Futures.immediateFuture(String.format("success future:%d", results.size()));
        }
    });

    Futures.addCallback(transform, new FutureCallback<Object>() {

        public void onSuccess(Object result) {
            System.out.println(result.getClass());
            System.out.printf("success with: %s%n", result);
        }

        public void onFailure(Throwable thrown) {
            System.out.printf("onFailure%s%n", thrown.getMessage());
        }
    });

    System.out.println(transform.get());
}

官方资料主页:https://awk.so/@code.google.com!/p/guava-libraries/wiki/ListenableFutureExplained

更多Guava博文:

  1. Guava – 并行编程Futures
  2. Guava – EventBus(事件总线)

Nightmare基于phantomjs的自动化测试套件

今天将介绍一款自动化测试套件名叫nightmare,他是一个基于phantomjs的测试框架,一个基于phantomjs之上为测试应用封装的一套high level API。其API以goto, refresh, click, type…等简单的常用e2e测试动作封装,使得其语义清晰,简洁。其官方在http://www.nightmarejs.org/.

如果你的项目测试不需要想需求和测试人员理解,那么基于nightmare测试或许是一个好的选择,你的降低测试代码的成本,以及测试套件的部署。我们可以选择基于jasmine-node等作为测试套件集成。

安装nightmare:

1
npm install nightmare

下面我们对比与远程phantomjs的对比:

原phantomjs的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
phantom.create(function (ph) {
  ph.createPage(function (page) {
    page.open('http://yahoo.com', function (status) {
      page.evaluate(function () {
        var el =
          document.querySelector('input[title="Search"]');
        el.value = 'github nightmare';
      }, function (result) {
        page.evaluate(function () {
          var el = document.querySelector('.searchsubmit');
          var event = document.createEvent('MouseEvent');
          event.initEvent('click', true, false);
          el.dispatchEvent(event);
        }, function (result) {
          ph.exit();
        });
      });
    });
  });
});

nightmare代码:

1
2
3
4
5
new Nightmare()
  .goto('http://yahoo.com')
  .type('input[title="Search"]', 'github nightmare')
  .click('.searchsubmit')
  .run();

一切显而易见,不用多说。

nightmare同时也支持插件方式抽取公用逻辑,以供复用和提高测试代码语意,如下例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * Login to a Swiftly account.
 *
 * @param {String} email
 * @param {String} password
 */

exports.login = function(email, password){
  return function(nightmare) {
    nightmare
      .viewport(800, 1600)
      .goto('https://swiftly.com/login')
        .type('#username', email)
        .type('#password', password)
        .click('.button--primary')
      .wait();
  };
};

使用代码很简单:

1
2
3
4
5
var Swiftly = require('nightmare-swiftly');
new Nightmare()
  .use(Swiftly.login(email, password))
  .use(Swiftly.task(instructions, uploads, path))
  .run();

TypeScript - Classes

简介

JavaScript语言基于函数和原型链继承机制的方式构建可重用的组件。这对于OO方面编程来说显得比较笨拙。在下一代的JavaScript标准ECMAScript 6为我们提供了基于class base的OO设计方式。在TypeScript中我们也允许使用这种方式,TypeScript将编译为目前大多数浏览器能允许的普通Javascript代码,所以我们不用在等待ECMAScript 6的到来了。

我们先看一个关于class-base的实例:

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

var greeter = new Greeter("world");

这种语法和我们先前在c#,java语言看见的很相似。在这里我们声明了一个Greeter的类,其中包含一个greeting的属性,构造函数,以及greet的方法。

你也许已经注意到了例子中的‘this’关键字,’this‘和java/C#一样代表对象实例的成员访问。

在最后一行我们利用‘new’关键字创建了一个Greeter的对象实例。这将会新建一个对象实例,并调用我们先前定义的构造函数初始化此对象。

继承

在TypeScript中我们可以使用我们常用的OO设计模式。当然对于OO设计最基本的是类型的继承(继承一个存在的类,复用存在的逻辑),下例就是一个关于类继承的例子:

class Animal {
    name:string;
    constructor(theName: string) { this.name = theName; }
    move(meters: number) {
        alert(this.name + " moved " + meters + "m.");
    }
}

class Snake extends Animal {
    constructor(name: string) { super(name); }
    move() {
        alert("Slithering...");
        super.move(5);
    }
}

class Horse extends Animal {
    constructor(name: string) { super(name); }
    move() {
        alert("Galloping...");
        super.move(45);
    }
}

var sam = new Snake("Sammy the Python");
var tom: Animal = new Horse("Tommy the Palomino");

sam.move();
tom.move(34);

在这个案例中展示了TypeScript的OO继承方式,它和其他语言很相似。在TypeScript中我们采用‘extends’关键字来表示类的继承关系。在这里你可以看见 ‘Horse’和’Snake’都是继承至’Animal’的子类实现。

在案例中也展示如何去重写父类的方法,在这里’Snake’和’Horse都各自创建了一个‘move’方法来重写父类’Animal’的‘move’方法,并和‘super’关键字来调用父类的方法。

Private/Public访问限制

Public为默认行为

你可能注意到了在上例中我们并没有用‘public’关键字去描述类的成员的访问级别让其可见。在C#这类语言中,我们必须显示的标注public关键字才能使得类的成员可见。但是在TypeScript中public为默认访问级别,而不是想c#一样private默认。

有时我们希望封装隐藏类的内部成员控制类成员的可见性,这个时候我们可以使用‘private’这类关键字来标示成员。如我们希望隐藏‘Animal’的name属性:

class Animal {
    private name:string;
    constructor(theName: string) { this.name = theName; }
    move(meters: number) {
        alert(this.name + " moved " + meters + "m.");
    }
}

理解private(私有)

TypeScript有一个结构化的类型(或者鸭子类型)系统。在我们比较两个不同类型,我们不关心它们来自哪里,只关心对类型的每个成员的兼容性。一旦所有的成员都是兼容的,那么我们就认为这两个类型也是兼容的。

当类型检查系统比较两个‘private’成员时,将会认为是不同的对象。对于两个类型比较,当一个类型拥有私有成员的时候,那么另外一个类必须包含相同声明的私有变量(同一处声明,多为继承体现)。如下例:

class Animal {
    private name:stringParameter properties;
    constructor(theName: string) { this.name = theName; }
}

class Rhino extends Animal {
    constructor() { super("Rhino"); }
}

class Employee {
    private name:string;
    constructor(theName: string) { this.name = theName; }   
}

var animal = new Animal("Goat");
var rhino = new Rhino();
var employee = new Employee("Bob");

animal = rhino;
animal = employee; //error: Animal and Employee are not compatible

在上例中我们有’Animal’和‘Rhino’两个类型,’Rhino’是‘Animal’的一个子类。同时我们也定义了一个 ‘Employee’的类,它和‘Animal’类完全相同。我们分别创建了第三个类的对象,并相互赋值,结果’Animal’和’Rhino’继承关系,所以对于私有字段name在‘Animal’中具有相同的声明 ‘private name: string’,他们是兼容的。但对于’Employee’则各自声明了一个私有name字段,对于私有字段是不相同的,所以我们不能将employee赋值给animal对象,他们是不兼容的类型。

参数属性(Parameter properties)

访问限制关键字public’和’private也可以通过参数属性方式快捷初始化类成员字段,参数属性可以让我们一步创建类成员。下例是上例中我们去掉了‘theName’,利用‘private name: string’声明在构造函数参数上,它会为我们创建一个私有的name成员的同时初始化这个字段。

class Animal {
    constructor(private name: string) { }
    move(meters: number) {
        alert(this.name + " moved " + meters + "m.");
    }
}

这里我们利用‘private’关键字为类创建了一个私有成员并初始化其值。对于public也类似。

访问器(Accessors)

TypeScript支持利用getters/setters来控制对成员的访问。让我们可以控制类的成员之间的访问方式。

下面演示如何转化普通的类为get/set方式,如下是没有get/set的方式:

class Employee {
    fullName: string;
}

var employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    alert(employee.fullName);
}

在这里我们允许任意的访问内部fullName成员。有时这可能不是我们所期望的。

在下边我们希望将其转化为在修改fullName的时候必须提供一个正确的passcode,使得不能任意修改此类name,如下:

var passcode = "secret passcode";

class Employee {
    private _fullName: string;

    get fullName(): string {
        return this._fullName;
    }

    set fullName(newName: string) {
        if (passcode && passcode == "secret passcode") {
            this._fullName = newName;
        }
        else {
            alert("Error: Unauthorized update of employee!");
        }
    }
}

var employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    alert(employee.fullName);
}

这里我们在修改fullName属性的时候验证了passcode值,是否有权限修改。你可以尝试修改passcode的值,使其不匹配,观察下会发生什么问题?

注意:访问器使用我们需要设置编译输出为ECMAScript 5。

静态属性

回到类主题,上面我们所描述都是关于如何创建类的实例成员。我们同样也可以创建类的静态成员,其可见性为类级访问。我们可以使用’static’ 关键字标注类级成员。在下面的例子中表格原点对于所有表格都是通用的,所以我们可以用‘static’来定义类级成员。那么可以采用类名(Grid.)来访问访问该成员,类似于对象成员的’this.‘.

class Grid {
    static origin = {x: 0, y: 0};
    calculateDistanceFromOrigin(point: {x: number; y: number;}) {
        var xDist = (point.x - Grid.origin.x);
        var yDist = (point.y - Grid.origin.y);
        return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
    }
    constructor (public scale: number) { }
}

var grid1 = new Grid(1.0);  // 1x scale
var grid2 = new Grid(5.0);  // 5x scale

alert(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
alert(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));

高级特性

构造函数

当我们在TypeScript中声明一个类的时候,有时可能会创建多种声明方式。首先类的实例方式:

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

var greeter: Greeter;
greeter = new Greeter("world");
alert(greeter.greet());

这里“var greeter: Greeter”首先声明一个Greeter类的实例变量。这在很多OO语言中是很自然的方式。

同时也利用new关键字实例化了这个类的实例,并调用构造函数初始化该对象。下面我们可以看看同等的JavaScript将会如何去做:

var Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
})();

var greeter;
greeter = new Greeter("world");
alert(greeter.greet());

这里’var Greeter’被赋值构造函数,并利用‘new’调用了这个方法得到类的实例。同样我们的类也可以包含静态变量。我们可以这么认为所有的类都可以拥有实例和静态两种类型的成员。

让我们对上例稍微做一些修改:

class Greeter {
    static standardGreeting = "Hello, there";
    greeting: string;
    greet() {
        if (this.greeting) {
            return "Hello, " + this.greeting;
        }
        else {
            return Greeter.standardGreeting;
        }
    }
}

var greeter1: Greeter;
greeter1 = new Greeter();
alert(greeter1.greet());

var greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there!";
var greeter2:Greeter = new greeterMaker();
alert(greeter2.greet());

这里‘greeter1’和上例工作很相似。我们初始化了‘Greeter’类,并调用此对象。其结果在上例已经看见。

接着,我们直接使用了类访问。首先我们定义了一个新的‘greeterMaker’的变量,这变量保持了Greeter类的类型信息,这里我们使用的是‘typeof Greeter’,这会返回Greeter自身的类类型信息。这个类型信息中会包含所以的静态成员信息和实例化对象的构造函数信息。然后通过‘new’ greeterMaker来创建一个Greeter的实例对象,在调用其方法greet。

利用interface来使用class

如上所述,类主要声明了类实例类型和构造函数两件事。因为类主要创建类型,所以我们可以在同一地方使用interface来替代它:

class Point {
    x: number;
    y: number;
}

interface Point3d extends Point {
    z: number;
}

var point3d: Point3d = {x: 1, y: 2, z: 3};

注意:TypeScript更准确说是为了类型检查的类型推断。