jOOQ:如何通过高内聚消除Bug

请先看下面这段代码,直觉告诉我们类似这样的方法可能会带来问题:

CompilationTask getTask(
    Writer out,
    JavaFileManager fileManager,
    DiagnosticListener<? super JavaFileObject> 
        diagnosticListener,
    Iterable<String> options,
    Iterable<String> classes,
    Iterable<? extends JavaFileObject> 
        compilationUnits
);

这是为什么呢?让我们接着深入研究。这是一个来自Java编辑器的Javadoc的一个示例:

Iterable<? extends JavaFileObject> compilationUnits1 =
    fileManager.getJavaFileObjectsFromFiles(
        Arrays.asList(files1));
 
compiler.getTask(null, fileManager, null, 
                 null, null, compilationUnits1)
        .call();

这会带来什么问题呢?我们有很多非常类型分散的参数,它们很可能被设为null。这降低了上述方法的复用性,或者按照JArchitect那帮家伙所说的,我们可能处于“痛苦地带”,因为这些方法具备低层次的稳定性和抽象性

  • 低稳定性: 在Java编辑器的将来版本,极有可能我们需要另一个指定的参数,例如另一个实现Iterable的类实例。这会增加API的不兼容性。
  • 低抽象性: 即使上述方法是一个接口方法,它也很少有机会被实现多次。因为以一种有用的方式来满足上述约束是很困难的。

要规避这类单一方法带来的问题,一种有效途径是使用建造者模式,正如Petri Kainulainen精彩地描述的那样。

高内聚解决“痛苦地带”

可能你认为对于编译器API来说这并不是很重要。但“高内聚”的最大价值,例如一个理想的稳定/抽象平衡,是能够使你的代码具有很高地重用性。它的美妙之处不仅在于能使你的开发者花费更少的时间完成特定的任务,还意味着你的代码能够很好地避免错误。举个例子,请从JOOQ的网站下载数据类型转化的逻辑代码。

上面只是一个单一数据类型转换API的调用层级指引的提取,它被整个框架所使用。所有API都通过那里指引。因此如果存在任何数据类型转换的bug,只有两种极端的表现:

  • 定位到上面树的单一方法/单一叶子节点
  • 扩展到整棵树

换句话说,任何与数据类型转化相关的bug要么只是表面问题,要不就是全面的灾难性问题。这基本上意味着几乎不可能存在区域性的回归测试,因为任何数据类型转换的回归测试都会使得数百个单元测试和集成测试运行失败。

如何达到高内聚

方法很简单:通过不断地重构。一个新功能应该从不只是局部地引入。举个例子,我们这里考虑问题: [#3023]DefaultRecordMapper没有将嵌入UDT映射为嵌入的POJO,因此我们想将JOOQ的RecordMapperProvider功能应用到嵌入式记录。为什么呢?想象我们有一个PERSON表,它包含Oracle对象类型的ADDRESS和STREET属性。是的,你只是标准化了这些数据,但请想象我们正在使用UDT:

CREATE TYPE street_type AS OBJECT (
  street VARCHAR2(100),
  no VARCHAR2(30)
);
 
CREATE TYPE address_type AS OBJECT (
  street street_type,
  zip VARCHAR2(50),
  city VARCHAR2(50)
);

现在,我们想要将这些数据递归映射到自定义的嵌入式POJO:

public class Street {
    public String street;
    public String number;
}
 
public class Address {
    public Street street;
    public String city;
    public String country;
}
 
public class Person {
    public String firstName;
    public String lastName;
    public Address address;
}

这个映射可以通过下面代码完成:

// The configuration object contains the
// Mapping algorithm implementation
Person person = DSL.using(configuration)
                   .selectFrom(PERSON)
                   .where(PERSON.ID.eq(1))
 
// We want to make the mapping algorithm recursive
// to automatically map Address and Street as well
                   .fetchOneInto(Person.class);

映射一个记录到一个POJO已经实现了,但递归地映射没有实现。当我们实现了递归的方式,我们就会想要重构现有的代码。上述的自定义映射SPIJOOQ3.1中被引入。它很简单,我们只需具备一个ConvertAll类型的单独实现点就行。

基于高内聚代码实现意味着:

  • 我们只需实现这个新的功能一次;
  • 实现该新功能比写这篇博客花费更少地精力;
  • 嵌入式记录映射和对话将以一种形式为所有用例服务;
  • 我们再增加了一个很棒的新功能同时,仅增加了很少的复杂度(降低了引入bug的风险)。

你不断地重构了吗?

我们无法预见完美的设计,它是慢慢地成长起来的。今天,我们知道了很多关于Java和集合的事情,话费了一些时间简单地介绍了新的Streams API。没有人能够随便就在JDK 1.2中实现如此一个伟大的新API,即使以那时的角度看,它已经相当不错了。

这对于你来说意味着两件事情:

  • 对于你的主要核心代码,要使它达到一个高内聚的状态很重要。如果你有一个电子银行的卖主,你的付款和回扣逻辑应该像上面一个精确,具备一个平衡的稳定性/抽象性比例。
  • 对于你的次要代码(例如用户界面、数据库访问),你应该依靠第三方软件,因为其他人花费了更多的时间使他们的代码具有高层次的质量。(用户界面方面:例如VaadinZK,数据库访问方面:例如HibernateJOOQSpring Data,只列举一部分)。

还有,如果你想要一个高内聚架构中的新功能,可能唯一需要完成的事情是这四行代码

原文链接: jooq 翻译: ImportNew.com - will
译文链接: http://www.importnew.com/12472.html
[ 转载请保留原文出处、译者和译文链接。]

关于作者: will

(新浪微博:@Fighter_D_Will

查看will的更多文章 >>



相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部