Register | Login

Stacking Code

public interface IBlog { string Dump(Stream consciousness); }

Readable Code with Extension Methods

Tuesday, 18 September, 2012 @ 7:32 AM < Adam Boddington
Tags: C#

UPDATE: @SimonCropp just tweeted a link to his FluentDateTime library which handles some of the extensions below. It's even available via NuGet. Nice one.

One of the ways I like to keep my code readable and expressive is around anything to do with the DateTime and TimeSpan classes. Using DateTime or TimeSpan directly can be a little hard to comprehend when reading later. Here's an example. Say we have an NHibernate Linq query retrieving all widgets that are less than a day old.

var widgets = session.Query<Widget>()
    .Where(w => w.CreatedOn > DateTime.Now.AddDays(-1));

Forget the Linq in this example, the bit I find hard to read is the date we're comparing with.

w.CreatedOn > DateTime.Now.AddDays(-1)

The code isn't immediately communicating its intent. It's the sort of code that really begs for a quick comment.

var widgets = session.Query<Widget>()
    // Get widgets that are less than a day old.
    .Where(w => w.CreatedOn > DateTime.Now.AddDays(-1));

The problem with adding a comment however is that the code is now repeated in English. Changing one now requires a change to the other. If these ever get out of sync chaos will abound, cats and dogs will rain from the sky, or worse, the technical lead may get a headache. Keep it DRY (yes, I think most comments violate DRY) and readable. Enter extension methods and a neat little trick from Ruby on Rails.

w.CreatedOn > 1.Day().Ago()

How do we achieve such awesomeness? It's surprisingly easy. First we create an extension method that gives us the time span we care about.

public static class TimeSpanHelper
{
    public static TimeSpan Days(this int days)
    {
        return TimeSpan.FromDays(days);
    }
}

We only care about one day in this particular example and it's not hard to cater for the singlular form.

public static TimeSpan Day(this int day)
{
    return Days(day);
}

Next, we create something that can cast our time span into the past.

public static class DateTimeHelper
{
    public static DateTime Ago(this TimeSpan timeSpan)
    {
        return DateTime.Now.Subtract(timeSpan);
    }
}

And the future (while we're here).

public static DateTime FromNow(this TimeSpan timeSpan)
{
    return DateTime.Now.Add(timeSpan);
}

Now we can write the Linq query like so.

var widgets = session.Query<Widget>()
    .Where(w => w.CreatedOn > 1.Day().Ago());

Much clearer. I don't think that code needs a comment. Nothing to get out of sync either. Your technical lead may still get a headache from the extension methods, but we can't have it all. ;)

Of course TimeSpanHelper can be extended to cater for seconds, minutes, hours, etc. Here's one more example. Say we wanted to find the time half a week from now. There's a few ways to do that.

84.Hours().FromNow();
(3.Days() + 12.Hours()).FromNow();
3.5.Days().FromNow();
0.5.Weeks().FromNow();

The last two require extending double instead of int, like this.

public static TimeSpan Weeks(this double weeks)
{
    return TimeSpan.FromDays(weeks * 7);
}

The double extensions can be handy at times, but I prefer to use int whenever I can. I think it's a little more readable.

Before I go, what do you think of this statement?

w.CreatedOn.IsNewerThan(1.Day().Ago())

I'll leave it up to you to implement.

There are 0 comments.


Comments

Leave a Comment

Please register or login to leave a comment.


Older
Visual Studio 2010 and Vim

Newer
Roy Osherove's TDD Kata 1

Older
Visual Studio 2010 and Vim

Newer
Roy Osherove's TDD Kata 1

browse with Pivot


About


Projects

Building Neno


RSS
Recent Posts

Codility Nitrogenium Challenge
OS X Lock
HACT '13
Codility Challenges
Priority Queue


Tags

Architecture (13)
ASP.NET (2)
ASP.NET MVC (13)
Brisbane Flood (1)
Building Neno (38)
C# (4)
Challenges (3)
Collections (1)
Communicator (1)
Concurrency Control (2)
Configuration (1)
CSS (5)
DataAnnotations (2)
Database (1)
DotNetOpenAuth (2)
Entity Framework (1)
FluentNHibernate (2)
Inversion of Control (5)
JavaScript (1)
jQuery (4)
Kata (2)
Linq (7)
Markdown (4)
Mercurial (5)
NHibernate (20)
Ninject (2)
OpenID (3)
OS X (1)
Pivot (6)
PowerShell (8)
Prettify (2)
RSS (1)
Spring (3)
SQL Server (5)
T-SQL (2)
Validation (2)
Vim (1)
Visual Studio (2)
Windows Forms (3)
Windows Service (1)


Archives


Powered by Neno, ASP.NET MVC, NHibernate, and small furry mammals. Copyright 2010 - 2011 Adam Boddington.
Version 1.0 Alpha (d9e7e4b68c07), Build Date Sunday, 30 January, 2011 @ 11:37 AM