32

What are the pros and cons in using fluent interfaces in Delphi?

Fluent interfaces are supposed to increase the readability, but I'm a bit skeptical to have one long LOC that contains a lot of chained methods.

Are there any compiler issues?
Are there any debugging issues?
Are there any runtime/error handling issues?

Fluent interfaces are used in e.g. TStringBuilder, THTMLWriter and TGpFluentXMLBuilder.


Updated:
David Heffernan asked which issues I was concerned about. I've been given this some thought, and the overall issue is the difference between "explicitly specifying how it's done" versus "letting the compiler decide how it's done".

AFAICS, there is no documentation on how chained methods actually is handled by the compiler, nor any specification on how the compiler should treat chained methods.

In this article we can read about how the compiler adds two additional var-parameters to methods declared as functions, and that the standard calling convention puts three params in the register and the next ones on the stack. A "fluent function method" with 2 params will therefor use the stack, whereas an "ordinary procedure method" with 2 params only uses the register.

We also know that the compiler does some magic to optimize the binary (e.g. string as function result, evaluation order, ref to local proc), but sometimes with surprising side effects for the programmer.

So the fact that the memory/stack/register-management is more complex and the fact that compiler could do some magic with unintentional side effects, is pretty smelly to me. Hence the question.

After I've read the answers (very good ones), my concern is strongly reduced but my preference is still the same :)

Community
  • 1
  • 1
Jørn E. Angeltveit
  • 3,029
  • 3
  • 22
  • 53
  • 2
    What i carried out from studying Delphi example in that article - is an urge to read a book entitled "Stop Thinking in Java" :-) – Free Consulting Feb 24 '11 at 12:20
  • 2
    Some people like it. Some people don't. What do you think? – David Heffernan Feb 24 '11 at 12:32
  • I don't like it. But that is primarily due to my uncertainty regarding the safety. I kinda like the code elegance in these classes though (if that makes any sense), but since this is not the traditional Delphi-way of doing things, I am skeptical to any side effects. I prefer to use the builder-DP approach in my own code. – Jørn E. Angeltveit Feb 24 '11 at 12:51
  • 1
    A don't see any safety issues. It's just about style. I don't know what builder-DP is! – David Heffernan Feb 24 '11 at 13:13
  • http://en.wikipedia.org/wiki/Builder_pattern – Jørn E. Angeltveit Feb 24 '11 at 13:16
  • @David: Will e.g. this: `TMyClass.Create.Random(SomeGlobalVar).Double(SomeGlobalVar).Free;` be equal to `m := TMyClass.Create; m.Random(SomeGlobalVar); m.Double(SomeGlobalVar); m.Free;` today and in the future? (Silly example - I know) – Jørn E. Angeltveit Feb 24 '11 at 14:07
  • @Jørn Yes today. Nobody can see into future. – David Heffernan Feb 24 '11 at 14:09
  • @David, it'll work the same now and in the future, unless a future version of Delphi finds a way to NOT allow calling functions on instances returned from other functions. And that's so basic functionality it would no longer be Delphi. Nor Pascal. – Cosmin Prund Feb 24 '11 at 14:56
  • @Jørn I don't really understand your concerns with safety. – David Heffernan Feb 24 '11 at 14:59
  • @David: It's safety as in certainty, not security. In a world where `a:=0.2; Assert(a=0.2);` fails, you need to know what you are doing. See some of the issues I was concerned about in the updated question. – Jørn E. Angeltveit Feb 25 '11 at 13:26
  • @David, @Cosmin: The language specification clearly says that statements should be separated with `;`'s. So that approach is waterproof now and in the future. How the compiler is supposed to deal with chained methods is not specified anywhere AFAIK, so the compiler could change e.g. the stack-/memory-management without breaking the language specification. Or am I wrong? – Jørn E. Angeltveit Feb 25 '11 at 13:28

5 Answers5

24

Everybody is just writing about negative issues so let's stress some positive issues. Errr, the only positive issue - less (in some cases much less) typing.

I wrote GpFluentXMLBuilder just because I hate typing tons of code while creating XML documents. Nothing more and nothing less.

The good point with fluent interfaces is that you don't have to use them in the fluent manner if you hate the idiom. They are completely usable in a traditional way.

EDIT: A point for the "shortness and readability" view.

I was debugging some old code and stumbled across this:

fdsUnreportedMessages.Add(CreateFluentXml
  .UTF8
  .AddChild('LogEntry')
    .AddChild('Time', Now)
    .AddSibling('Severity', msg.MsgID)
    .AddSibling('Message', msg.MsgData.AsString)
  .AsString);

