Automate the patterns

hurlbert
5 min readAug 6, 2020

C# is not a functional language. I love C#. It can be highly productive. But there’s a dirty secret, existing code often sucks and every day others create more existing code.

Functional programming can help. It’s easy for it to be too complicated and there’s definitely a learning curve. I’ve noticed that developers that learn functional programming first and then are exposed to OOP/Procedural tend to be better at abstraction and logic, better at OOP, procedural, and functional.

Can we introduce just enough functional to be faster with higher quality but not so much that we can’t use existing frameworks? I believe we can and the results are impressive. Most of the code is infrastructure code and it can have a huge impact on the less experienced team members.

Here is a bit of code:

private void AddColumn<T>( DataFrameColumn pColumn )
{
Columns.Add( pColumn );
}

Obviously this is naive and you can’t actually code like this. How many questions do you have about a one line function? Can pColumn be null? Can pColumn be added to Columns more than once? Real code rarely looks like this. This prototype code gets refactored to look like this:

private void AddColumn<T>( DataFrameColumn pColumn )
{
if( pColumn != null )
{
if( !Columns.Exists( pColumn ) )
{
try
{
Columns.Add( pColumn );
}
catch( Exception ex )
{
// Now what?
}
}
}
}

We’re still ignoring exception handling and probably a few other things, like what happens if Columns is null. Creating quality code is hard.

With the help of some extension methods to NotNull we can ensure that pColumn in not null, as we Filter() and Map() we can convert the NotNull to a Maybe<T and the resultant code is back to one line but does everything that the refactored code above does, including exception handling.

private void AddColumn<T>( NotNull<T> pColumn ) where T : DataFrameColumn
{
pColumn
.Filter( col => !Columns.Exists( col ) )
.Map( col => Columns.Add( col ) );
}

Higher quality, less code, faster to create, and does a better job of communicating it’s intent, isn’t that what writing code is all about?

More importantly, look at the code again. Here’s the pseudo code version of the original:

if( PARAMETER(S) PASS SOME NULL / RANGE CHECKS )
{
if( CHECK-OUR-METHOD'S-PRECONDITIONS )
{
try TO CATCH ANY EXPLOSIONS
{
DO THE WORK
}
catch( Exception ex )
{
FIGURE OUT A REASONABLE EXCEPTION STRATEGY
}
}
}

If you check your code base, I’m pretty sure you’ll find many methods that fit this pattern. Everywhere this pattern exists should probably be one-liners. One liners is they can be in-lined. Many lines is every difficult to reuse.

More importantly, the most common bugs I find are when one of these sections are left out. No null check: bug. No range check: bug. Skip the preconditions: bug. Unexpected exception: bug. And all this ignores whether the missing ELSE clauses are on purpose or an oversight.

C# is moving in the right direction. C# 8 introduced null parameters and settings to control when and where nulls can be passed. It will take years if not decades for these changes to be understood and implemented. And the checking of the parameters for null is only a single example of what I’ve shown here. What about exception handling, range checking, pre-conditions, and more?

One of the nice things about using the NotNull<T> is that it’s backward compatible in a way that the null-parameters in C# 8+ are not. NotNull uses implicit type conversion to wrap parameters in a NotNull. It’s a nifty trick and highly efficient. And exceptions happen at the caller, not within the called code. Debugging is much easier.

I’ve worked on code bases where the pattern above was followed in “most methods” and yet no one has ever noticed because local variations obscure the pattern. Every developer is convinced that they’ve independently solved this pattern, never realizing that they’re all doing it. How many things in life are this way?

The objections to the monadic style are substantial, but the automation of the above pattern is also substantial. Here’s what the above pattern looks like when it’s automated:

private void MyMethod<T>( NotNull<T> pMyParameter )
{
pMyParameter
.Filter( parameter => LogicalCheck(parameter) )
.OnError( ex => Log(ex) )
.Map( parameter => …do-something-with-parameter… );
}

Is this version better? I’m not sure that’s for me to say. But from an intent point of view I feel like it’s more explicit. I go back and forth about what’s better and it’s a good struggle. On the one hand, the raw logic requires only basic programming skills to understand. On the other hand, it’s rife with errors and other problems. Have you noticed yet that there are no ELSE clauses on the two IF statements? Wait? What? A bug based on the failure of the IF statements is very difficult to find because of this.

To understand the automated version, on the other hand, you should be familiar with what .Filter() does, what .OnError() does and what .Map() does. That’s a heavy lift compared to just understanding IF/ELSE and TRY/CATCH.

It is worth noting that the monadic version is 1 line of code whereas the raw-code version is 5 lines of code. From a maintenance perspective it’s easier to maintain one line than five lines, even when the one line is more complex. The five line version requires you to “see” exactly the same pattern in your minds eye as the monadic version. The monadic version, on the other hand, requires that you understand the pattern. In a very real sense ignorance is bliss. Any jackass can accidentally code (discover?) the raw-code version. Only a trained craftsman, familiar with (in this case) the NotNull<T> and Maybe<T>, will understand the monadic version. That is the primary objection (as I understand it) to this style. Detractors say, “screw that complicated stuff just use code review and get the coders to do the raw code correctly.” I think that’s a solid criticism and if that were the end of it, I’d agree.

But, as my friend Jeff correctly points out, our job is to contain complexity. I’ve typed the above example to illustrate these concepts in the simplest way I could think of, but in reality this is not how you and your colleagues are actually going to code these patterns. In the real world there are many more considerations.

The NotNull and Maybe objects contain a lot of complexity. There is a lot more nuance than can be spelled out here. And by automating these you contain that complexity. You are able to reuse that logic every time the pattern above appears. Consequently there’s a chance your code quality will improve. I can do everything an object does with procedural code, but it’s a bear. Just use class and let the language construct help me with those complexities. (I may hate oop, but I’m also a master oop coder and use oop every day, all day)

Personally, I’d rather do a code review to ensure that my automation got used correctly than a code review to verify that the raw-code was correctly repeated 100 times. How about you?

--

--