Wednesday, December 19, 2007

The dangers of reducing coupling.

This is a continuation of part 1

Once programmers discover the joys of sectioning off code and reduced coupling there's a tendency to go a bit too far. (This is all from a java programmer perspective)

One of the common anti-patterns people fall into is to try and manage their dependencies by tracking which classes/packages know about which other classes/packages. The idea is that a class should only know about certain other classes. As a result it should be possible to group related classes into a package and then use a tool to automatically generate a map of dependencies between packages. This map can then be used to figure out where classes are making dependencies and destroy stupid ones.

There's a few problems with this:

1) It gives a false sense of security because two classes can depend on one another without having a compile time dependencies. The most common problem I've seen is some sort of complex event-driven system that ends up getting tied in knots because the code was written using this event driven system partly as an attempt to compile time dependencies with the goal of retaining the positive aspects avoiding compile time dependencies.
2) Compile time dependencies are not run time dependencies. Even if you consciously try and avoid falling into the trap of turning your compile time dependencies into run time dependencies, you will still fail because there's often no way of expressing the run time behavior of a system with a static, compile time dependency map. In the worse case, the attempt to do so will put limits on what sorts of patterns you can use while coding in order to try and keep the static dependency graph matching with the actual real graph.

Essentially these two reasons boil down to 1) it won't work in practice and 2) even if you could make it work in practice it still wouldn't be a good idea.

Keeping your inter-package dependencies clear and clean is absolutely a good idea. It is not, however, a panacea. It can't solve world hungry and it won't bring anyone back from the dead.

The thing with managing compile time dependencies is it's works up to a point. That point is the point at which the compilers understand how your program is put together.

What you should be trying to do is manage the coupling of your code. A dependency is a hint that there's some sort of coupling. It could be high or low but the hint is there's a coupling. If the compiler doesn't show a link that doesn't mean there's no coupling, it just means there's no compile time coupling. To be more accurate it means there's not compile time dependency. A compile time dependency can be thought of a form of coupling.

Ok, so let's say we're a developer and we've seen the light and now we know that coupling is bad. Is there any other ways to screw this up? Yep. Trying to reduce coupling to zero.

Reducing coupling between components to 0 can't work. I've seen people try to do this by removing as many constraints as possible.. For example, removing compile times checks via using something like a Map or events or something. Don't do this. There are languages out there that don't have compilers. Ask programmers in those languages if they experience problems with things being too coupled.

Another favorite is to keep chopping up code way past the point of sanity. The way to replicate this at home is to take a reasonably well written program and try to make every line of it into a framework. After a few hours you'll wind up with a dense soup of spaghetti.

This happens because you can't remove coupling. If you try to remove coupling by splitting things into incredibly tiny pieces you end up with a program with more tightly coupled components and more coupling related issues and more complexity than ever.

Before I cojntinue I'd like to introduce the idea of cohesion. Cohesion represents the idea that some things in life are inheritantly* coupled together tightly. A real world example of this is a table leg. Every molecule in that table leg can be viewed as a separate entity. However, it makes sense to manipulate the table leg as a whole so we don't consider the fact it's made up of molecules which are made up of atoms. Sure it's a lie but it's a convenient lie that makes the world easier to understand.

* however the heck you spell that.

The good programmers recognize a cohesive object when they see one.

Using this approach of building sections of code with high internal cohesion and low external coupling you can build some fairly amazing things.

It works in ANY language.

Oh, you can even use the trick recursively. Build an object out of some cohesive properties then use a collection of relatively coupled object to build a meta-object and so on. As such you can build mind bindingly complex things.

Everytime you use Swing or SOAP or RSS to write an application you're building a sort of meta-object that includes libraries with high internal cohesion. TCP/IP, XML, Swing object like JTable (shudder)..

Ok, so how do we do this? Well, the easy way is to do test driven development. I'm not sure why but test driven development seems to help programmers make programs that have objects with higher internal cohesion. I have my theories:

1) It actually forces programmers to think first - to design*.
2) Writing unit tests is easier if you have objects with simple interfaces that aren't dependent on a myriad of things.
3) Writing objects with simple interfaces also means you want to clump related functionality into one object so you don't drive yourself insane writing tests for millions of little, tiny objects.

* Design is the "D" word. Don't say it at any agile software conferences or you'll spend the next half an hour explaining yourself.

I suspect that taken together these things can be responsible for the worst hyperbole I heard while at the SD2007 best practices conference. I quote (more or less):

"Test driven development is the silver bullet Fred Brooks say didn't exist."


Low coupling, high cohesion. It's the mantra of good software.

Further reading:

No comments: