Java开发网 Java开发网
注册 | 登录 | 帮助 | 搜索 | 排行榜 | 发帖统计  

您没有登录

» Java开发网 » 技术文章库  

按打印兼容模式打印这个话题 打印话题    把这个话题寄给朋友 寄给朋友    该主题的所有更新都将Email到你的邮箱 订阅主题
flat modethreaded modego to previous topicgo to next topicgo to back
作者 诊断 Java 代码:消除包间的耦合关联
palatum



CJSDN高级会员


发贴: 451
积分: 80
于 2003-03-07 14:20 user profilesend a private message to usersend email to palatumsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
诊断 Java 代码:消除包间的耦合关联

基于组件编程给连接可测试代码带来更大的灵活性
级别:入门


Eric E. Allen (eallen@cs.rice.edu)
博士研究生,Java 编程语言团队,Rice 大学
2003 年 3 月

在测试程序时,如何应付模拟外部资源和库连接这一艰巨任务?基于组件的编程和去耦包相关性可以解决这个问题。基于组件的软件开发作为促进代码的一种手段而经常受到人们赞誉,另外它还有助于生成可测试代码。Eric Allen 用一些 Jiazzi(一种使用 Java 语言进行基于组件编程的功能强大的自由工具)示例展示了这种编程概念。可以通过论坛提出您对本文的想法,以飨笔者和其他读者。(您也可以单击本文顶部或底部的“讨论”参加论坛。)
测试优先编程(test-first programming)中反复遇到的一个问题是,似乎不可能对程序的许多部分进行自动测试。尤其当程序在很大程度上要利用外部资源和库时,似乎很难对它进行测试,因为没有很好的方法来模拟程序与这些外部资源的连接。

然而,虽然只使用 Java 代码很难测试这样的程序,但有一种类型的编程(带有开发工具)可以解决这个问题 — 基于组件的编程。

基于组件的编程和 Java 语言
我所说的基于组件的编程是指什么?我只是指,编程时程序的各个单元处于分布状态,而不是象 JavaBeans 或类似技术这样的运行时“组件”。

从概念上讲,这些分布的各个单元大致类似于 Java 包。然而,Java 语言中的包非常受限,因为它们相互之间是耦合的。每个包中的类与它们导入的包之间是硬连接的(因为这些类必须显式地引用所导入的包)。

由于这些包之间是相互耦合的,因此很难统一用提供同一功能的其它包的引用来替代程序中这些包的引用。

同样,独立开发的各个团队可能偶尔会用到重复的包名,这些团队试图使用对方的包时,就会引起问题。为确保包名的唯一性,Sun 强烈主张每个开发团队使用这样的约定:用倒序排列的因特网地址作为团队开发的所有包的前缀。开发人员通常都遵守这个约定,但未必总是如此。

然而即便严格遵守了这个包命名约定,仍然有其它一些原因使程序员想解除组件之间的耦合。其中一个原因是,这样可以更有效地测试这些组件 — 在谈到基于组件的编程工具(Jiazzi 组件系统)时,会解释这一点。

Jiazzi:针对 Java 语言的组件系统
Jiazzi 是一个富有前途的、用 Java 语言进行基于组件编程的系统,它与 JVM 完全兼容,并且完全解除了各组件间的耦合,它是犹他大学计算机科学系所开发的一个项目。这个系统使程序员可以叠加组件,并在现有 Java 代码之上将这些组件连接起来。而不需修改 Java 语言或 JVM。

开发人员的描述
Jiazzi 开发人员是这样描述的:

……是这样一个系统,支持用 Java 编写的大规模二进制组件的构造 [添加了对用 Java 编写的大规模二进制组件的支持]。可以将 Jiazzi 组件看成是对 Java 包的泛化,同时向这些 Java 包添加了外部链接和独立编译的支持。Jiazzi 组件很实用,因为它们是从标准 Java 源代码构造出来的。Jiazzi 既不需要对 Java 语言扩展,也不需要对编写 Java 源码进行特殊的约定,这些扩展和约定将写在组件内部。我们的组件是富有表现力的,因为 Jiazzi 支持循环组件链接和 mixin,在开放的类模式中一起使用了循环组件链接和 mixin,这种模式支持将具有新特性的模块添加到现有的类。
当前的 Jiazzi 实现用链接程序(linker,用于操作组件)和存根生成器(使 Jiazzi 可以与常规的 Java 源码编译器共同使用)集成进了 Java 平台。Jiazzi 中的组件可以包含、导入和导出 Java 类,可以跨组件边界使用 Java 平台的用于继承的语言内支持。除了富有表现力之外,这些组件还很健壮 — 可以分别对组件的实现和链接进行类型检查。

观察解除组件的耦合
让我们研究一个 Java 包 view 的简短示例,来看一下 Jiazzi 如何解除组件间的耦合,这个示例用到了 GUI 库包。我们将调用 toolkit 包。为了引用该包中的所有类,在我们的包中源文件的开头,放置了一条 import 语句:

package view;

import toolkit.*;

...



通常,这会将 view 包与 toolkit 包联系起来。但可以设想一下,我们希望编译这个源文件,却不能确定我们实际上正在导入哪个包。我们没有将 toolkit 与 view 包硬连接起来,而是设想在这两个包之上定义一个函数,它可以接受 toolkit 包,并返回与 toolkit 包相关的 view 包。在 Jiazzi 中,这些函数称为单元(unit)。

单元类似于 LEGO 积木;可以将它们拼装在一起创建一个程序。如果将单元视为函数,则可以说,Jiazzi 提供了函数复合(functional composition)。每个单元接受一个或多个带有指定“包签名”的包,同样,可以用指定的签名导出一个或多个包。

包签名类似于类型;它们限制了包的形状。包签名会定义包中所期望的类以及这些类的方法签名等。导出包的签名可以取决于导入包的签名。

有两类单元:

原子(atom),是包之间的简单映射
复合(Compound),是其它单元的组合
原子描述了直接导入和导出的包。复合从所组合的单元继承了导入和导出的包。如果将单元视为 LEGO 积木,那么原子就是单个的 LEGO 积木,复合就是从多个 LEGO 积木构建而来的结构。

在单独的文件中,用特定的规范语言描述单元。该语言给单元输入和输出的包分配名称。例如,这里有一个简单的原子单元,它接受 toolkit 包,并输出 view 包:

atom app_view {
  import toolkit: toolkit_s;
  export view: view_s;
}



这个单元称为 app_view。它将“toolkit”名称分配给它所导入的包。声明这个包以与 toolkit_s 这个特定的包签名相匹配。该单元导出的包称为 view,并声名这个包以与 view_s 签名匹配。

正如前面所提到的,包签名类似于类型;它们限制了可能传递给某个单元(或从某个单元返回)的参数的类型。例如,toolkit_s 签名可以象这样指定一组类(这些类非常类似于 javax.swing 包中的类):
signature toolkit_s {
  class Frame {
    public Container getContentPane();
    public Component getGlassPane();
    ...
  }

  class OptionPane {
    public Object getDialog();
    ...
  }
  ...
}



我们还可以象这样指定 view_s 签名:

signature view_s {
  class EditorPane {...}
  class InteractionsPane {...}
  ...
}



当然,在签名中所引用的某些类本身可能在单独的包中。为了解除特定包中包签名间的耦合,Jiazzi 允许对包签名使用参数。在签名名称后的尖括号内是签名的包参数。例如,我们可能希望 toolkit_s 签名使用 awt 包参数,如下所示:

signature toolkit_s<awt> {
  class Frame {
    public awt.Container getContentPane();
    public awt.Component getGlassPane();
    ...
  }

  class OptionPane {
    public Object getDialog();
    ...
  }
  ...
}



然后,我们必须设计新的签名(称为 awt_s),并修改 app_view toolkit 来接受 awt 包,并用这个包实例化 toolkit_s 签名:

