Saturday, March 24, 2018

Forward-Looking with Java's @Deprecated

I have occasionally run into a situation in which I have needed to introduce a new API or construct for others to try out, but have known that it might change based on others' feedback after some use of it. In such cases, I've wanted to somehow annotate the construct to warn other developers of the tentativeness of this newly added construct. There are several alternatives that I have considered in these cases.

Third-party Annotation

The Javadoc documentation for Guava's @Beta annotation states:

Signifies that a public API (public class, method or field) is subject to incompatible changes, or even removal, in a future release. An API bearing this annotation is exempt from any compatibility guarantees made by its containing library. Note that the presence of this annotation implies nothing about the quality or performance of the API in question, only the fact that it is not "API-frozen."

This explanation of the use of @Beta seems to imply this is a good fit for a "new" construct that may be removed. I talked more about this annotation in the blog post "Two Generally Useful Guava Annotations".

Other considerations when using a third-party library's annotation is that the third-party library must be included on one's classpath and that there is typically no out-of-the-box support in the most popular Java IDEs to indicate special treatment of the construct annotated with the annotation.

Custom Annotation

If one is not using the library with the annotation for any other reason, it can seem a bit heavy to add a new library dependency simply for an annotation when it's relatively straightforward to write one's own custom annotation. I have written about writing a custom @Unfinished annotation before and that post discussed how to create corresponding custom IDE inspections in NetBeans 8.0.2 and IntelliJ IDEA 14.0.3 for this custom annotation.

The following code listing provides an example of a custom annotation one could use for this purpose.

@Preview Annotation

package dustin.examples.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Preview
{
   /**
    * Anticipated release in which Preview status will no longer apply.
    *
    * @return Anticipated release of feature
    */
   String transition() default "";

   /**
    * Version in which this preview feature was introduced.
    *
    * @return Release in which this preview feature was introduced.
    */
   String since() default "";

   /**
    * Reasons this construct is considered "preview."
    *
    * @return Reasons this construct is considered preview.
    */
   String[] reasons() default {};
}

The custom annotation lacks any out-of-the-box support in the popular Java IDEs.

Comments Only

Annotations don't necessarily need to be used and simple comments (Javadoc or otherwise) could explain that a particular construct is preliminary and might go away. However, comments are weaker in many ways than annotations in terms of communicating intent. It is much more difficult to have an IDE or other tooling parse comments than to process annotations.

@Deprecated Annotation and @deprecated Javadoc Tag

One can use @Deprecated to annotate a deprecated construct with a standard annotation that IDEs, tools, and scripts can easily process. Unfortunately, the @Deprecated annotation never got the full support I had hoped it would get for more specifically specifying why something was deprecated when it was decided to make the JDK 9 enhanced @Deprecated much less ambitious. The Javadoc @deprecated tag can be used to document that the deprecation is actually for a "new" construct that might go away, but also might not go away. The @Deprecated annotation and the @deprecated Javadoc tag can be removed if it's decided to keep the "preview" construct.

Although the @Deprecated annotation and @deprecated Javadoc tag enjoy benefits from being standards that include built-in IDE support and awareness among most Java developers, it can still feel a bit inappropriate to use these to mark a new construct that may go away, but may stick around. The "When to Deprecate" section of the document "How and When To Deprecate APIs" states, "When you design an API, carefully consider whether it supersedes an old API." It further lists three reasons for deprecation which are "insecure, buggy, or highly inefficient", "going away in a future release", and "encourages bad coding practices".

I'm not the only one who thinks of "deprecation" as marking something likely to be removed or that should not be used. Nicolas Fränkel outlines the feature lifecycle in Java and explains that deprecation in Java is "a bold and clear statement to everyone that a feature version has no future, at least in its current form."

In the jdk-dev mailing list message "JEP 12: Treatment of standard APIs supporting preview features," Alex Buckley writes:

We'd like to use deprecation-for-removal-at-birth as the way to flag "this API is intimately connected to a preview feature". If the preview feature becomes permanent, then the deprecation would be removed. This jump from terminal-deprecation to no-deprecation is novel, but not mad -- deprecation has a variety of meanings, and its historical use in the JDK is not a good guide to anything.

Buckley also cites a paragraph from JEP 277 ("Enhanced Deprecation") regarding use of the deprecation mechanisms (I have highlighted the same portion Buckley emphasized):

Deprecation is a technique to communicate information about the life cycle of an API: to encourage applications to migrate away from the API, to discourage applications from forming new dependencies on the API, and to inform developers of the risks of continuing dependence upon the API.

The JDK 9-introduced "enhanced" @Deprecated annotation can help a bit in this situation (that Buckley termed "deprecation-for-removal-at-birth") via its newly added "since" and "forRemoval" elements. Specifying the @Deprecated annotation's forRemoval() as false and specifying its since as the same version as the Javadoc @since tag might help developers to see that the construct was deprecated from the beginning with no current plans to remove it. For such an approach to be most effective, it'd probably be write to explicitly state forRemoval as false rather than relying on its implicit default.

It may be that we Java developers will need to start thinking of @Deprecated and @deprecated a bit differently than in the past. Although the @Deprecated annotation and @deprecated Javadoc tag still "inform" us of "the risks of continuing dependency" upon the annotated/described construct, it may be incorrect to assume that such a construct is necessarily going away at some point in the future. If we get used to this alternate meaning in deprecated JDK constructs, we'll be more likely to consider using the same approach with our own newly added and still tentative features.

No comments: