Infer Type

From Intoj.org

Jump to: navigation, search

Contents

[edit] Purpose

to minimize coupling between classes by replacing the declared types of program elements and type arguments with new, maximally general ones having no unneeded members


[edit] System requirements

  • Eclipse 3.2
  • Java 5


[edit] Rationale

It is common wisdom that variables should be typed with abstract interfaces rather than concrete classes. Usually, the smaller these interfaces, the better, since every superfluous member establishes superfluous coupling. Infer Type computes, for a single or a set of declaration elements of the same type, that smallest (most general) type that can be used in place of the original type, and redeclares the declaration element(s) accordingly. Thus, it is like an automatic version of Extract Interface, combined with a mixture of Generalize Declared Type and Use Supertype Where Possible. However, Infer Type cannot be replaced by any combination of these, so that it is actually a new refactoring.


[edit] History

Infer Type is the initial refactoring tool for Eclipse that drove the development of ITcore and many of the other tools offered by intoJ. It implements a new refactoring that decouples classes by computing for a selected declaration element its maximally general type (interface or abstract class), i.e., that type that contains only the members accessed through the declaration element and the ones it gets assigned to. It then offers the redeclaration of the involved declaration elements with the inferred type (or types, in case this should be necessary).

Infer Type has a history of three versions:

  • Infer Type 1 (2002) is a naive implementation that can only imcompletely handle assignments. It is still the basis of the old intoJ Suite.
  • Infer Type 2 (2004) is built directly on Eclipse's AST. It performs class hierarchy analysis to determine the set of members required for each declaration element. Infer Type 2 reached a fairly stable (and correct) implementation status: it was extensively tested by automatically applying to large code bases. However, it was restricted to the type system of Java 1.4.
  • Infer Type 3 (2007) is based on type constraints and can handle all of Java 5. It utilizes ITcore. Since due to their limitations all older versions of Infer Type are now obsolete, version numbering has been reset and Infer Type 3 starts with version number 1.


[edit] What Infer Type does

Infer Type computes for a given reference ("declaration element") the transitive closure of all members accessed on the reference and all other references it might get assigned to. From the closure, it creates a new type (interface or abstract class), inserts it into the type hierarchy, and redeclares the reference with it. Infer Type regards particulars of Java’s type system such as subtyping (of course), overloading (to some extent), access to protected and private members, and even type casts; the program resulting from the refactoring will always (except for bugs in the refactoring) be type correct, even if this requires introduction of more than one new type along an assignment chain.

Infer Type

  • uses, and extends, Eclipse’s built-in type constraint framework;
  • handles generics;
  • infers types from import statements, meaning that it computes the exact required interface of a class;
  • computes the exact required interface for a whole package or project;
  • generalizes type arguments for generic type references; and
  • leverages Eclipe’s refactoring framework, by providing a full preview to all changes.

See examples for some more in-depth information.