atom app_view {
  import
    my_toolkit: toolkit_s%lt;awt_s>;
    my_awt: awt_s;
  export my_view: view_s;
}



接着,Java 源文件可以引用 my_toolkit 和 my_view 这两个已经分配的名称,就好象它们是真正的包名一样。事实上,Jiazzi 可以让我们在根本不做任何修改的情况下重新编译上面的源文件,以引用这些已经分配的名称!(请继续阅读下面的内容,其中解释了如何这样做。)

象 LEGO 积木一样,可以使用复合单元拼装单元。复合单元以特殊的“link”子句将其它单元组合起来,其中“link”子句标识出由一些单元导出的类,而这些类是由其它单元导入的。例如,可以将 app_view atom 与 default_toolkit 和 default_awt 单元按如下方式组合起来:

atom default_toolkit {
  import my_awt: awt_s;
  export my_toolkit: toolkit_s<my_awt>;
}

atom default_awt {
  export my_awt: awt_s;
}

compound app {
  export my_view: view_s;

  local v: app_view, a: default_awt, t: default_toolkit;

  link
    a@my_awt to t@my_awt, a@my_awt to v@my_awt,
    t@my_toolkit to v@my_toolkit,
    v@my_view to my_view;
}



注意单元 app 中的 local 子句。这条子句定义了表示“单元实例”的局部变量。这些单元实例中的类是实际链接的元素。通过链接单元实例(而不是直接链接单元),Jiazzi 可以清楚地表明复合单元中的链接不影响各组成单元的定义。

通过为程序指定这些签名、原子和复合,我们描述了如何将程序中的各个包链接在一起。Java 源文件是指单元所输入和输出的包,但在另一方面它们看上去象普通的 Java 文件。下一节将详细讲述如何使用 Jiazzi 来以单元所指定的方式将 Java 类真正链接在一起。

Jiazzi 的工作原理
Jiazzi 分三个阶段编译代码:

首先,将一组签名和单元定义传送给 Jiazzi 存根生成器,然后,Jiazzi 存根生成器为传递给该生成器任何单元的签名中所导入的所有类生成存根类文件。

然后,常规的 Java 编译器使用这些类文件来编译源文件,这些源文件对应于提供给存根生成器的单元所导出的类。

编译完源文件后,Jiazzi 单元链接程序检查结果类文件是否与原来单元中所声明的类签名匹配。必需要有这一步,因为:

Jiazzi 可以与任何第三方的编译器共同使用
Jiazzi 从不检查 Java 源文件
(顺便说一句,请注意,Jiazzi 这种事实上从不检查 Java 源代码的方法有其优越之处。它使 Jiazzi 可以与用于 JVM 的非 Java 语言的编译器一起使用,譬如 Jython、JSR-14 和 NextGen 编译器。事实上,Jiazzi 本身就是用 JSR-14 编写的。)

检查完之后,组件链接程序为提供给它的每个单元生成一个 JAR 文件。这个 JAR 文件包含已编译的源文件和存根以及作为元数据的签名信息。接着,通过将这些 JAR 文件传递给 Jiazzi 及相应的复合单元,从而将这些 JAR 文件链接起来。

Jiazzi 单元链接程序是脱机工作的,并且单独地工作在类文件常量池之上,而且会重命名隐藏的方法,从而避免了偶尔会发生的方法名称冲突。

通过特定的类装入器,还可以联机链接单元。然而,由于不能使用编译单元所依据的存根类,因此必须在类装入器中进行类型检查,作为“递增性整体程序分析”。事实上,Jiazzi 程序员目前必须将脱机链接和联机链接结合起来使用,因为在标准 Java 库中,有许多类只能通过类装入器来链接。

当前系统的另一个限制是,重命名会影响到 JNI 和反射库。尤其是不能重命名本机方法,因为它们是用 C 语言编写的。其结果是,许多类库(那些过度依赖 JNI 和反射的库)不能作为 Jiazzi 组件进行重新打包。

