Why bother with `@Deprecated` when you change the signature of the API method/constructor?

Answered

Why bother with @Deprecated when you change the signature of the API method/constructor? Deprecation is supposed to be a notice to API users that this is going away, not that it is already gone, touch luck.

Just delete the effing method and be done with it. Explicitly discouraging use of any utility classes provided by the IDE implementation.

If utility classes are not part of the API then it should be stated as such, otherwise suddenly changing the API makes plugins that use it fail without a heads up to the authors, with the encouraging exception notice to the users that the plugin is not part of JetBrains implementation and really the fault of the plugin author’s ability to provide working code.

I would prefer if this was the official stance that plugins should copy whatever classes they use to ensure the rug is not pulled from under them by the next revision. I have encountered this enough times to bring attention to the practice. I already have a dozen such class copies in my plugin because the API methods/classes suddenly vanished, causing exceptions.

I cannot afford the effort to keep separate code bases to support IDE versions from 2016.3 to 2019.2 EAP.

I have encountered API changes such as: abstract class to interface change which need reflection in order to support before and after versions; name changes without creating a @Deprecated dummy class for the old name and extending the new named class to give plugins time to switch over and other API evolutions without @Deprecated grace period for migrating code.

I am all for API evolution but if there is no time given to migrate plugin code then it is not an API but internal implementation that should be used at your own risk, if at all.

Here is an example of a useless deprecation because ALL previous API methods are gone due to signature changes.

Not only will this break all existing distributions attempting to use the old interface, it gives no time to migrate to new API before the plugin starts failing.

Here is the previous revision API:


public class NameFilteringListModel<T> extends FilteringListModel<T> {
  private final Function<? super T, String> myNamer;
  private int myFullMatchIndex = -1;
  private int myStartsWithIndex = -1;
  private final Computable<String> myPattern;

  public NameFilteringListModel(JList<T> list,
                                final Function<? super T, String> namer,
                                final Condition<? super String> filter,
                                final SpeedSearch speedSearch) {
    this(list, namer, filter, () -> speedSearch.getFilter());
  }

  public NameFilteringListModel(JList<T> list, final Function<? super T, String> namer, final Condition<? super String> filter, final SpeedSearchSupply speedSearch) {
    this(list, namer, filter, () -> {
      final String prefix = speedSearch.getEnteredPrefix();
      return prefix == null ? "" : prefix;
    });
  }

  public NameFilteringListModel(JList<T> list, final Function<? super T, String> namer, final Condition<? super String> filter, Computable<String> pattern) {
    super(list.getModel());
    myPattern = pattern;
    myNamer = namer;
    setFilter(namer != null ? (Condition<T>)t -> filter.value(namer.fun(t)) : null);
    list.setModel(this);
  }
  // ...
}

Here is what it now looks like in intellij-community master:

public class NameFilteringListModel<T> extends FilteringListModel<T> {
  private final Function<? super T, String> myNamer;
  private int myFullMatchIndex = -1;
  private int myStartsWithIndex = -1;
  private final Computable<String> myPattern;

  /** @deprecated explicitly sets model for a list. Use other constructors instead. */
  @Deprecated
  public NameFilteringListModel(JList<T> list,
                                Function<? super T, String> namer,
                                Condition<? super String> filter,
                                SpeedSearchSupply speedSearch) {
    this(list.getModel(), namer, filter, () -> StringUtil.notNullize(speedSearch.getEnteredPrefix()));
    list.setModel(this);
  }

  public NameFilteringListModel(ListModel<T> model,
                                Function<? super T, String> namer,
                                Condition<? super String> filter,
                                Computable<String> pattern) {
    super(model);
    myPattern = pattern;
    myNamer = namer;
    setFilter(namer != null ? (Condition<T>)t -> filter.value(namer.fun(t)) : null);
  }
  // ...
}

Notice that there is not a single constructor compatible with the previous API revision.

Don’t get me wrong, I love JetBrains IDEs and ability to extend their functionality through plugins. I must point out that maintaining plugins with any complexity is so much harder because there is no consistent approach of evolving the API while giving API users minimal time to adjust their code.

Expecting a separate distribution for every supported IDE version/revision is not realistic in my case and probably most plugins with significant range of supported IDE versions and actively evolving functionality. If I were to do this I would be maintaining half a dozen branches all of which would need to be merged for new features with slightly modified code.

8 comments
Comment actions Permalink

Hi, Vladimir!

 

NameFilteringListModel is a public class in "platform-api" module, so it is clearly a part of the API. We do perform external usages search in order not to break existing plugins and check binary compatibility before changing API, but source compatibility is sometimes out of reach.

