There are many ways in which code can be bad, but you don’t have to experience them all firsthand to learn how to solve them. That’s because smart programmers and writers have researched and cataloged many of these problems, giving them memorable names and even publishing guides for how to solve them. We call some of these problems “code smells,” and this post is about a specific one: message chains.

Tim Robinson No GIF by The Lonely Island  Find & Share on GIPHY

We’ll start with some fundamentals. You’ll learn what message chains are, why they result in bad code, and see some examples of things that might look like chains, but aren’t. Then, we’ll walk you through the steps you should take to solve this problem. Before wrapping up, we’ll share some final tips on how to prevent this code smell from happening in the first place.

Let’s dig in.

What Is a Message Chain?

As promised, let’s start with a quick message chain 101.

A message chain is a code smell that consists of chaining method call on top of other method calls, creating a long chain

A message chain occurs when you chain method calls on top of other method calls, creating a long chain. “Message” in this context means a method call, because in the tradition of Smalltalk and other languages, when you call a method of an object you are sending it a message.

Let’s see a quick example in C#:

payrollService.GetDataForMonth(2022, 5).GenerateReport().ConvertToSpreadsheet().Print();

The example above is simple and contains only four chained calls. Real-life message chains are often way longer than this—and yes, the longer the chain, the worse.

An even worse version of the message chain code smell is when each method call has a long parameter list!

But why are such method calls bad in the first place?

Why Are Message Chains Bad?

Though you might have a legitimate reason for having a message chain, most of the time that’s not the case. So, let’s walk through the main reasons why message chains are bad. I’ll list the areas affected by the code smell and then I’ll expand on each:

  • code coupling
  • code robustness
  • complexity
  • testability

Message Chains Make the Code Too Coupled

Where a message chain exists, tight coupling isn’t far away, which is a phenomenon that results in other code smells too like feature envy. Look at that original C# example again carefully, and think about how much that code “knows”:

  • It knows that the return of GetDataForMonth is something that has a GenerateReport() method—maybe an IReportable instance or something.
  • Additionally, the example code knows that the return of GenerateReport() has a method called ConvertToSpreadsheet() method.
  • ConvertToSpreadsheet(), in its turn, returns something that has a Print() method.

The original object payrollService—the chain’s first link, if you will—is tightly coupled to the subsequent objects. Should the public interface of any of those objects change—or a new link be added to the hierarchy—the code in the example would have to be updated.

When classes depend upon one another extensively, you risk violating the Law of Demeter. This can result in the tight coupling code smell but other code smells as well, like parallel inheritance hierarchies.

Highly-coupled code results in a high rate of code rework, which is when recently changed code has been updated again. With LinearB, you can monitor the amount of rework your team is having to do. This visibility helps you identify issues like tight coupling that are slowing down your developers.

rework ratio

Message Chains Make the Code Less Robust

When objects rely excessively on each other, a change to one of them can initiate a chain reaction, resulting in a cascade of changes across the codebase. This is the shotgun surgery code smell.

The longer a message chain is, the larger the opportunity for coupling there is. Additionally, each of the objects on the chain could be null, which means that, the longer the chain, the higher the likelihood of an error due to a null reference.

In other words: message chains make your code less robust. Even small changes might result in a lot of additional work—which discourages said changes, even when they’re beneficial—and the chain might result in bugs.

Because bugs are so harmful to the user experience, you should strive to avoid them. One way to get ahead of bugs is by preventing high-risk work from getting push into production.

LinearB offers this functionality right out of the box. With our WorkerB automation bot, you can set up alerts to notify you and members of your team when a pull request with high-risk work has been created. This enables you to inspect the diff and address any problems before they go into production.

Laugh in the face of danger with high risk work detection. Schedule a demo of WorkerB.
Want to eliminate risky behavior around your PRs? Book a demo of LinearB today.

Message Chains Make the Code More Complex

Message chains necessarily add complexity to the code. They make the code:

  • less readable: It might be hard to understand what happens after each of those calls.
  • harder to reason about: Each of those calls can result in side effects, for instance.
  • hard to change: As you’ve seen, even a small change to one object might result in a cascade of change across the codebase.

Since you’re likely to find the same message chain several times across a codebase, message chains also contribute to another serious problem: code duplication.

Message Chains Affect Testability

Message chains affect testability in many ways. First, they make the tests themselves coupled to the structure of the chain. Should the hierarchy change, you’d have to update the code and the tests as well. That contributes to fragile tests, which turns test maintenance into a burden and make engineers less enthusiastic to unit test their code.