如上所说,复合单元描述其它单元间的连接。这些链接与该复合的导入和导出单元的绑定名称相关;已链接的单元完全不会意识到已将它们链接起来了(链接程序最终会将这些复合单元宏展开成原子)。

通过这种方式,用简单的重新编译换入和换出新包,从而使我们可以创建和分发程序的 JAR 文件。而不需要涉及一行 Java 源代码。

此外,其他 Jiazzi 用户能够在可以用 JAR 文件之前,根据程序所提供的类进行开发;他们所需要的只是相应导出的包签名。他们对包所做的扩展也只与那个包签名的任何其它实现相链接。

单元测试和 Jiazzi
基于组件的编程提供了许多优点。最常受到人们赞誉的优点是组件更大程度地方便了代码重用。可以单独分发组件成品,并按照自己的意愿将其插进新的应用程序。而且这种类型的编程还使单元测试效率更高。

在测试时,可以使用特殊的“仿制品”组件将程序的各个组件链接起来,这些“仿制品”组件的类只是记录测试组件的行为。实质上,这些仿制品组件充当了与记录器(请参阅参考资料中的“进行记录器测试以正确调用方法”)类似的角色,但它是在组件这一层次上的。

被测试组件类似于笛卡尔的钵中之脑(brains in a vat);它们不能区分是与仿制品组件相连还是与真正的组件相连。例如,在先前的样本应用程序中,我们可以编写一个特殊的 test_app 复合,它将 app_view 单元与 test_toolkit 单元联系起来,如下所示:

compound test_app {
  export my_view: view_s;

  local v: app_view, a: default_awt, t: test_toolkit;

  link
    a@my_awt to t@my_awt, a@my_awt to v@my_awt,
    t@my_toolkit to v@my_toolkit,
    v@my_view ot my_view;
}



test_toolkit 包会导出与 toolkit 相同签名的包,但这个包只包含一些仿制的对象,使 app_view 认为它是与真正的 toolkit 相连。这些仿制对象可以记录 app_view 的行为,因为可以在单元测试时检查这些记录。

将这个系统与 Java 包系统相比较会发现,在这个系统中,每个源文件必须显式地声明要导入的包。在 Java 包系统中,欺骗整个包链接到测试包的唯一方法是编辑所有的源文件,然后重新编译这些文件。这一过程不能真正实现自动化测试。

遗憾的是,因为 Jiazzi 不能处理反射或 JNI(这一点是有情可原的),而且还因为许多内置的 Java API 广泛地使用这些设施,所以不可能将现有的 API 包转换成 Jiazzi 组件。但是,如果可以转换成 Jiazzi,那么我们能够对程序执行更强大的测试。例如,在测试期间,可以将使用 Swing API 的程序连接到仿制的 Swing 组件,从而在没有真正尝试画图形对象的情况下,可以确保调用所有相应的 API。用类似的手段,我们可以测试与 java.io 包、Java3D、JDBC、RMI 以及任何其它 API 的交互,在这些 API 中,性能或功能的本质造成了客户机难以进行测试。

好了,这是一个美好的梦想,是吗?不过,即使反射和 JNI 不能让我们使现有的 API 成为一流的 Jiazzi 单元,但仍可能有折衷的办法。

虽然不能去耦现有 API 之间的连接,但可以去耦新单元与这些 API 的连接。实质上,可以将这些 API 与只导出类的特殊单元捆绑在一起;导入仍然与现有的 Java 包系统硬性地连接在一起。如果这些 API 是真正的单元,则这样做可以实现 99% 的功能;只有那些想对现有 API 构建第三方选择的程序员会对此不满意。

可喜的是,Jiazzi 将朝着这个方向或通过类似的途径变化着,这使我们可以和现有的 API 一起使用它。同时,它还为测试不使用反射或 JNI 的包提供了功能强大的机制。

去耦历史
最初开发 Jiazzi 的动机是为特性的可扩展性创建一种机制 — 将一个设计拆分成几个特性(每个单独的组件包含其中一个特性)。基于组件的编程主张,在以后的编程中,开发人员可以从组件供应商提供的成品组件上开发程序。具有通用签名的组件就可以象汽车零部件一样进行互换。

