Discussions

News: Are single element annotations a disaster waiting to happen?

  1. In "Are single element annotations a disaster waiting to happen?," Geert Bevin says that "Java should simply detect when an annotation has a single element that has no default value and allow that one to be used without an explicit name," to avoid a problem caused by changes to an annotation down the road.

    The problem, specifically, is exposed when an annotation has one element (and therefore a default value), and over time gains other parameters, which invalidates earlier usage of the annotation that uses the default value.

    What do you think? Geert suggests that "the only option ... is to never use single element annotations," which lowers usability for simple annotations. If changes in the annotations specification are to occur, they should occur sooner rather than later; should the laguage specification change to address this?

    Threaded Messages (25)

  2. Mostly, the problem you highlight can be resolved at design-time. Many annotations have only one logical parameter. If I want to create an annotation(s) to record the author of a method, I might create '@AuthorEmail' (which will only ever have one parameter and can take advantage of the shortened syntax) or '@Author' (which is a little more broad and will used named parameters). If I create an annotation called '@Author' and use the single parameter syntax to record an email address and latter decide I want to record a telephone number too then I have made a design mistake. I can opt either to maintain backwards compatibility or force code changes from anybody who uses my annotation.

    I am not sure that your proposed solution is sensible either because, unless Ive misunderstood, adding a second parameter without a default value will drop you back at square 1.

    Annotation design is no different than any other type of class design, the parameters of an annotation form part of it's public interface and should be considered carefully. If there is any possibility at all that the annotation may expand in the furture then it is better to used named parameters and make a little note in the comments.

    Disaster is pushing it a little dont you think?
  3. +1

    Anything that exposes some kind of external interface, whether that is a Java interface/class, C/C++ header files, or even a web service, has the exact same problem.

    The solution is found in tools (refactoring), versioning (causing people to expect a new interface), depreciation (backwards compatibility), and so forth.

    The solution is not to stop using interfaces, header files, and web services.

    Discouraging the use of single element annotations as a disaster is overblowing the seriousness of the problem.
  4. It seems that the most important point I talked about is just neglected. Changing code is problematic, but acceptable. However, the fact that it relies on the default 'value' element name is not, since once you start extending your annotation interface, the semantic meaning can become totally wrong. This is the sole part of the language spec that makes existing behavior communicate the wrong meaning, just by adding additional elements.
  5. I don't really see the seriousness of the problem as you describe it -- perhaps because I see the solution more in good programming practices than language fix its.

    I can go and change the semantic meaning of a method without changing its parameters or name, thus breaking a ton of code. It really is not much different than me extending an annotation to mean more than what I originally planned. In both cases it is my responsibility to audit the code/run tests and ensure I haven't introduced bugs.

    I also do not see how your solution would prevent something like that. In your example, say I decide "name" can really have a default value of "theName" and I have another element that really is the one that needs to be defined going forward, "type".

    If I change the annotation to have a "type" element and a default value for "name", I just pulled the rug out from under everything that uses it.

    Not much different from adding elements to something with "value", or changing the meaning of "value". In fact, in that case, I'd wager a programmer is more likely to rename value to something else & syntatically break the code (which is a good, safe thing to do since it forces them to review it).
  6. I don't get it.

    If the new annotation type member has a default value, everything works fine:

    @Param("foo")

    will work with both

    @interface Param {
      String value();
    }

    and

    @interface Param {
      String value();
      String defaultValue() default "";
    }

    Now, if you add an annotation type member that doesn't have a default:

    @interface Param {
      String value();
      String defaultValue();
    }

    then obviously,

    @Param("foo")

    will no longer work since both value() and defaultValue() need to receive a value.

    However, when you do that, you are breaking existing code anyway.

    What's the big deal?!?

    --
    Cedric
    TestNG
  7. Cedric,

    instead of being forced to use 'value' as the single element name, you can from the beginning give everything a semantically correct name which continues to be used for the entire life of the annotation. Now, everybody is forced to use 'value' merely for the sake of being able to use annotation declarations without a specific element name.

    Also, there's no way at all to provide a backwards compatible path and handle the extended annotations interface in the logic that interprets them. Now, it's simply impossible to do so. (see my reply to Thomas)
  8. Geert,
    Cedric,instead of being forced to use 'value' as the single element name, you can from the beginning give everything a semantically correct name which continues to be used for the entire life of the annotation. Now, everybody is forced to use 'value' merely for the sake of being able to use annotation declarations without a specific element name.Also, there's no way at all to provide a backwards compatible path and handle the extended annotations interface in the logic that interprets them. Now, it's simply impossible to do so. (see my reply to Thomas)
    That's a fine solution to a non-existent problem.

    You didn't address the objections that I posted above.

    You make it sound as if the entire annotation design is flawed whereas the problem you describe will only happen to developers who choose to use value() in an initial version of their @interface and then later decide to modify that interface in a way that will break existing code.

    It's a bit like complaining that adding a method to an interface will break existing clients and concluding that Java is broken...

    --
    Cedric
    TestNG
  9. Cedric, I specifically talked about "single element annotations" and said nothing about "the entire annotation design". Annotations are not meant to have the same purpose of use as regular methods or interfaces in that you don't implement them, you use them. When I have a method that has additional arguments, I can add a second version with that new argument and still keep the old one for backwards compatibility, the same thing for a constructor. While annotation interfaces use a method notation for their elements, these are not methods, but variables, you even use the assignment operator. The usage semantics of annotations are much closer to methods or functions than to the implementation of interfaces.

    More so, you'll sprinkle annotations throughout your entire code as additional meta data, as you did with XML before. Granted XML doesn't have something as "single element annotations", so you implicitly design your DTDs and Schemas in a way that allows you to evolve them over time with the ability of providing backwards compatibility. Single element annotations don't allow this, and as such I conclude that you should never use them.

    By relying on naming however ('value'), Sun makes it even worse in fact, since when 'value' does happen to be the semantically correct name for the first and single element in your annotation, you implicitly are forced to chose a path that leads to the impossibility of backwards compatibility. I do think that this is bad language design.
  10. When I have a method that has additional arguments,
    Annotations are *types*, you can't compare them to methods.

    Add a method to an interface, you break clients.

    Add an non-default annotation type member to an annotation, you break clients.

    It's really that simple and it has nothing to do with value().

    You want to keep backward compatibility? Create an @interface Param2. It's a fairly standard design pattern used everywhere in Swing, SWT and the Windows world.
    The usage semantics of annotations are much closer to methods or functions than to the implementation of interfaces.
    Not sure how you reached that conclusion. Again: annotations are types.
    Single element annotations don't allow this
    They do if you make backward compatible changes to your annotation, that is: add members that have default values. If you fail to do this, you are breaking clients for reasons that have nothing to do with the semantic of value().
    and as such I conclude that you should never use them.
    That's quite extreme. I can think of a couple of reasons to discourage the use of value(), but certainly none that are anywhere close to your line of reasoning...

    --
    Cedric
    TestNG
  11. Come on Cedric, you don't *use* annotations as types, you use them as methods or functions. You have a name, with brackets and a series of name-value pairs. You don't implement an interface or extend an abstract class.

    Euhm @interface Param2 is about as ugly as it gets imho. Of course if there's no option, you have to do that, but it's a last resource. With single element annotations that you want to keep backwards compatible, you're immediately forced to do so.
  12. @interface Param2 is about as ugly as it gets imho. Of course if there's no option, you have to do that, but it's a last resource.
    It's not the last resort, it's the only resort if you want to evolve your API without breaking any client.

    Again, see COM or the Eclipse API's, where this design pattern can be found everywhere.

    --
    Cedric
    TestNG
  13. Geert,

    "you use them as methods or functions"?

    Methods/functions have functionality in them. Annotations do not. Annotations merely hold information, they do not provide any functionality. That's why we have to use the "helper" or "handler" pattern with annotations to do anything with them in a reusable way (Hibernate's validator annotations are a nice example). And, yes you do use them as types. When I ask for an annotation, I do so by type:

    MyAnnotation a = clazz.getAnnotation( MyAnnotation.class );
  14. James, to the user of annotations is doesn't matter a single bit if they contain the functionality themselves or if through inspection the functionality is given. When I talked about "use" I obviously mean the users of your library, not the developers that inspect the annotations.

    Let's see:
    @Copyright("2002 Yoyodyne Propulsion Systems")

    or:
    @RequestForEnhancement(
        id = 2868724,
        synopsis = "Enable time-travel",
        engineer = "Mr. Peabody",
        date = "4/1/3007"
    )

    Now those totally feel as functions, constructors, methods, ... I don't care which name, but certainly not as types.

    While I'm at it, as the developer of the library you even don't get to work fully with them as types, show me how to extend existing annotations, how to have a common base class, how to create annotation 'interfaces', ...
  15. Thomas, with the solution that I propose, it's at least possible to design an interface that can evolve over time. I can choose a semantically correct first element name, and I can add new elements over time (with a default value) so that existing code that uses the annotation isn't broken. Of course the logic that interprets these annotation elements then has to sensibly handle the default values, but that's just normal development practice for providing backwards compatible behavior.

    At least it would be *possible* to create a backwards compatible path for people that were using the single element annotation.

    With the current approach, when using single element annotations, you know that all existing code will break if your annotation interface extends over time. Saying that "it just needs to be designed properly from the start" is naive. You never know what will happen a few months or years down the road. Surely we're past the thought that you can anticipate all possible requirements of a project at a single point in time. Having used a single element annotation implicitly either makes your annotation stagnate or enforces an unnecessary overhead on your users for new versions.

    Refactoring tools don't help either, since annotations are typically provided by a library, tool or framework. When the new release comes out with a backwards incompatible annotation interface, people can't use annotation refactoring to instantly change all their code since the refactoring of the original annotation interface has already been done.

    I think that it's a big deal that when using single element annotations, you're actually locked into the behavior I explained.
  16. Solved by refactoring tools.[ Go to top ]

    Annotation refactorings doesn't appear to be any harder than the other refactoring already supported by Eclipse or IDEA. So just file a feature request for this.

    Keep the code simple and clear, let the tools do the rest. :)
  17. Let's just face it. Java 5 Annotations is a design ship that was sunk by the Sun/JCP iceberg. Seriously, these things are a syntactic nightmare that is nothing short of wrong.

    Look:

    public @interface Todo
    {
        String whatToDo() default "nothing";
    }

    First of all, let's remember way back (10 years ago for me) when we took our first programming course. What did they teach you about interfaces? Yes, that's right, they're a declaration of behavior.

    Now let's fast forward to today (or 5 years ago for the .NET folks), where we have this neat concept called "metadata", also known as Annotations to Java folks. What is metadata? It's a data structure, or a group of data structures you can apply to your code to better describe it.

    So WHY are we using a behavioral syntax to define a data structure???

    What is this??:

    public @interface Todo
    {
        String whatToDo() default "nothing";
    }

    It's like a method...with a value...and an interface that becomes a data structure....and code that doesn't look like it should compile by any stretch of even remotely sane logic!!!!??? AAARRRGH!!!

    Okay. Someone explain to me how the absolute most useless new feature in Java 5, Typesafe Enumerations, was good enough to get its own keyword, but Annotations had to suffer this travesty of language design.

    What, pray tell, would have been wrong with this:

    public annotation Todo {
      String whatToDo = "nothing";
    }

    Heck, we could even do something crazy, and support a constructor for ordinal, nameless parameters:

    public annotation Todo {
      public Todo (String whatToDo){}
      public String otherInformation = null; //look, a default!
    }

    Don't give me that "naming conflict" excuse either...there's a really neat concept called pattern matching that could easily tell the difference between an annotation type declaration and an annotation variable or field name. Or at the very least, call it @nnotationif you have to.

    No matter how you look at it, it can't get any worse than this:

    public @interface Todo
    {
        String whatToDo() default "nothing";
    }

    The only reason I can think of why this sucks so bad would be that they did it on purpose to take the Suckage Spotlight off of the garbage that is "Java Generics". Thanks for nothing.

    No, I'm not bitter. ;-)

    Clinton
  18. What, pray tell, would have been wrong with this:

    public annotation Todo {
      String whatToDo = "nothing";
    }
    It would have broken existing code.

    Would have been fun to have you on the Experts Group :-)

    --
    Cedric
    TestNG
  19. It would have broken existing code.

    I think that's a big load of BS. Using very simple pattern matching you could determine the difference between:

      public annotation Todo {
      }

    And

      public String annotation;

    Come on man, even Microsoft can do that in the C# compiler. But I'll relent, and agree with you. Let's say that pattern matching is a bad idea. Here are other options that would NOT have broken existing code, but would still make WAY more sense:

    public @annotation Todo {
      String whatToDo = "nothing";
    }



    This requires a new keyword, but named using what is an illegal identifier. At least it makes SOME sense, and doesn't overload keyword normally used in a completely different context. It also allows you to impose restrictions on the class design, such as no methods allowed, or whatever else makes sense.

    But honestly, why couldn't it just be a class? This would require no changes, and makes perfect sense.

    public class Todo implements Annotation {
      String whatToDo = "nothing";
    }

    We could have allowed constructors to validate ordinal parameters upon compile time. Similarly, maybe we could have allowed JavaBeans properties to validate named parameters at compile time. Wow, what an amazing concept! More value, for less work!

    I seriously don't get it Cedric. How did this get screwed up so bad? I'm seriously losing faith in Sun and the JCP, especially when I'm seeing better stuff come from the ivory tower at Microsoft...

    Sad.

    Clinton
  20. I seriously don't get it Cedric.
    Obviously.
    How did this get screwed up so bad? I'm seriously losing faith in Sun and the JCP, especially when I'm seeing better stuff come from the ivory tower at Microsoft...Sad.Clinton
    All the ideas you suggest have been considered, debated and rejected.

    Bottom line is: you're two years too late and I don't really want to dive back into all this.

    The spec is there and it's final, so how about discussing how to use it well instead of trying to change the past?

    --
    Cedric
    TestNG
  21. you're two years too late and I don't really want to dive back into all this.The spec is there and it's final, so how about discussing how to use it well instead of trying to change the past?

    You're right, and I'm just ranting. I suppose I'm just really, really disappointed. And since others were voicing concerns, I figured I'd voice mine.

    I don't expect to change the spec. My question was: how did it end up sucking so bad -- more in the spirit of, how do we avoid other new features suffering the same fate.

    Or perhaps just simply: why can Microsoft do it "right", and we can't? And if that continues, how long can we expect Java to remain competitive? In the long term, what will keep Java more compelling than Microsoft .NET and Mono?

    But perhaps this is the wrong forum for that.

    Cheers,
    Clinton
  22. my question was: how did it end up sucking so bad
    Of course, you are entitled to your opinion, but had you taken part in the debates back then, you would understand all the compromises and decisions that were made better and I'm betting you would probably change your opinion somewhat.

    I was part of the Experts Group and I don't agree with all the decisions that were made, but that's the idea behind a standard and a community process, so I'm fine with it.

    In the end, I think JSR 175 is a fairly solid v1.0 specification that is already proving itself quite useful if I can judge by the increasing number number of frameworks that use it and by the overall feeling of happiness that users seem to show once they start using it.

    Take a deep breath, try using it and make suggestions for v1.1. There are plenty of improvements and additions that are possible...

    --
    Cedric
    TestNG
  23. Already a disaster[ Go to top ]

    With annotations you can put configuration data in Java code. Now I see methods with 20 lines of annotations and 3 lines of Java code.

    Talk about a maintainence nightmare.

    This stuff sucks and makes code very hard to work on.

    Configuration data was best left in XML files.
  24. XML confiig is a disaster[ Go to top ]

    How can you say that annotations is a night mare compared to XML? In an XML you have to keep track of the method name and signature and that is a maintenance nightmare, if anything.
  25. XML confiig is a disaster[ Go to top ]

    Annotations only make sense when there's a one-to-one mapping between the meta data and the entities in your class. Whenever the meta data is more structured and provides concepts that are not directly related to classes, methods, properties, or than span/group several of those ... annotations are even worse than XML.

    I do believe that they serve a purpose and I'm using them occasionally, they just don't replace external configuration in many situations and I think they could have been better designed.
  26. XML confiig is a disaster[ Go to top ]

    How can you say that annotations is a night mare compared to XML? In an XML you have to keep track of the method name and signature and that is a maintenance nightmare, if anything.
    Do you really think that having the webservice endpoint
    in an annotation is the best thing after sliced bread ?
    I would say that annotations, as Java generics, are not a disaster but may easily create disasters when put in the wrong hands.
    And the current trends (see webservices and JPA) are the best examples to show in the hands of which we are.
    With a well known parallel I could say:
    1. first rule of annotations : do not annotate
    2. first rule of generics: if you are not able to describe a generalized xxx without comment after 1 month, remove generics

    Guido.