[edit] Why you might want to use Infer Type

  1. Mutual decoupling of client and server Suppose a client turns to a server for some specific service. In order to deliver this service, the server requires certain information from the client, precisely which depending on the state of the server or its environment. Rather than passing all the information that is possibly needed with the service request (method invocation), the client makes itself known to the server (by passing this as a parameter) so that the server can call back the client for whatever it needs to know. The method signature of the service as implemented by the server will then look something like void service(Client c). Well-factored client/server relationships are usually m:n. While in the given case it is obvious how to abstract from different servers (simply by introducing an interface declaring the service in question and letting all servers implement it), it is less trivial to find an abstraction of the different clients, since what the servers need from them is buried in the servers’ code and – possibly – that of their delegates. Inferring the type of the service's formal parameter c (on which the callback is to be issued) does exactly this.
  2. Determining the required interface of a class While the provided interface of a class (or rather its instances) is clearly defined by its publicly accessible members, the required interface is buried in the variable declarations and the code that makes use of these declarations. Infer Type can be used to extract the exact required interfaces of a class, simply by performing it on its instance variables (fields), formal parameters of methods, and temporaries. (Note that while the number of provided interfaces of a class is usually determined by the number of its supertypes (and thus can be found in the class's declaration), the required interfaces are usually less explicit; a first approximation can be found in the import statements required by the class definition.)
  3. Determining the interface of a mock object To test an object that depends on the behaviour of another object in isolation (as done, for instance, in unit testing), this other object (which is not the subject of testing) is usually replaced by a mock object. Mock objects must exhibit the same interface as the objects they replace, but only to the extent that is actually required by the object under test. By executing Infer Type on a variable that holds the mock (to be) object, this interface can be automatically generated from the test code.
  4. Verifying the safe use of an object The type inference part of the Infer Type refactoring may also be used in isolation. For instance, the Java API somewhat unorthodoxly defines Stack as subtype of Vector, allowing its users to pass a stack where a vector is required. This risks a stack being used by library code (to which it is passed as an actual parameter) in such a way that the specification of a stack is broken, for instance by inserting or removing elements at arbitrary positions. By inferring the type on the formal parameter (with declared type Vector) of the library method in question one can check – without working through the code – whether dangerous methods are being accessed. Because the computation of the inferred type is based on a static program analysis, the result is conservative, i.e., it may include elements that are never called in any possible invocation of the method.
  5. Extracting collaboration roles Reengineering legacy code may require the identification of UML-style collaborations. The participants of such a collaboration (sometimes called collaboration roles) are protocol specifications, or interfaces. In order to extract those interfaces, one has to identify the links (as represented by variables) underlying the collaboration. Inferring their types then yields the different roles played by each collaborator, which together specify the requirements (required protocol) of a role-playing class.
  6. Applying the Dependency Inversion Principle (DIP) The Dependency Inversion Principle says that modules of a program should only depend on others if these are on the same level of abstraction as the dependent. For instance, in a layered architecture the dependency on the (implementations of) lower layers should be lifted by introducing abstractions placed at the higher layers. In case of OOP, this means that the classes implementing lower layers should have explicit, separate interfaces allowing the higher layers to become independent of these classes. Conceptually, these interfaces can be considered part of the higher, abstract levels – they represent their required interfaces. Such interfaces can be derived and introduced with Infer Type in one step. In the case of functional decomposition, the Dependency Inversion Principle requires that the components of a function possess suitable abstractions that make the composite function independent from the components' concrete implementation. For instance, for a copy function that depends on read and write functions to become independent of concrete on read and write implementations (which might be members of different classes representing a specific source and sink), these functions should be factored out into specific interfaces, representing abstract read and write functions. Such can also be done with Infer Type.
  7. Applying the Interface Segregation Principle (ISP) The Interface Segregation Principle requires that different clients of a server class are served through different interfaces (named client/server interfaces here). This not only reduces the dependency of the client on the server, but also that of the server on the client: if the client changes and with it its needs from the server, the server's interface must change as well. Since different clients are served through different interfaces, other clients of the server remain unaffected. The Infer Type refactoring lets you derive segregated client/server interfaces from your code, simply by applying it on the references ("declaration elements") representing the client/server dependencies.
  8. Applying the Dependency Injection Pattern The Infer Type refactoring also helps you introduce the Dependency Injection Pattern to your code. This will be covered by a separate refactoring Inject Dependency.


[edit] Usage

After having copied this jar into Eclipse’s plugin directory, and after having restarted Eclipse, Infer Type can be used as follows:

  • In an Editor, place the curser at a declaration element (variable or method declaration), or a single type import declaration, and start Infer Type from the context menu.
  • In an Outline view, select a declaration element or an import of a type and start Infer Type from the context menu.
  • Save all changes to files if so prompted.
  • Infer Type presents the inferred specifications of one or more types (interfaces or abstract classes) that contain all members needed. Additional members can be selected, if deemed appropriate. However, these will be unneeded for the selected declaration element(s), therefore introducing unnecessary coupling.
  • If an inferred type does not yet exist, or if you want to create a new type anyway, enter a suitable name.
  • Review the changes to be made by the refactoring by pressing the Preview button.


[edit] Things to note

  • Infer Type aims at decoupling classes. Therefore, it computes only interfaces or, in case an interface cannot be inserted in the type hierarchy (because it would have to subtype a class), abstract classes with the protocol of an interface (i.e., classes containing only public abstract methods).
  • Because Infer Type introduces only interfaces or correpsonding abstract classes, it does not infer the types of declaration elements accessing non-public or static members, or fields.
  • Because Infer Type adds new supertypes to the types of declaration elements, it cannot execute if the source code of these types is inaccessible. This is typically the case for library classes such as the JDK’s.
  • Because it must maintain assignment compatibility, Infer Type usually also redeclares other declaration elements than the ones it was started on. If these declaration elements are declared with supertypes of the original type, the inferred type may have to subtype these supertypes. In this case, if the supertype is a class, the inferred type cannot be an interface. Infer Type tries to insert an abstract class instead, but fails if the original type already has a superclass and the new superclass cannot be inserted in the superclass chain.


[edit] Download

You can download the Infer Type refactoring tool here:

To install, copy the jar into Eclipse's plugin directory and restart the workbench.