The Infer Type Primer

Infer Type helps you to decouple your programs, particularly in the context of pluggable and service-oriented architectures.

For a quick introduction, refer to this hands-on example set up in the context of Martin Fowler's Dependency Injection Pattern.

What Infer Type is

Infer Type is a new Eclipse refactoring that redeclares a reference with its smallest possible interface. It is a kind of "Extract Interface meets Use Supertype Where Possible", but more intelligent than the former and less global in its effect than the latter.

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. For this, it uses a static program analysis of the assignments, parameter passes, and returns in a program. 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.

Why you might want to use Infer Type

A first brief example of how Infer Type might ease your life as a coder is given here. Admittedly, this example is simplistic, so perhaps it needs more to convince you.

  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   In case you hadn't noticed, the Infer Type refactoring helps you introduce the Dependency Injection Pattern to your code. Here is how it works.
     
  9. Extra Bonus: Graphical Type Hierarchy Viewer   In case you're still not convinced: the Infer Type feature comes with a neat tool that allows you to view the type hierarchy above a selected type graphically: just right click on a type in the explorer, and select "Open Type Hierarchy Diagram" from the context menu!

How Infer Type works

This article describes much of how Infer Type works.

How Infer Type has been tested

Once you have installed Infer Type, you have also installed a refactoring named Multiple IT Refactorer. This refactoring repeatedly visits all declaration elements of the project, package or class on which you called it and applies Infer Type on each one of them until no more changes to your program are possible. Multiple IT Refactorer is non-interactive: it introduces new types without prompting the user. As a result, you get a program in which all elements are declared with minimal (ie, containing only the members needed) or maximally general (ie, no more general supertype could be introduced which could be used instead) types. While this refactoring itself has only theoretical merit (it can be used to compute the cost of maximally decoupling your code), it tests Infer Type systematically: after each change to the program, the program must compile cleanly. The refactoring checks this continuously.

We have tested Infer Type by applying the Multiple IT Refactorer on various projects, the most prominent being JUnit and JHotDraw. These projects were somewhat special in that only moderate use of arrays and inner types, as well as the ?: operator was made. We expect most remaining bugs to relate to these language constructs.

Our testing strategy cannot check whether the introduced types are truly minimal, ie, whether or not they contain members that are not needed in any context. For this, one could try to remove members from types at random and see whether the program still compiles. Also, it does not check whether the refactoring affects the semantics of the program; since only types in declarations are changed and since all introduced new types are abstract, such errors would likely be associated with static and/or dynamic binding. A theoretical proof that such problems cannot occur might be possible; we have however not attempted such.

Known problems of Infer Type

see here

How to get Infer Type

see here


home

The IntoJ Team, 2005, 2006, 2007.