3
\$\begingroup\$
new MethodExecuter()
    .ForAllInvocationsCheckCondition(!Context.WannaShutDown)
        .When(shouldRun).ThenInvoke(x => x.Methods(new Action[] { B.Process, A.Process }))
        .When(shouldRun).ThenInvoke(x => x.Method(B.Process).Repeat(100))
        .Invoke(x => x.Method(C.Process));

I deliberately do not say anything about what this API does. I did show this API my team mates and they did not like it. For them it is unreadable.

What do you think about the API? What tips do you have? What do you think about complexitiy, usability and readability of fluent APIs and how to achieve good APIs?

EDIT Why did I build this API?

Well, I have a bunch of methods that are invoked synchronously when a condition is false. The condition applies foreach method invocation.

if (!Context.WannaShutDown) A.Process();
if (!Context.WannaShutDown) B.Process();
if (!Context.WannaShutDown) C.Process();
if (!Context.WannaShutDown) D.Process();
if (!Context.WannaShutDown) E.Process();
if (!Context.WannaShutDown) F.Process();
if (!Context.WannaShutDown)
{
   if (condition) 
   {
       for (int i = 0; i <= 100 i++)
       {
           Z.Process();
       }
   }
...

I do not like such crap code. What I wanted to do is to solve "the problem" once with a class that is responsible for invocating methods. I tried to build an API that is as simple as possible, but it seems I failed.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ How enjoyable is it to debug this? Does the fluent syntax make it more difficult? What is this abstraction buying you? Fluent APIs are not better ipso facto. Similar to how a .ForEach operator is not necessarily better than a simple foreach loop. With that being said, this could be a good design, but I think you need to explain a bit the pain it abstracts away. To put it another way, C# already has a language facility for executing methods: the language itself is already designed for this purpose. \$\endgroup\$
    – Kirk Woll
    Commented Jun 21, 2011 at 20:52
  • \$\begingroup\$ .ForEach let you express things very very compact. Debuging is not that big problem, because you only have to set breakpoints at the method that shall be invoked. \$\endgroup\$
    – Rookian
    Commented Jun 21, 2011 at 21:12
  • \$\begingroup\$ you would probably be well-served if you read this Eric Lippert article: blogs.msdn.com/b/ericlippert/archive/2009/05/18/… \$\endgroup\$
    – Kirk Woll
    Commented Jun 22, 2011 at 15:57

2 Answers 2

1
\$\begingroup\$

Kirk Woll makes some excellent points in his comment which could have been an answer IMHO.

Fluent APIs are not better ipso facto.

You are entirely right in wanting to refactor your given code sample in the edit, but it might be worthwhile to first explore some other possibilities before deciding on using a fluent API.

A first concern is the Context class, as it looks like this is a global which is accessed from any location (A, B, C, ...). If there is any way to remove this global state, that might already be a more important improvement.

Using only your given code sample (assuming a IProcess interface), you could already improve it by writing something like this:

Dictionary<IProcess, int> executeOperations = new Dictionary<IProcess, int>
{
    { A, 1 }, { B, 1 }, { C, 1 }, { D, 1 }, { E, 1 }, { F, 1 },
    { Z, 100 }
}

foreach ( var executeItem in executeOperations )
{
    if ( !Context.WannaShutDown )
    {
        for ( int i = 0; i <= executeItem.Value; ++i )
        {
            executeItem.Key.Process();
        }
    }
}

It's not clear what the condition in your code sample is meant to be, or why only Z needs to check for it, but for demonstration purposes I believe the above example shows there are other ways of refactoring your given code without having to rely on fluent syntax. You could replace the int value in the dictionary to a struct containing more execution parameters.

That said, I believe your supplied sample is still too small to conclusively say whether or not using fluent syntax could be justified.

\$\endgroup\$
0
\$\begingroup\$

Fluent APIs in general always make me think back to pooQuery. Fluent APIs (in my opinion) are aimed at the same goal that resulted in the creation of COBOL: to be readable and "comprehensible" to non-programmers. Programmers, of course, know that this is a fallacy. The difficulty in programming is not understanding the particular syntax of a given programming language, but in formulating the logical steps needed to implement a particular algorithm. Knowing this, verbose, english-like languages ("fluent APIs") do not help, and indeed, only serve to obfuscate the algorithm.

\$\endgroup\$
10
  • 1
    \$\begingroup\$ I think dismissing all fluent APIs with this broad stroke of yours ignores the many APIs that are best expressed fluently. \$\endgroup\$
    – Kirk Woll
    Commented Jun 21, 2011 at 20:55
  • \$\begingroup\$ @Kirk: Such as? \$\endgroup\$
    – Mark
    Commented Jun 21, 2011 at 20:57
  • \$\begingroup\$ Let's see: Ninject, NHibernate, MS Entity Framework, etc. \$\endgroup\$
    – Kirk Woll
    Commented Jun 21, 2011 at 20:59
  • \$\begingroup\$ @Kirk: All these APIs worked just fine (and still do) without fluent APIs. Whether or not they are "best expressed fluently" is merely your opinion. In my opinion, they are not. \$\endgroup\$
    – Mark
    Commented Jun 21, 2011 at 21:01
  • 1
    \$\begingroup\$ @Kirk: the fact that a particular project only exposes a fluent API is not proof that a fluent API is better, it's only proof that whoever designed and implemented the API believes it's better. It's still an opinion. The question is subjective, and I provided my opinion. You're free to provide yours as well. \$\endgroup\$
    – Mark
    Commented Jun 21, 2011 at 21:25

Not the answer you're looking for? Browse other questions tagged or ask your own question.