Thursday 29 January 2009

The x => Factor

I may be swimming against the tide here, but I don't understand why it has become an idiom of C# to use x as the parameter name in lambda expressions, as in:
var matching = list.ForEach(x => x.Version > 1)
You wouldn't expect to see
foreach(var x in list)
{
if(x.Version > 1)
{
yield return x;
}
}
I suspect it has a lot to with conciseness. Whilst I highly value conciseness, I value readability over conciseness.

Sunday 25 January 2009

News Feeds Changed to Feedburner

I have changed the news feeds for this blog to use FeedBurner. You may want to update your links.

Thursday 22 January 2009

Explicit Exception Messages

What Happened

I was adding code, deep in the bowels of the data persistence layer, to update auditing flags when entities where saved. The code received the UserName of the logged in user, looked it up in the User table, and placed the UserId in the CreatedBy or ModifiedBy field as appropriate. If the user could not be found a SecurityException was raised containing the message
User with a login of [user name] cannot be found in the User table
I tested it, everything worked fine, so I committed my changes and moved onto the next task; which was some changes to the Log-In page. Several hours, later I was ready to test my changes to the Log-In page. Whenever I hit the [OK] button, I got a SecurityException with the message
User with a login of [user name] cannot be found in the User table
Having already forgotten my earlier changes in the persistence layer, I took the message at face value and assumed I had messed up with my changes to the log in page. I then wasted a couple of hours undoing and redoing my work, but to no avail, the exception stubbornly remained.

Eventually I was "smart" enough to follow the exception's stack trace which led me to the code I had changed earlier in data persistence layer. It turned out the membership provider was trying to update the LastLoggedIn date on the user, when my code tried to apply the ModifiedBy flag it couldn't get the logged in user because the log in process wasn't complete yet.

Lessons Learnt

  • When you get an exception don't just look at the message, use the stack trace (duh?)

  • Make sure you throw appropriate exception types. The fact that I was getting a SecurityException made me think this was a security issue, when in fact it was a data persistence issue. Something like DatabaseRecordNotFoundException would have been better.

  • Write exception messages that provide as much contextual information as possible. When writing the message don't assume that the message will be read in the same context as it is thrown. What do I mean by this? The error message I wrote made perfect sense in the context in which I was working, the data persistence layer. But once it escaped the DP layer, and bubbled up to the presentation it made much less sense and sent me off in the wrong direction, wasting time. Imagine how much worse this would be if it occurred some months later in production, and the error message arrived via the help desk with no stack trace.

The error message I finally decided upon read
During a data persistence operation for [entity name] an attempt was made to update the auditing field [CreatedBy/ModifiedBy]. This failed because a user with a login of [user name] cannot be found in the User table

A Pattern for Fluent Syntax

I've always been interested in making code as readable as possible and recently have become increasingly interested in fluent APIs. I have noticed that most of my fluent work has followed a similar pattern. I present this pattern here in the hope it helps others deliver their own fluent interfaces.

Fluent Interface

Typically APIs provide methods with multiple parameters, many of which are not required for most uses. To overcome this, many programming languages have features such as method overloading or optional parameters. Whilst in many cases this helps API usage, it can result in bloated and hard-to-use APIs.

Fluent APIs take a different approach, breaking down the API into its consituent parts and guiding the programmer through its usage. Fluent API takes advantage of the features of modern development environments, particularly "code-complete", to help provide this guidance. The aim for a fluent API is that it should be easy to write and easy to read.


How it Works

The fluent syntax of your API will be provided by the public methods of a class; this class is named the Lexicon. The key to the pattern, is that each method called should build up some state in the Lexicon and then return a reference back to a lexicon so that another method can be used from a Lexicon, and so on, to build up a meaningful statement.


Consider the following fluent syntax for counting files in a folder:

int files = FileSystem.GetDrive("C:")
.OpenFolder("Program File")
.GetFileCount();
FileSystem.GetDrive is the entry-point into this fluent API. The entry-point will often be a constructor or static creation method. In all cases it will return a reference to a lexicon instance.

OpenFolder is a continuation. Continuations are methods that will set some state within the lexicon and then return an instance to the lexicon.

GetFileCount is an end-point for this API. An end-point is a method that takes some action based on the state of the lexicon. It will return some value that is the result of the fluent API call, or void, but will not return an instance of a lexicon.

A lexicon can have multiple entry-point, continuations, and end-points. Where the fluent API is large, it is common to break the API into more than one lexicon. Control is passed from one lexicon to another by a continuation which returns an instance of a different lexicon type. The new lexicon will probably need to know the context of the original lexicon; this is normally done by passing the original lexicon to the new lexicon in its constructor.


When to use it

Paradoxically, whilst a fluent API makes the reading and writing of that API easier, often the code making up that API is complicated by the addition of the fluent syntax. Consequently, Fluent Interface, should only be used in areas where the effort of extending the syntax is rewarded by frequent reuse. Typically, this applies to areas of cross-cutting concern such as configuration, logging or auditing.

It is worth considering the complexity of the API before implementing it as fluent. For a simple interface with only a handful of methods it is probably not worth the effort.


Example (C#)

For this example we are going to consider a fluent syntax for obtaining information about the file system. I have split the API into multiple lexicons. This would possibly be considered overkill in the real-world but have done so here to make the example more complete.

Firstly lets look at the FileSystem lexicon which provides fluent syntax for dealing with drives on your system:

public class FileSystem
{
internal string DriveSpec { get; private set; }

private FileSystem(string driveSpec)
{
DriveSpec = driveSpec;
}

// 1st entry point
public static FileSystem GetDrive(string driveSpec)
{
if(!IsValidDrive(driveSpec))
{
throw new ArgumentException("Not a valid drive");
}
return new FileSystem(driveSpec);
}

// 2nd entry point
public static FileSystem MapDrive(string location, char assignedLetter)
{
string driveSpec = assignedLetter.ToString();

if(IsValidDrive(driveSpec))
{
throw new ArgumentException("Drive is already assigned");
}

NetworkUtilities.MapDrive(location, assignedLetter);

return GetDrive(driveSpec);
}

// continuation transfering control to another lexicon
public FolderLexicon RootDirectory()
{
return new FolderLexicon(this,"\\");
}

// 1st end point
public long AvailableBytes
{
get
{
DriveInfo info = new DriveInfo(DriveSpec);
return info.AvailableFreeSpace;
}
}

// 2nd end point
public string Name
{
get
{
DriveInfo info = new DriveInfo(DriveSpec);
return info.VolumeLabel;
}
}
}

And now the FolderLexicon used to hold the syntax for accessing information about file system folders

public class FolderLexicon
{
// holds a reference to FileSystem
// so the entire context can be discovered
private readonly FileSystem drive;

private readonly StringBuilder path = new StringBuilder();

// entry point - note that this lexicon can
// only be entered through a FileSystem
internal FolderLexicon(FileSystem drive)
{
this.drive = drive;
path.Append(drive.driveSpec);
path.Append("\\");
}

//continuation
public FolderLexicon OpenFolder(string dirSpec)
{
path.Append("\\");
path.Append(dirSpec);

if(!Directory.Exists(path.ToString()))
{
throw new ArgumentException("Folder does not exist");
}

return this;
}

//end point
public int GetFileCount()
{
DirectoryInfo dInfo = new DirectoryInfo(path.ToString());
return dInfo.GetFiles().Length;
}
}