Please post a java snippet showing the exact signature you are interested in and it'll be preserved in the next 2019.1 EAP.

 

0
Comment actions Permalink

Hi Gregory, my post was less related to this issue and more a venting of accumulated frustration, so it came out harsher than intended and should be ignored. In this particular case the incompatibility was related to changes in other classes using this class that I had to copy to my plugin because of incompatibility with previous IDE versions.

The frustration is more with not having a consistent expectation of the API. I understand that it is difficult or impossible to improve the API while keeping backwards compatibility.

In some cases the API has deprecated for several major releases which gives more than ample time to adjust. In other cases there was a change causing current distributions to fail without warning. I only found out when the plugin exception reports started arriving. 

This is the reason I started working mostly in daily built intellij-community to try and get a heads up on API changes so I can see how I can accommodate previous versions and current release in the same distribution. Waiting for the official EAP releases is too late for me because users start using EAP versions expecting the plugin to work.

Feel free to ignore my post until I can compile a concrete list of examples that can be addressed.

0
Comment actions Permalink

Understood, no problems. I do own a couple of open-source plugins myself, trying to keep everything in one branch, so I feel your pain. I'd suggest to keep communication channels open and discuss broken-API-related changes openly here or (maybe even better) right in GitHub comments.

We really need to cleanup 10+ years old, clumsy and (often) broken APIs.

To reiterate once more: we monitor binary incompatibilities across all published plugins, we use Java default methods, etc. & etc... And we respond to vocal requests and revert some breaking changes to keep API compatible for a longer time. So please feel free to bring those things up, don't suffer silently.

0
Comment actions Permalink

Thank you Gregory,

I can confirm existence of some of those 10yr old APIs. I tend to code from examples not documentation. Most of my how to knowledge of the API is gained from learning from existing implementations in intellij-community. In a few cases I happened to take an example from old code and suffered until I found a better example or saw a comment from an expert on how it should be done.

I will definitely communicate more when I see issues coming up in the API changes.

I can attest that the support offered to plugin developers today is way ahead of what it was when I started over 3 years ago. I must adapt to rely more on today's level of support rather than operate out of habit and try to figure/solve everything that comes up on my own.

 

0
Comment actions Permalink

Vladimir, please feel free to ping me on gitter directly when you encounter breakages.

Also we try to include all known breakages in SDK docs as reference http://www.jetbrains.org/intellij/sdk/docs/reference_guide/api_changes_list.html

0
Comment actions Permalink

Thanks Yann, I will try to overcome by natural tendency not to be a bother and learn to call for help when I encounter a show stopper that I cannot easily solve. 

There is also my responsibility in the cause for some problems with maintenance.

I need to discontinue supporting older IDE versions and make the cut-off no more than 2 major versions back, which would mean once 2019.1 EAP came out only 2017 or later is supported.

Two years worth of versions of IDEs is plenty for a small shop (me, myself and I) to maintain. Trying to support older versions takes valuable time from implementing new features and being able to do my own old code cleanup. I have tons of comments in the code like: Delete this when 2016 is no longer supported, followed by commented out code using the new API.

I will only support Version 2017 or later for new versions of the plugin.

0
Comment actions Permalink

I definitely feel this pain. I develop Cursive, it's a full language support plugin so it touches a lot of APIs. In the end I ended up with a branch per IntelliJ base platform, and scripts to do the merging. It's annoying, but the degree of annoyance depends on the particular API - I've been working with the External System API and that's been tough since it's been under heavy development for a long time now and has a lot of API churn even as far back as two years. If I had known that ahead of time I wouldn't have used it.

I also support the last two years of IntelliJ versions, so I usually have 7 branches (6 versions and the latest EAP). I manually test the latest version, but I rely on crowdtesting and my integration tests for the other branches, it's not feasible to test them all. With the subscription model most people are on the latest version anyway.

0
Comment actions Permalink

Colin, my licensed plugin users tend to upgrade to latest, some use the EAP channel. They also tend to open GitHub issues or send me e-mails when they have a problem. The free version however is another story.

I get exception reports for plugin versions over two years old running on IDE versions 2018 or 2017 which are supported by the the latest plugin releases. They rarely if ever file issues or contact me so I do not have an opportunity to have a conversation with them.

I don't get it. How can you not upgrade your plugins at least after upgrading the IDE? API changes, features break, new features are added, bugs fixed. Are they software developers or creative writers clinging to their trusty Underwood?

I am going to change the exception report server code to reject reports from plugin versions over 2 years. Maybe failing to send an error report will prompt some of them to contact me or file a GitHub issue where I can politely ask them to upgrade their antique plugin.

0

Please sign in to leave a comment.