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.
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!
What is S for? Singleton
Singletons ensure that a single instance of a class is created, which seems to make sense. For example, if you have to play sounds in your system, you probably have a single SoundManager class. You call SoundManager.playSound(), and it sets the appropriate volume, loads the sound file, and launches the platform’s media player.
But the real problem is when this becomes your way of viewing most functionality. It becomes tempting to have a singleton for everything. Then you’re stuck creating those pesky manager classes that handle connections, video, databases, and more.
What is ultimately happening is that it is handling global state information. Global state information is model information that ends up being shared across modules or portions of your application. Usually, having a system dependent on global state variables is a bad idea. The problem is that any part of the program can change the state. Consider the sound example again. Your code level is set the volume level to 4. Then some other part of the program sets it to 11. You have to go back and set it to 4. Or you have to find the offending part of code doing the other setting of the value.
What's the solution?
Ask yourself if you really need a singleton. Why does this class needs to be accessed as if it were global data? See if there isn’t a compelling reason for it not to be.
What is T for? Tight Coupling
Here’s the real problem with a singleton; any class that uses one is tightly coupled to it. That class can’t be extracted and used elsewhere. It has to drag the singleton along.
You can reduce coupling by coding to an interface instead of an implementation. You saw earlier where if you tightly coupled the controller with the view, that is, the controller had direct access to the view’s widgets, that changing the view required changes to the controller.
This same situation (being dependent on the actual implementation, rather than in an interface) can happen in many situations. Be aware that changes in a class's implementation can cause other classes (which depend on it) to change as well.
What is U 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.
For that reason, there are two GameEvaluators (high and low) in our card game instead of a single method that passes a boolean parameter to pick between the two. Testing a method that does two things is harder than testing a method that does one thing.
What is P for? Premature Optimization
Premature optimization refers to when an anticipated problem is dealt with before it is a problem.
But...that's good, right?
Not always. For example, in our card game, we need to shuffle the deck. We use the Java random number generator to generate indexes and move the cards around by swapping their locations.
Shuffling can be a slow and repetitive process. We could design a lightning-fast algorithm that not everyone can understand, but cards don't get shuffled that often. So optimizing that algorithm really isn't worth the effort.
What is I 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 upperLeftCornerX and upperLeftCornerY, you would know immediately.
What is D for? Duplication
Duplication is a really 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/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 fine 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 is so much commonality between these two pieces?
Can the duplicated code be put into a common base class?
Can I extract out 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. 😉
Now, test what you've understood about SOLID principles in the end-of-part quiz. After that, meet me in the next part to learn about design patterns!