Embracing fluent interfaces
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.
Comments
Absolutely. Works fine for the simple scenarios, but I was on a project last year where the developers had vomited SQL all through the app, through every "layer". Trying to follow all the if/then logic to build an enormous SQL string to execute and identify injection problems was not fun!
This is for anyone writing code I may have to look at one day: If you're going to use SQL strings in your app people PLEASE wrap them in some kind of DAL class(s) so they're all in one place!
Besides readability, there is a big benefit of your fluent examples that you didn't mention. I have written a lot of complex searches like the one you mention using string concat to build SQL or HQL statements. I would like to think that I have done it cleverly, making the code reasonably understandable. Nonetheless, future developers must always take extra time when changing these methods...for one simple reason. The compiler is quite happy if put logic errors into string literals. What's it worth to catch errors at compile time versus runtime?
In the Regex example, is the result of the Fluent interface the string given in the first half? Or is it the Regex's internal structure? If the latter, then there could also be significant runtime savings as well.
I think query in straightforward fluent interface can be as easy to read as the
generated SQL:
Prepare
But now I can do
Prepare
and get a magic paging CTE under SQL Server and degradable behavior under other engines.
Also Tables.Customer.Country == "USA" can be typesafe if Tables.Customer.Country is a QueryField<string>.
Ayende,
I would have to agree with you. I have been experimenting with a fluent interface myself for our CRM platform. I have found that it is far easier to read, and IMHO maintain (as a side effect of improved readability).
I also believe the learning curve is much simpler, due to using a syntax which includes operator overloads to allow:
Where(Contact.Columns.Age > 18 && Contact.Columns.Age < 35 || Contact.Columns.Name.StartsWith("C"))
James,
That depends on the implementation, but I don't think that regex's structured are exposed externally, so it probably build the string.
Then again, this is the perfect place to put best practices into place.
Andy,
Take a look at NHQG, I did some serious work there to that end.
Your regex comparison isn't fair: split that regex onto multiple lines and use RegexOptions.IgnorePatternWhitespace. You do bring up a very interesting point, as my usual impulse is to go with compiler-checkable syntax. In the regex situation, I would prefer a multi-line, properly-indented, literal regex with IgnorePatternWhitespace. The regex fluent interface you've provided is ridiculously wordy compared to a regex. I've never really had to dynamically build regexes like I've had to do with SQL, so I'm probably unduly biased.
I couldn't disagree with jeff more. There is a fundamental concept that he is missing..... programming language are for humans, not computers (sort of speak). Otherwise we would be writing code in the equivalent to punch cards.
Fluent interfaces offer increase speed, if programmers could type faster without using intellsense then it wouldn't exist in almost every single modern IDE.
Fluent interfaces are a higher level form of abstraction, in a sense that's exactly why programming language were invented.
I'm happy as long as the goal of the interface is to be composable not just verbose.
The objectives of fluent interfaces are a bit muddy. They can introduce a higher level of abstraction along the lines of the Builder design pattern. However part of it is about introducing extra static type information and extra noise words for the sake of Intellisense support or RAD.
There are many ways to accomplish the latter objective. Unfortunately we're a bit constrained by the languages and the tools here.
Total agree with the second example, and that's why I still don't use the LINQ now.
So, basically you're saying that you wouldn't want to do string concat, but would rather use a DOM (which underlies the fluent interface)....but the same argument, I take it, still doesn't apply to building html? ;-)
/Mats
Mats,
Take a look at Brail's, it has a DSL for building HTML, as a matter of fact.
In this case, it depends on what you are trying to do.
Most cases, it is not just string generation.
You can generate a sql statement with a template language:
select * from Customers
<% if has_conditions: %>
where
<% if first_name_starts_with:
need_and = true
%>
firstname like @first_name_starts_with+ '%'
<% end %>
<% if last_name_starts_with: %>
<% if need_and: %>
and
<% end %>
lastname like @last_name_starts_with + '%'
<% end %>
<% end %>
This is not readable. In fact, it this is how your HTML template looks like, this is not readable, and you need to rethink how you are doing it.
But frankly, most of the time the HTML contains very simple logic, rarely inter-related and rarely nested.
"In fact, it this is how your HTML template looks like, this is not readable, and you need to rethink how you are doing it"
Interesting! I must admit that's pretty much how all the examples I've seen of RoR, MonoRail etc have come across...but you're saying proper modularization gets rid of stuff like that? Completely?
But are you in effect saying that someone using templates that look like that ^^ is doing it (RoR, MR) wrong? I may be totally off here, but I kinda think that's news...?
/Mats
I am saying that too much logic in the template is wrong, yes.
You should have loops, a few ifs, but not much more.
If you do have nested ifs or complex logic, it is time to extract it, and yes, it is entirely possible.
Comment preview