Often, where a method has many non-functional concerns, it’s easy for the core intent to be lost in noise. Take the following, extremely contrived, example:
public static void NoisyMain(string[] args)
{
Console.WriteLine("Logging started...");
try
{
using (var transaction = new Transaction())
{
transaction.Begin();
try
{
Console.WriteLine("Body");
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
throw;
}
}
}
catch (Exception)
{
Console.WriteLine("There was a problem...");
throw;
}
Console.WriteLine("Logging completed.");
}
It’s only the highlighted line that has any application function. Yet it’s swamped by code. While the code that surrounds it is essential, it arguably reduces the maintainability of this method as the logging and transaction management code must be negotiated before arriving at the method’s essence.
A technique I’ve started to use to improve the signal-to-noise ratio is in-line decoration. It takes the concept of the Decorator design pattern and implements it using .Net delegates. This style of implementation allows a fluent interface to be used that supports chaining and, I think, improves readability:
public static void EssentialMain(string[] args)
{
Decorate.With.Logging(() =>
Decorate.With.CommittedTransaction(
tx =>
{
Console.WriteLine("Body");
}));
}
The root of the implementation is the Decorate class:
public class Decorate
{
public static Decorate With
{
get
{
return new Decorate();
}
}
}
It’s designed to provide a foundation for extension methods that will support the various decorating aspects:
public static class LoggingDecorator
{
public static void Logging(
this Decorate baseDecorator,
Action thisAction)
{
Console.WriteLine("Logging started...");
try
{
thisAction();
}
catch (Exception)
{
Console.WriteLine("There was a problem...");
throw;
}
Console.WriteLine("Logging completed.");
}
}
public static class TransactionDecorator
{
public static void CommittedTransaction(
this Decorate baseDecorator,
Action<object> thisAction)
{
Console.WriteLine("Transaction started...");
var currentTransaction = new object();
try
{
thisAction(currentTransaction);
Console.WriteLine("Transaction committed.");
}
catch (Exception)
{
Console.WriteLine(
"Something went wrong. Transaction rolled back.");
throw;
}
}
}
Each decorating aspect can be represented in its own class. This allows the Decorate class to become a point of extensibility where future aspects can be added without modifying any existing code. Once the extension methods are written and the appropriate namespace is imported, the new decorating methods are available.
It's worth noting that while this technique can certainly reduce the noise in methods like these, this technique has its downsides. It can make debugging more difficult as the pathway through the code is not as clear. It can also be just as vulnerable to the problem it tries to solve, in that if you get carried away and start to chain many decorators, the essence of the code's intent will be just as hard to decipher as in the original version.