annotations considered harmful

closed set

You add annotations to your code, but their effects are handled magically™, somewhere else™ off in library code that you’re not supposed to think about. It is hard if not impossible to define your own annotations that are handled the same way as the magic™ ones.

This leads to the type of problem that kopper described here where you get lost between all the layers of Magic™. Ok so you’re working with a web framework and you slap the “serialize this response as json” annotation on your method, and now all of a sudden you can return whatever-old-object and it will get serialized to json (somehow), and now you need to tweak how the json comes out (an example i’ve seen: the api takes a json array but only nonempty ones; if the array is empty you’re supposed to omit the entire key).

If you can’t reach in and change the library, what can you do? Probably call your own json serialization method and return JsonObject and totally defeat the purpose of the annotation.

wrong abstraction level

Annotations assume business logic lives in classes, fields, and methods; but classes, fields, and methods are not themselves business logic. They are one of many tools used to implement business logic. But there are other tools.

Have you ever copypasted a field five times just to apply the same annotation to each copy? If only you were working in a programming langauge with loops! Oh wait you are, but the thing you’re sprinkling annotations on is needlessly tied to the concept of fields, which you can’t loop over, so you can’t use a loop.

Or you have a thing which would be nice to parametrize, but the annotations only take string constants, so you can’t stamp out multiple closely related copies that differ by a constructor parameter.

external representations aren’t internal representations

This is a common problem with libraries that take a class (possibly with annotations on each field) and automatically add ways to read and write it as json.

Let’s say i’m working with 24-bit colors. The best way to work with colors inside the program is as a single int packed in 0x00RRGGBB format or something. The best way to work with colors as a user is with a hex-encoded string starting with #, or maybe a color name. But if I make my field an int it is occasionally really hard to convince these frameworks to use something other than an int in the automatically generated JSON.

That’s fine, you protest; I can annotate the field with a “custom” serializing and deserializing function. Well now we have two classes of serializer (builtin vs. custom, and you just know the custom one is not going to feel the same), and also we have a related problem: after my color config option I want to put a Minecraft enchantment ID. Notably, this is not “a string” - it appears a string in the JSON, sure, but I need to parse it into an enchantment at runtime - and it’s possible to add/remove enchantment IDs to the game dynamically depending what datapacks are loaded in the current Minecraft world, so there is no way to parse this string without reference to a specific Minecraft world. How do I get this reference into the “custom” deserialization function? Can we really not dream any bigger than threadlocals?

just one more annotation bro

You can’t control the code handling your class, you can only control what annotations are on it. Therefore everything you could possibly want to do through code needs to have its own annotation.

Is the null value serialized as JSON null or as a missing field? Annotation. Is the empty array serialized as a missing field or as []? Annotation. Are you dastardly enough to have a field which you don’t want to include as json? Annotation for that too. Do you want to reject unknown fields when deserializing or not (annotation)? What format is used to serialize the date (annotation) and does it accept more lenient variants of the format when deserializing (annotation)?

And somehow it’s still never enough! You have a json object with a type key, and the object you should construct requries dispatching off the value of type? Is there an annotation for that?

Most libraries seem to be all-or-nothing which is annoying. You can either go on the happy annotation path, and if you want to tweak something even just a little bit you can skip out on all the annotations and drop into a much lower-level api, but there’s nothing in between. With google gson, if you want that “serialize an empty array as a missing key” behavior on your class you need to write one of these custom handlers and it means you can’t even use annotations for the easy stuff anymore, like marking fields to skip or rename.