Arguments Against Annotations

Viewed 7892

My team is moving to Spring 3.0 and there are some people who want to start moving everything into Annotations. I just get a really bad feeling in my gut (code smell?) when I see a class that has methods like this: (just an example - not all real annotations)

@Transaction
@Method("GET")
@PathElement("time")
@PathElement("date")
@Autowired
@Secure("ROLE_ADMIN")
public void manage(@Qualifier('time')int time) {
...
}

Am I just behind the times, or does this all seem like a horrible idea to anyone else? Rather then using OO concepts like inheritance and polymorphism everything is now by convention or through annotations. I just don't like it. Having to recompile all the code to change things that IMO are configuration seems wrong. But it seems to be the way everything (especially Spring) is going. Should I just "get over it" or should I push back and try to keep our code as annotation free as possible?

12 Answers

It's 2018 and this point is still relevant.

My biggest problem with annotations is that you don't have an idea what the annotations are doing. You're cutting some caller code off and hiding it somewhere disconnected from the callee.

Annotations were introduced to make the language more declarative and less programmatic. But if you're moving the majority of the functionality to annotations, you are effectively switching your code to a different language (and not a very good one at that). There's very little compile-time checking. This article makes the same point: https://blog.softwaremill.com/the-case-against-annotations-4b2fb170ed67

The whole heuristic of "move everything to configuration so that people don't have to learn how to code" has gotten out of control. Engineering managers aren't thinking.

Exceptions:

  • JUnit
  • JAX-RS

Annotations often introduce dependencies where such dependencies do not belong.

I have a class which happens by coincidence to have properties which resemble the attributes from a table in an RDBMS schema. The class was created with this mapping in mind. There is clearly a relationship between the class and the table but I am happy to keep the class free from any metadata declaring that relationship. Is it right that this class makes a reference to a table and its columns in a completely different system? I certainly don't object to external metadata that associates the two and leaves each free of an understanding of the other. What did I gain? It is not as if metadata in the source code provides type safety or mapping conformance. Any verification tool that could analyze JPA annotations could equally well analyze hibernate mapping files. Annotations did not help.

At one contract, I had created a maven module with a package of implementations of interfaces from an existing package. It is unfortunate that this new package was one of many directories within a monolithic build; I saw it as something separate from the other code. Nonetheless, the team was using classpath scanning so I had to use annotations in order to get my component wired into the system. Here I did not desire centralized configuration; I simply wanted external configuration. XML configuration was not perfect because it conflated dependency wiring with component instantiation. Given that Rod Johnson didn't believe in component based development, this was fair. Nonetheless, I felt once again that annotations did not help me.

Let's contrast this with something that doesn't bother me: TestNG and JUnit tests. I use annotations here because I write this test knowing that I am using either TestNG or JUnit. If I replace one for the other, I understand that I will have to perform a costly transition that will stray close to a rewrite of the tests.

For whatever reason, I accept that TestNG, JUnit, QUnit, unittest, and NUnit owns my test classes. Under no circumstances does either JPA or Hibernate own those domain classes which happen to get mapped to tables. Under no circumstances does Spring own my services. I control my logical and physical packaging in order to isolate units which depend upon either. I want to ensure that a move away from one doesn't leave me crippled because of all the dependencies it left behind. Saying goodbye is always easier than leaving. At some point, leaving is necessary.

I think it depends to some extent on when you started programming. Personally, I think they are horrid. Primarily because they have some quasi-'meaning' which you will not understand unless you happen to be aware of the annotation in question. As such they form a new programming language all by themselves and move you further away from POJOs. Compared to (say) plain old OO code. Second reason - they can prevent the compiler doing your work for you. If I have a large code base and want to refactor something or rename something I'd ideally like the compiler to throw up everything that needs to be changed, or as much as possible. An annotation should just be that. An annotation. Not central to the behaviour of your code. They were designed originally to be optionally omitted upon compilation which tells you all you need to know.

And yes, I am aware that XML config suffers in the same way. That doesn't make it worse, just equally bad. At least I can pretend to ignore that though - it doesn't stare me in the face in every single method or parameter declaration.

Given the choice I'd actually prefer the horrible old J2EE remote/home interfaces etc (so criticised by the Spring folks originally) as at least that gives me an idea of whats happening without having to research @CoolAidFrameworkThingy and its foibles. One of the problems with the framework folks is that they need to tie you to their framework in order to make the whole enterprise financially viable. This is at odds with designing a framework well (i.e. for it to be as independant and removeable from your code as possible).

Unfortunately, though, annotations are trendy. So you will have a hard time preventing your team using them unless you are into code reviews/standards and the like (also, out of fashion!)

I read that Stroustup left annotations out of C++ as he feared they would be mis-used. Sometimes things go in the wrong direction for decades, but you can hope things will come full circle in time..

Annotations are plain bad in my experience:

  • Inability to enforce type safety in annotations
  • Serialization issues
  • Cross compiling (to for instance javascript) can be an issue.
  • Libraries/frameworks requiring annotations exclude non-annotated classes from external libraries.
  • not overridable or interchangeable
  • your projects eventually becomes strongly dependant on the system that requires the annotations

If Java would have something like "method literals" you could annotate a class in a corresponding annotation class. Something like as following: Take for instance javax.persistence, and the following annotated class:

@Entity
class Person
{
    @Column
    private String firstname;
    public String getFirstname() { return firstname; }
    public void setFirstname(String value) { firstname = value; }

    @Column
    private String surname;
    public String getSurname() { return surname; }
    public void setSurname(String value) { surname = value; }

}

Instead of the annotations, I'd suggest a mapping class like:

class PersonEntity extends Entity<Person> {
    @Override
    public Class<Person> getEntityClass() { return Person.class;}

    @Override
    public Collection<PersistentProperty> getPersistentProperties() {
         LinkedList<PersistentProperty> result = new LinkedList<>();
         result.add(new PersistentProperty<Person>(Person#getFirstname, Person#setFirstname);
         result.add(new PersistentProperty<Person>(Person#getSurname, Person#setSurname);
         return result;
    }
}

The fictional "#" sign in this pseudo java code represents a method literal, which, when invoked on an instance of the given class, invokes the corresponding delegate (signed with "::" since java 8) of that instance. The "PersistentProperty" class should be able to enforce the method literals to be referring to the given generic argument, in this case the class Person.

This way, you have more benefits than annotations can deliver (like subclassing your 'annotate'-class) and you have none of the aforementioned cons. You can have more domain-specific approaches too. The only pre annotations have over this, is that with annotations you can quickly see whether you have forgotten to include a property/method. But this too can be handled more concise and more correct with better metadata support in Java (think for instance of something like required/optional like in Protocolbuffers)

Related