Introduction
Long ago, in a data center far away, there was a programmer who was tired of writing the same code over and over and over. He sipped his coffee, walked to the printer room to get the 400 pages of his source code, and then went back to his deck of punch cards and had an epiphany: What if I only write a single punch card to be reused from my other punch cards? I could save 40 punch cards (and about 50 pages of source code)!
The Helper Method was born.
OK, so maybe thats just a bit of a dramatization of what happened, but who knows? In an infinite universe Star Wars is a documentary somewhere. The point is that boilerplate code has been around a long time, and it has been a problem for just as long. Today we have Resharper and IDEs that magically tell you how to write better code. Instead of you figuring out the best way to write that block of code, you tell the IDE the gist of what you want and it will offer to reformat it for you.
But what about when the magic-black-box cant see the patterns in your boilerplate and wont help you. Do you get out your moldy box of reusable punch cards? No! You download GPS.SimpleHelpers
from nuget.org and make your life a lot easier.
Background
GPS.SimpleHelpers
is a collection of classes that speed you through your development of some truly gnarly use cases that theres just no good way to handle until lambdas came along and changed the world (for the better) forever.
Simple Helpers currently has three problem domains covered: code timing, exception handling, and data marshalling.
Using the Code
First, you can get the full source code from Github. If you dont care about the full source code and just want to start using GPS.SimpleHelpers
, you can retrieve it from Nuget.org with your favorite package manager (I like paket) by installing package GPS.SimpleHelpers.
Stopwatch
First, lets talk about some of the most repetitive boilerplate code in your projects. Metrics gathering with the Stopwatch
class. Weve all written this code before:
var sw = new Stopwatch();
sw.Start();
System.Diagnostics.Debug.WriteLine(sw.Elapsed);
sw.Stop();
Thats four lines of code too much! What if we could do all of that in one line of code?
StopwatchHelpers.TimeAction(() => ,
e => Debug.WriteLine);
Now weve encapsulated the pattern and we dont have to keep writing the same boilerplate over and over.
Lets look at how we did that.
public static void TimeAction(Action action, Action<long> onFinish)
{
var sw = new System.Diagnostics.Stopwatch();
sw.Start();
action();
onFinish(sw.ElapsedMilliseconds);
sw.Stop();
}
Its really the same code, we just take two Actions
as input to be executed. One great thing about this technique is that you can encapsulate your logic for handling the elapsed time with something like this.
public void LogTimer(long elapsed)
{
if(elapsed >= 1000) _log.DEBUG($RED ALERT! {elapsed} ms passed!);
if(elapsed >= 500) _log.INFO(${elapsed} ms passed.);
}
StopwatchHelpers.TimeAction(MyLongAction, LogTimer);
Now you can inject complex handling logic into your timings and not pollute your methods with non-related logic.
TimeAction
has the following overloads. They all work the same way:
public static TReturn TimeAction<TData, TReturn>(
TData value, Func<TData, TReturn> func, Action<long> onFinish)
public static void TimeAction<TData>(
TData value, Action<TData> action, Action<long> onFinish)
public static void TimeAction(Action action, Action<long> onFinish)
public static long TimeAction(Action action)
public static long TimeAction<TData>(TData value, Action<TData> action)
Try-Catch-Finally
Another common boilerplate activity is try-catch-finally
blocks.
var dbContext = new DbContext();
var exceptions = new ConcurrentQueue<Exception>();
try
{
dbContext.Table.ForEach(rec =>
{
try
{
SomeActionsWithDbContext(dbContext);
}
catch (Exception ex)
{
ex.LogSomewhere(your message);
enqueue(exceptions, ex);
}
}
}
finally
{
someCleanupLogic(exceptions);
dbContext.Dispose();
}
ProcessExceptions(exceptions);
Thats a lot of code! Lets try that again with SafeCallHelpers
.
var dbContext = new DbContext();
var exceptions = new ConcurrentQueue();
SafeCallHelpers.TryCall(() =>
{ enqueue(exceptions, SafeCallHelpers.TryCall(() =>
{ someActionsWithDbContext(dbContext); });
}
,() =>
{
someCleanupLogic(exceptions);
dbContext.Dispose();
});
ProcessExceptions(exceptions);
Now, you can remove the ugly boilerplate templates. Using this pattern has a couple of advantages:
- 1. Forces you to implement consistent
Exception
handling. - 2. Forces you to implement consistent
finally
blocks. Its very easy to forget to utilize the finally block to dispose of your IDisposable
objects.
This helper may not be your style. Thats ok, unlike the StopwatchHelpers
, this is here as a personal preference and dislike of deeply nested try-catch-finally
blocks, which I find hard to read, especially as the nesting gets deeper.
Data Marshalling
Concurrent processing causes one to start to understand the value of coding for asynchronicity (yup, I think I just made up a word). To that end, we have constructs like the ConcurrentDictionary
which uses the AddOrUpdate
and GetOrCreate
methods. GetOrCreate
is really interesting because it forces a return value to come back from the ConcurrentDictionary
, so you never have to code for a null
return.
This is very powerful from a semantic aspect. Removing null
reference checks when the result is guaranteed to be an instance of the requested object allows for much cleaner code. Microsoft helped a lot with the null
propagation operator, but you still must account for nulls
from the propagation.
Take this code for example.
var dictionary = new ConcurrentDictionary<string, Record>();
using(var dbContext = new DbContext())
{
Parallel.ForEach(dbContext.Table.Where(rec => rec.Field == someKey), rec =>
{
dictionary.AddOrUpdate(someKey, rec);
}
}
var value = dictionary.GetOrAdd(someSpecificKey,
() => { return new Record { RequiredField = Unset }; });
Microsofts parallel processing additions with .Net 4.0 make thread-safe code a breeze. But what if we want to do this same thing with sequential code? You could still use the ConcurrentDictionary
, but it has a lot of overhead compared to the standard Dictionary
object.
var dictionary = new Dictionary<string, Record>();
using(var dbContext = new DbContext())
{
foreach(var rec in dbContext.Table.Where(rec => rec.Field == someKey))
{
dictionary.Add(someKey, rec);
}
}
var value = dictionary[someKey];
if(value == null) value = new Record { RequiredField = Unset };
Thats reasonable, right? Except its really, really, easy to forget the null check and then you wind up with the infamous null
reference exceptions. Lets look at it with the Marshaller in place.
var value = SafeMarshalling.GetOrBuild(
() => { return dictionary[someKey]; },
() => { return new Record { RequiredField = Unset }; });
Yay! No null reference checking! You always get a value.
Putting it all together
Ok, so lets make one big ball of spaghetti to prove this all works together. 😊
var exceptions = new List<Exception>();
var dictionary = new Dictionary<string, string>();
AddException(exceptions, (SafeCallHelpers.TryCall(() =>
{
StopwatchHelper(() =>
{
dictionary.Add(someKey, SafeMarshalling.GetOrBuild(
() => SomeExpensiveGetter,
() => SomeExpensiveBuilder));
}, elapsed => LogElapsedTime);
}, CleanupMethod));
From these nine lines of code weve done the following:
- Used an expensive getter or expensive builder to create an
object
. - Add that object to a
Dictionary
. - Caught any
Exceptions
. - Disposed any
IDisposable
objects.
Lets look at the same code written traditionally.
var exceptions = new List<Exception>();
var dictionary = new Dictionary<string, string>();
try
{
var sw = new Stopwatch();
sw.Start();
var someValue = SomeExpensiveGetter();
if(someValue == null) someValue = SomeExpensiveBuilder();
dictionary.Add(someKey, someValue);
LogElapsedTime(sw.Elapsed);
sw.Stop();
}
catch(Exception ex)
{
AddException(exceptions, ex);
}
finally
{
CleanupMethod();
}
Thats twice as much code! And I personally dont find it any cleaner.
Conclusion
Its easy to dismiss helper methods as just crutches that real programmers dont need. I dont know if Im a real programmer, but Ive been writing code since I was 13, and Ive had a pretty nice career applying repeatable code patterns to complex problems. Dont dismiss the power of the helper method until youve tried it for yourself!
Interesting Facts
I wrote my first helper method (subroutine) in CBM BASIC 2.0 on a Commodore VIC 20 in 1984.
Revisions
Version 1.0.0