Java 8 Friday:大多数内部DSL已过时

Data Geekery ,我们喜欢Java。 而且,由于我们真的很喜欢jOOQ的流畅的API和查询DSL ,我们对Java 8将为我们的生态系统带来什么感到非常兴奋。

Java 8星期五

每个星期五,我们都会向您展示一些不错的教程风格的Java 8新功能,这些功能利用了lambda表达式,扩展方法和其他好东西。 您可以在GitHub上找到源代码

大多数内部DSL已过时

这是目前市场上最先进的内部DSL之一的供应商的说法。 让我解释:

语言很难

学习新语言(或API)非常困难。 您必须了解所有的关键字,构造,语句和表达式类型等。这对于外部DSL,内部DSL和“常规” API都是正确的,它们本质上是内部DSL,但流畅度较低。

使用JUnit时,人们已经习惯使用hamcrest匹配器 。 它们有六种语言(Java,Python,Ruby,Objective-C,PHP,Erlang)可用,这一事实使它们成为一个不错的选择。 作为特定领域的语言,他们已经建立了易于阅读的习惯用法,例如

assertThat(theBiscuit, equalTo(myBiscuit));
assertThat(theBiscuit, is(equalTo(myBiscuit)));
assertThat(theBiscuit, is(myBiscuit));

阅读此代码时,您将立即“理解”所声明的内容,因为API的读法像prosa。 但是学习用此API编写代码更加困难。 您将必须了解:

  • 所有这些方法来自哪里
  • 存在哪些方法
  • 谁可能使用自定义匹配器扩展了障碍
  • 扩展DSL时的最佳做法是什么

例如,在上面的示例中,三个之间到底有什么区别? 我什么时候应该使用另一个? 是is()检查对象身份吗? equalTo()是否检查对象是否相等?

hamcrest教程继续着以下示例:

public void testSquareRootOfMinusOneIsNotANumber() {
    assertThat(Math.sqrt(-1), is(notANumber()));
}

您可以看到notANumber()显然是一个自定义匹配器,在实用程序中的某个地方实现了:

public class IsNotANumber
extends TypeSafeMatcher<Double> {

  @Override
  public boolean matchesSafely(Double number) {
    return number.isNaN();
  }

  public void describeTo(Description description) {
    description.appendText("not a number");
  }

  @Factory
  public static <T> Matcher<Double> notANumber() {
    return new IsNotANumber();
  }
}

尽管这种DSL的创建非常容易,并且可能也很有趣,但是出于简单的原因,开始着手编写和增强自定义DSL是很危险的。 它们绝不比其通用的,功能相同的同类更好-但它们却更难维护。 考虑一下Java 8中的上述示例:

用功能代替DSL

假设我们有一个非常简单的测试API:

static <T> void assertThat(
    T actual, 
    Predicate<T> expected
) {
    assertThat(actual, expected, "Test failed");
}

static <T> void assertThat(
    T actual, 
    Predicate<T> expected, 
    String message
) {
    assertThat(() -> actual, expected, message);
}

static <T> void assertThat(
    Supplier<T> actual, 
    Predicate<T> expected
) {
    assertThat(actual, expected, "Test failed");
}

static <T> void assertThat(
    Supplier<T> actual, 
    Predicate<T> expected, 
    String message
) {
    if (!expected.test(actual.get()))
        throw new AssertionError(message);
}

现在,将hamcrest匹配器表达式与其功能等效项进行比较:

// BEFORE
// ---------------------------------------------
assertThat(theBiscuit, equalTo(myBiscuit));
assertThat(theBiscuit, is(equalTo(myBiscuit)));
assertThat(theBiscuit, is(myBiscuit));

assertThat(Math.sqrt(-1), is(notANumber()));

// AFTER
// ---------------------------------------------
assertThat(theBiscuit, b -> b == myBiscuit);
assertThat(Math.sqrt(-1), n -> Double.isNaN(n));

有了lambda表达式和经过精心设计的assertThat() API,我可以肯定,您将不再寻找用匹配器表达断言的正确方法。