I knew immediately what the code does. If, however, the code would look like this (and I'm not claiming that this even compiles, I just threw it together for demo):

var
  xmlData: IXMLNode;
  xmlDoc : IXMLDocument;
  xmlKey : IXMLNode;
  xmlRoot: IXMLNode;

  xmlDoc := CreateXMLDoc;
  xmlDoc.AppendChild(xmlDoc.CreateProcessingInstruction('xml', 
    'version="1.0" encoding="UTF-8"'));
  xmlRoot := xmlDoc.CreateElement('LogEntry');
  xmlDoc.AppendChild(xmlRoot);
  xmlKey := xmlDoc.CreateElement('Time');
  xmlDoc.AppendChild(xmlKey);
  xmlData := xmlDoc.CreateTextNode(FormatDateTime(
    'yyyy-mm-dd"T"hh":"mm":"ss.zzz', Now));
  xmlKey.AppendChild(xmlData);
  xmlKey := xmlDoc.CreateElement('Severity');
  xmlDoc.AppendChild(xmlKey);
  xmlData := xmlDoc.CreateTextNode(IntToStr(msg.MsgID));
  xmlKey.AppendChild(xmlData);
  xmlKey := xmlDoc.CreateElement('Message');
  xmlDoc.AppendChild(xmlKey);
  xmlData := xmlDoc.CreateTextNode(msg.MsgData.AsString);
  xmlKey.AppendChild(xmlData);
  fdsUnreportedMessages.Add(xmlKey.XML);

I would need quite some time (and a cup of coffee) to understand what it does.

EDIT2:

Eric Grange made a perfectly valid point in comments. In reality, one would use some XML wrapper and not DOM directly. For example, using OmniXMLUtils from the OmniXML package, the code would look like that:

var
  xmlDoc: IXMLDocument;
  xmlLog: IXMLNode;

  xmlDoc := CreateXMLDoc;
  xmlDoc.AppendChild(xmlDoc.CreateProcessingInstruction(
    'xml', 'version="1.0" encoding="UTF-8"'));
  xmlLog := EnsureNode(xmlDoc, 'LogEntry');
  SetNodeTextDateTime(xmlLog, 'Time', Now);
  SetNodeTextInt(xmlLog, 'Severity', msg.MsgID);
  SetNodeText(xmlLog, 'Message', msg.MsgData.AsString);
  fdsUnreportedMessages.Add(XMLSaveToString(xmlDoc));

Still, I prefer the fluent version. [And I never ever use code formatters.]

gabr
  • 26,580
  • 9
  • 75
  • 141
  • 2
    @gabr, not voting your answer down because technically it's correct (much less typing). However, you can get much less typing by using with poorly (with MyQuery, MyButton, MyTreeView, MyOtherThing do...). It basically makes the code extremely difficult to debug and maintain, even though you save lots of keystrokes. I think fluent interfaces might end up with the same problems. – Ken White Feb 24 '11 at 14:24
  • @Ken, I never run into any debugging problems with GpFluentXMLBuilder. If the 'fluent' part becomes too long it is quite simple to split it into multiple segments or multiple methods working on the same fluent interface. – gabr Feb 24 '11 at 14:34
  • 2
    +1. Don't forget about the saved local variable. Doesn't sound like much, but I mostly used Fluent interfaces when filling lists at runtime, using different classes. Without fluent-style interfaces you end up with one variable for each class type you need to use, even if you only need to set up the value for one single property. – Cosmin Prund Feb 24 '11 at 15:02
  • @gabr: You wrote GpFluentXMLBuilder. :-) What about all the code you write using it, though, if someone new (and unfamiliar with it) has to take it over for some reason? How much harder will FI make it for them to pick things up? As for debugging, I quite frequently find myself having to unroll complex if statements (if SomeFunc(somevar) or SomeotherFunc(anothervar) or ((SomeThingElse(somevar) and AnotherThing(thisvar))... that someone else wrote; debugging without figuring out the meaning of all of the possibilities and then rewriting is a PITA. – Ken White Feb 24 '11 at 23:34
  • 1
    @Ken, I actually hope it would make life for any other programmer that has to read my code simpler and not more complicated. – gabr Feb 25 '11 at 02:21
  • 1
    @gabr: Thank you for the positive vibes. :) But is "less typing" really an important issue these days? With code completion, code templates, code generation, advanced cut'n'past, advanced search'n'repace and editor macros, I very seldom miss the ability to type fewer characters. – Jørn E. Angeltveit Feb 25 '11 at 13:44
  • 7
    @Jørn E. Angeltveit - *in the case of XML*, fluent interfaces actually makes easier to see the expected result document from code. I cannot state how easier is to create documents with complex structures using this approach. And since you can clearly "see" the output, it is easier to maintain -- for example, adding a new element in a specific location. – Leonardo Herrera Feb 25 '11 at 18:12
  • 1
    @Leonardo, good point! Yes, the code is more "obvious" when using fluent interfaces. – gabr Feb 25 '11 at 19:52
  • 2
    @gabr, I'll have to look at GpFluentXMLBuilder again. I didn't think so the first time I looked, but you could be right (and @Leonardo's point seems to be good as well). Thx. – Ken White Feb 25 '11 at 20:04
  • Your example is a little biased however, as the standard's XML DOM interfaces are probably the worst way to handle a DOM (any DOM). It's possible to write builder classes that'll minimize the code size difference to your fluent interface, and provide greater clarity at the same time. In the fluent interfaces, you have very little in the way of safety and flow control, it's basically, write-once, read-never, and pray you won't have to maintain such code if it wasn't written by someone with VERY strict code styles. Automated code formatting will also wreak havoc on fluent interfaces. – Eric Grange Mar 05 '11 at 09:52
  • What about a more OO-style? f.i. "SetNodeTextDateTime(xmlLog, 'Time', Now);" would be more readable IMHO as "xmlLog.NodeTextDateTime['Time']:=Now;" – Eric Grange Mar 24 '11 at 09:45
  • @Eric: Feel free to add your own example written with your popular DOM wrapper. – gabr Mar 29 '11 at 06:42
  • 1
    Raise, dormant thread! Today I had to modify some code I wrote years ago. I had used @gabr fluent interfaces (modified to work with Secure Black Box XML). Guess what, the code was scrambled (I tend to press Ctrl-D and format the whole document all the time) but it took me about two whole minutes to add a break here and there, find the place I needed to modify, and I was done. – Leonardo Herrera Aug 19 '20 at 15:17
18

Compiler issues:

If you're using interfaces (rather than objects), each call in the chain will result in a reference count overhead, even if the exact same interface is returned all the time, the compiler has no way of knowing it. You'll thus generate a larger code, with a more complex stack.

Debugging issues:

The call chain being seen as a single instruction, you can't step or breakpoint on the intermediate steps. You also can't evaluate state at intermediate steps. The only way to debug intermediate steps is to trace in the asm view. The call stack in the debugger will also not be clear, if the same methods happens multiple times in the fluent chain.

Runtime issues:

When using interfaces for the chain (rather than objects), you have to pay for the reference counting overhead, as well as a more complex exception frame. You can't have try..finally constructs in a chain, so no guarantee of closing what was opened in a fluent chain f.i.

Debug/Error logging issues:

Exceptions and their stack trace will see the chain as a single instruction, so if you crashed in .DoSomething, and the call chain has several .DoSomething calls, you won't know which caused the issue.

Code Formatting issues:

AFAICT none of the existing code formatters will properly layout a fluent call chain, so it's only manual formatting that can keep a fluent call chain readable. If an automated formatter is run, it'll typically turn a chain into a readability mess.

Eric Grange
  • 5,931
  • 1
  • 39
  • 61
7

Are there any compiler issues?

No.

Are there any debugging issues?

Yes. Since all the chained method calls are seen as one expression, even if you write them on multiple lines as in the Wikipedia example you linked, it's a problem when debugging because you can't single-step through them.

Are there any runtime/error handling issues?

Edited: Here's a test console application I wrote to test the actual Runtime overhead of using Fluent Interfaces. I assigned 6 properties for each iteration (actually the same 2 values 3 times each). The conclusions are:

  • With interfaces: 70% increase in runtime, depends on the number of properties set. Setting only two properties the overhead was smaller.
  • With objects: Using fluent interfaces was faster
  • Didn't test records. It can't work well with records!

I personally don't mind those "fluent interfaces". Never heard of the name before, but I've been using them, especially in code that populates a list from code. (Somewhat like the XML example you posted). I don't think they're difficult to read, especially if one's familiar with this kind of coding AND the method names are meaningful. As for the one long line of code, look at the Wikipedia example: You don't need to put it all on one line of code.

I clearly remember using them with Turbo Pascal to initialize Screens, which is probably why I don't mind them and also use them at times. Unfortunately Google fails me, I can't find any code sample for the old TP code.

Cosmin Prund
  • 25,498
  • 2
  • 60
  • 104
  • +1 interesting to read that they are even faster (when implemented with objects) than classic line-by-line code, tx for running tests – mjn Mar 05 '11 at 18:48
3

I would question the benefit of using "fluent interfaces".

From what I see, the point is to allow you to avoid having to declare a variable. So the same benefit the dreaded With statement brings, with a different set of problems (see other answers)

Honestly I never understood the motivation to use the With statement, and I don't understand the motivation to use fluent interfaces either. I mean is it that hard to define a variable ? All this mumbo jumbo just to allow laziness.

I would argue that rather than increase readability it, which at first glance it seems to do by having to type/read less, it actually obfuscates it.

So again, I ask why would you want to use fluent interfaces in the first place ?

It was coined by Martin Fowler so it must be cool ? Nah I ain't buying it.

  • 6
    "All this mumbo jumbo just to allow laziness." - Just like to point out that the same was said about assembly language and 3GLs and IDEs and form designers and generative programming and garbage collection and automated refactoring tools and... – Kenneth Cochran Feb 24 '11 at 19:55
2

This is a kind of write-once-read-never notation that is not easy to understand without going through documentation for all involved methods. Also such notation is not compatible with Delphi and C# properties - if you need to set properties, you need to rollback to using common notations as you can't chain property assignments.

Eugene Mayevski 'Callback
  • 45,135
  • 8
  • 71
  • 121
  • `This is a kind of write-once-read-never notation that is not easy to understand without going through documentation for all involved methods.` This is an overstatement. It's quite readable when used in the right situations. – Shannon Matthews May 29 '13 at 06:02