Beware of too concise code

Introduction

As developers we often strive to build concise code in order to avoid useless ceremony that would distract from its purpose.

We do that by leveraging the syntactic sugar offered by the languages to reduce code verbosity and increase its expressiveness but it can become a trap and lead to code that does not do what is expected.

In this article, I’ll illustrate this point with a powerful C# feature: async methods.

An async parsing method

Here is an async method that parses integers:

static async Task<int> ParseInt(string s)
{
    Console.WriteLine($"Start parsing '{s}'.");
    await Task.Delay(1);
    Console.WriteLine($"End parsing '{s}'.");
    return int.Parse(s);
}

As it is a “pure” function, calls are independent and can be executed in parallel.

A (too) concise code

And here is a naive usage of it:

static async Task TestParallelismKO()
{
    int sum = await ParseInt("1") + await ParseInt("2") + await ParseInt("3");

    Console.WriteLine($"Sum is {sum}");
}

The output is disappointing:

Start parsing '1'.
End parsing '1'.
Start parsing '2'.
End parsing '2'.
Start parsing '3'.
End parsing '3'.
Sum is 6

The result is correct but the calls are sequential!

More verbosity to understand

Here is a longer version that makes the root cause clear:

static async Task TestParallelismKO_Verbose()
{
    var i1 = await ParseInt("1");
    var i2 = await ParseInt("2");
    var i3 = await ParseInt("3");

    int sum = i1 + i2 + i3;

    Console.WriteLine($"Sum is {sum}");
}

So we are waiting for each parsing operation to be completed before jumping to the next.

The fix

Instead, we should:
1. First, run all the parsing tasks,
2. Then, wait for them to complete (whatever the order)

Here is an implementation of this:

static async Task TestParallelismOK()
{
    var t1 = ParseInt("1");
    var t2 = ParseInt("2");
    var t3 = ParseInt("3");

    int sum = await t1 + await t2 + await t3;

    Console.WriteLine($"Sum is {sum}");
}

And here is the output:

Start parsing '1'.
Start parsing '2'.
Start parsing '3'.
End parsing '3'.
End parsing '1'.
End parsing '2'.
Sum is 6

The calls are executed in parallel as expected.

Conclusion

Using syntactic sugar is great for readability and productivity but we must understand what is happening under the hood at the risk of writing incorrect code whose misbehaviour might be spotted later or even never.

4 thoughts on “Beware of too concise code

    • Thanks for your feedback.
      You are right, code refactoring is playing a major role here too, syntactic sugar is more for the await operator, but I consider the fact we can combine the awaits freely, like in arithmetic operations, as more syntactic sugar. 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

Prove me you\'re human :) *