12

Why coupling will destroy your application and how to avoid it

Left unchecked, tight coupling between components (especially when distributed around your application) can slowly kill your software; rendering it hard to maintain and much more likely to suffer from bugs.

In software engineering, coupling is the manner and degree of interdependence between software modules; a measure of how closely connected two routines or modules are; the strength of the relationships between modules.

Wikipedia 

When developing software, we tend to keep a mental picture of the software we write as we go along. This is why developers so often complain about interruptions; because it takes time to build that mental map of the problem you’re working on and any interruption can set you back causing you to pick up the pieces and start building the picture again.

Even when you split your code into multiple classes you have knowledge of the “other half” of the equation (the calling or called classes).

Born together

As a result, it’s easy to couple the software together. At the exact moment you write the code, the coupling is hidden from you because your mental map encompasses both halves of the solution. In effect it is entirely possible to simply take an implementation, divide it in half and spread it between two classes. The two classes end up depending on each other and consequently they are forever joined and rippling changes are inevitable.

Low cohesion coupling

Probably the most blatant coupling you’ll ever see in code is magic strings. As soon as we use “Apple” in two places we’ve coupled our code together. It only takes one of them to be renamed to break our application.

When it comes to how damaging this coupling is, distance matters.

Small gap

We can weaken the coupling by bringing it closer together. If these two strings exist within the same small method then the coupling may well be manageable. Anyone who looks at the code will see both usages of the string and understand that changes to the declaration will affect the usage.

But when the coupling is spread around your app then the risk of bugs dramatically increases.

Coupling is usually contrasted with cohesion. Low coupling often correlates with high cohesion, and vice versa. Low coupling is often a sign of a well-structured computer system and a good design, and when combined with high cohesion, supports the general goals of high readability and maintainability.

Wikipedia 

Exceptional coupling

Magic strings are easy enough to spot; here’s a different example.

    public class ShoppingCart
    {
        private Dictionary<string, int> _products = new Dictionary<string, int>();
        private int _total;

        public ShoppingCart()
        {
            _products["apple"] = 10;
            _products["pear"] = 20;
        }        

        public void Scan(string item)
        {
            if (!_products.ContainsKey(item))
                throw new UnknownItemException();
            else
                _total += _products[item];
        }                   
    }

Our favourite Kata presents a different problem. If the calling code attempts to scan a non-existent item, what should happen?

The approach taken here introduces a few issues.

  • Tight coupling between the exception being thrown and the code which handles it.
  • The calling code has to assume knowledge of which exceptions might be thrown and then handle them.
  • The exception may bubble up through the application and may not be handled at all.
  • If the exception is eventually caught, the handling code may be a long way away from the shopping cart where the problem originated.
  • When reasoning about the code starting at the top of the application it’s entirely possible we’ll have to click through several layers to find what might throw such an exception in the first place.

We can weaken this coupling by ensuring that our calling code doesn’t rely on knowledge of the logic inside our shopping cart. One way is to use a delegate method.


    public class ShoppingCart
    {
        public void Scan(string item, Action itemNotFound)
        {
            if (!_products.ContainsKey(item))
                itemNotFound();
            else
                _total += _products[item];
        }                   
    }

By providing our shopping cart with a coping strategy for missing items, we’ve ensured that the calling application retains control of the details of how to handle the error and our shopping cart need only execute the provided coping strategy when it makes sense to do so.

    public class Application
    {
        public void Main()
        {
            var cart = new ShoppingCart();
            cart.Scan("mango", () => Render.ErrorMessage("Item not recognised"));
        } 
    }

Bonus C# 6 Feature

There is one slight wrinkle with our code, if null is passed as the second argument to our scan method our code will blow up. We should really check if we have a handler before calling it.

        public void Scan(string item, Action itemNotFound)
        {
            if (!_products.ContainsKey(item))
            {
                if(itemNotFound != null)
                    itemNotFound();
            }               
            else
                _total += _products[item];
        }   

