Embracing fluent interfaces

time to read 3 min | 455 words

Jeff Atwood is talking about languages in languages, and suggest that you would avoid using fluent interfaces to hide a language semantics. I completely disagree. Let us talk about the example. The first one deals with regular expressions:

//Regular expression
Pattern findGamesPattern = new Pattern(@"<div\s*class=""game""\s*id=:"(?<gameID>\d+)-game""(?<content>.*?)
<!--gameStatus\s*=\s*(?<gameState>\d+)-->
");

// Fluent interface
Pattern findGamesPattern = Pattern.With.Literal(@"<div")
    .WhiteSpace.Repeat.ZeroOrMore
    .Literal(@"class=""game""").WhiteSpace.Repeat.ZeroOrMore.Literal(@"id=""")
    .NamedGroup("gameId", Pattern.With.Digit.Repeat.OneOrMore)
    .Literal(@"-game""")
    .NamedGroup("content", Pattern.With.Anything.Repeat.Lazy.ZeroOrMore)
    .Literal(@"<!--gameStatus")
    .WhiteSpace.Repeat.ZeroOrMore.Literal("=").WhiteSpace.Repeat.ZeroOrMore
    .NamedGroup("gameState", Pattern.With.Digit.Repeat.OneOrMore)
    .Literal("-->");

I can read the fluent interface, but I need to parse the regex pattern. There are tradeoffs there, because as a regex gets more complex, it is getting unreadable. The fluent interface above keep it maintainable even for complex regular expression.

The second example deal with SQL:

// Embdded language
IDataReader rdr = QueryDb("SELECT * FROM Customer WHERE Country = 'USA' ORDER BY CompanyName");

// Fluent interface
IDataReader rdr = new Query("Customer")
	.WHERE(Customer.Columns.Country, "USA")
	.OrderByAsc(Customer.Columns.CompanyName)
	.ExecuteReader();

Here it is much easier to read the SQL statement, right? Except that the statements are not equal to one another. Using "select *" is inefficient, you want to explicitly say what columns you want (even if you want them all).

Nevertheless, the SQL code is much easier to read. It doesn't handle parameters, though, so it will probably encourage SQL injection, but I digress.

So, we have two examples, which mostly go in two different direction, we are inconclusive, right?

No, because one thing that Jeff didn't cover was logic.

I have a regular expression that need to change based on some sort of logic. Imagine validating a document with several options, validating ID per country, validating phone numbers by area, validating email address that belong to a certain company, etc. Even if we have only three choices in each category, this put us in way too many separate regexes to maintain. We need to construct them dynamically.

Now, anyone here thinks that regular expressions that are being constructed using string concat are going to be easy to understand? Easy to write? Maintainable?

What about SQL? Do you think that you can build this search form by simply concating SQL? Do you think it would be maintainable?

I am sorry, but I don't see this approach working when you move beyond the static, one of, stuff. Sure, I write a lot of SQL to look at my data, but it is done in management studio, and it doesn't go into my application. I can't afford the maintainability issues that this will cause.