Codifying the Secret Sauce

Each application has its secret sauce, it’s reason for existing. Codifying the secret sauce is instrumental in writing maintainable and successful applications.

Wait. What is codifying? Patience my friend, we’ll get there.

First let’s hypothesize:

You’ve just been promoted to Lead Software Engineer (Congratulations!). Your CEO’s first task is creating a new product for the company. It’s a ground-up accounting application. The executives feel having a custom accounting solution will give them an edge on the competition.

A few months have passed, most of the cross-cutting concerns are developed (yay you!). The team is now focused on the yummy goodness of the application: the business domain (the secret sauce). This is where codifying the secret sauce begins.

Codifying, is putting structure around an essential concept in the business domain.

In accounting, the price (P) earning (E) ratio (P/E Ratio) is a measurement of earnings for a company. A high P/E ratio suggests high earnings growth in the future. The P/E ratio is calculated by taking the market value per share (share price) divided by the earnings per share (profit – dividends / # of outstanding shares).

A simple, and I argue, naive implementation:

public class Metric
{
    public string Name { get; set; }
    public decimal Value {get; set}
    public int Order {get; set;}
}
public class AccountingSummary
{
    public Metric[] GetMetrics(decimal price, decimal earnings)
    {
        var priceEarningsRatio = price/earnings;
        
        var priceEarningsRatioMetric = new Metric 
        {
            Name = "P/E Ratio",
            Value = priceEarningsRatio,
            Order = 0
        }
        return new [] {priceEarningsRatioMetric};
    }
}

If this is only used one place, this is fine. What if you use the P/E ratio in other areas?

Like here in PriceEarnings.cs

var priceEarningsRatio = price/earnings;

And here in AccountSummary.cs

var priceEarningsRatio = price/earnings;

And over here in StockSummary.cs

var priceEarningsRatio = price/earnings;

The P/E Ratio is core to this application, but how it’s implemented, hardcoded in various places, makes the importance of the P/E Ratio lost in a sea of code. It’s just another tree in the forest.

You also open to the risk of changing the ratio in one place but not the other. This may throw off downstream calculations. These types of errors are notoriously difficult to find.

Often testers will assume if it works in one area, it’s correct in all areas. Why wouldn’t the application use the same code to generate the P/E Ratio for the entire application? Isn’t this the point of Object Oriented Programming?

I can imagine an error like this making it into production and not discovered until a visit from your executive who demands to know why the SEC’s P/E Ratio calculations are different from what the company filed. That’s not a great place to be.

Let’s revisit our P/E ratio implementation and to see how we can improve upon our first attempt.

In accounting systems formulas are a thing, let’s put structure around formulas by adding an interface:

public interface IFormula
{
    decimal Calculate<T>(T model);
}

Each formula now is implemented with this interface giving us consistency and predictability.

Here’s our improved P/E Ratio after implementing our interface:

We’ve added a PriceEarningsModel to pass the needed data into our Calculate method.

public class PriceEarningsModel
{
    public decimal Price {get; set;}
    public decimal Earnings {get; set;}
}

Using our PriceEarningsModel, we’ve created an implementation of the IFormula interface for the P/E Ratio.

public class PriceEarningsRatioFormula : IFormula
{
    public decimal Calculate<PriceEarningsModel>(PriceEarningsModel model)
    {
        return model.Price / model.Earnings;
    }
}

We’ve now codified the P/E Ratio. It’s a first-class concept in our application. We can use it anywhere. It’s testable, and a change impacts the entire application.

As a reminder, here’s the implementation we began with:

public class Metric
{
    public string Name { get; set; }
    public decimal Value {get; set}
    public int Order {get; set;}
}
public class AccountingSummary
{
    public Metric[] GetMetrics(decimal price, decimal earnings)
    {
        var priceEarningsRatio = price/earnings;
        
        var priceEarningsRatioMetric = new Metric 
        {
            Name = "P/E Ratio",
            Value = priceEarningsRatio,
            Order = 0
        }
        return new [] {priceEarningsRatioMetric};
    }
}

It’s simple and gets the job done. The problem is the P/E Ratio, which is a core concept in our accounting application doesn’t stand out. Engineers not familiar with the application or the business domain won’t understand its importance.

Our improved implementation uses our new P/E Ratio class. We inject the PriceEarningsRatioFormula class into our AccountSummary class.

We replace our hardcoded P/E Ratio with our new `PriceEarningsRatioFormula` class.

public class AccountingSummary
{
    private PriceEarningsRatioFormula _peRatio;
    
    public AccountingSummary(PriceEarningsRatioFormula peRatio)
    {
        _peRatio = peRatio;
    }
    
    public Metric[] GetMetrics(decimal price, decimal earnings)
    {
        var priceEarningsRatio = _peRatio.Calculate(new PriceEarningsModel 
        { 
            Price = price, 
            Earnings = earnings
        });
        
        var priceEarningsRatioMetric = new Metric 
        {
            Name = "P/E Ratio",
            Value = priceEarningsRatio,
            Order = 0
        }
        return new [] {priceEarningsRatioMetric};
    }
}

One could argue there is a bit more lifting with the PriceEarningsRationFormula over the previous implementation and I’d agree. There is a bit more ceremony but the benefits are well worth the small increase in code and in ceremony.

First, we gain the ability to change the P/E ratio for the entire application. We also have a single implementation to debug if defects arise.

Lastly, we’ve codified the concept of the PriceEarningsRatioFormula in the application. When a new engineer joins the team, they’ll know formulas are essential to the application and the business domain.

There are other methods of codifying (encapsulate) the domain, such as microservices and assemblies. Each approach has its pros and cons, you and your team will have to decide what’s best for your application.

Ensconcing key domain concepts in classes and interfaces creates reusable components and conceptual boundaries. Making an application easier to reason with, reducing defects and lowering the barriers to onboarding new engineers.

Garbage Collection Types in .Net Core

Memory management in modern languages is often an afterthought. For all intents and purposes, we write software without nary a thought about memory. This serves us well but there are always exceptions…

In California, there are extensive financial reporting requirements for Local Education Agencies (LEA), an LEA can be a county, a district, a charter or a single school. Most LEAs create their own financial reports which are usually centered around Excel, it’s no surprise when each report is different. To solve this problem the California Board of Education commissioned software to generate financial reports. 

I was a part of the development team. 

My first stop was the testing logs, Ed-Pro’s logs pointed to high memory usage, perhaps there was a memory leak? An engineer observed that Ed-Pro’s calculations used a large amount of short-lived memory. If the memory wasn’t cleaned up quickly, it could appear like a memory leak.

Ed-Pro is built on top of .Net Core, Microsoft’s multi-platform framework. In .Net Core, memory is divided into three categories: Short-lived (Gen0), medium lived (Gen1), and long-lived (Gen2). Gen0 is for short-lived data that quickly goes out of scope, Gen1 is for medium lived memory that hangs around for a bit longer, it too also eventually goes out of scope and Gen2 is long-lived memory that may live for the life of the application. Gen0 memory is constantly reclaimed, Gen1 is reclaimed less frequently than Gen0, and Gen2 is reclaimed even less frequently than Gen1.

The only sure way to understand the memory usage of Ed-Pro was to profile it, below is a screenshot using dotMemory by JetBrains.

As suspected, we found large amounts of Gen0 memory (the blue), so much so, it appeared that Garbage Collection couldn’t keep up. A strategy to compensate for a large amount of memory, caused Garbage Collection to oscillated between increasing memory space (adding more memory for the application’s use) and cleaning it up. During the cleanup cycles, the application is unresponsive.

At first, we were stumped, isn’t the purpose of the GC to keep memory tidy? Two articles were instrumental in our understanding of how Garbage Collection works in .Net: Mark Vincze’s article Troubleshooting high memory usage with ASP.Net Core on Kubernetes and Fundamentals of Garbage Collection by Microsoft. Both are great reads and brought clarity to the memory usage in Ed-Pro. 

Here’s a summary of what we learned, there are two types of Garbage Collection in .Net: Server Garbage Collection and Workstation Garbage Collection.

Server Garbage Collection makes a couple of assumptions: First, there is ample memory available and second, the processors are multi-core and are fast. Both can be true, but we live in a world of virtual machines and Docker where it’s more likely that both assumptions are false.

Server Garbage Collection allows memory to build, at some point, it does one of two things: it either increases the memory space allowing memory to grow or it frees up orphaned memory. When it chooses to free memory, the Garbage Collection starts the process on a high priority thread. The high priority thread is a higher priority than the application; if the machine is fast, the clean up shouldn’t be noticed. However, if it’s not, it’ll cause the application to halt until the clean up is completed.

Workstation Garbage Collection operations differently. It continuously runs reclaiming memory on a thread with the same priority as the application. This means it’s also competing for resources with the application which can cause application slowness. The upside is the application’s memory usage can stay quite low, primarily when it uses large amounts of Gen0.

As a default, if .Net Core detects a server, it runs the Server Garbage Collection type, which was the case with our application. To run the Workstation Garbage Collection type add the following snippet to your project file:

  <PropertyGroup> 
    <ServerGarbageCollection>false</ServerGarbageCollection>
  </PropertyGroup>

We made this configuration change to Ed-Pro, using dotMemory, we profiled Ed-Pro’s memory with Workstation Garbage Collection enabled and loaded the same screens as in the previous test. Here are the results:

The memory usage is significantly decreased. The Gen0 allocations are virtually non-existent. Beyond the differences in the graph, the Server Garbage Collection memory usage topped 1 gig while the Workstation Garbage Collection topped at roughly 200 megs.

Every application is different. Our application used a ton of temporary data and thus uses a ton of Gen0 memory. Your application may leverage longer lived memory such as Gen1 or Gen2 in which Server Garbage Collection makes a whole lot of sense. My advice is to profile your memory under different conditions for an idea of how memory is used and then decide which mode is best for you application.

You Are Not Your Code

It’s not personal.

Your code reflects neither your beliefs, nor your upbringing, nor your character.

Your thoughts and your opinions evolve, new ideas form, and you change. 

The you of today will be different from the you of tomorrow.

Embrace the difference, you and your code are better because it.

The Collection Comparer, Finding the Differences Between Two Collections

Have you had to compare two collections and execute some logic based on whether the item is in the source collection, in the comparing collection or in both? Yeah, me too, I needed to merge data from the UI and the database. I couldn’t find a good solution, so, I wrote a collection comparer.

To illustrate how this works let’s look at an example.

In the source data we have the values 1, 3, 4, 6, and in the
comparing collection we have the values 1, 2, 3, 4, 5.

The source data is missing the 2 and the 5 when compared to the comparing collection, and the comparing collection is missing the 6 when compared to the source collection.

Let’s walk through this merge:

  1. in both (update)
  2. only in the comparing collection (add to source)
  3. in both (update)
  4. in both (update)
  5. only in the comparing collection (add to source)
  6. only in the source collection (remove from source)

Here what the code looks like:

var source = new []{1, 3, 4, 6};
var collection = new[] {1, 2, 3, 4, 5};

source.CompareTo(collection, (s, d) => s == d)
    .OnlyInSourceCollection(s=> {/* do something */})
    .OnlyInComparingCollection(s=>{/* do something */})
    .InBoth(s=> {/*do something*/})
    .Process();

Why not use LINQ?

You can use LINQ, however, LINQ will iterate the collections at least 3 times which doesn’t include operating (adding, updating, and deleting) on the data. Using the CollectionComparer, the data is only iterated twice.

There are faster ways to find the differences such as a binary search, but a binary search only works with integers. The collection comparer supports any type of comparison. The comparison is defined with this code: (s, d) => s == d.

The source code is found on GitHub.

Implementing Request Caching in ASP.Net Core

At some point in an application’s development, usually, fairly early on, you realize the application is slow. After some research, the culprit is, unnecessarily retrieving the same data, and a light goes off, and you think: “I need some caching.”

Caching is an invaluable pattern for eliminating redundant calls to a database or a third party API. Microsoft provides IMemoryCache for time-based caching, however sometimes time-based caching isn’t what you need. In this article, we look at Request Scoped caching and how it can benefit us.

What is Request caching? Request caching is a mechanism to cache data for the life of a web request. In dot-net, we’ve had this ability in some capacity with the HttpContext.Items collection, however, HttpContext is not known for its injectability.

Request Scoped caching has a few benefits: First, it eliminates the concern of stale data. In most scenarios, a request executes in less than a second and which typically isn’t long enough for data to become stale. And secondly, expiration isn’t a concern because the data dies when the request ends.

Out of the box, Asp.Net Core doesn’t have injectable caching. As mentioned earlier, HttpContext.Items is an option, but it’s not an elegant solution.

Luckily for us, ASP.Net Core gives us the tools to create an injectable Request Caching implementation by using the built-in dependency injection (DI) framework.

The built-in DI framework has three lifetimes for dependencies: Singleton, Scoped, and Transient. Singleton is for the life of the application, Scoped is for the life of the request and Transient is a new instance with each request.

I’ve created an interface modeled after the IMemoryCache interface to keep things consistent.

Interface

public interface IRequestCache
{
    /// <summary>
    /// Add the value into request cache. If the key already exists, the value is overwritten.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <typeparam name="TValue"></typeparam>
    void Add<TValue>(string key, TValue value);

    /// <summary>
    /// Remove the key from the request cache
    /// </summary>
    /// <param name="key"></param>
    void Remove(string key);

    /// <summary>
    /// Retrieve the value by key, if the key is not in the cache then the add func is called
    /// adding the value to cache and returning the added value.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="add"></param>
    /// <typeparam name="TValue"></typeparam>
    /// <returns></returns>
    TValue RetrieveOrAdd<TValue>(string key, Func<TValue> add);

    /// <summary>
    /// Retrieves the value by key. When the key does not exist the default value for the type is returned.
    /// </summary>
    /// <param name="key"></param>
    /// <typeparam name="TValue"></typeparam>
    /// <returns></returns>
    TValue Retrieve<TValue>(string key);
}

Implementation

public class RequestCache : IRequestCache
{
    IDictionary<string, object> _cache = new Dictionary<string, object>();

    /// <summary>
    /// Add the value into request cache. If the key already exists, the value is overwritten.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <typeparam name="TValue"></typeparam>
    public void Add<TValue>(string key, TValue value)
    {
        _cache[key] = value;
    }

    /// <summary>
    /// Remove the key from the request cache
    /// </summary>
    /// <param name="key"></param>
    public void Remove(string key)
    {
        if (_cache.ContainsKey(key))
        {
            _cache.Remove(key);
        }
    }

    /// <summary>
    /// Retrieve the value by key, if the key is not in the cache then the add func is called
    /// adding the value to cache and returning the added value.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="add"></param>
    /// <typeparam name="TValue"></typeparam>
    /// <returns></returns>
    public TValue RetrieveOrAdd<TValue>(string key, Func<TValue> add)
    {
        if (_cache.ContainsKey(key))
        {
            return (TValue)_cache[key];
        }

        var value = add();

        _cache[key] = value;

        return value;
    }

    /// <summary>
    /// Retrieves the value by key. When the key does not exist the default value for the type is returned.
    /// </summary>
    /// <param name="key"></param>
    /// <typeparam name="TValue"></typeparam>
    /// <returns></returns>
    public TValue Retrieve<TValue>(string key)
    {
        if (_cache.ContainsKey(key))
        {
            return (TValue)_cache[key];
        }

        return default(TValue);
    }
}

Using ASP.Net Core’s DI framework we’ll wire it up as Scoped.

services.AddScoped<IRequestCache, RequestCache>();

Usage

public class UserService
{
    private readonly IRequestCache _cache;
    private readonly IUserRepository _userRepository;

    public UserService(IRequestCache cache, IUserRepository userRepository)
    {
        _cache = cache;
        _userRepository = userRepository;
    }

    public User RetrieveUserById(int userId)
    {
        var buildCacheKey = UserService.BuildCacheKey(userId);

        return _cache.RetrieveOrAdd(BuildCacheKey, () => { return _userRepository.RetrieveUserBy(userId); });
    }

    public void Delete(int userId)
    {
        var buildCacheKey = UserService.BuildCacheKey(userId);

        _userRepository.Delete(userId);
        _cache.Remove(BuildCacheKey(userId));
    }

    private static string BuildCacheKey(int userId)
    {
        return $"user_{userId}";
    }
}

That’s it! Request Caching is now injectable in any place you need it.

Visit the Git Repository and feel free to take the code for a spin.

Workaround for ‘Template parse errors’ in Angular

This was one of the more frustrating issues with Angular 2/4/+. It’s not an issue with Angular 2/4/+ per se, but with how webpack bundles the supporting HTML files.

This issue happens when you run webpack with the production flag (webpack -p). The compilation completes, but running the site generates a runtime error: Template parse errors

[generic]
Uncaught Error: Template parse errors:
Unexpected closing tag “a”. It may happen when the tag has already been closed by another tag. For more info see https://www.w3.org/TR/html5/syntax.html#closing-elements-that-have-implied-end-tags (“ Home

[/generic]

The suspected HTML:

[html]

[/html]

I couldn’t find anything wrong with this HTML. I ran it through multiple online HTML validators, and the HTML always came back as valid HTML. After a few weeks of beating my head against the wall and getting nowhere, I figured there must be a way around this issue.

What I discovered, via a GitHub thread (sorry I lost the link to it) was it’s the HTML loader trying to minimize the HTML that’s causing the problem. If the HTML minimization is turned off, this issue goes away. To be fair, most folks with this error had invalid HTML. I encourage you to check your HTML before working around this issue, you may be just pushing the issue down the road.

Turning off HTML minimization is easy as updating your HTML-loader in the webpack.config.js.

Before:
[js]
{
test: /.html$/,
loader: ‘html-loader’
},
[/js]

After:
[js]
{
test: /.html$/,
use: [{
loader: ‘html-loader’,
options: {
minimize: false,
removeComments: false,
collapseWhitespace: false,
removeAttributeQuotes: false,
}
}],
},
[/js]

In a Single Page Application, Should I process on the Client or the Server?

One of the selling points of the Single Page Application (SPA) was offloading work traditionally performed on the server onto the client. I feel the SPA has delivered on this promise.

However, it’s not all roses. It’s easy to get overzealous and push too much work to the client. We often forget that we can’t control the client’s environment — the client can be anything from a ten-year-old machine to the latest-and-greatest smartphone with access to varying internet speeds. The only thing we can count on is the user viewing our site in a browser.

Processing

In my experience, intensive processing and data needing consistency between the server and the client, such as date conversions or data requiring precise calculations, such as money, are prime candidates for server-side rendering.

Paging

A common task performed on the client is paging. With small datasets this works great; however, small datasets never stay small. As data grows, the application slows and eventually becomes unresponsive. Unfortunately, you don’t know it’s happening because it’s on the client and even worse it doesn’t occur on all clients making it hard to troubleshoot.

Moving paging from the client to the server will alleviate paging related performance issues on the client. You might be thinking, “but now I am making a ton of API calls. Making so many API calls doesn’t seem optimal.” True, it does seem that way, but you’ll be surprised how fast your data returns from the server. And the best part? You control the server and can increase capacity as needed.

At the end of the day, you want to provide the user a rich responsive experience and sometimes that’s letting the server do the heavy lifting.

Summary

Summarizing, when possible we want to offload work to the client, but by doing so, we can quickly overburden the client. Keeping arduous tasks such as paging and intensive processing on the server can protect us from overwhelming the client.

Examining the Case for Switch Statements

For nearly 50 years, the switch statement (also known as the case statement) has been an integral part of programming. In recent years, however, some are claiming that the switch statement has outlived its usefulness. Others go even further by labeling the switch statement as a code-smell.

In 1952, Stephen Kleene conceived the switch statement in his paper, Introduction to Metamathematics. The first notable implementation was in ALGOL 58 in 1958. Later, the switch statement was included in the indelible C programming language, which, as we know, has influenced most modern programming languages.

Fast forward to the present day and virtually every language has a switch statement. However, a few languages have omitted the switch statement. The most notable being Smalltalk.

This piqued my curiosity, why was the switch statement excluded from Smalltalk?

Andy Bower, one of the creators/proponents behind Dolphin Smalltalk shared his thoughts on why Smalltalk excluded the switch statement:

When I first came to Smalltalk from C++, I couldn’t understand how a supposedly fully fledged language didn’t support a switch/case construct. After all when I first moved up to “structured programming” from BASIC I thought that switch was one of the best things since sliced bread. However, because Smalltalk didn’t support a switch I had to look for and understand, how to overcome this deficiency. The correct answer is, of course, to use polymorphism and to make the objects themselves dispatch to the correct piece of code. Then I realized that it wasn’t a “deficiency” at all, but Smalltalk was forcing me into much finer grained OOP design than I had got(ten) used to in C++. If there had been a switch statement available it would have taken me a lot longer to learn this or, worse, I might still be programming C++/Java pseudo-object style in Smalltalk.
I would contend that in normal OOP there is no real need for a switch statement. Sometimes, when interfacing to a non-OOP world (like receiving and dispatching WM_XXXX Windows messages that are not objects but just integers), then a switch statement would be useful. In these situations, there are alternatives (like dispatching from a Dictionary) and the number of times they crop up doesn’t warrant the inclusion of additional syntax.

Was Andy right? Are we better off without the switch statement? Would other languages also benefit from excluding the switch statement?

To shed some light on this question, I’ve put together a comparison between a switch statement, a dictionary, and polymorphism. Let’s call it a smackdown. May the best implementation win!

Each implementation has a method taking one parameter, an integer, and returns a string. We’ll use cyclomatic complexity and maintainability index to examine each implementation. We’ll then take a holistic view of all three implementations.

The code.

Switch Statement

Maintainability Index72
Cyclomatic Complexity6
    public class SwitchWithFourCases
    {
        public string SwitchStatment(int color)
        {
            var colorString = "Red";

            switch (color)
            {
                case 1:
                    colorString = "Green";
                    break;

                case 2:
                    colorString = "Blue";
                    break;

                case 3:
                    colorString = "Violet";
                    break;

                case 4:
                    colorString = "Orange";
                    break;

            }

            return colorString;
        }
    }

Dictionary

Maintainability Index73
Cyclomatic Complexity3
public class DictionaryWithFourItems
{ 
    public string Dictionary(int color)
    {
        var colorString = "Red";
        var colors = new Dictionary<int, string> {{1, "Green"}, {2, "Blue"}, {3, "Violet"}, {4, "Orange"}};
        var containsKey = colors.ContainsKey(color);
        if (containsKey)
        {
            colorString = colors[color];
        }

        return colorString;
    }
}

Polymorphism

Total Maintainability Index94
Total Cyclomatic Complexity15

Interface

Maintainability Index100
Cyclomatic Complexity1
public interface IColor
{
    string ColorName { get; }
}

Factory

Maintainability Index76
Cyclomatic Complexity4
public class ColorFactory
{
    public string GetColor(int color)
    {
        IColor defaultColor = new RedColor();
        var colors = GetColors();
        var containsKey = colors.ContainsKey(color);
        if (containsKey)
        {
            var c = colors[color];
            return c.ColorName;
        }

        return defaultColor.ColorName;
    }

    private static IDictionary<int, IColor> GetColors()
    {
        return new Dictionary<int, IColor>
        {
            {1, new GreenColor()}, 
            {2, new BlueColor()}, 
            {3, new VioletColor()}, 
            {4, new OrangeColor()}, 
            {5, new MagentaColor()}
        };
    }
}

Implementation

Maintainability Index97
Cyclomatic Complexity2
public class BlueColor : IColor
{
    public string ColorName => "Blue";
}

public class RedColor : IColor
{
    public string ColorName => "Red";
}

public class GreenColor : IColor
{
    public string ColorName => "Green";
}

public class MagentaColor : IColor
{
    public string ColorName => "Magenta";
}

public class VioletColor : IColor
{
    public string ColorName => "Violet";
}

The Results

Before I dive into the results, let’s define Cyclomatic Complexity and Maintainability Index:

  • Cyclomatic Complexity is the measure of logic branching. The lower the number, the better.  
  • Maintainability Index measures maintainability of the code. It’s on a scale between 0 and 100. The higher the number, the better.
 Cyclomatic ComplexityMaintainability Index
Switch Statement672
Dictionary373
Polymorphism1594

We will examine cyclomatic complexity first.

The results for cyclomatic complexity are straightforward. The dictionary implementation is the simplest. Does this mean it’s the best solution? No, as we’ll see when we evaluate the maintainability index.

Most would think as I did, the implementation with the lowest cyclomatic complexity is the most maintainable — how could it be any other way?

In our scenario, the implementation with the lowest cyclomatic complexity isn’t the most maintainable. In fact in our scenario, it’s the opposite. The most complex implementation is the most maintainable! Mind blown!

If you recall, the higher the maintainability index score, the better. Cutting to the chase, polymorphism has the best maintainability index score — but it also has the highest cyclomatic complexity. What gives? That doesn’t seem right.

Why is the most complex implementation the most maintainable? To answer this, we must understand the maintainability index.

The maintainability index consists of 4 metrics: cyclomatic complexity, lines of code, the number of comments and the Halstead volume. The first three metrics are relatively well known, but the last one, the Halstead Volume, is relatively unknown. Like, cyclomatic complexity, the Halstead Volume attempts objectively measure code complexity.

In simple terms, Halstead Volume measures the number of moving parts (variables, system calls, arithmetic, coding constructs, etc.) in code. The higher the number of moving parts the more complexity. The lower the number of moving parts, the lower the complexity. This explains why the polymorphic implementation scores high on the maintainability index; the classes have little to no moving parts. Another way to look at the Halstead Volume is it measures “moving parts” density.

What is software, if it’s not to change? To reflect the real world, we are introducing change. I’ve added a new color to each implementation.

Below are the revised results.

 Cycolmatic ComplexityMaintainability Index
Switch Statement770
Dictionary373
Polymorphism1795

The switch statement and the polymorphic approaches both increased in cyclomatic complexity by one unit, but interestingly, the dictionary didn’t increase. At first I was puzzled by this, but then I realized the dictionary considers the colors as data and the other two implementations treat the colors as code.I’ll get down to the brass tacks.

Turing our attention to the maintainability index, only one, the switch statement, decreased in maintainability. Polymorphism’s maintainability score improved and yet the complexity also increases (we’d prefer it to decrease). As I mentioned above, this is counter-intuitive.

Our comparison shows that dictionaries can, from a complexity standpoint, scale infinitely. The polymorphic approach is by far the most maintainable and seems to increase in maintainability as more scenarios are added. The switch statement increases in complexity and decreases in maintainability when the new scenario was added. Even before we added the new scenario, it had the worst cyclomatic complexity and maintainability index measures.

Jem Finch from Google shared his thoughts on the switch statements shortcomings:

1. Polymorphic method implementations are lexically isolated from one another. Variables can be added, removed, modified, and so on without any risk of impacting unrelated code in another branch of the switch statement.  

2. Polymorphic method implementations are guaranteed to return to the correct place, assuming they terminate. Switch statements in a fall through language like C/C++/Java require an error-prone “break” statement to ensure that they return to the statement after the switch rather than the next case block.


3. The existence of a polymorphic method implementation can be enforced by the compiler, which will refuse to compile the program if a polymorphic method implementation is missing. Switch statements provide no such exhaustiveness checking.


4. Polymorphic method dispatching is extensible without access to (or recompiling of) other source code. Adding another case to a switch statement requires access to the original dispatching code, not only in one place but in every place the relevant enum is being switched on.


5. … you can test polymorphic methods independent of the switching apparatus. Most functions that switch like the example the author gave will contain other code that cannot then be separately tested; virtual method calls, on the other hand, can.

6. Polymorphic method calls guarantee constant time dispatch. No sufficiently smart compiler is necessary to convert what is naturally a linear time construct (the switch statement with fall through) into a constant time construct.

Unfortunately, or fortunately, depending on your camp, most languages have a switch statement, and they aren’t going anywhere anytime soon. With this in mind, it’s good to know what’s happening under the hood when compiling switch statements.

There are three switch statement optimizations that can occur:

  1. If-elseif statements – When a switch statement has a small number of cases or sparse cases (non-incremental values, such as 10, 250, 1000) it’s converted to an if-elseif statement.  
  2. Jump Table – In larger sets of adjacent cases (1, 2, 3, 4, 5) the compiler converts the switch statement to a jump table. A Jump Table is essentially a Hashtable with a pointer (think goto statement) to the function in memory.
  3. Binary Search – For large sets of sparse cases the compiler can implement a binary search to identify the case quickly, similar to how an index works in a database. In extraordinary cases where cases are a large number of sparse and adjacent cases, the compiler will use a combination of the three optimizations.

Summary

In an object oriented world the switch statement, conceived in 1952, is a mainstay of the software engineer. A notable exception is Smalltalk where the designers opted to exclude the switch statement.

When compared to alternative equivalent implementations, the dictionary, and polymorphism, the switch statement did not fare as well.

The switch statement is here to stay, but as our comparison has shown there are better alternatives to the switch statement.

The implementations are available on Github.