无论这一天是否会到来,即使没有繁荣的组件软件市场,基于组件编程仍有许多好处。尤其是,这种类型的编程使单元测试效率更高。

正如我们所展示的,用 Jiazzi 进行基于组件的编程提供了一种功能强大的测试程序组件的手段,并且它可以使用现有的 Java(或 Jython,或 JSR-14)源文件。我们可以从这一强大的工具中获益。这一工具及其它类似的工具是面向测试编程的必要工具。

下次,我将讨论 Jam(一种 Java 语言的扩展),它允许基于 mixin 的编程。就象 Jiazzi 提供了去耦包相关性的方法一样,mixin 提供了去耦类相关性的方法。正如您可能猜到的,minxin 给我们提供了测试程序的另一种强大的机制。



作者 Re:诊断 Java 代码:消除包间的耦合关联 [Re:palatum]
floater

Java Jedi

总版主


发贴: 3233
积分: 421
于 2003-03-08 00:09 user profilesend a private message to usersearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
Good to see someone did this. Keep eyes open!

I just have hard time to understand this(computer terminologies) in chinese, but finally got it(translate it back to english, Big Smile).



"Any fool can write code that a computer can understand. Good programmers write code that humans can understand."
- Martin Fowler, Refactoring - Improving the Design of Existing Code
作者 OK,Here is the English Version [Re:floater]
palatum



CJSDN高级会员


发贴: 451
积分: 80
于 2003-03-09 18:23 user profilesend a private message to usersend email to palatumsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
Diagnosing Java code: Decoupling package dependencies

Component-based programming brings more flexibility to wiring testable code
Level: Introductory


Eric E. Allen (eallen@cs.rice.edu)
Ph.D. candidate, Java programming languages team, Rice University
November 2002

In testing programs, how do you meet the tough challenge of simulating outside resources and library connections? Component-based programming and decoupling package dependencies may be the answer. Often touted as a means of facilitating code reuse, component-based software development also makes for more testable code. Eric Allen illustrates this concept of programming with examples from Jiazzi, a powerful, free tool for component-based programming using the Java language. Share your thoughts on this article with the author and other readers in the discussion forum. (You can also click Discuss at the top or bottom of the article to access the forum.)
One issue that comes up again and again in test-first programming is that many parts of a program seem impossible to test automatically. In particular, programs that make heavy use of outside resources and libraries seem hard to test because there's no good way to simulate the program's connections to these outside resources.

However, although such programs can be hard to test with Java code alone, there is a style of programming (with development tools) that addresses this problem -- component-based programming.

Component-based programming and the Java language
What do I mean by component-based programming? I mean simply programming with units of program distribution, not such run-time "components" as JavaBeans or similar technologies.

Conceptually, these units of distribution correspond roughly to Java packages. However, packages in the Java language are quite limited in that they aren't decoupled from one another. The classes in each package are hardwired to the packages that they import (because they must refer to these packages explicitly).

Because the packages aren't decoupled from each other, it's difficult to uniformly replace package references in a program with other package references that provide the same functionality.

Also, separate development teams may accidentally use overlapping package names, causing trouble when teams try to use each other's packages. To ensure distinct package names, Sun has strongly urged the convention that each development team use the reverse of its Internet address as a prefix to all packages that the team develops. This convention is often followed, but not always.

Still, even if the package-naming convention were followed perfectly, there are other reasons programmers would want to decouple components from one another. One reason is that we can test the components much more effectively -- we'll explain this as we talk about a tool for component-based programming, the Jiazzi component system.

Jiazzi: A component system for the Java language
One promising system for component-based programming in the Java language that offers full compatibility with the JVM and complete decoupling of components is Jiazzi, a development project from the computer science department at the University of Utah. This system allows programmers to superimpose components and wire them together atop existing Java code. No modifications of the Java language, or the JVM, is required.

The developer's description
The developers of Jiazzi describe it as:

