Indirection without abstraction — An example

TL;DR: Sometimes, focusing just on duplicated code will produce harmful indirection. Abstractions that talk about behavior are often more useful.

I understand that software design is a team activity that primarily focuses on costs reduction. The reasons for that may well deserve a series of posts, and we will see about that in future posts.

To produce well-designed software, we need to balance code duplication and coupling, i.e., when a block is duplicated, we extract it and reuse it wherever the duplicated code was found, increasing our coupling to the new extracted piece of code.

We do that to make software cheaper: we want to go from having to replicate changes in all instances of a duplicated block to just having to maintain one of them.

There is a tradeoff we need to think about: a wrong abstraction won’t help us achieve that goal because it will tell a story that doesn’t match what our programs do and it will get in the way of other refactors.

Understanding what the code does is a requirement even to start thinking about changing it, and producing the right abstractions is hard. Often we find ourselves, scratching our heads, trying to figure out what we were thinking — what was the intention — when we wrote a particular piece of code, probably weeks or months ago.

Some tools help us write better abstractions. The rule of three and giving proper names are two excellent tools.

Unfortunately, focusing just on duplication will get us on the wrong path sometimes, producing harmful indirection without abstraction.

Consider the following piece of code. It is from a Java Swing component:

Is it easy to get what’s that updateExportingLabel() method doing? After giving it some thought, you will probably know that it is cycling a label’s text content.

In this piece of code there are at least two duplicated things:

  • The string "Exporting"
  • The string "."

We can choose to extract those strings to get rid of that duplication:

Remember: we create abstractions to reduce costs. We need them to make changes more manageable and to assist us while trying to understand what’s going on.

I think we can agree that our new abstractions don’t make the situation worse, but they don’t help either. In fact, some could argue that it was better off without them.

Some questions arise:

  • What was the risk of leaving those magic strings around? What problem were we solving?
  • Is there something else going on? Maybe there is a hidden behavior that would benefit from its own abstraction.

Consider this refactor:

By adding the verb cycle(), we are giving a lot more context. We are also dealing with immutable values and expressions, which are easier to understand too.

At this point, I’m happy with the result, but I want to talk a little more about how extracting magic strings can prevent some abstractions to emerge.

This ExportPanelForm class has more responsibilities than managing that label, and you would expect it to have more static and instance members than the ones I’m showing here.

That means that our EXPORTING, DOT, and cycle() abstractions are mixed with other stuff that may get in the way of us figuring out what’s what.

We could use composition to solve this problem:

Now, there is a new Cycler abstraction that holds those strings for us and exposes an API that’s easier to understand and won’t get mixed with the rest of stuff in the ExportPanelForm.

In my opinion, there is no reason for extracting those magic strings now.

Conclusion

Please, bear in mind that, even if this is a real-world software example, it is quite simple and would not be a big deal to leave it the way it was at the beginning. It is just an excuse to illustrate a problem and reason about it.

I have talked about how we need to balance duplication and coupling, which leads us to create new abstractions. Sometimes focusing just on duplication will work just fine, but I have illustrated with an example that this can be not enough.

I have also shown a scenario where I consider it safe to have magic strings hanging around, provided they are guarded by proper abstractions.

--

--

This is an outdated bio about a guy who loves to code.

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store