Luckily, if you happen to be using C# 6 you can take advantage of the null-conditional operator to seamlessly handle this possibility without the conditional if statement.

        public void Scan(string item, Action itemNotFound)
        {
            if (!_products.ContainsKey(item))
            {
                itemNotFound?.Invoke();
            }               
            else
                _total += _products[item];
        }     

In summary

Of course there are many other forms of coupling.  Put the effort into learning to spot them. By identifying and reducing coupling in your code, your application can live a long and happy life.

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #2049()

  • beton

    Great and simple example with callback delegate. Thanks for that!

  • Bud Goode

    You’ve just described “Connascence”. It comes in many different forms and either removing connascence or changing more severe forms of connascence to less severe forms will greatly de-couple your application. Read more here: http://connascence.io/

    • Hi Bud, I have looked at Connascence before but didn’t think to directly reference it here (I should have). Thanks for the link.

  • Pingback: We're not gonna link. We can't link, Bendis. You know why? Because we are so very pretty. We are just too pretty for God to let us link - Magnus Udbjørg()

  • Tia Suksomrat

    I appreciate your first section, it is well explained. However, the Exception example is kind of out. Unhandled exception crashing the app is by design, because when something goes wrong you don’t know how wrong it is and whether it is safe for the program to go on.

    The example looks fine because it is trivial, but in real application there will be more requirements. For example, the Scan function should return item details for the caller to display on the UI, and that’s when callback function will not very intuitive choice because the execution flow will still go on and you will need null-checking for item-not-found case anyway.

    While throwing Exception in this case is also arguably not a best choice for the job, the implementation is more solid. Microsoft .NET Framework Design Guidelines does state that if the method might throw exception in foreseeable scenario, you should provide an alternative Try… method for that, like dictionary’s TryGetValue or int.TryParse to signal the programmer that the method might fail. In this case, TryScan that returns a ScanResult is good enough to be better than using callback alternative.

    The similar exception problem that I face is that exception is not a concrete type, so sometimes class library just lazily throw some common .NET exception (like IOException) and then it is coupled forever. If the method choose to throw other kind of exception, the code compiles and then breaks at run-time, and it will be error-prone trying to fix that even if the class library is our own. However, it is not the reason to abandon .NET exception concept, we just have to use it correctly.

    • Hi Tia

      Thanks for your feedback.

      The exceptions case is an interesting one. At this moment in time, my preference is to not throw exceptions for predictable behaviour.

      It seems in this case to be a predictable event and reasonable business requirement to handle an unknown item being scanned.

      Exploring the idea that the Scan function should return item details, I’m not sure this is a given. If we apply the concept of tell don’t ask here then I would look for a way to avoid requiring the Scan method to return anything.

      A few ways come to mind. We could pass in two callback delegates, one for the ItemNotFound case and another for the ItemScannedOK case.

      Alternatively, why not give the ShoppingCart the means to log what has been scanned but not return that data. The observer pattern could fit here, I might give the cart a reference to a Receipt object (directly coupled listener) which it can call to indicate when an item has been successfully scanned.

      Receipt.RecordItem(item, _products[item]);

      Or I could use events.

      Eventbus.Raise(new ItemAddedToCart(item, _products[item]))

      http://silkandspinach.net/2014/11/06/on-paperboys-newsagents-and-exceptions/ discusses these options (with examples).

      As always, there’s no shortage of options. I guess the important point is how, by being aware of the coupling (as @bud_goode:disqus pointed out, Connascence) we can keep a check on the habitability of our code.

  • Pingback: Les liens de la semaine – Édition #175 | French Coding()

  • Pingback: Dew Drop – March 14, 2016 (#2207) | Morning Dew()

  • I’ve written another post to address some of the feedback here

    http://new.jonhilton.net/2016/03/14/apply-tell-dont-ask-and-reduce-coupling/

  • Pingback: Reduce coupling: Free your code and your tests - JonHilton.Net()