Wednesday, August 10, 2022

Dependency Injection

 

After many years of practicing dependency injection via many different frameworks (Spring, Guice, Dagger to name a few), I've decided framework based dependency injection is no longer for me. Every framework starts life as a small, lightweight, easy to use DI tool, and slowly over time morphs into some kind of monstrosity.

If it was just me, maybe I could live with this. I could avoid the bad parts, the parts that make code hard to read at 2am in the morning. But it's not just me. Almost nothing significant gets done without a large number of people working on it - and at some point you just can't stop that one 'beautiful to the beholder' feature being used.

Some time later you spot it being used again and try and stop it. But it's hard to argue with 'but it's already used over there, and over there, and even there'. And it's just as hard to clean those up. And so it spreads. Oozing slowly across your code base, infecting other code bases.

But even if I was strong willed enough to fight every one of those fights to the death, until glorious victory, I'd still be using this monster. I'd be consuming it. I'd be forced to keep using the latest versions, because you know there are important security fixes in it. They just happen to come along with yet another dozen attractive, code reducing features. Yet more reasons for folks to argue about using those features.

"But what's the alternative?", you say, "Surely you can't just discard dependency injection? It's so valuable. It's a core design pattern. Heck, the Gang of Four wrote about it.". Fellow traveler - they did not. Some will tell you that's because unit testing wasn't important back then, that it wasn't a thing.

So what did they write about? Why, Strategy, Singleton and Factory method patterns. Combine them and you see something quite similar to Dependency Injection. If you keep these separate, you end up with more or less the same end capabilities, but with (in my opinion) much more readable code. Certainly at 2am in the morning.

So that's what I'm going to do. I'm just going to focus on writing readable code. I'll leave the injection for the addicts.


Tuesday, August 9, 2022

final'ly

In Java the final keyword can be applied to variables, classes, fields and methods. But why should we use the final keyword? Isn’t it just more noise and code bloat? I believe the answer is No!, final is an important tool in the code readability toolbox. Remember, we write code once, but we read it hundreds of times.


Class

Final classes are defined in the language specification here: https://docs.oracle.com/javase/specs/jls/se18/html/jls-8.html#jls-8.1.1.2

The primary use is to prevent subclassing. This “is useful when the class hierarchy is used to model the kinds of values in a domain, rather than as a mechanism for code inheritance and reuse”.

In other words, when a reader encounters sealed or final classes, they know that this is a complete implementation, and they do not need to search the code base for extensions. This is particularly useful when performing code reviews.

Method

Final methods are defined in the language specification here: https://docs.oracle.com/javase/specs/jls/se18/html/jls-8.html#jls-8.4.3.3

The primary use is “to prevent subclasses from overriding or hiding it (the method)”. This is a useful tool for the code writer to limit and constrain access to class internals - though arguably extracting a final class may simplify reading and understanding the code in future.

More interesting though, is the specification (unusually) points out an optimization benefit: “At run time, a machine-code generator or optimizer can ‘inline’ the body of a final method”. This has the usual inlining performance benefit, but also allows the runtime optimizer to perform enhanced optimizations as it can now ‘see’ a larger surface area to optimize over.

Variable

Final variables are defined in the language specification here: https://docs.oracle.com/javase/specs/jls/se18/html/jls-4.html#jls-4.12.4

Final variables may only be assigned to once. This “can serve as useful documentation that its value will not change and can help avoid programming errors”. 

What class of programming error is being prevented here? Most of the bugs I’ve personally found and fixed through enforcing final variables have been of the scoping variety (assigning a value to a variable instead of a field e.g. name=name instead of this.name=name).

I have also found that enforcing final variables has helped me avoid mutating state unnecessarily, through questioning why I needed to mutate certain state. This has led to me writing code that is significantly easier to test without the use of mocks. (ymmv).

Final variables can reduce the cost of garbage collection (by reducing the need for memory barriers - you don’t need to protect an address from writes if it cannot change). Final primitive variables can also lead to some significant performance improvements. They allow the compiler to perform static (compile time) optimizations it could not otherwise do.

Field

Final fields are defined in the language specification here: https://docs.oracle.com/javase/specs/jls/se18/html/jls-8.html#jls-8.3.1.2

Final fields “must be definitely assigned ... at the end of every constructor of the class in which it is declared”. I find this tremendously helpful in preventing bugs where folks forget to set fields. Consider using the JDK 14 added Record class which is a special case of this where you only use the constructor and getters.

Final fields have similar benefits as final variables and methods.

Dependency Injection

  After many years of practicing dependency injection via many different frameworks (Spring, Guice, Dagger to name a few), I've decided ...