... a system that enables the construction of [adds support for] large-scale binary components in Java. Jiazzi components can be thought of as generalizations of Java packages with added support for external linking and separate compilation. Jiazzi components are practical because they are constructed out of standard Java source code. Jiazzi requires neither extensions to the Java language nor special conventions for writing Java source code that will go inside a component. Our components are expressive because Jiazzi supports cyclic component linking and mixins, which are used together in an open class pattern that enables the modular addition of new features to existing classes.
The current implementation of Jiazzi integrates into the Java platform using a linker (for manipulating components) and a stub generator (to allow Jiazzi to be used with normal Java source compilers). Components in Jiazzi contain, import, and export Java classes and the Java platform's in-language support for inheritance can be used across component boundaries. Besides being expressive, the components are robust -- the implementation and linking of a component can be separately type checked.

Watch it decouple components
To see how Jiazzi decouples components, let's consider a short example of a Java package, view, that makes use of the GUI library package. We'll call this package toolkit. To refer to all classes in the package, we would put an import statement at the top of the source files in our package:

package view;

import toolkit.*;

...



Normally, this would tie the view package to the toolkit package. But imagine that we wanted to compile this source file without nailing down which package we're actually importing. Instead of hardwiring the connections between the toolkit and view packages, we can envision defining a function over packages that takes a toolkit package and returns a view package that depends on it. In Jiazzi, these functions are called units.

Units are like LEGO bricks; they can be snapped together to create a program. If we view units as functions, we can say that Jiazzi provides for functional composition. Every unit takes one or more packages with specified "package signatures" and exports one or more packages, again with a specified signature.

Package signatures are like types; they constrain the shape of a package. A package signature would define the classes expected in a package, the method signatures of those classes, and so on. The signatures of the exported packages may depend on those imported.

There are two types of units:

Atoms, which are simple mappings between packages
Compounds, which are compositions of other units
Atoms describe the packages they import and export directly. Compounds inherit the imported and exported packages from the units they compose. If we view units as LEGO bricks, atoms are individual LEGO bricks and compounds are structures built from multiple LEGO bricks.

Units are described in separate files with a special specification language. This language assigns names to the packages input and output by a unit. For example, here is a simple atomic unit that takes in a toolkit package and outputs a view package:

atom app_view {
  import toolkit: toolkit_s;
  export view: view_s;
}



This unit is named app_view. It assigns the name "toolkit" to the package it imports. This package is declared to fit a particular package signature called toolkit_s. The package exported by the unit is called view and is declared to fit the signature view_s.

As mentioned earlier, package signatures are like types; they constrain the shapes of the arguments that may be passed to (or returned from) a unit. For example, signature toolkit_s may specify a set of classes very similar to that of package javax.swing like this:
signature toolkit_s {
  class Frame {
    public Container getContentPane();
    public Component getGlassPane();
    ...
  }

  class OptionPane {
    public Object getDialog();
    ...
  }
  ...
}



We could also specify signature view_s as follows:

signature view_s {
  class EditorPane {...}
  class InteractionsPane {...}
  ...
}



Of course, some of the classes referred to in a signature may themselves be in a separate package. To allow decoupling of package signatures from specific packages, Jiazzi allows for parameterized package signatures. The package parameters of a signature occur within angle brackets after the name of the signature. For example, we might want to parameterize signature toolkit_s by an awt package, as follows:

signature toolkit_s<awt> {
  class Frame {
    public awt.Container getContentPane();
    public awt.Component getGlassPane();
    ...
  }

  class OptionPane {
    public Object getDialog();
    ...
  }
  ...
}



