You’ve seen some good approaches to implementing our system. Now let’s look at the opposite end: the ideas that make things worse. You want to avoid these as much as possible. Fortunately, they also have an acronym, just like SOLID. These are called STUPID. You might also hear people describe them as design anti-patterns. They’re like design patterns, except they make your code worse rather than better!
The hard part about avoiding these approaches is that they make sense at the time. You need to get some code working, and these approaches work, at least in the short term. But usually at the expense of long-term maintainability.
So avoid the temptation, and find a way to apply a SOLID principle instead. The best way to avoid bad habits is to be aware of them. So let’s dive in!
“S” Is for Singleton
A singleton is an object that guarantees that it is the only instance of its type! If you have one of them, you can’t create a second one.
It can have its uses, but in modern programming, it is usually avoided. Why?
It’s troublesome to write unit tests for (see U for untestability below!).
You can’t subclass a singleton.
It breaks the O in SOLID - if requirements change and you do need another instance of a singleton, you have to modify all the code that relied on it being unique.
For situations where it is useful, there are other more SOLID solutions.
Unlike some programming languages, it’s a little tricky to implement this anti-pattern in Python (you can do it with a decorator if you want), so at least you’re unlikely to fall into this trap by accident!
“T” Is for Tight Coupling
Tight coupling is where two classes (or modules) are so interdependent that you often have to make changes to the other if you make changes to one.
It makes your code less reusable (because you also have to reuse all of the baggage that comes along with each tightly-coupled class), and as a direct result, more difficult to test.
“U” Is for Untestability
There are many reasons why a class is difficult or impossible to test. But it usually boils down to tight coupling with some other component. If you require many dependencies for a class to work correctly, that indicates it needs to be rewritten. Testing a component can also be tricky when it violates single responsibility and does too many things.
“P” Is for Premature Optimization
Premature optimization happens when an anticipated problem is dealt with before it is a problem.
But… that's good, right?
Not always. For example, we have to return all cards to the deck at the end of our card game and then shuffle the deck.
But we have no information about what order the cards are currently in (except for the cards that we just used in the last game)! So we don’t have to shuffle the whole deck - which could be a slow process.
Instead, when we return the cards to the deck, we can insert them in random positions using a smart algorithm that ensures perfect random mixing. (In fact, it’s much harder than it sounds to get perfect random mixing here, but that’s a digression!)
But cards don’t get shuffled that often. So optimizing that algorithm isn’t worth the effort - and worse: we’ve written a bunch of complicated code that future developers will take longer to understand.
“I” Is for Indescriptive Naming
While this seems like something you can easily avoid, it appears quite often. It occurs because, at the time you are writing code, the problem and solution make sense. Let's say you are dealing with a rectangle, so you name the upper-left corner variables x1
and y1
. That makes sense.
Months later, when looking at the code, you (or someone else) see these variables. What is x1
? You have to read the code to know. If you had named the variables upper_left_corner_x
and upper_left_corner_y
, you would know immediately.
“D” Is for Duplication
Duplication is an easy trap to fall into. You need to add a new feature. It should work like another feature, but a little different. What do you do? Copy, paste, and modify. If this approach continues, you end up with code duplicated in many places. If something fundamental has to change, all those copied places have to be found and modified.
Is it always bad to copy and paste?
Copying and pasting are acceptable when you have to get something in place in a short time. But, you need to go back and find a better long-term solution. Ask yourself:
Why is there so much commonality between these two pieces?
Can the duplicated code be put into a common function or class?
Can I extract an interface and put the slightly different pieces into different implementations?
There is no single answer that works in every situation. The big idea here is to remember that all of these STUPID approaches are easy to do. They make sense at the time. The problems they create don’t show up until later in the project.
Let’s Recap!
STUPID stands for:
Singleton
Tight coupling
Untestability
Premature optimization
Indescriptive naming
Duplication
STUPID approaches lead to difficult-to-maintain and hard-to-test code designs.
It’s easy to fall into STUPID traps, so stay vigilant and ask yourself the right, SOLID questions. 😉
And that’s everything! Now test your understanding with the end-of-part quiz.