Technical Practices: No `new` Inline
No new
inline?
Coming from a predominately Java and C# background, new
is how the language instantiates an object so it's a nice short phrase.
When we write a method and need a collaborator, we'll typically instantiate the collaborator right there.
public void DoStuff(){
var thing = new Thing();
...
}
I consider this very bad form. We can't allow ourselves this laziness of instantiating objects in the flow we need them.
Why?
Why don't we want to instantiate the objects when we need them?
What does the object do?
What does it really do?
Are you going to go check every object for exactly what it does?
All the time?
Every commit?
For those of you that are telling yourself lies by answering, "Yes" - Welcome to the club. Sure, we CAN know, we CAN do our best to keep tabs... why?
Why do this extra work? The class we're working on is the class we should be focused on. We shouldn't care what our collaborators do, or how they do it.
WHen we instantiate the collaborator in our methods, we MUST care how they do things because there's no way to no do them.
Inline instantiation is the tightest form of coupling.
The simplest reason I give to not instantiate inline is to avoid tight coupling.
Avoid Tight Coupling
We want loosely coupled classes. We've knowng this for decades and yet our industry tends to write tightly coupled code.
Wrtiting code with new
inline was my major blocker to understanding why to use interfaces.
I just new it up; the interface provided no abstraction, I was using the concrete instance. I saw the problem... I just went the wrong way. I stopped the application of interfaces. Sure I still used them when it was PAINFULLY obvious - but as a different technical practice - interface everything. :)
We want to avoid having explicit concerte dependencies, tight coupling, in our code. Not doing so makes many things harder, if not impossible to do.
The only way to break tight coupling is to not instantiate the classes in the flow they are being used. We'll get to this, but let's look at some of the ways having new inline makes our life as developers harder.
Dependency Injection
When you use new
inline to create the collaborators then you've prevented from using dependency injection.
I'm super against dependency injection frameworks. I think they enable developers to not really think about how objects interact. It makes it more challenging to see what's happening. It infects... Well... I'll talk about that later.
When we use new
inline we prevent our ability to invert control, to do some form of dependency injection.
I'm ignoring the situation where you use tools like PowerMock during testing to replace the instantiation with what you want. Powermock as a tool is amazing; but it's power allows us to remain sloppy as developers. As a result, I'm 100% agaisnt it's use for anything except establishing tests around functionality you DO NOT have the source code for. If you have the source code that's using the library code - wrap it and then you have no need to PowerMock it.
These tools, including dependency injection frameworks, and their impact on developer discipline is a post for another time; and it's being worked on.
Getting back on track - How does this impact dependency injection? The object instantiation is done inline... There's a new
inline. We're unable to replace the concrete type being used. This is the core of the tight coupling, and how it kills dependency injection.
This is one of the tie ins to always having a contract, some kind of contract definition, interface or abstract class is great - but not an instantiatable object. The worst of all would be a sealed/final class. Then we're fundamentally blocked from doing an inversion of control. I don't care about black magic tools that hide what's happening - these are tools we don't want to use. If we're unable to create another version that abides by the same contract, we're unable to do dependency injection. Despite how effective I was with Powermock to work around the limitations. I have these technical practices to make the code better so I wouldn't need those tools.
Instantiating the object inline will prevent us from utilizing dependency injection and tightly couple our code to other concrete types.
When we create the objects where we use them, we're going to increase the scope of change for that instantiated object. When that object changes, there's going to be a lot of place to look to ensure we're still behaving correctly.
Contracts
When we create objects in the flow we use them - contracts become meaningless. Interfaces make no sense.
As mentioned above, For YEARS, this held me back from the understanding I have now about the importance of having behavior contracts defined. Interfaces were never about communicating what an object does, it was always about enforcing an object to do certain things. It was about forcing objects to do certain things instead of letting other objects know what they can do.
Interfaces make no sense for an object to have when you are instantiating them inline. You HAVE the concrete class right there - what does the interface provide? ... Nothing.
Instantiating an object inline removes the value that a contract has in the codebase. It makes it REALLY easy to make a mistake and add/use a method on the concrete object. It makes it really easy to add that little extra onto the object because the interface doesn't need it, but... just this one... No.
Instantiating inline allows objects to explode, to do more than the contract they agree to.
When we do type inspection to get access to a method not part of the contract, we're accessing behavior not part of the contract - we clearly have the wrong contract.
We are able to limit our classes to hold ONLY contracts. It is under our power as developers to write the code to never hold a reference to any concrete type.
Let's just start with Method Parameters, never write a paramter of a concrete type. Done. Simple - Well...
If it's was just that simple we'd be able to do it much more frequently. The problem is that we can't do it well in isolation. If ONLY our class tries to follow these technical practices, it'll be hard to do it entirely.
The classes we depend on need to implement contracts for this simple case, which isn't always the way they were written.
The other way a class holds variables is at the class level. Same with method parameters, don't have any class variables that are of a non-contract types.
Practicing This
There's one way to do this - don't instantiate objects in the flow you use them. What this means is that in a method, there's the limitation on how to have a collaborator.
We can use a class variable; we can use a parameter passed in, or we can use the result of a method from another collaborator.
When we enforce never using new
inline, we'll find ways to restructure the code to enable us to follow this practice.
It definitely forces us to restructure the code, but that enables the code to be better structured. To be clearer about what and how it does things, as well as enabling testability.