Our goal as software developers is to produce code that can be easily maintained. This is frequently a challenge; as writing good code is hard.
µObjects is a way of Object Oriented Programming that I've found gives us this. There are a number of ways that µObjects improve the maintainability of code; but how do we create the objects to get these wins? There's a number of guidelines in place for creating the µObjects that give you these. Today we'll dive into aspects that help keep the objects micro; keeping interfaces short, keeping a class focused, and keeping class variables tightly coupled.
Keeping interfaces short
There's the practicle advice of always using interfaces but that's not just it. Java's stream interface is over 40 methods. That's an interface that wants to do it all.
We can't write code like that. It's unmaintainable.
We want to craft small objects that do one thing very well.
We'll beat on the Stream interface for a bit to demonstrate how this can be many smaller classes.
The simplest indicator that there's an object beggin to get out is matching prefixes to a method. flatMap
, flatMapToDouble
, flatMapToInt
, flatMapToLong
; four things doing pretty much the same thing.
Let's create a FlatMapStream
interface (shhhh; naming is hard) which has these. Or why not just the one; and force the user to be explicit about what they want back. The helper methods don't save a lot.
We have a couple Match
methods; allMatch
, anyMatch
and noneMatch
. StreamMatch
sounds like a good one here; with just all
, any
and none
methods.
Two collect
methods.
Four map
methods; threee reduce
methods.
I could go on; but the point is that we don't want interfaces that have groups of related functions. If things are more closely related to each other than other things - tease out a new class. If it's hard to extract those methods - Then you've written it wrong. Unrelated methods shouldn't be coupled in a class. If they are; and it's hard to extract - you've created code that's hard to maintain.
Keeping interface methods as closely related as possible is going to save time and effort maintaining the code. This focus on relationships is a common theme for Object Oriented Programming. Objects have a purpose, they have a job. Don't explode that. Don't make it a god class.
Keeping classes focused
In a project utilizing µObjects; we use the Chain of Responsibility pattern in a number of places. I'm sure another post I'll get into how this helps us focus individual classes; as well as extract out commonality - so I'll save that tangent for then.
Often we have a UI control that triggers the chain; such as when a user clicks 'Log In'. It starts a login chain. We found it very cumbersome for the UI to know about ALL the components required for the login flow.
Part of the single responsibility and single reason for change is what the class knows about. The UI knew about the login process. We wanted the control to just know how to trigger the log in flow. If the flow changed, but not how it's triggered, then the UI shouldn't have to be modified; and yet it did. This didn't sit well with the team; and we sought out a way to change it.
It didn't take long. µObjects have a very simple process of dealing with a class that does two things; make it two classes.
With a UI that knew how to trigger the flow and what the flow consisted of; we broke it into a class that knew how to trigger and a class that knew how to construct.
Very literally; we have a class who's sole responsibility is to know how to build the login flow.
The UI instantiates this class (and later references it via an interface) and it instantiates all the components required.
This decoupled the UI from over a dozen classes. We applied this same type of "Cosntructing Class" to a few other places in the login flow; and it vastly simplified and decoupled the flows from each other. Now when we have to modify a certain piece; it's very clear where and how to modify. The more frequent we've run into is error handling.
In our "PreNetworkCall" flow; we just have to stack up some error handlers.
new Prepper(new Initalizer(new 404Handler(new 500Handler(new 401Handler(...)))))...
If we find a new exception; we find the correct place; and insert the class.
new Prepper(new Initalizer(new 404Handler(new NetworkTimeOutHandler(new 500Handler(new 401Handler(...))))))...
I want to say this is clearly contrived; but really; it's just crappy names. This is what we do - in a lot of places.
With µObjects; you're chaining a lot of behavior together. It's going to go against everything you've done before - but this is Object Oriented Programming. It's creating a collection of objects that work together to create complex behavior. I could have coded up a Login
class that handles ALL of these components. Maybe even has the API to get the JWT token for server calls... but that's... that's a disturbingly large class. It'd be the LoginGod
.
Keeping variables focused
To maintain maintainability we need the class variables to be tightly coupled. I was originally writing this as keeping the variables limited; but it's not about limiting them; it's about having the variables in the class be tightly coupled. If you can't tease them apart; then they belong together.
Look at all the variables in a class - then see what ones are more closely related than others.
If you have five class variables; I'd bet that two of them are more closely related to each other than to the other three variables. It's not that there's no relationship - just that some will have a closer relationship. We want to encapsulate this closer relationship. Create a class around it. Then instead of two variables; you have one. Down to four class variables. What methods now move to the other class? Your interface starts to shrink.
Continue this process until nothing else falls out. Some times when I do this wil a class that's built up five or so class variables; I'll extract it into three or so new classes (sometimes realizing a class already exists for that relationship). With these three changed variables... sometimes I'll tease out a new class to encapsulate a new relationship of new classes. Then the class holds two variables. I don't normally push much beyond that. Some classes make a decision; and they have to hold multiple things.
The highest priority
The class focused and interface small components can be driven by keeping class variables tightly coupled.
If you can get a wedge inbetween some variables and break them into their own class - do it. The system as a whole will be better for it.
Focus on encapsulating variable relationships into a class; and it will become easier to push classes to be more focused and interfaces to be shorter.