It starts with one little line of code.
Don’t worry, just put it here for now. You can fix it later.
And then one method. Or two. Maybe you put up a good fight.
Wait..that doesn’t belong in this class! Let’s organize this..
But after a while..
Oh really? You think you have time for that? Ha! You barely made your last deadline..
Cute design principles DON’T pay our bills! Finished work DOES.
We’ll fix it later...I promise. That’s it...that’s it.
Bloaters Slow Down Business
One of our clients is a well-known financial institution. But for security reasons, I’ll call them Global Bank.
Global Bank wants to delight their existing customers and bring on new ones. But to build new features faster, they took a shortcut: Global Bank put most of their business logic in a single class.
That’s right...we’ve got a bloater.
Let’s call it MyClassIsTooLargeHelperImpl.
Now when Global Bank tries to add a new feature, they struggle. This class is hard to understand and a nightmare to update.
And if Global Bank can’t build new features quickly, their customers will suffer.
But we’re here to help.
How to Deflate Your Bloater
Step 1: Draw a diagram of your paradise (new architecture)
Everything started out well.
Our architect drew a diagram of our new architecture: a set of small services that were easy to understand and modify.
Soon Global Bank would be able to build faster than ever.
Even though it was just a rough outline, it gave our team some direction. And, this picture also helped us explain our design to impacted teams (See Step 9).
Step 2: List all methods in your bloater from largest to smallest
Thirteen thousand lines to go..
We had our work cut out for us. But the line count gave our team something to rally around.
“Alright! Let’s go!”
Step 3: Find a potential service
I like the rule of three here.
We started from the largest method, and tried to find at least two other related ones. The key is to make sure your service performs a main function. Hopefully it’s clear which methods belong together..
If your methods aren’t descriptive, you might need to READ what they’re doing. In a rush, a developer might have written a super descriptive name like getStuff() along with..
// TO-DO: find a suitable name for getStuff()
Step 4: Choose a descriptive name for your service
Our services started out very descriptive.
But after a while, we became desperate to deflate our bloater.
That's when it happened.
Come on...you have to get this redesign done by next week.
“I know...let’s call it FetchService!”
Besides, you can always fix it later in the project..
No. Just...no. We were finding services where there weren’t any.
As it turns out, no one is immune to the temptation of a good shortcut.
So when we were about to create a new service, we asked:
- Will we know what this does by the name alone?
- Does this service perform a valuable function?
- Do these methods really belong together?
If we couldn’t say yes to all three, we didn’t create a new service. Because in the future, poorly defined services become your new bloaters.
Step 5: Move methods from your bloater to your new service
Unfortunately, we found that our bloater was basically a web -- all methods were tied to each other.
So instead, we had to temporarily make some private methods public in the bloated class. Then our new services could call these functions until we move them out too.
Step 6: Update your other classes to use your new services
Ctrl + Shift + G was a trusted ally in this.
Step 7: Remove unused methods in the bloater
Twelve thousand, four hundred eighty lines to go!
On our daily calls, we’d celebrate as we saw our bloater lose some air. I highly recommend it.
It’s the little things..
Step 8: Run a smoke test. Immediately.
I wish we had done this after creating our first service.
Because just as we were basking in the triumph of our progress..
“Hey guys..the server isn’t starting..”
“What do you mean we have missing spring beans!?”
“Unable to initialize th-- Oh, wait! I forgot to..”
We found things we missed. And you will too. Fix it early and add it to your steps for your next service. Don’t say I didn’t warn you :)
Step 9: Meet with teams involved to review your changes
Here’s another one I wish we’d done a bit earlier.
After we’d refactored about half the class, we had a meeting with teams that would be impacted by our changes. We wanted to help them understand why we were restructuring and what changes were being made.
The fewer the “Where’s my code?” moments, the better.
Since there were over three teams involved in the meeting, I imagined the worst.
But what we got was..
“So...you’re just restructuring the code? You're not making any changes?”
“...and, after you're done we’ll be able to find things easier and build faster?”
(Long pause and discussion amongst themselves)
“...ok! Sounds good! Keep us in the loop!”
Step 10: Review Your Code
At the end, we checked for circular dependencies or duplicate code.
I highly recommend CDA for analyzing dependencies because it gives you a nice picture of your design flaws.
PMD-CPD is also great for finding duplicate methods. No pretty pictures though.
* Repeat steps 3-10 until you can’t find new services
* Repeat steps 2-10 with any new services that become huge
Small Services, Big Impact
“Two thousand three hundred lines!”
When we finished, we had a facade class backed by fifteen small services. Each service is descriptive, relatively simple, and much easier to modify.
Now Global Bank can rapidly build new features -- winning the hearts of new and old customers alike!
And so they rejoiced.
Well, ok. Not quite. But maybe someday they will.
When deadlines are near, we’ve all taken shortcuts with hopes to fix it later. But if we don’t have time to build it right, we’ll have to find time to build it twice. So to keep moving fast..
Work to reduce the scope, not your code quality
And then you, like Global Bank, will continue to delight your customers.