Then we would have to devise a new signature (let's call it awt_s) and modify our app_view toolkit to take an awt package and instantiate the toolkit_s signature with it:

atom app_view {
  import
    my_toolkit: toolkit_s%lt;awt_s>;
    my_awt: awt_s;
  export my_view: view_s;
}



The assigned names my_toolkit and my_view may then be referred to by Java source files, just as if they were real package names. In fact, Jiazzi would allow us to recompile the above source file to refer to these assigned names without any modification at all! (Keep reading for the explanation of how this is done.)

Like LEGO bricks, units can be snapped together using compound units. Compound units compose other units in a special "link" clause that identifies classes exported by some units with classes imported by others. For example, our app_view atom could be composed with default_toolkit and default_awt units as shown here:

atom default_toolkit {
  import my_awt: awt_s;
  export my_toolkit: toolkit_s<my_awt>;
}

atom default_awt {
  export my_awt: awt_s;
}

compound app {
  export my_view: view_s;

  local v: app_view, a: default_awt, t: default_toolkit;

  link
    a@my_awt to t@my_awt, a@my_awt to v@my_awt,
    t@my_toolkit to v@my_toolkit,
    v@my_view to my_view;
}



Notice the local clause in unit app. This clause defines local variables to denote "unit instances." The classes in these unit instances are the elements that are actually linked. By linking unit instances instead of units directly, Jiazzi makes it clear that the linkages in the compound unit have no effect on the definitions of the constituent units.

By specifying these signatures, atoms, and compounds for a program, we describe how the packages in our program are linked together. The Java source files refer to the packages input and output by our units, but they otherwise look just like ordinary Java files. The next section will detail how we can use Jiazzi to actually link Java classes together in the manner specified by the units.

How Jiazzi works
Jiazzi compiles code in three phases:

First, a set of signature and unit definitions are fed to the Jiazzi stub generator, which then generates stub class files for all of the classes imported in the signature of any unit given to it.

These class files are then used by a conventional Java compiler to compile the source files corresponding to the classes exported by the units given to the stub generator.

After compilation of the sources files, the Jiazzi unit linker checks that the resulting class files match the class signatures declared in the original units. This is necessary because:

Any third-party compiler can be used with Jiazzi
Jiazzi never examines the Java source files
(By the way, notice that Jiazzi's approach of never actually viewing the Java source code is a big win. It allows us to use Jiazzi with compilers for non-Java languages targeted for the JVM, such as Jython, JSR-14, and NextGen. In fact, Jiazzi itself is written in JSR-14.)

After checking, the component linker generates a JAR file for each unit given to it. This JAR contains the compiled sources and stubs, as well as the signature information, as metadata. These JARs can then be linked together by feeding them to Jiazzi alongside appropriate compound units.

The Jiazzi unit linker works offline and solely over class file constant pools, renaming hidden methods to avoid accidental method-name collision.

Units can also be linked online by a special class loader. However, because the stub classes that the units were compiled against are not available, type checking must be done in the class loader as an "incremental whole-program analysis." In fact, at present, Jiazzi programmers must use a combination of offline and online linking, as many classes in the standard Java libraries can be linked to only through the class loader.

Another limitation of the current system is that renaming interferes with JNI and the reflection libraries. In particular, native methods are not renamed, as they are written in C code. As a result, many of the class libraries (which rely heavily on JNI and reflection) can't be repackaged as Jiazzi components.

As noted, compound units describe connections between other units. The linkages are in relation to the bound names of the imported and exported units of the compound; the linked units are completely unaware that they are linked (the linker ultimately macro-expands these compound units into atoms).

In this way, we can create and distribute JAR files for a program in such a way that we could swap new packages in and out with a simple recompilation. Not a single line of Java source code need be touched.

Additionally, other users of Jiazzi can develop against the classes provided by our program before our JAR file is available; all they would need are the package signatures corresponding to what we export. And their extensions would link just as well to any other implementation of that package signature.

Unit testing and Jiazzi
Component-based programming offers big advantages. The most often touted advantage is that components allow for greater code reuse. Off-the-shelf components can be distributed separately and plugged into new applications at will. But this style of programming also allows for much more effective unit testing.

During testing, the constituent components of a program can be linked with special "mock" components whose classes simply record the actions of the components they test. In essence, these mock components serve a similar role to that of Recorders (see "Recorders test for proper method invocation" in Resources), but at the component level.

The tested components are like Cartesian "brains in vats"; they can't tell the difference between being wired to mock components or to real components. For example, in our sample application earlier, we could write a special test_app compound that wires our app_view unit with a test_toolkit unit, like this:

compound test_app {
  export my_view: view_s;

  local v: app_view, a: default_awt, t: test_toolkit;

  link
    a@my_awt to t@my_awt, a@my_awt to v@my_awt,
    t@my_toolkit to v@my_toolkit,
    v@my_view ot my_view;
}



Package test_toolkit would export a package of the same signature as toolkit, but this package would consist solely of mock objects that fooled app_view into thinking that it was linked to a true toolkit. These mock objects could record the actions performed by app_view, as these recordings could be checked in the unit tests.

Compare this system with the Java package system, where every source file must explicitly state the packages it imports. In the Java package system, the only way to fool a whole package into linking to a test package would be to edit all of the source files and recompile. That procedure doesn't really lend itself to automated testing.

Unfortunately, because Jiazzi (quite reasonably) can't handle reflection or JNI, and because these facilities are used extensively by many of the built-in Java APIs, the existing API packages can't be converted into Jiazzi components. But if they could, we'd be able to perform much more powerful tests over our programs. For instance, a program that used the Swing API could be wired to a mock Swing component during testing that ensured all of the appropriate API calls were made without actually trying to draw graphics objects. In a similar manner, we could test interaction with the java.io package, Java3D, JDBC, RMI, and any other API where the performance or nature of the functionality makes clients difficult to test.

Okay, nice dream, right? But even if reflection and JNI prevent us from making existing APIs into first-class Jiazzi units, there is a potential compromise.

Although connections between the existing APIs couldn't be decoupled, connections from new units to these APIs could be. In essence, the APIs could be bundled in special units that only exported classes; their imports would still be hardwired through the existing Java package system. Doing so would give us 99 percent of the functionality we'd have if these APIs were bona fide units; the only programmers that wouldn't be happy would be those who would like to build third-party replacements to the existing APIs.

Hopefully, Jiazzi evolution will move in this direction, or on a similar path that allows us to use it with the existing APIs. In the meantime, it still provides a very powerful mechanism for testing packages that don't use reflection or JNI.

Decoupling the past
The original motivation behind Jiazzi was as a mechanism for feature extensibility -- decomposing a design into multiple features (in which each feature is put in a separate component). Component-based programming advocates envision a future in which developers build programs from off-the-shelf components provided by component vendors. Components with common signatures could be swapped in and out like car parts.

Whether or not that comes to pass, there are big advantages to component-based programming even without a ubiquitous component software market. In particular, this style of programming allows for much more effective unit testing.

As we've shown, component-based programming with Jiazzi provides a very powerful way to test program components, and it works with existing Java (or Jython, or JSR-14) source files. We can all benefit from this powerful tool. It and others like it are an essential part of test-oriented programming.

Next time, I'll discuss Jam, an extension of the Java language that allows for mixin-based programming. Just as Jiazzi provides a way to decouple package dependencies, mixins provide a way to decouple class dependencies. As you might have guessed, mixins provide us with yet another powerful mechanism for testing a program.



作者 Re:诊断 Java 代码:消除包间的耦合关联 [Re:palatum]
floater

Java Jedi

总版主


发贴: 3233
积分: 421
于 2003-03-14 04:31 user profilesend a private message to usersearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
Oh, thank you, feel much better. Will read it on the train.


"Any fool can write code that a computer can understand. Good programmers write code that humans can understand."
- Martin Fowler, Refactoring - Improving the Design of Existing Code

flat modethreaded modego to previous topicgo to next topicgo to back
  已读帖子
  新的帖子
  被删除的帖子
Jump to the top of page

   Powered by Jute Powerful Forum® Version Jute 1.5.6 Ent
Copyright © 2002-2021 Cjsdn Team. All Righits Reserved. 闽ICP备05005120号-1
客服电话 18559299278    客服信箱 714923@qq.com    客服QQ 714923