Posted on 7/5/2009 9:59:50 PM by Justin Etheredge
A good friend of mine, Kevin Israel, said on twitter today:
"how freakin long are *we* gonna keep building tightly coupled software that needs to constantly evolve!?"
At first I wanted to say "until programmers stop sucking so much", but unfortunately, there is often much more to the picture than the usual "you're surrounded by idiots" response. Many of us do continue to build tightly coupled software, and we do it for a variety of reasons. Some of us just don't know any better, we've never been exposed to or experienced what exactly people mean by "loosely coupled" software. Others see creating loosely coupled software as another layer of complexity that they just don't need inside of their applications. And yet others, have seen and experienced it, but still don't understand how to implement it effectively. Unfortunately, at this point I think that majority of developers fall into the first camp, they just don't know what it means to build "loosely coupled" software.
So why is that? Well, most of the projects that they have worked on plug A into B and off they go! Decouple A from B? Why? A needs B! And therein lies the problem. If I have a plug, and then I have an outlet, what is the reasonable response? The reasonable response is to put the plug into the outlet. So if I have a class which needs to write out to a file, why wouldn't I just write it like this?
public class Report
{
private string contents;
public void SaveReport(string path)
{
using (var sr = new StreamWriter(path))
{
sr.Write(this.contents);
}
}
}
At first glance, to someone that is only thinking of the class from a purely functional perspective, there is nothing wrong with that. The class performs the action that I want, in the manner that I want it to. Unfortunately we are looking at the problem from a non-programmers perspective. It would be the same as if I, as a non-engineer, decided that I was going to build a bridge. I would put up some pylons, build the road surface which goes across it, and voila....I have a bridge. I might have copied the materials and design that I saw on most bridges, but none of the usual things that goes through a bridge builders mind during construction ever crossed through mine... what weights will this bridge support? How long will it last? What material are the pylons set into? What is the span of the crossing? The list just goes on and on... A professional bridge builder probably has hundreds of questions which have to be answered before a bridge can be put into place. Sure, my bridge might work (I might get lucky), or it might collapse when a heavy rain comes. Who knows? I certainly don't.
Thankfully most of the software that we write isn't quite as life and death as bridge building is. If it was, most of us probably wouldn't have jobs anymore. But even though our software doesn't involve life and death, it usually does involve dollars, and to most businesses this is life and death. Many businesses rely on their software to conduct transactions, and they rely on their software to change in order for them to keep up with the market. When businesses need software to change, they really can take two completely different directions.
Direction 1 is to write the software as always, let it grow into a big ball of mud and keep throwing more and more programmers at the problem until the system can be cobbled into a useful state. If we want to put this into computer science terms, this is the brute force approach, and it is by far the most common approach. Since developers in the United States are so expensive, this is also the approach that forces many companies into outsourcing.
Direction 2 is to write loosely coupled software that can more easily modified and maintained. This is a harder approach (in the beginning), but it in the end you will end up with software the is much easier to maintain. Thus requiring less developers. Or at least in theory. :-) Remember...
In theory, there is no difference between theory and practice. But, in practice, there is.
-Jan L.A. van de Snepscheut
So you may be saying now, "okay Mr. Smarty Pants, if the code you wrote up above isn't the right way to do it, then how am I supposed to do it?" Well, that is a good question. One way would be to say that it is a violation of SRP to have the report class responsible for writing out the file to disk, and that the process of writing to disk should be put into another class, and then this dependency should be passed into it. Another way would be to have a class which is responsible for writing out reports to disk, and then passing in a report which is then written to disk by the class. I'm sure that there are many people that will have some very strong opinions about how this is to be accomplished. Right now, the details of how to execute this trivial task are not important. What is important is that in order for us to call ourselves Software Engineers or Software Craftsmen, we must consider the coupling of our software at both the micro level (between classes) and at the macro level (between subsystems.
If you don't put thought into how these systems will interact, and how they can be separated for testing or for replacement, then we will end up with a jumbled mess. We have to be constantly evaluating our systems in order to keep them in check. Software systems become unmaintainable because we let them grow on their own, without keeping a keen eye on how that growth is proceeding until one day we realize that our software is a tangled mess of copied code and interwoven dependencies. And we have no one to blame but ourselves.
Final Note: It is odd, but I feel as if I write "think, damn you!" posts way too much. I honestly believe that the biggest problem that most developers have is that they take action without considering the reasons or implications. If you found this post way too "duuuuuuuuuuuh" then please move along, but I think that our lives as software developers would be so much easier if we just took a few more minutes each day to stop and think about what we are building.