<!doctype html public "-//w3c//dtd html 4.0 transitional//en"> | |
<html> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> | |
<meta name="Author" content="Jim des Rivières"> | |
<meta name="GENERATOR" content="Microsoft FrontPage 4.0"> | |
<link rel="stylesheet" href="../../default_style.css" type="text/css"> | |
<title>Evolving Java-based APIs</title> | |
</head> | |
<body> | |
<h1> | |
Evolving Java-based APIs</h1> | |
Jim des Rivières, OTI | |
<p>Revision history:<br> | |
June 8, 2001 - revision 1.02 - Added note about breakage due to adding API method | |
to classes that may be subclassed. <br> | |
January 15, 2001 - revision 1.01 - Added suggestion about making obsolete hook | |
methods final. <br> | |
October 6, 2000 - revision 1.0</p> | |
This document is about how to evolve Java-based | |
APIs while maintaining compatibility with existing client code. The main subjects | |
covered are: | |
<ul> | |
<li> | |
<a href="#API elements">API Java Elements</a></li> | |
<li> | |
<a href="#API Prime Directive">API Prime Directive</a></li> | |
<li> | |
<a href="#Achieving API Contract Compatibility">Achieving API Contract | |
Compatibility</a></li> | |
<li> | |
<a href="#Achieving API Binary Compatibility">Achieving API Binary Compatibility</a></li> | |
</ul> | |
Without loss of generality, we'll assume that there is a generic <b>Component</b> | |
with a <b>Component API</b>, with one party providing the Component and | |
controlling its API. The other party, or parties, write <b>Client</b> code | |
that use the Component's services through its API. This is a very typical | |
arrangement. | |
<h2> | |
<a NAME="API elements"></a>API Java Elements</h2> | |
All parties need to understand which Java elements (packages, interfaces, | |
classes, methods, constructors, and fields) are part of the Component API, | |
which Clients may use, and those which are part of the internal Component | |
implementation and are off limits to Clients. Having a clearly-defined | |
and marked boundary between API and non-API is therefore very important. | |
The following convention uses the visibility-limiting features of the Java | |
language to distinguish those Java elements which are considered API from | |
those which are not: | |
<blockquote> | |
<li> | |
<b>API package</b> - a package that contains at least one API class or | |
API interface. The names of API packages are advertised in the Component | |
documentation. These names will appear in Client code; the names of non-API | |
packages should never appear in Client code. Note that Clients must be | |
prohibited from declaring their code in Component packages (API or otherwise).</li> | |
<li> | |
<b>API class</b> - a <code>public</code> class in an API package, or a <code>public</code> | |
or <code>protected</code> class member declared in, or inherited by, some other | |
API class or interface. The names of API classes appear in Client code.</li> | |
<li> | |
<b>API interface</b> - a <code>public</code> interface in an API package, or | |
a <code>public</code> or <code>protected</code> interface member declared in, or | |
inherited by, some other API class or interface. The names of API interfaces | |
appear in Client code.</li> | |
<li> | |
<b>API method</b> - a <code>public</code> or <code>protected</code> method either | |
declared in, or inherited by, an API class or interface. The names of API | |
methods appear in Client code.</li> | |
<li> | |
<b>API constructor</b> - a <code>public</code> or <code>protected</code> constructor | |
of an API class. The names of API constructors appear in Client code.</li> | |
<li> | |
<b>API field</b> - a <code>public</code> or <code>protected</code> field either | |
declared in, or inherited by, an API class or interface. The names of API | |
fields appear in Client code.</li> | |
</blockquote> | |
The following elements are not considered API: | |
<ul> | |
<li> | |
Any package that is not advertised in the Component documentation as an | |
API package.</li> | |
<li> | |
All classes and interfaces declared in non-API packages. However, when | |
API classes and interface extend or implement non-API classes, the non-API | |
classes and interface may contribute API elements nevertheless.</li> | |
<li> | |
Non-<code>public</code> classes and interfaces in API packages.</li> | |
<li> | |
Default access and <code>private</code> methods, constructors, fields, and | |
type members declared in, or inherited by, API classes and interfaces.</li> | |
</ul> | |
<h2> | |
<a NAME="API Prime Directive"></a>API Prime Directive</h2> | |
As the Component evolves from release to release, there is an absolute | |
requirement to not break existing Clients that were written in conformance | |
to Component APIs in an earlier release. | |
<blockquote> | |
<p><b>API Prime Directive:</b> <i>When evolving the Component | |
API from release to release, do not break existing Clients.</i></p> | |
</blockquote> | |
Changing an API in a way that is incompatible with existing Clients would | |
mean that all Clients would need to be revised (and even in instances where | |
no actual changes are required, the Client code would still need to be | |
reviewed to ensure that it still works with the revised API). Customers | |
upgrading to a new release of the Component would need to upgrade all their | |
Clients at the same time. Since the overall cost of invalidating existing | |
Client code is usually very high, the more realistic approach is to only | |
change the API in ways that do not invalidate existing Clients. | |
<p>As the Component API evolves, all pre-existing Clients are expected | |
to continue to work, both in principle and in practice. | |
<p>Suppose a Client was written to a given release of the Component and | |
abided by the contracts spelled out in the Component API specification. | |
<br>The first requirement is that when the Component API evolves to follow-on | |
releases, all pre-existing Client must still be legal according to the | |
contracts spelled out in the revised Component API specification, without | |
having to change the Clients source code. This is what is meant by continuing | |
to work in principle. | |
<blockquote> | |
<p><b>API Contract Compatibility:</b> <i>API changes must not | |
invalidate formerly legal Client code.</i></p> | |
</blockquote> | |
Since the set of Clients is open-ended, and we have no way of knowing exactly | |
which aspects of the API are being counted on, the only safe assumption | |
to make when evolving APIs is that every aspect of the API matters to some | |
hypothetical Client, and that any incompatible change to the API contract | |
will cause that hypothetical Client to fail. | |
<blockquote> | |
<p><b>API Usage Assumption:</b> <i>Every aspect of the API matters | |
to some Client.</i></p> | |
</blockquote> | |
Under this assumption, deleting something in the API, or backtracking on | |
some promise made in the API, will certainly break some Client. For this | |
reason, obsolete API elements are notoriously difficult to get rid of. | |
Obsolete API elements should be marked as deprecated and point new customers | |
at the new API that replaces it, but need to continue working as advertised | |
for a couple more releases until the expense of breakage is low enough | |
that it can be deleted. | |
<p>Clients are generally written in Java and are compiled to standard Java | |
binary class files. A Client's class files are typically stored in a JAR | |
file on the Client's library path. It would be unsatisfactory if a Client's | |
class files, which were compiled against one release of the Component, | |
do not successfully link and execute correctly with all later releases | |
of the Component. This is what is meant by continuing to work in practice. | |
<blockquote> | |
<p><b>API Binary Compatibility:</b> <i>Pre-existing Client binaries | |
must link and run with new releases of the Component without recompiling.</i></p> | |
</blockquote> | |
Achieving API binary compatibility requires being sensitive to the Java | |
language's notion of binary compatibility [<a href="http://java.sun.com/docs/books/jls/second_edition/html/binaryComp.doc.html#44872">JLS2, chapter | |
13</a>]. | |
<p>While the idea that the Java source code for existing Clients should | |
continue to compile without errors against the revised Component API, this | |
is not strictly necessary (and not always achievable). For instance, adding | |
a new public interface to an existing API package may introduce an ambiguous | |
package reference into source code containing multiple on-demand type (".*") | |
imports. Similarly, removing a method <code>throws</code> declaration for a | |
checked exception may cause the compiler to detect dead code in a <code>try</code>-<code>catch</code> | |
block. Happily, the kinds of problems that could be introduced into Client | |
source | |
code can always be easily corrected. The notion of <b>API source compatibility</b> | |
is not a requirement. (Note: Problems detected by a Java compiler are therefore not | |
necessarily indicators of any kind of API compatibility that we care about.) | |
<p>The following sections discuss how API contract and binary compatibility | |
can be achieved. | |
<h2> | |
<a NAME="Achieving API Contract Compatibility"></a>Achieving API Contract | |
Compatibility</h2> | |
<blockquote> | |
<blockquote> | |
<blockquote> | |
<blockquote> | |
<blockquote><i>"How could I have broken anything? All I did was change | |
a comment."</i></blockquote> | |
</blockquote> | |
</blockquote> | |
</blockquote> | |
</blockquote> | |
Since API contracts are captured by the API specification, any change to | |
the API specification risks making code written against the old specification | |
incompatible with the revised specification. | |
<p>The most confining situation is an API that is specified by one party, | |
implemented by a separate second party, and used by yet a different third | |
party. For example, a standards body promulgates a pure specification (such | |
as the HTTP protocol) but leaves it up to others to write browsers and | |
servers. In such cases, making any changes to the existing specification | |
will almost certainly break client code, implementations, or both. | |
<p>Fortunately, the case is typically lop-sided. Most commonly, the party | |
responsible for specifying the API also provides the sole implementation. | |
Indeed, this is our earlier assumption about the Component. In this situation, | |
the API owner can unilaterally decide to change the API specification and | |
fix up the implementation to match. However, since they can't do anything | |
about the client code already using the API, the changed API must be contract | |
compatible with the old API: all existing contractual obligations must | |
be honored. Contracts can be tightened to allow users to assume more (and | |
require the implementation to do more); this does not invalidate existing | |
code which would have been written assuming less. Conversely, contracts | |
cannot be loosened to require users to assume less, as this could break | |
existing uses. | |
<p>Note that in some cases, the contractual roles are reversed. The party | |
responsible for specifying the API provides the uses, whereas other parties | |
provide the implementations. Callback interfaces are a prime example of | |
this situation. Contracts can be loosened to require implementors to provide | |
less (and allow the client to assume less); this does not invalidate existing | |
implementations which would have been written under more stringent rules. | |
Conversely, contracts cannot be tightened to require implementors to provide | |
more, as this could break existing implementations. | |
<p>When contemplating changing an existing API contract, the key questions | |
to ask are: | |
<ul> | |
<li> | |
What roles does the API contract involve?<br> | |
For a method contract, there is the caller and the implementor. In | |
the case of frameworks, there is also an additional contract between superclass | |
and subclass regarding default behavior, extending, and overriding.</li> | |
<li>Which role or roles will each party play?<br> | |
For many Component API methods, the Component plays the role of exclusive | |
implementor and the Client plays the role of caller. In the case of Component | |
callbacks, the Component plays the caller role and the Client plays the | |
implementor role. In some cases, the Client might play more than one role.</li> | |
<li> | |
Is a role played exclusively by the Component?<br> | |
Component API changes coincide with Component releases, making it feasible | |
to change Component code to accommodate the changed APIs.</li> | |
<li> | |
For roles played by Clients, would the contemplated API change render invalid | |
a hypothetical Client making legal usage of the existing API?</li> | |
</ul> | |
The following examples illustrate how this analysis is done. | |
<h3> | |
Example 1 - Changing a method postcondition</h3> | |
Standard method contracts have two roles: caller and implementor. Method | |
postconditions are those things that an implementor must arrange to be | |
true before returning from the method, and that a caller may presume to | |
be true after the return. This first example involves a change to a postcondition. | |
<p>Consider the following API method specification: | |
<p><code>/** Returns the list of children of this widget.</code> | |
<br><code> * @return a non-empty list of widgets</code> | |
<br><code> */</code> | |
<br><code>Widget[] getChildren();</code> | |
<p>The contemplated API change is to allow the empty list of widgets to | |
be returned as well, as captured by this revised specification: | |
<p><code>/** Returns the list of children of this widget.</code> | |
<br><code> * @return a list of widgets</code> | |
<br><code> */</code> | |
<br><code>Widget[] getChildren();</code> | |
<p>Would this change break compatibility with existing Clients? It depends on the role played by the Client. | |
<p>Looking at the caller role, this change would break a hypothetical pre-existing | |
caller that legitimately counts on the result being non-empty. The relevant | |
snippet from this hypothetical caller might read: | |
<p><code>Widget[] children = widget.getChildren();</code> | |
<br><code>Widget firstChild = children[0];</code> | |
<p>Under the revised contract, this code would be seen to be in error because | |
it assumes that the result of invoking <code>getChildren</code> is non-empty; | |
under the previous contract, this assumption was just fine. This API change | |
weakens a postcondition for the caller, and is not contract compatible | |
for the caller role. The contemplated change would break Clients playing | |
the caller role. | |
<p>Looking at the implementor role, this change would not break a hypothetical | |
pre-existing implementor which never return empty results anyway. Weakening | |
a method postcondition is contract compatible for the implementor role. | |
The contemplated change would not break Clients playing the implementor | |
role. | |
<p>So the answer as to whether this change breaks compatibility with existing | |
Clients hinges on which role(s) the Client plays. | |
<p>Another form of postcondition change is changing the set of checked | |
exceptions that a method throws. | |
<h3> | |
Example 2 - Changing a method precondition</h3> | |
Method preconditions are those things that a caller must arrange to be | |
true before calling the method, and that an implementor may presume to | |
be true on entry. This second example involves a change to a precondition. | |
<p>Consider the following API method specification: | |
<p><code>/** Removes the given widgets from this widget's list of children.</code> | |
<br><code> * @param widgets a non-empty list of widgets</code> | |
<br><code> */</code> | |
<br><code>void remove(Widget[] widgets);</code> | |
<p>The contemplated API change is to allow empty lists of widgets to be | |
passed in as well: | |
<p><code>/** Removes the given widgets from this widget's list of children.</code> | |
<br><code> * @param widgets a list of widgets</code> | |
<br><code> */</code> | |
<br><code>void remove(Widget[] widgets);</code> | |
<p>Would this change break compatibility with existing Clients? Again, | |
it hinges on the role played by the Client. | |
<p>Looking at the caller role, this change would not break hypothetical | |
pre-existing callers since they pass in non-empty lists. However, this | |
change would break a hypothetical pre-existing implementations that legitimately | |
assumed that the argument is not empty. | |
<p>The relevant snippet from this hypothetical implementor might read: | |
<p><code>Widget firstChild = widgets[0];</code> | |
<p>Under the revised contract, this code would be seen to be in error because | |
it assumes that the argument is non-empty; under the previous contract, | |
this assumption was just fine. This API change weakens a method precondition, | |
and is not contract compatible for the implementor role. The contemplated | |
change would break Clients that implement this method. | |
<h3> | |
Example 3 - Changing a field invariant</h3> | |
Fields can be analyzed as having two roles: a getter and a setter. The | |
Java language does not separate these roles particularly, but it does have | |
the notion of final fields which eliminates setters from the equation. | |
(Perhaps a better way to divvy this up is to say that there is a getter | |
role and a getter/setter role.) The API specification for a field | |
is usually in the form of an invariant that holds for the lifetime of the | |
field. | |
<p>Consider the following API field specification: | |
<p><code>/** This widget's list of children, or <code>null</code>.</code> | |
<br><code> */</code> | |
<br><code>Widget[] children;</code> | |
<p>The contemplated API change is to get rid of the possibility of the | |
<code>null</code> | |
value: | |
<p><code>/** This widget's list of children.</code> | |
<br><code> */</code> | |
<br><code>Widget[] children;</code> | |
<p>Would this change break compatibility with existing Clients? | |
<p>This change would break a hypothetical pre-existing setter that legitimately | |
sets the field to <code>null</code>. On the other hand, it would not break | |
a hypothetical pre-existing getter that legitimately had to assume that | |
the field could be <code>null</code>. This API change weakens a field invariant, | |
and is not contract compatible for the setter role. | |
<h3> | |
<a NAME="add method"></a>Example 4 - Adding an API method</h3> | |
Can adding an API method to a class or interface break compatibility with | |
existing Clients? | |
<p>If the method is added to an interface which Clients may implement, | |
then it is definitely a breaking change. | |
<p>If the method is added to a class (interface) which Clients are not | |
allowed to subclass (to implement), then it is not a breaking change. | |
<p>However, if the method is added to a class which Clients may subclass, | |
then the change should ordinarily be viewed as a breaking change. The reason | |
for this harsh conclusion is because of the possibility that a Client's | |
subclass already has its own implementation of a method by that name. Adding | |
the API method to the superclass undercuts the Client's code since it would | |
be sheer coincidence if the Client's existing method met the API contract | |
of the newly added method. In practice, if the likelihood of this kind | |
of name coincidence is sufficiently low, this kind of change is often treated | |
as if it were non-breaking. | |
<h3> | |
General Rules for Contract Compatibility</h3> | |
Whether a particular Component API change breaks or maintains contract | |
compatibility with hypothetical pre-existing Clients hinges on which role, | |
or roles, the Client plays in the API contract(s) being changed. The following | |
table summarizes the pattern seen in the above examples: | |
<br> | |
<table BORDER COLS=4 WIDTH="100%" > | |
<tr> | |
<td ROWSPAN="2">Method preconditions</td> | |
<td WIDTH="10%" height="23">Strengthen</td> | |
<td width="30%"><b><font color="#FF0000">Breaks compatibility for callers</font></b></td> | |
<td width="30%">Contract compatible for implementors</td> | |
</tr> | |
<tr> | |
<td WIDTH="10%">Weaken</td> | |
<td>Contract compatible for callers</td> | |
<td><b><font color="#FF0000">Breaks compatibility for implementors</font></b></td> | |
</tr> | |
<tr> | |
<td ROWSPAN="2">Method postconditions</td> | |
<td>Strengthen</td> | |
<td WIDTH="10%">Contract compatible for callers</td> | |
<td><b><font color="#FF0000">Breaks compatibility for implementors</font></b></td> | |
</tr> | |
<tr> | |
<td WIDTH="10%">Weaken</td> | |
<td><b><font color="#FF0000">Breaks compatibility for callers</font></b></td> | |
<td>Contract compatible for implementors</td> | |
</tr> | |
<tr> | |
<td ROWSPAN="2">Field invariants</td> | |
<td WIDTH="10%">Strengthen</td> | |
<td>Contract compatible for getters</td> | |
<td><b><font color="#FF0000">Breaks compatibility for setters</font></b></td> | |
</tr> | |
<tr> | |
<td WIDTH="10%">Weaken</td> | |
<td><b><font color="#FF0000">Breaks compatibility for getters</font></b></td> | |
<td>Contract compatible for setters</td> | |
</tr> | |
</table> | |
<h2> | |
<a NAME="Achieving API Binary Compatibility"></a>Achieving API Binary Compatibility</h2> | |
<blockquote> | |
<blockquote><i>"[A]n object-oriented model must be carefully designed so | |
that class-library transformations that should not break already compiled | |
applications, indeed, do not break such applications."<br> | |
</i>---Ira Forman, | |
Michael Conner, Scott Danforth, and Larry Raper, "Release-to-Release Binary | |
Compatibility in SOM", in <i>Proceedings of OOPSLA '95.</i></blockquote> | |
</blockquote> | |
Achieving API binary compatibility depends in part on the Java language's | |
notion of binary compatibility: | |
<blockquote>"A change to a type is <i>binary compatible with </i>(equivalently, | |
does not <i>break binary compatibility </i>with) preexisting binaries if | |
preexisting binaries that previously linked without error will continue | |
to link without error." (<a href="http://java.sun.com/docs/books/jls/second_edition/html/binaryComp.doc.html#44952">JLS2, | |
13.2</a>)</blockquote> | |
Reference: <a href="http://java.sun.com/docs/books/jls/second_edition/html/binaryComp.doc.html#44872">Gosling, | |
Joy, Steele, and Bracha, <i>The Java Language Specification</i>, Second | |
Edition, Addison-Wesley, 2000; chapter 13 Binary Compatibility</a>. | |
<p>The tables in the following sections summarize which kinds of changes | |
break API binary compatibility. | |
<p>Bear in mind that many changes will have effects in several places. | |
For example, defining a new public interface with one public method and | |
adding that interface as the superinterface of an existing interface has | |
the following ramifications: | |
<ul> | |
<li> | |
a new public API interface is added to an API package</li> | |
<li> | |
the superinterface set of the existing API interface has expanded</li> | |
<li> | |
a new public API method is added to the existing API interface</li> | |
</ul> | |
Each of these individual net effects could break binary compatibility. | |
Use the tables to determine whether the <i>net effects </i>preserve or | |
break compatibility. | |
<h3> | |
Evolving API packages</h3> | |
It is always possible to evolve the Component API to include a new API | |
package. However, once introduced in a release, an API package cannot easily | |
be withdrawn from service. When an API package becomes obsolete, its API | |
classes and API interfaces should continue to work but be marked as deprecated. | |
After a couple of releases, it may be possible to phase out an obsolete | |
API package. | |
<p>The names of non-public (non-API) classes and interfaces in API packages | |
do not appear in Client source code or binaries. Non-API classes and interfaces | |
can be added or deleted without jeopardizing binary compatibility. However, | |
once made public in a release, these classes and interfaces are part of | |
the API and cannot easily be withdrawn from service without breaking existing | |
Clients. When an API class or interface becomes obsolete, it should continue | |
to work but be marked as deprecated. | |
<br> | |
<table BORDER COLS=3 WIDTH="100%" > | |
<tr> | |
<td>Add API package</td> | |
<td width="20%">-</td> | |
<td width="20%">Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Delete API package</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Add API interface to API package</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Delete API interface from API package</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Add API class to API package</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Delete API class from API package</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Add non-<code>public </code>(non-API) class or interface to API package</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Delete non-<code>public</code> (non-API) class or interface from API package</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Change non-<code>public</code> (non-API) class or interface in API package | |
to make public (API)</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Change <code>public</code> class or interface in API package to make non-<code>public</code></td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Replace API class by API interface of same name</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (1)</td> | |
</tr> | |
<tr> | |
<td>Replace API interface by API class of same name</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (1)</td> | |
</tr> | |
</table> | |
<p>(1) API class-interface gender changes break binary compatibility, even | |
in cases where the class/interface is used by, but not implemented by, | |
Clients. This is because the Java VM bytecodes for invoking a method declared | |
in an interface are different from the ones used for invoking a method | |
declared in a class. | |
<h3> | |
Evolving API Interfaces</h3> | |
Evolving API interfaces is somewhat more straightforward than API classes | |
since all methods are <code>public</code> and <code>abstract</code>, all fields | |
are <code>public</code> <code>static</code> and <code>final</code>, all type members | |
are <code>public</code> and <code>static</code>, and there are no constructors. | |
<br> | |
<table BORDER COLS=3 WIDTH="97%" > | |
<tr> | |
<td ROWSPAN="2">Add API method</td> | |
<td width="35%">If method need not be implemented by Client</td> | |
<td width="25%">Binary compatible (0)</td> | |
</tr> | |
<tr> | |
<td>If method must be implemented by Client</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (1)</td> | |
</tr> | |
<tr> | |
<td>Delete API method </td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td ROWSPAN="2">Add API field</td> | |
<td>If interface not implementable by Clients</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>If interface implementable by Clients</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (2)</td> | |
</tr> | |
<tr> | |
<td>Delete API field</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Expand superinterface set (direct or inherited)</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Contract superinterface set (direct or inherited)</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (3)</td> | |
</tr> | |
<tr> | |
<td>Add, delete, or change static initializers</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td ROWSPAN="2">Add API type member</td> | |
<td>If interface not implementable by Clients</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>If interface implementable by Clients</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (2)</td> | |
</tr> | |
<tr> | |
<td>Delete API type member</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Re-order field, method, and type member declarations</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
</table> | |
<p>(0) Although adding a new method to an API interface which need not | |
be reimplemented by Clients does not break binary compatibility, a pre-existing | |
Client subclass of an existing implementation might still provide a pre-existing | |
implementation of a method by this name. See <a href="#add method">Example | |
4</a> in the preceding section for why this breaks API contract compatibility. | |
<p>(1) Adding a new method to an API interface that is implemented by Clients | |
(e.g., a callback, listener, or visitor interface) breaks compatibility | |
because hypothetical pre-existing implementations do not implement the | |
new method. | |
<p>(2) Adding an API field to an API interface that is implemented by Clients | |
(e.g., a callback, listener, or visitor interface) breaks binary compatibility | |
in a different way. A field added to a superinterface of C may hide an | |
instance field inherited from a superclass of C, causing linking errors | |
to be detected. Because of this fact, it is important to distinguish between | |
API interfaces that Clients should implement from those that Clients should | |
merely use. API interfaces that Clients should implement should not include | |
fields. | |
<p>(3) Shrinking the set of API interfaces that a given API interfaces | |
extends (either directly or inherited) breaks compatibility because some | |
casts between API interfaces in hypothetical pre-existing Client code between | |
will no longer work. However, non-API superinterfaces can be removed without | |
breaking binary compatibility. | |
<h4> | |
Evolving API interfaces - API methods</h4> | |
All methods in an API interface are implicitly <code>public</code> and <code>abstract</code>, | |
and are therefore all considered API methods. | |
<br> | |
<table BORDER COLS=3 WIDTH="100%" > | |
<tr> | |
<td>Change formal parameter name</td> | |
<td width="20%" height="0">-</td> | |
<td width="20%">Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Change method name</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Add or delete formal parameter</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Change type of a formal parameter</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Change result type (including <code>void</code>)</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Add checked exceptions thrown</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (1)</td> | |
</tr> | |
<tr> | |
<td>Add unchecked exceptions thrown</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Delete checked exceptions thrown</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (1)</td> | |
</tr> | |
<tr> | |
<td>Delete unchecked exceptions thrown</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Re-order list of exceptions thrown</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
</table> | |
<p>(1) Adding and deleting checked exceptions declared as thrown by an | |
API method does not break binary compatibility; however, it breaks contract | |
compatibility (and source code compatibility). | |
<h4> | |
Evolving API interfaces - API fields</h4> | |
All fields in an API interface are implicitly <code>public</code>, <code>static</code>, | |
and <code>final</code>; they are therefore all considered API fields. | |
<p>Because of binary compatibility problems with fields, the Java Language | |
Specification recommends against using API fields. However, this is not | |
always possible; in particular, enumeration constants to be used in <code>switch</code> | |
statements must be defined as API fields. | |
<br> | |
<table BORDER COLS=3 WIDTH="100%" > | |
<tr> | |
<td>Change type of API field</td> | |
<td width="35%">-</td> | |
<td width="20%"><b><font color="#FF0000">Breaks compatibility</font></b> (1)</td> | |
</tr> | |
<tr> | |
<td ROWSPAN="2">Change value of API field</td> | |
<td>If field is compile-time constant value</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (2)</td> | |
</tr> | |
<tr> | |
<td>If field is not compile-time constant value</td> | |
<td>Binary compatible</td> | |
</tr> | |
</table> | |
<p>(1) All field type changes break binary compatibility, even seemingly | |
innocuous primitive type widenings like turning a <code>short</code> into an | |
<code>int</code>. | |
<p>(2) Java compilers always inline the value of constant fields (ones | |
with compile-time computable values, whether primitive or <code>String</code> | |
type). As a consequence, changing the value of an API constant field does | |
not affect pre-existing Clients. Invariably, this fails to meet the objective | |
for changing the API field's value in the first place. | |
<h4> | |
Evolving API interfaces - API type members</h4> | |
All type members in an API interface are implicitly <code>public</code> and | |
<code>static</code>; | |
they are therefore considered API type members. The rules for evolving | |
an API type member are basically the same as for API classes and interfaces | |
declared at the package level. | |
<h3> | |
Evolving API Classes</h3> | |
Evolving API classes is somewhat more complex than API interfaces due to | |
the wider variety of modifiers, including <code>protected</code> API members. | |
<br> | |
<table BORDER COLS=3 WIDTH="97%" > | |
<tr> | |
<td ROWSPAN="2">Add API method</td> | |
<td width="40%">If method need not be reimplemented by Client</td> | |
<td width="25%">Binary compatible (0)</td> | |
</tr> | |
<tr> | |
<td>If method must be reimplemented by Client</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (1)</td> | |
</tr> | |
<tr> | |
<td>Delete API method </td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td ROWSPAN="2">Add API constructor</td> | |
<td>If there are other constructors</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>If this is only constructor</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (2)</td> | |
</tr> | |
<tr> | |
<td>Delete API constructor</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td ROWSPAN="2">Add API field</td> | |
<td>If class is not subclassable by Client</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>If class is subclassable by Client</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (3)</td> | |
</tr> | |
<tr> | |
<td>Delete API field</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Expand superinterface set (direct or inherited)</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Contract superinterface set (direct or inherited)</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (4)</td> | |
</tr> | |
<tr> | |
<td>Expand superclass set (direct or inherited)</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Contract superclass set (direct or inherited)</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (4)</td> | |
</tr> | |
<tr> | |
<td>Add, delete, or change static or instance initializers</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td ROWSPAN="2">Add API type member</td> | |
<td>If class is not subclassable by Client</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>If class is subclassable by Client</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (3)</td> | |
</tr> | |
<tr> | |
<td>Delete API type member</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Re-order field, method, constructor, and type member declarations</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Add or delete non-API members; that is, <code>private</code> or default | |
access fields, methods, constructors, and type members</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Change <code>abstract</code> to non-<code>abstract</code></td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Change non-<code>abstract</code> to <code>abstract</code></td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (5)</td> | |
</tr> | |
<tr> | |
<td>Change <code>final</code> to non-<code>final</code></td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Change non-<code>final</code> to <code>final</code></td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (6)</td> | |
</tr> | |
</table> | |
<p>(0) Although adding a new method to an API class which need not be reimplemented | |
by Clients does not break binary compatibility, a pre-existing subclass | |
might still provide a pre-existing implementation of a method by this name. | |
See <a href="#add method">Example 4</a> in the preceding section for why | |
this breaks API contract compatibility. | |
<p>(1) Adding a new method to an API class that must be reimplemented by | |
Clients breaks compatibility because pre-existing subclasses would not | |
provide any such implementation. | |
<p>(2) Adding the first constructor to an API class causes the compiler | |
to no longer generate a default (public, 0 argument) constructor, thereby | |
breaking compatibility with pre-existing code that invoked this API constructor. | |
To avoid this pitfall, API classes should always explicitly declare at | |
least one constructor. | |
<p>(3) Adding a new field to an API class that is subclassed by Clients | |
breaks binary compatibility. A field in a superinterface of C may hide | |
an added field inherited from a superclass of C, causing linking errors | |
to be detected when a static field hides an instance field. Apart from | |
the binary compatibility issues, it is generally good software engineering | |
practice that API classes should not expose any fields. | |
<p>(4) Shrinking an API class's set of API superclasses and superinterfaces | |
(either directly or inherited) breaks compatibility because some casts | |
in pre-existing Client code will now longer work. However, non-API superclasses | |
and superinterfaces can be removed without breaking binary compatibility. | |
<p>(5) Pre-existing binaries that attempt to create new instances of the | |
API class will fail with a link-time or runtime error. | |
<p>(6) Pre-existing binaries that subclass the API class will fail with | |
a link-time error. | |
<h4> | |
Evolving API classes - API methods and constructors</h4> | |
<table BORDER COLS=3 WIDTH="96%" > | |
<tr> | |
<td>Change body of method or constructor</td> | |
<td width="20%">-</td> | |
<td width="25%">Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Change formal parameter name</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Change method name</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Add or delete formal parameter</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Change type of a formal parameter</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Change result type (including <code>void</code>)</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Add checked exceptions thrown</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (1)</td> | |
</tr> | |
<tr> | |
<td>Add unchecked exceptions thrown</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Delete checked exceptions thrown</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (1)</td> | |
</tr> | |
<tr> | |
<td>Delete unchecked exceptions thrown</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Re-order list of exceptions thrown</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Decrease access; that is, from <code>protected</code> access to default | |
or <code>private</code> access; or from <code>public</code> access to <code>protected</code>, | |
default, or <code>private</code> access</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Increase access; that is, from <code>protected</code> access to <code>public</code> | |
access</td> | |
<td>-</td> | |
<td>Binary compatible (2)</td> | |
</tr> | |
<tr> | |
<td>Change <code>abstract</code> to non-<code>abstract</code></td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Change non-<code>abstract</code> to <code>abstract</code></td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (3)</td> | |
</tr> | |
<tr> | |
<td>Change <code>final</code> to non-<code>final</code></td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td ROWSPAN="2">Change non-<code>final</code> to <code>final</code></td> | |
<td>If method not reimplementable by Clients</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>If method reimplementable by Clients</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (4)</td> | |
</tr> | |
<tr> | |
<td>Change <code>static</code> to non-<code>static</code></td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Change non-<code>static</code> to <code>static</code></td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Change <code>native</code> to non-<code>native</code></td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Change non-<code>native</code> to <code>native</code></td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Change <code>synchronized</code> to non-<code>synchronized</code></td> | |
<td>-</td> | |
<td>Binary compatible (5)</td> | |
</tr> | |
<tr> | |
<td>Change non-<code>synchronized</code> to <code>synchronized</code></td> | |
<td>-</td> | |
<td>Binary compatible (5)</td> | |
</tr> | |
</table> | |
<p>(1) Adding and deleting checked exceptions declared as thrown by an | |
API method does not break binary compatibility; however, it breaks contract | |
compatibility (and source code compatibility). | |
<p>(2) Perhaps surprisingly, the binary format is defined so that changing | |
a member or constructor to be more accessible does not cause a linkage | |
error when a subclass (already) defines a method to have less access. | |
<p>(3) Pre-existing binaries that invoke the method will fail with a runtime | |
error. | |
<p>(4) Pre-existing binaries that reimplement the method will fail with | |
a link-time error. | |
<p>(5) Adding or removing the <code>synchronized</code> modifier also has a | |
bearing on the method's behavior in a multi-threaded world, and may therefore | |
raise a question of contract compatibility. | |
<h4> | |
Evolving API classes - API fields</h4> | |
Because of binary compatibility problems with fields, the Java Language | |
Specification recommends against using API fields. However, this is not | |
always possible; in particular, enumeration constants to be used in <code>switch</code> | |
statements must be defined as API constant fields. | |
<br> | |
<table BORDER COLS=3 WIDTH="100%" > | |
<tr> | |
<td>Change type of API field</td> | |
<td width="30%">-</td> | |
<td width="25%"><b><font color="#FF0000">Breaks compatibility</font></b> (1)</td> | |
</tr> | |
<tr> | |
<td ROWSPAN="2">Change value of API field</td> | |
<td>If field is compile-time constant</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (2)</td> | |
</tr> | |
<tr> | |
<td>If field is not compile-time constant</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Decrease access; that is, from <code>protected</code> access to default | |
or <code>private</code> access; or from <code>public</code> access to <code>protected</code>, | |
default, or <code>private</code> access</td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Increase access; that is, from <code>protected</code> access to <code>public</code> | |
access</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td ROWSPAN="3">Change <code>final</code> to non-<code>final</code></td> | |
<td>If field is non-static</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>If field is static with compile-time constant value</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (3)</td> | |
</tr> | |
<tr> | |
<td>If field is static with non-compile-time constant value</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Change non-<code>final</code> to <code>final</code></td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (4)</td> | |
</tr> | |
<tr> | |
<td>Change <code>static</code> to non-<code>static</code></td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (5)</td> | |
</tr> | |
<tr> | |
<td>Change non-<code>static</code> to <code>static</code></td> | |
<td>-</td> | |
<td><b><font color="#FF0000">Breaks compatibility</font></b> (5)</td> | |
</tr> | |
<tr> | |
<td>Change <code>transient</code> to non-<code>transient</code></td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Change non-<code>transient</code> to <code>transient</code></td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
</table> | |
<p>(1) All field type changes break binary compatibility, even seemingly | |
innocuous primitive type widenings link turning a <code>short</code> into an | |
<code>int</code>. | |
<p>(2) Java compilers always inline the value of constant fields (ones | |
with compile-time computable values, whether primitive or <code>String</code> | |
type). As a consequence, changing the value of an API constant field does | |
not affect pre-existing Clients. Invariably, this does not meet the objective | |
for changing the API field's value. | |
<p>(3) Java compilers always inline the value of constant fields (ones | |
with a compile-time computable values, whether primitive or <code>String</code>type). | |
As a consequence, changing an API constant field into a non-<code>final</code> | |
one does not propagate to pre-existing Clients. Invariably, this does not | |
meet the objective for making the API field non-<code>final</code>. | |
<p>(4) Making an API field final breaks compatibility with pre-existing | |
binaries that attempt to assign new values to the field. | |
<p>(5) Changing whether an API field is declared static or not results | |
in link-time errors where the field is used by a pre-existing binary which | |
expected a field of the other kind. | |
<h4> | |
Evolving API classes - API type members</h4> | |
The rules for evolving an API type member are basically the same as for | |
API classes and interfaces declared at the package level, with these additional | |
rules for changing access modifiers: | |
<br> | |
<table BORDER COLS=3 WIDTH="100%" > | |
<tr> | |
<td>Decrease access; that is, from <code>protected</code> access to default | |
or <code>private</code> access; or from <code>public</code> access to <code>protected</code>, | |
default, or <code>private</code> access</td> | |
<td width="20%">-</td> | |
<td width="20%"><b><font color="#FF0000">Breaks compatibility</font></b></td> | |
</tr> | |
<tr> | |
<td>Increase access; that is, from <code>protected</code> access to <code>public</code> | |
access</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
</table> | |
<h3> | |
Evolving non-API packages</h3> | |
The names of non-API packages, classes, and interfaces do not appear in | |
Client source code or binaries. Consequently, non-API packages, classes, | |
and interfaces can be added or deleted without jeopardizing binary compatibility. | |
However, when non-API classes and interfaces containing <code>public</code> | |
or <code>protected</code> members are among the superclass or superinterface | |
sets of API classes and interfaces, non-API changes may have ramifications | |
to API methods, fields, and constructors. | |
<br> | |
<table BORDER COLS=3 WIDTH="100%" > | |
<tr> | |
<td>Add non-API package</td> | |
<td width="20%">-</td> | |
<td width="20%">Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Delete non-API package</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Add class or interface to non-API package</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Delete class or interface in a non-API package</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
<tr> | |
<td>Change existing class or interface in non-API package</td> | |
<td>-</td> | |
<td>Binary compatible</td> | |
</tr> | |
</table> | |
<h2> | |
<a NAME="Data Compatibility"></a>Data Compatibility</h2> | |
The Component implementation may need to store and retrieve its internal | |
data from a file. For example, Microsoft Word stores a document in a file. When one | |
of these files may live from release to release, clients would break if | |
the format or interpretation of that data changed in an incompatible way. | |
<br><b>Data compatibility</b> is an additional issue for components with | |
persistent data. | |
<p>The standard technique is to tag all stored data with its format version | |
number. The format version number is increased when the format is changed | |
from one release to the next. The Component implementation contains readers | |
for the current format version and for all past versions, but usually only | |
the writer for the current format version (unless for some reason there | |
is an ongoing need to write older versions). | |
<h2> | |
<a NAME="Standard Workarounds"></a>Standard Workarounds</h2> | |
When evolving APIs, the prime directive places serious constraints on how | |
this can be done. | |
<p>Here are some standard techniques that come in handy when you're caught | |
between a rock and a hard place. They're not necessarily pretty, but they | |
get the job done. | |
<h3> | |
Deprecate and Forward</h3> | |
When some part of the Component API is made obsolete by some new and improved | |
Component API, the old API should be marked as deprecated using the <code>@deprecated</code> | |
Javadoc tag (the comment directing the reader attention to the replacement | |
API). When feasible, the implementation of the old API should forward the | |
message to the corresponding method in the replacement API; doing so will | |
mean that any performance improvements or bug fixes made to the implementation | |
of the new API will automatically be of benefit to clients of the old API. | |
<h3> | |
Start over in a New Package</h3> | |
Even simpler than Deprecate and Forward, the Component API and implementation | |
can be redone in new packages. The old API and implementation are left | |
in the old location untouched, except to mark them as deprecated. Old and | |
new API and implementations co-exist independent of one another. | |
<h3> | |
Adding an argument</h3> | |
Here is a simple technique for adding an argument to a method that is intended | |
to be overridden by subclasses. For example the <code>Viewer.inputChanged(Object | |
input)</code> method should get an additional argument <code>Object oldInput</code>. | |
Adding the argument results in pre-existing clients overridding the wrong | |
method. The workaround is to call the old method as the default implementation | |
of the new method: | |
<p><code> public void inputChanged(Object input, Object oldInput) {</code> | |
<br><code> inputChanged(input);</code> | |
<br><code> }</code> | |
<p>Pre-existing clients which override the old method continue to work; | |
and all calls to the old method continue to work New or upgraded clients | |
will override the new method; and all calls to the new method will work, | |
even if they happen to invoke an old implementation. | |
<h3> | |
"2" Convention</h3> | |
The first release of an API callback-style interface didn't work as well | |
as hoped. For example, the first release contained: | |
<p><code>public interface IProgressMonitor {</code> | |
<br><code> void start();</code> | |
<br><code> void stop();</code> | |
<br><code>}</code> | |
<p>You now wish you had something like: | |
<p><code>public interface IProgressMonitor {</code> | |
<br><code> void start(int total);</code> | |
<br><code> void worked(int units);</code> | |
<br><code> void stop();</code> | |
<br><code>}</code> | |
<p>But it's too late to change <code>IProgressMonitor</code> to be that API. | |
So you mark <code>IProgressMonitor</code> as deprecated and introduce the new | |
and improved one under the name <code>IProgressMonitor2</code> (a name everyone | |
recognizes as the second attempt): | |
<p><code>public interface IProgressMonitor2 extends IProgressMonitor {</code> | |
<br><code> void start(int total);</code> | |
<br><code> void worked(int units);</code> | |
<br><code> void stop();</code> | |
<br><code>}</code> | |
<p>By declaring the new interface to extend the old one, any object of | |
type <code>IProgressMonitor2</code> can be passed to a method expecting an | |
old <code>IProgressMonitor</code>. | |
<h3> | |
COM Style</h3> | |
The "COM style" is to not implement interfaces directly but to ask for an interface | |
by using <code>getAdapter(someInterfaceID)</code>. This allows adding new interfaces | |
in the implementation without breaking existing classes. | |
<h3> | |
Making Obsolete Hook Methods Final</h3> | |
As a framework evolves, it may sometimes be necessary to break compatibility. | |
When compatibility is being broken knowingly, there are some tricks that | |
make it easier for broken clients to find and fix the breakage. | |
<p>A common situation occurs when the signature of a framework hook method | |
is changed. Overridding a hook method that is no longer called by the base | |
class can be tough to track down, especially if the base class contains | |
a default implementation of the hook method. In order to make this jump | |
out, the obsolete method should be marked as <code>final</code> in addition | |
to being deprecated. This ensures that existing subclasses which override | |
the obsolete method will no longer compile or link. | |
<h2> | |
<a NAME="Defective API Specifications"></a>Defective API Specifications</h2> | |
As hard as one might try, achieving perfect APIs is difficult. The harsh | |
reality is that some parts of large Component API will be specified better | |
than others. | |
<p>One problem is specification bugs---when the API spec actually says | |
the wrong thing. Every effort should be made to catch these prior to release. | |
<p>Another problem is underspecification---when the API spec does not specify | |
enough. In some cases, the implementor will notice this before the API | |
is ever released. In other cases, the specification will be adequate for | |
the implementor's needs but inadequate for clients. When an API is released | |
in advance of serious usage from real clients, it may be discovered too | |
late that the specification should have been tighter or, even worse, that | |
the API should have been designed differently. | |
<p>When you find out that you're saddled with a defective API specification, | |
these points are worth bearing in mind: | |
<ul> | |
<li> | |
APIs are not sacrosanct; it's just that breaking compatibility is usually | |
very costly. For a truly unusable feature, the cost is likely much lower.</li> | |
<li> | |
Tightening up a seriously weak specification can often be achieved without | |
breaking compatibility by changing the specification in a way consistent | |
with the existing implementation. That is, codify more of how it actually | |
works to ensure that clients that currently work continue to work in subsequent | |
releases.</li> | |
<li> | |
Breaking compatibility in a limited way may be cheaper in the long run | |
that leaving a bad patch of API as it is.</li> | |
<li> | |
If you break compatibility between releases, do it in a controlled way | |
that only breaks those Clients that actually utilize of the bad parts of | |
the API. This localizes the pain to affected Clients (and their downstream | |
customers), rather than foisting a "Big Bang" release on everyone.</li> | |
<li> | |
Document all breaking API changes in the release notes. Clients appreciate this | |
much more than discovering for themselves that you knowingly broke them.</li> | |
</ul> | |
<h2> | |
<a NAME="A Word about Source Code Incompatibilities"></a>A Word about Source | |
Code Incompatibilities</h2> | |
While the idea that the Java source code for existing Clients should continue | |
to compile without errors against the revised Component API, this is not | |
strictly necessary (and not always achievable). API contract and binary | |
compatibility are the only hard requirements. Source code incompatibilities | |
are not worth losing sleep over because the Client's owner can easily correct | |
these problems if they do arise with only localized editing of the source | |
code. | |
<p>The following is a list of known kinds of Java source code incompatibilities | |
that can arise as APIs evolve: | |
<ul> | |
<li> | |
Ambiguities involving type-import-on-demand declarations.</li> | |
<ul> | |
<li> | |
Triggered by: adding an API class or interface.</li> | |
<li> | |
Remedy: add single type import declaration to disambiguate.</li> | |
<li> | |
Avoidance strategy: use at most one type-import-on-demand declaration per | |
compilation unit.</li> | |
</ul> | |
<li> | |
Ambiguities involving overloaded methods.</li> | |
<ul> | |
<li> | |
Triggered by: adding an overloaded API method or constructor.</li> | |
<li> | |
Remedy: add casts to disambiguate ambiguously typed arguments.</li> | |
<li> | |
Avoidance strategy: put casts on null arguments.</li> | |
</ul> | |
<li> | |
Ambiguities involving field and type member hiding.</li> | |
<ul> | |
<li> | |
Triggered by: adding an API field.</li> | |
<li> | |
Remedy: add qualification to disambiguate ambiguous field references.</li> | |
<li> | |
Avoidance strategy: none.</li> | |
</ul> | |
<li> | |
Ambiguities involving fields and local variables.</li> | |
<ul> | |
<li> | |
Triggered by: adding an API field.</li> | |
<li> | |
Remedy: rename conflicting local variables to avoid new field name.</li> | |
<li> | |
Avoidance strategy: don't declared API fields in classes and interfaces | |
that Clients implement.</li> | |
</ul> | |
<li> | |
Problems involving checked exceptions thrown by methods.</li> | |
<ul> | |
<li> | |
Triggered by: removing checked exceptions from a method's <code>throws</code> | |
clause.</li> | |
<li> | |
Remedy: add or remove exception handlers as required.</li> | |
<li> | |
Avoidance strategy: none.</li> | |
</ul> | |
</ul> | |
<p><FONT face="Times New Roman, Times, serif" | |
size=2>Copyright © 2000, 2002 Object Technology International, Inc.</FONT></p> | |
</body> | |
</html> |