Thursday, December 12, 2019

I think you left something out

I recently had the pleasure of working with some auto-generated Java code.  All the accessors and mutators were auto-generated, so was the predicate.  The only thing missing was the constructor.  You could manipulate one of these objects in any way you desired, but there was no easy way to instantiate one in the first place.  I eventually figured out a way to instantiate one through reflection.  I created a JSON version of the object and deserialized it.  Crude, but effective.

I ran into a similar problem where I needed an instance of an object, but some idiot decided to make all the constructors protected.  In this case, I created an unwanted subclass with a public constructor, called that instead, and then upcast to the object I really wanted.

Of course I was using these objects in a way the author of the code did not intend they be used, but the author wasn't omniscient and didn't foresee my use case (which was testing).  By trying to prevent unintended uses, he simply forced me to write nasty code to get around his ultimately ineffective roadblocks.  The author could have spent his time more productively creating a library with a more flexible API to handle unforeseen uses rather than inventing useless impediments to getting the job done.

I still cannot seem to reply to comments. I type a nice reply, hit the “publish” button, and the reply disappears, never to be seen again.

I'm working at a company that requires 100% branch coverage in tests. This requires teasing white-box libraries into returning malformed replies to cover all the branches in the error handler — even the ones that cannot actually be taken because the library doesn't actually generate malformed replies. So I have to supply the malformed replies myself, and it isn't easy if there aren't data constructors. We don't have AspectJ, and the source code isn't always available. And even when it is, it's simply not a good idea to modify it so that it is capable of generating errors it wouldn't otherwise be able to. This leaves me to resort to the hacks.

I was wrong. I just stumbled across the first example. It turns out that the constructor was generated, it just had no arguments. Now this is not unusual in Java (although I consider it bad form. You should be able to construct and initialize object in one step rather than have to construct an skeleton object and smash in the field values. This reduces your chances of creating partially initialized objects.) What made this a problem is that the generated code had no mutators, so you could only create skeleton objects, you couldn't put any values in them. I did remember correctly that I deserialized a JSON version to create an object with actual values in it. This seemed a better option than directly using reflection because the JSON object “looked” like the resulting object I wanted.

I'm no fan of just making every field mutable by default — just the opposite, in fact. Objects should usually be immutable. But you ought to have a way to create an initialized object without resorting to reflection.

1 comment:

John Cowan said...

Java was invented by a smart programmer (the Great Quux) for use by mediocre programmers. He already invented a programming language for smart programmers, and look how successful that was. If you give a mediocre programmer a way to break your code, they will.

Doing white-box testing without the source code is a Really Bad Idea anyway. Use AspectJ (the Java MOP) if you have to do that, since it rebuilds classes at the bytecode level. If you do have the source code, introduce your modifications on a separate branch; hopefully they will be able to merge with master. Otherwise, well, back to the hacks.