Refactoring: Taming The Bunnies
Brent Simmons had a monster class – in his case a 2500-line view controller – that he wanted to break up into smaller classes. He describes this as “creating a basketful of bunny objects.”
We used to have one big mother class that was complicated and hard to understand. Now we have a smaller mother class and a bunch of very simple new bunny classes.
This is the way one hopes things will work. But, as Brent notes, it doesn’t always work that way.
The separation into separate objects wasn’t nearly clean, in other words. I had to expose a bunch of internal methods and properties of the view controller to its bunnies. Way more than I felt comfortable with. (And there’s no @bunnies keyword to protect those.)
And some of the bunnies had to know about each other: the drag controller delegate needed to know about the table view delegate and datasource. The timeline cell delegate needed to know about the datasource. Etc.
We have a basket of bunnies and a ball of yarn: the bunnies are cute, but the tangle isn’t. The new objects seem simple, but they all know too much about each other and they all communicate in complicated ways – ways so complicated that life was better when everything was in one monster mother class.
Sometimes, that’s the answer. But sometimes, refactoring further can drive you to a new design. Test-driven design throughout will help prevent errors from creeping in – and that’s important because we’re going to be yanking code all over the place.
Push Data Down
First, you may be able to simplify things in the old mother class by pushing properties and instance values down from the old class to the bunny class that is its primary user. This can eliminate a lot of communication between the bunnies and the mother class. Ideally, only one bunny needs this data. If a few bunnies need to share, that might be OK.
When possible, it’s better for the mother to tell the bunnies what to do than to ask them about their internal state. This can often simplify coordination and improve encapsulation.
If bunny methods repeatedly need to get stuff from the mother object, consider passing the information they need as arguments. If you have too many arguments, make an argument object. If a method on bunny A needs to talk to some other bunny, pass the bunny as an argument.The point here is to unravel the tangled strands of yarn and to knit up your raveled sleeve of care.
Pull Methods Up
When bunnies need to talk to each other, you wind up with lots of inter-bunny murmuring.
self.depth = [self.controller.driller depth];
Get rid of this by adding convenience methods in the mother class. (Yes: this makes the mother class bigger, and yes, the whole point of the exercise was to make the mother class smaller. Trust me on this.) The extra convenience methods are trivial, they’re easy to write, and they’re unlikely to break. At the same time, you:
- avoid violating the Law of Demeter
- reduce inter-bunny murmuring
In fact, you may soon be able to remove a bunch of dependencies between bunnies at the cost of a few tiny methods in the mother class.
Continue this refactoring until the bunnies no longer need to know about each other, and talk only to their mother.
Extract A Nanny Class
The last refactoring round made the bunnies better, but now we’ve made the mother class worse. No worries! Take all those little methods you just made in the mother and sprout a Nanny class.
Now, when the bunnies want something from the mother or from another bunny, they talk to the Nanny. When the mother class wants a bunny to do something, it tells the Nanny.
The Nanny knows where to find each bunny and the mother, but otherwise it has no state. It’s got lots of intimate knowledge – it #include’s each bunny – but that’s OK because it doesn’t really do very much with that knowledge. It just relays messages between the bunnies, and passes messages from the bunnies to the mother and from the mother to the bunnies.
The nanny, in short, is a new class that abstracts that tangle of yarn. In trade school lingo, it’s a multiple Facade, an interface between bunnies and the mother class and also an interface amongst in homogenous bunny classes.
Often, you may find that the Nanny has a bunch of methods that talk to the same two or three bunny classes. If the Nanny starts to get complicated, you can hare off the parts that deal with that group of bunnies into its own Nanny.
Small Bunnies Really Are Better
I mistrust box-and-line diagrams; they’re often an indication of architectural astronautics. Is all this refactoring getting us anywhere? We started with a 2500-line mother class. Perhaps we end up with 8 classes. The mother’s still 1000 lines, and the nannies and bunnies are 200-400 lines. Are we better off?
Yes, we probably are.
First, everything is much simpler. Five years from now, you get a crash report right in the middle of Driller, because it’s being passed a WarpDrive and those hadn’t even been invented back in 2014. With only a handful of methods, fixing Driller to work with a WarpDrive won’t be hard. You probably won’t even need to glance at the other bunnies.
But even if you need to look through every single bunny and every nanny, you’re still better off. Small classes are easy to understand. 2500-line classes make you nervous now, and they’ll make you nervous in 2019.
Testing bunnies is likely to be straightforward. To test the old mother class, you probably need to build a model and then mock a view, and you’re going to need to plant sensors or breakpoints to get internal state. Trust me, you don’t want that.
Just this morning, Tinderbox was crashing when a table inside a popover called
reloadData, thanks to some asynchronous code that was bashing the model just as we were reloading. In a five-method bunny that was so simple it couldn’t possibly crash, a concurrency error was an obvious place to look and the fix took twenty minutes. In a fifty-method mother, especially one you don’t really trust, you could spend days hunting that crash.
I’m pretty sure that the advantage of bunny classes can, in fact, be quantified. And perhaps it has! Let me know.
This recent series has been getting a lot of traffic from colleagues, and also at least one college class. (Hi, all you Manitobans!) I’ve not been seeing much email. Have something I should know about? Email me.