请注意,不幸的是,我们不能使用Double::isNaN方法引用,因为它与Predicate<Double>不兼容。 为此,我们必须在断言API中执行一些原始类型的魔术,例如

static void assertThat(
    double actual, 
    DoublePredicate expected
) { ... }

然后可以这样使用:

assertThat(Math.sqrt(-1), Double::isNaN);

好但是…

……您可能会听到自己在说,“但是我们可以将匹配器与lambda和流结合起来”。 是的,我们当然可以。 我现在已经在jOOQ集成测试中做到了。 我想跳过所有不在系统属性中提供的方言列表中的SQL方言的集成测试:

String dialectString = 
    System.getProperty("org.jooq.test-dialects");

// The string must not be "empty"
assumeThat(dialectString, not(isOneOf("", null)));

// And we check if the current dialect() is
// contained in a comma or semi-colon separated
// list of allowed dialects, trimmed and lowercased
assumeThat(
    dialect().name().toLowerCase(),

    // Another matcher here
    isOneOf(stream(dialectString.split("[,;]"))
        .map(String::trim)
        .map(String::toLowerCase)
        .toArray(String[]::new))
);

……那也很整洁,对吗?

但是为什么我不干脆写:

// Using Apache Commons, here
assumeThat(dialectString, StringUtils::isNotEmpty);
assumeThat(
    dialect().name().toLowerCase(),
    d -> stream(dialectString.split("[,;]"))
        .map(String::trim)
        .map(String::toLowerCase())
        .anyMatch(d::equals)
);

无需Hamcrest,只需普通的旧lambda和溪流!

现在,当然,可读性只是一个问题。 但是上面的示例清楚地表明,不再需要 Hamcrest匹配器和Hamcrest DSL。 鉴于在接下来的2-3年内,所有Java开发人员中的大多数将非常习惯于每天使用Streams API,而不是非常习惯于使用Hamcrest API,因此,我敦促JUnit维护人员不要使用使用Hamcrest以支持Java 8 API。

哈姆克雷斯特现在被认为是坏人吗?

好吧,它过去已经达到了目的,人们对此已经有所适应。 但是,正如我们在上一篇有关Java 8和JUnit Exception匹配的文章中已经指出的那样,是的,我们确实相信Java的人们在过去的十年中一直在树错误的树。

缺少lambda表达式已导致各种完全膨胀的 ,现在也有些无用的库 。 许多内部DSL或注释魔术师也受到影响。 不是因为他们不再解决以前遇到的问题,而是因为它们还没有支持Java-8。 Hamcrest的Matcher类型不是功能接口,尽管将其转换为一个接口很容易。 实际上,Hamcrest的CustomMatcher逻辑应该被拉到Matcher接口中,成为默认方法。

使用诸如AssertJ之类的替代方案,事情不会变得更好。该替代方案创建了一个替代DSL,现在它已通过lambda和Streams API变得过时了(就呼叫站点代码冗长而言)。

如果您坚持使用DSL进行测试,那么无论如何Spock可能都是一个更好的选择。

其他例子

Hamcrest只是这种DSL的一个示例。 本文展示了如何通过使用标准的JDK 8构造和几个实用程序方法几乎完全将其从堆栈中删除,无论如何,您可能很快就会在JUnit中使用它们。

Java 8将为上个十年的DSL辩论带来很多新的吸引力,因为Streams API还将大大改善我们看待转换或构建数据的方式。 但是,当前许多DSL尚未为Java 8做好准备,并且尚未以功能性方式进行设计。 对于难以学习的事物和概念,它们有太多的关键字,可以使用函数更好地建模。

该规则的一个例外是jOOQjRTF之类的DSL,它们以1:1的方式对实际存在的外部DSL进行建模,继承了所有现有的关键字和语法元素,从而使它们从一开始就很容易学习。

你拿什么

您对上述假设有何看法? 最喜欢的内部DSL是什么,由于Java 8已过时,它可能在未来五年内消失或完全转换?

翻译自: https://www.javacodegeeks.com/2014/06/java-8-friday-most-internal-dsls-are-outdated.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值