Using Local Functions to Replace Comments

One idea I’ve been thinking about recently is the replacement of comments with local function calls.

Now this idea doesn’t mean that it’s ok to have massive functions that have no functional cohesion but instead in some circumstances it may improve readability.

In C#, local functions were introduced in C# 7.0. They essentially allow you to write a function inside of a method, property get/set, etc.

As an example take the following code:

public static void ProcessSensorData(string data)
{
    // HACK: occasionally a sensor hardware glitch adds extraneous $ signs
    data = data.Replace("$", "");

    string upperCaseName = data.ToUpperInvariant();
    Save(upperCaseName);
}

private static void Save(string data)
{
    // Save somewhere etc.
    Console.WriteLine(data);
}

In the preceding code there is a hack to fix broken sensors that keep adding extra $ signs.

This could be written using a local function as follows:

public static void ProcessSensorData(string data)
{
    FixExtraneousSensorData();
    string upperCaseName = data.ToUpperInvariant();
    Save(upperCaseName);

    void FixExtraneousSensorData()
    {
        data = data.Replace("$", "");
    }
}

Notice in this version, there is a local function FixExtraneousSensorData that strips out the $ signs. This function is named to try and convey the comment that we had before: “occasionally a sensor hardware glitch adds extraneous $ signs”. Also notice the local function has direct access to the variables of the method in which they’re declared, in this case data.

There are other options here of course such as creating a normal non-local class-level function and passing data to it, or perhaps creating and injecting a data sanitation class as a dependency.

Replacing Arrange, Act, Assert Comments in Unit Tests

As another example consider the following test code:

[Fact]
public void HaveSanitizedFullName()
{
    // Arrange
    var p = new Person
    {
        FirstName = "    Sarah ",
        LastName = "  Smith   "
    };

    // Act
    var fullName = p.CreateFullSanitizedName();

    // Assert
    Assert.Equal("Sarah Smith", fullName);
}

Notice the comments separating the logical test phases.

Again these comments could be replaced with local functions as follows:

[Fact]
public void HaveSanitizedFullName_LocalFunctions()
{
    Person p;
    string fullName;

    Arrange();
    Act();
    AssertResults();
    
    void Arrange()
    {
        p = new Person
        {
            FirstName = "    Sarah ",
            LastName = "  Smith   "
        };
    }

    void Act()
    {
        fullName = p.CreateFullSanitizedName();
    }

    void AssertResults()
    {
        Assert.Equal("Sarah Smith", fullName);
    }
}

This version is a lot longer and although we’ve rid ourselves of the comments the test body is a lot longer, with more lines of code, and I think is probably not as readable. Obviously the test is very simple, if you’ve got a lot of test arrange code for example you could just abstract the arrange phase perhaps.

Another option in the test code to remove the comments is to make use of the most basic unit of design – white space. So for example we could remove comments and still give a clue to the various phases as follows:

[Fact]
public void HaveSanitizedFullName_WhiteSpace()
{
    var p = new Person
    {
        FirstName = "    Sarah ",
        LastName = "  Smith   "
    };


    
    var fullName = p.CreateFullSanitizedName();

    

    Assert.Equal("Sarah Smith", fullName);
}

I think the tactical use of local functions like in the first example to replace the hack comment  may be more useful than replacing the (arguably extraneous) arrange act assert comments in tests.

Let me know in the comments if you think this is a good idea, a terrible idea, or something that you might use now and again.

SHARE:

Comments (17) -

  • rosdi

    1/20/2020 6:35:57 AM | Reply

    I like the second sample, makes the code more readable. But your first example (ProcessSensorData) does not add value to me, a simple comment will do. Comments are not evil, but excessive comments could indicate some kind of problem with the code though...

    • Jason Roberts

      1/22/2020 1:56:43 AM | Reply

      Hi Rosdi - yes it was a simple example, if the logic was more complex then perhaps it would have made a better example Smile

  • Duncan

    1/20/2020 5:21:54 PM | Reply

    I believe your urge, to replace a commented n lines of code with a well-named function, is "clean code"-approved. I use regions to achieve the same result, because I can collapse them (local functions can be collapsed too). I'll mull this over more....

    • Jason Roberts

      1/22/2020 1:57:23 AM | Reply

      Hope it helps Duncan Smile

  • Stefano

    1/20/2020 10:31:33 PM | Reply

    I use frequently local functions to replace comments and, furthermore, to replace #region blocks

  • Giorgi Dvalishvili

    1/21/2020 7:36:32 AM | Reply

    Local Functions is a good idea I used them and agree it`s better than comments. I think tests do not need Arrange, Act, Assert Comments

    • Jason Roberts

      1/22/2020 1:58:14 AM | Reply

      Hi Giorgi - I am also not a fan of every test having the AAA comments

  • Kristian Hellang

    1/21/2020 8:08:17 AM | Reply

    You just introduced a closure, which would end up allocating on every single call. Probably not what you want if this is in a hot path Smile Making the nested method static and passing the data to it would probably be better.

    • Jason Roberts

      1/22/2020 1:58:48 AM | Reply

      Thanks Kristian Smile

  • Fred Bloggs

    1/21/2020 8:31:39 PM | Reply

    Don't use local functions. Pascal has always had them, and they were a nightmare then (Delphi programmers learned to avoid them, for example). I once saw local functions nested 10 levels deep. Totally unreadable.

    Also, if you need to use arrange, act and assert comments in your unit tests, then your unit tests are not clear enough. Your last example is ok. We should all know the rule, we don't have to keep stating it every time. Whenever I see those comments I immediately think I'm looking at code written by a novice unit tester.

    • Jason Roberts

      1/22/2020 2:00:37 AM | Reply

      Hi Fred, like anything they could be used to harm rather than help readability. I also personally do not like AAA comments in each and every test.

  • Paul A

    1/21/2020 10:07:43 PM | Reply

    Replacing comments with method calls is something I do very frequently. It's usually worth it since I end up having to use the same code somewhere else. Local functions look weird to me, I don't know if I would adopt those too quickly. I'm also picky when it comes to white space, having all those empty lines ... ugh.

    • Jason Roberts

      1/22/2020 2:01:54 AM | Reply

      Hi Paul, local functions could potentially be used if you don't want to "pollute" the class with private functions.

      • Bryan M

        1/27/2020 5:08:49 AM | Reply

        I agree, if there are bits of functionality that you know do not even need to be used by other class methods, then a local function is preferred over adding class-level private functions. I like to use the local function when the outer method takes two or three pieces of data of same type and needs to do a function on each one.

  • Pablo Rausch

    2/3/2020 3:37:09 PM | Reply

    In the first example, I think the comment is ok, is justifying that piece of code someone can delete if he thinks it doesn't add value. The option of extracting that to a function is ok too, I wouldn't use a local function for that (it would be impossible to test that part of the code alone)
    The second example I think comments are not necessary at all. Arrange-Act-Assert structure is expected, so a simple empty line will do the job for a developer used to work with tests.

    • Jason Roberts

      2/3/2020 11:50:10 PM | Reply

      Thanks for your input Pablo Smile

  • Mouad Cherkaoui

    2/6/2020 8:12:20 PM | Reply

    Thanks for the great article!
    I guess that it should also simplify the refactoring process since  you can promote some local function to be globally accessible to the rest of the code  whether through new helpers or extension methods

Pingbacks and trackbacks (1)+

Add comment

Loading