S.O.L.I.D. Software Development, One Step at a Time
Most professional software developers understand the academic definitions of coupling, cohesion, and encapsulation. However, many developers do not understand how to achieve the benefits of low coupling, high cohesion and strong encapsulation, as outlined in this article. Fortunately, others have created stepping stones that lead to these goals,
resulting in software that is easier to read, easier to understand and easier to change. In this article series, I will define three of the primary object-oriented principles and show how to reach them through the five S.O.L.I.D. design principles.
Have you ever played Jenga? It’s that game of wooden blocks that are stacked on top of each other in rows of three. In Jenga you try to push or pull a block out of the stack and place it on top of the stack without knocking the stack over. The player that causes the stack to fall loses.
Have you ever thought you were playing a game of Jenga when you were writing or debugging software? For example, you may need to change one field on one screen. You study the stack of code, you look for that little space of light peaking between the classes, and you make the change in the one place that you thought would be safe. Unfortunately, you didn’t realize that the code you were changing was referenced in several critical processes through some strange levels of indirection. The resulting crash of the software stack has left you the loser, cleaning up the mess with your boss breathing down your neck about the customer being upset about their lost data, and … How many times have you been there? I can’t even begin to count how often it’s happened to me.
Software development does not have to be like a game of Jenga.
There is good news, though: software development does not have to be like a game of Jenga. In fact, software development should not be like any game where there are winners and losers. What you want, instead, is a sustainable pace of software development where everyone wins. You want to ensure that you don’t overwork the developers, that you don’t pressure managers to say “just get it done,” and the customer gets the software they want in a timeframe they agree to.
A Sustainable Pace
When trying to set a sustainable pace in any endeavor, you first need to understand how far you need to go. You also need to know how fast you need to get there. For example, if you want to run a 50-meter dash, you should run as fast as you possibly can and then push yourself to try to run faster. However, if you want to run an 800-meter race you should set a somewhat slower pace. When you start talking about significant distances like a marathon, the pace becomes significantly slower. For such a distance, you want to set a pace that you can maintain throughout the race.
In software development, you can think of the pace you need to run as the timeline of the project combined with the expected features and functionality.
In software development, you can think of the pace you need to run as the timeline of the project combined with the expected features and functionality. For shorter timeframes, you need to run faster. However, you also have to consider how much functionality you can reasonably add to your system, given a short timeframe. If you only need a few features and you need to get it done quickly, you may need to sprint toward the goal. If your customer expects you to cram too many features into a short timeframe, you have a higher likelihood of burning out. Imagine trying to sprint for the duration of a marathon, or even a 1600-meter race. The probability of sustaining that pace for that distance is going to approach zero as you continue moving forward. You need to work with your management, your team, and your customers to set the expectations of how fast you can run for a given period.
Assuming that you have a reasonable number of features for a given timeframe, you now have to set the pace for feature development in your daily activities. Object-oriented software development has various principles, patterns and practices that help you achieve the sustainable pace you need. The principles include coupling-the extent to which the parts of the system rely on each other; cohesion-the extent to which the parts of a system work together for a single purpose; and encapsulation-the extent to which the details of implementation are hidden. Built on top of these principles are various design and implementation patterns such as strategy, command, bridge, etc. When you combine these principles, patterns and practices they will help you to create systems that are well factored, easy to understand, and easy to change.
The Object-Oriented Principles
Ideally, you want to ensure that your systems have low coupling and high cohesion. These two principles help you to create the building blocks of a software system. You also want to ensure that you have self-contained building blocks-that is, they are well encapsulated. You don’t want the concerns of one building block leaking into other blocks. If you create building blocks that have the correct size and shape, you can put them together in meaningful ways to solve your problems.
Often it seems that developers only discuss these principles in academic settings. Most universities with degrees that cover software development provide at least a cursory introduction to them. However, many software developers seem to miss their correct usage, causing more problems than they solve. Indeed, developers can very easily misapply these principles in the wrong place at the wrong time. To avoid this situation, you need to understand how coupling, cohesion, and encapsulation correctly play into developing software solutions.
Coupling in software development is defined as the degree to which a module, class, or other construct, is tied directly to others. For example, the degree of coupling between two classes can be seen as how dependent one class is on the other. If a class is very tightly coupled (or, has high coupling) to one or more classes, then you must use all of the classes that are coupled when you want to use only one of them.
You can reduce coupling by defining standard connections or interfaces between two pieces of a system. For example, a key and a lock have a defined interface between them. The key has a certain pattern of ridges and valleys, and the lock has a certain pattern of pins and springs. When you place the right key in the lock, it pushes the pins into a position that allows the mechanism to lock or unlock. If you place the wrong key into the lock, the pins will not move into the correct position and the mechanism won’t move.
In software development, developers also work with standard connections and interface definitions. Object-oriented languages such as C++, C#, Java, and Visual Basic have some constructs that allow you to define those interfaces implicitly and explicitly. Whether it’s a class’ public methods and properties, an abstract base class, an explicit interface or other form of abstraction, these constructs allow you to define common interaction points between parts of your system. Without abstraction to decouple these common points of interaction, you are left with pieces of the system that must know about each other directly. Basically, this means you are stuck with a key that is welded directly to the pins of a lock, preventing you from removing the key, and compromising the security of that lock.
Imagine that you are working with the structure in Figure 1. The software works fine, at the moment, and you can fix bugs when you need to. Then your boss hands you a new requirement and you realize that the module highlighted in red can handle most of the requirement. You would like to reuse that module but you don’t need any of the surrounding modules for this new feature. When you try to pull out the module in red, though, you quickly realize that you’ll have to bring several more modules with it due to the high coupling between this module and the ones surrounding it.
Figure 1: A tightly coupled system.
Now imagine a class that has zero coupling. That is, the class depends on nothing else and nothing else depends on it. What benefit does that offer? For one, you can use that class anywhere you want without having to worry about dependencies coming along with it. However, you essentially have a useless class. With zero coupling in the class, you won’t be able to get any information into or out of it. Try to create a class in .NET that does not rely on anything-not an integer, not a string, not a Console or Application static reference; not even the implied object inheritance of every construct in .NET. Go ahead… try it… see how useful that is in your system.
Coupling is not inherently evil. If you don’t have some amount of coupling, your software will not do anything for you.
Coupling is not inherently evil. If you don’t have some amount of coupling, your software will not do anything for you. You need well-known points of interaction between the pieces of your system. Without them, you cannot create a system of parts that can work together, so you should not strive to eliminate coupling entirely. Rather, your goal in software development should be to attain the correct level of coupling while creating a system that is functional, understandable (readable by humans), and maintainable.
By: Derick Bailey
Derick Bailey is a software developer, architect, and technology leader in central Texas. He has more than 12 years of professional software development experience and more than 20 years of experience writing code. Derick is a blogger with LosTechies.com and has been active in developer communities around Texas (including Austin, San Antonio and Dallas) and online.