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.
Loved the article? Hated it? Didn’t even read it?
We’d love to hear from you.
Thank you for this brilliant article.
Loosely coupling explanation is not a easy task and you did it well.
Good warning!
As long you and your team members are conscious about coupling, in my opinion, tight coupling is OK!. The big ball of mud will only happen if there is no feeling for complexity in the team. Often this feeling gets suppressed when outside pressure is added, which effectively holds the mud together 🙂
Direction 2 smells too much like premature optimization. So I suggest Direction 1.5 🙂
Loose coupling on demand by refactoring?
The discrimination of two extremes is a simplification. Consider that there are always shades of gray.
Bender
Tight coupling is often the proof you are not unit testing your application.
So, instead of creating actual software people should create architecture?
Don’t get me wrong, I’m not saying one shouldn’t care about software architecture, but sometimes you really don’t need that loose coupling. You’d better concentrate on the features for users, rather than making something that might be useful in future.
So what would be the decoupled example from above?
public class Report
{
string Contents;
public void SaveReport(IFileWriter writer, string path)
{
writer.SaveFile(path,this.Contents);
}
}
public class FileWriter: IFileWriter
{
public void SaveFile(string path, string data)
{
using (var sr = new StreamWriter(path))
{
sr.Write(data);
}
}
}
@Marcus I think that passing in the IFileWriter into the report class through its constructor would be easier. Then you could resolve the Report through an IoC container and have it injected automatically. But it depends on how you have your application setup, and whether or not you are using a container.
If you aren’t using a container, then it might be easier to just create the IFileWriter from within the Report, and then have a separate constructor that can take an IFileWriter so that you can override it for testing. You lose flexibility, but at least you allow the dependency to be replaced at test time.
@Mike I agree, sometimes you don’t need the loose coupling. The only problem is that when you do need it, and you didn’t build your application that way, it can be impossible to add in. And yes, I know that this is a YAGNI violation, but applications grow, and I’d rather have one that is flexible and testable.
I think this (and other "Think, damn you!" posts are right on, and I think part of the solution must be education and buy-in from above in the organization. At the outset of any project, I always try to think of the best way to architect a solution but inevitably feel that time crunch come down and oftentimes sacrifice some extra design considerations in favor of requested extra features. I guess we all need to strike a good balance between the two, I am just saying that managers need to recognize that if the software is intended to stand as long as the proverbial bridge, it had better be built only after a long period of design/planning.
I don’t think I would pass the writer to the report. I think it leads to feature-envy in refactoring terms. I think I would reverse the dependcies and pass the report to the output conector, this way I can build up my save/formatting code/parameters without hammering the report class.
What if this report needs to go to email. how does that get handled with just a filename ? surely you aren’t going to add a saveToEmail method to the report class. and add yet another heavy dependency…
@Mike I would also prefer to reverse the dependencies. In my post I even said that this would be one option. The class which writes the report to the disk would really just be a decorator on the report.
In my post though, I was trying to avoid the technical details, since developers can often get bogged down on such issues.
@Justin
I think having example of how to fix the problem are important, even in this short post. I think many devs do not do this because they are not aware of the alternate way to solve the problem and why this alternate way is better. I think thats why I always liked the GoF book so much, it explained the "why" you would employ a particular pattern.
In this coupling issue, It would go a long way to explain what penalty you are paying by having these tighly coupled dependencies and how to get out.
The problem is that developers will instantly think of design-patterns and complex technical solutions to address this question.
Instead, should a Report object be responsible for saving itself, or should that be left instead to a "ReportSaver" object? Applying technical hacks on top of poorly constructed object-models will ironically result in less maintainable software in the long term.
Yes, highy decoupled components can only be achieved if we incorporate two practises
1) TDD, it will force you refactoring and you will achieve highest decoupling
2) One must design software at pure abstraction.
Problem is that developers develop software and engineers engineer software, we must convert developer to engineer.
Loose coupling is obviously the ultimate goal to strive for – loosely coupled architectures however require a substantial amount of "up-front" design which increases upfront costs and a perceived "delay". In the real world, first to market wins – big ball of mud or not
Most developers may be in the first camp, but I bet most developers that read programming blogs are in the third.
@justin
Good blog post. Ironically my good friend just blogged about the importance of testing and used the same bridge analogy.
http://app.arat.us/blog/2009/07/the-importance-of-unit-testing-and-functional-testing/
On another note, right now we’re enthralled with message oriented architectures because it lends itself perfectly to producing loosely coupled code.
I would love to hear your thoughts on that particular architecture.
Because nobody has read the seminal work on the topic:
http://www.amazon.com/Reliable-Software-Through-Composite-Design/dp/0442256205/ref=sr_1_3?ie=UTF8&s=books&qid=1247777387&sr=8-3
I think that a lot of people don’t write loosely coupled software because they don’t know how to. They don’t know what StructureMap is, or what it’s for, or why it matters.
I do think it’s odd when people don’t want to do things "the right way"… the reason we write loosely coupled software is that it’s easier!
My two cents:
1. Applications are always/often based on business processes.
2. (existing) Business processes are often tightly coupled themselves
3. Programmers (often) see business processes and the business organization as statics (they don’t want to argue with business…)
Result : tightly coupled solutions SEEMS the moste efficient route to solving problems.
There seems to be an alliance between programmers and business consultants here… 🙂
This is also a discussion between long term / sustainable and short term solutions ! In an broader sense : the momentum seems to be now on sustainable solutions > Take it !!
Oh yeah, and there are people involved who are just ignorant
Thank you for this article! I was searching for confirmation that i’m not the only one who feels this way. I’m seeing code being written tightly couple with 2 completed separate services but have a small overlap, but with the tight coupling in the coding that small overlap is now a big overlap under the hood and will be a pain point going forwards. You cannot deploy each interdependently, you cannot easily replace each part independent, one has the potential of bringing down the other, and the list goes on.
*crying* what happened to good coding practices *crying*