Chains make the setup for the tests more complex, as well; you’ll have to create an entire tree of objects just to test a single simple scenario.

Finally, the test code might become less readable and more clunky. That might not sound like a big problem, but it is: Unit tests are also documentation, so it’s important that your tests are as readable as possible at all times.

What Is Not a Message Chain?

Not every time you see a chain of calls means you have a message chain. Or, maybe it is technically a message chain, but you’re still following good object-oriented programming principles.

Pipeline-style Programming

For instance, many languages allow a functional-like style of programming in which you can chain many functions to create a “pipeline” to process and filter data. Here’s a C# example using LINQ:

var names = employees
    .Where(x => x.AdmissionYear >= 2019)
    .Select(x => $"{x.LastName.ToUpper()}, {x.FirstName}");

The problems you’ve just seen about message chains don’t apply here:

  • The methods aren’t likely to change, since they’re extension methods from the .NET framework itself, so you’re not adding coupling.
  • They return the same type—either IEnumerable<T>, or a derived type.
  • Since the LINQ methods are pure—i.e. they don’t create side-effects—they are easy to reason about and don’t affect testability.

I’m using C# as an example, but the same applies to languages that offer the same style of programming. For instance, here’s the equivalent code in JavaScript:

let names = employees
    .filter(x => x.admissionYear >= 2019)
    .map(x => `${x.lastName.toUpperCase()}, ${x.firstName}`);

The Builder Pattern

The design pattern called builder comes in handy when you need to facilitate the creation of complex objects—especially when the object has many optional properties.

Often, a builder has many methods that either return a reference to itself—that gets mutated after each call—or a new object, as in the case of an immutable builder. That allows you to chain calls, generating code that’s concise:

var connectionString = new ConnectionStringBuilder()
    .AddHost("localhost")
    .AddUser("admin")
    .AddPassword("admin")
    .AddPort("1234")
    .ToString();

In the example above, you’re chaining several methods, yet I don’t think this is a code smell. Since all methods both belong to and return the same type, there isn’t a lot of coupling—other than the client being coupled to the builder. The builder itself is both easily testable and might be used to make testing easier as well. It also observes the single responsibility principle. Although using the builder pattern does increase complexity a bit, the code is still highly readable and easy to reason about.

To solve this smell, the easier solution is to apply a specific refactoring called hide the delegate

How to Solve the Message Chain Code Smell

To solve this smell, the easier solution is to apply a specific refactoring called hide the delegate. This refactoring consists of hiding all of the calls under a single method. So, by applying this refactoring, you could turn our first C# example into the following:

payrollService.PrintSpreadsheetForMonth(2022, 5);

Internally, the calls would still be happening, but they’d become private. This large class has shrunk, i.e. it it is a smaller public interface that the client needs to know and worry about. Coupling is lower, since changes to the underlying methods now impact only the PrintSpreadsheetForMonth method. As a consequence, the code is now more robust, less complex, and easy to test, understand, and change. Simply put, now you have clean code.

It’s important to note that solving one code smell can result in a different one. In this case, moving functionality into a class could result in the data clump code smell.

Sometimes, your only hope is to mitigate the chaining, rather than eliminate it. In this case, move method is another useful refactoring. Going back to the original code example, you could do this:

payrollService.GetDataForMonth(2022, 5).GenerateReport().PrintAsSpreadsheet();

You’re essentially moving a method call up a level, eliminating one link of the chain. This often also eliminates another code smell, the middle man.

A final tip to avoid/solve the message chain smell is to think in terms of separation of concerns. Does the payroll service really need to be in charge of converting the monthly report into different formats and even printing it? Probably not. In this case, it’d probably be wise to think of a plug-in architecture in which you can connect new formatters/printers at will, which would make the code more modular and reusable and prevent the payroll service from becoming a god class.

Break Your Chains and Create Better Software

Code smells are a great metaphor to start a conversation about code quality. In this post, you’ve learned about the message chain smell, why it’s bad, and how to fix it. Wouldn’t it be great if you could avoid this smell—and others—appearing in your code to begin with?

With hard work, discipline, and some help from good tooling, it is possible:

With LinearB’s metrics, Team Goals, and WorkerB automation bot, you have everything you need to keep your team consistently delivering high-quality code. We can empower you to eliminate code smells like message chains, and do so much more.

To learn about all the ways LinearB can help your team to ship more features faster, set up a demo today.

Improve your engineering organization at every level with LinearB
Want to improve your engineering processes at every level? Get started with a LinearB free-forever account today!