Lumpers & Splitters
For now the lumpers are winning
I spent some time last year (year before? who knows, it’s covid) refactoring Excalidraw. A friend, Christopher Chedeau, wrote it to give a hand-written look to drawings. Another friend, Pete Hunt, added support for distributed drawing. Others jumped in and added their favorite features.
The functionality was impressive. Usage was growing like crazy. The code was not to my taste. Excellent—they made it run, I’ll make it right. What a great way for me to practice big changes in small safe steps.
I nibbled away at the code, one little diff at a time. I made things slightly more cohesive by moving bits of code that changed in the same PRs so they were adjacent. Doing this showed me that App.tsx was doing several things at once—application lifecycle, swapping changes with remote drawings, handling user interface events.
I decided I’d extract all the distributed editing logic in one place. I called the object Portal because it spanned between worlds. First I moved distribution-related state into Portal. Then I started moving bits of logic that only used Portal state into Portal. Some was easy to move. Other logic mixed distributed editing with other concerns so I just left it.
Then one day I fetched a new version and discovered that someone had moved all the logic from Portal back into App, leaving only the state. WTF?
WTF was that I’m a splitter and I was working with lumpers. There’s a tradeoff in design between:
Elements with more, simpler sub-elements and simpler relationships between those sub-elements. App had many sub-elements. They were simple, just functions and state. Many didn’t refer to each other at all. But it was all there in one place if you wanted to find anything.
Elements with fewer, richer sub-elements and richer relationships. My Portal bundled together 3-4 pieces of state and App’s relationship to it was getting richer as I added methods with intention revealing names to Portal, replacing blobs of code.
Lumpers like everything in one place. They’ll take the hit on cohesion, with all that implies, in order to not have to reason across elements.
Splitters like lots of little elements with rich relationships. They’ll take the hit on coupling from time to time in order to not have to think about too much at once.
I’m a splitter. I’ve never been able to deal with much complexity at once so I developed thinking tools for partitioning complexity instead. If my audience is lumpers, though, it behooves me to speak their language.
My theory, with absolutely no evidence beyond intuition, is that lumpers have had bad experiences with poor naming, that required jumping all over the code to see what was happening. Now they want to see all the code in one place because they don't trust code that they can't see. Of course this is a good way to justify my splitter habits.
Great Article! I oscillate between the two. I tend to like to split wherever possible... but sometimes I don't have the name(s) for that which is being lumped. Sometimes when things are lumped I find it easier to clean, not always, I'm learning...
If the things that are split out are doing too many things, I usually start by inlining them where I'm working... so they become lumpy at least for a second.
I try not to let "good code" be the enemy of "slightly less terrible" code.