
Fluent Interfaces Are Bad for Maintainability - ingve
http://www.yegor256.com/2018/03/13/fluent-interfaces.html
======
Mister_Snuggles
While I don’t like Fluent interfaces at all, I don’t like the alternative
presented much either.

My dislike of Fluent interfaces stems from two things. First, it’s trying to
contort an English-like sentence out of a programming language, which just
looks and feels wrong. It leads to weird methods like ‘.method’ in the
example. Second, Fluent interfaces really try to paper over the fact that the
language lacks an equivalent to Visual Basic’s ‘with’[0] statement.

On the other hand, the example presented is not great either. Should
ResponseAssertStatus be an object? Wouldn’t it make more sense to do the
request then have a separate line of code to evaluate the status? How does it
make sense for BodyOfResponse to be constructed out of some kind of
ResponseAssertStatus object?

Between the two options, the Fluent interface makes more sense, even if the
underlying implementation ends up being kind of ugly.

[0] [https://docs.microsoft.com/en-us/dotnet/visual-
basic/languag...](https://docs.microsoft.com/en-us/dotnet/visual-
basic/language-reference/statements/with-end-with-statement)

~~~
braythwayt
I second the notion that fluency is a language syntax problem that should not
be solved with the design of interfaces.

Smalltalk also solved this problem with the semi-colon, but for some damn fool
reason we followed C syntax but didn't find an alternative. And now we're
trying desperately to replicate `&.` in every library and language, but we
still don't have an easy way to write:

    
    
        someObject
          .doThis();
          .doThat();
          .dontForgetToFinishUp().

~~~
mhomde
I always wanted a language that was built upon the concept on just passing
things forward in a pipe, and was like an hybrid between functional and c
style languages, like:

    
    
      someobject => doThis() => doThat() => dontForgotToFinishUp()
    

then you could also possible send multiple objects into that pipe:

    
    
      obj1, obj2 => doThis() => doThat() => dontForgotToFinishUp()
    

you'd have to have some semantics for sending them separately, as an
ienumerable or as parameters but perhaps that could be done

    
    
      (obj1, obj2) => doThis() => doThat() => dontForgotToFinishUp()
    
      [obj1, obj2] => doThis() => doThat() => dontForgotToFinishUp()
    

add on top of that something that could abstract away multithreading/tasking
in certain cases (automatically assigning them to tasks) and I'd be a happpy
coder :)

~~~
kortex
I spent a half day playing around with something very similar to this. I
wanted a concise language for describing data pipelines in Pandas, and was
(ab)using python dunder methods (operator overloading) to this end. Like:

`data | groupby, "author" | mean`

Would create a graph object, which could be lazily evaluated, run in Dask, TF,
etc.

It started to get ugly when passing in multiple parameters into a function. I
had to watch out for left and right associativity, and manage casting of
arguments.

It was a fun little experiment but I'm not sure how much it would actually
improve workflows. If that sounds interesting, let me know and I'll poke at it
again.

~~~
wenc
I'm not sure if you are aware, but there are several efforts out there to give
Python a more data-pipeline-friendly (composable pipe) syntax:

1) Coconut: [http://coconut-lang.org/](http://coconut-lang.org/)

2)
[https://github.com/JulienPalard/Pipe](https://github.com/JulienPalard/Pipe)

3) Pandas also has a new dataframe pipe method.
[https://pandas.pydata.org/pandas-
docs/stable/generated/panda...](https://pandas.pydata.org/pandas-
docs/stable/generated/pandas.DataFrame.pipe.html)

I would look at those before rolling out a custom solution.

------
LeanderK
I don't agree with the fundamental concern. I think sacrificing
maintainability for ease of use is a valid thing to do.

Saving 100x 20 minutes in developer time for the library users for taking an
hour more to implement a feature is a good trade.

Every rule has an exception, but fluent interfaces being bad for
maintainability is often justified.

~~~
mcphage
> I think sacrificing maintainability for ease of use is a valid thing to do.

I agree. If you're not willing to make your developers' lives easier, then
what are you doing writing a public library in the first place?

------
rjbwork
I disagree with a few caveat - you need to go with the Interface Per Operation
pattern, use a builder, and provide many parameters to your methods. You're
guiding your user through the object building pipeline, so bonus points if you
constrain them to only certain operations at certain points in the pipeline so
that they don't shoot themselves in the foot.

------
Sharlin
> That is the biggest problem with fluent interfaces—they force objects to be
> huge.

Only if your language doesn't support adding methods to types after the fact,
like C# extension methods, Rust traits, or Scala implicits.

~~~
snarfy
Even without that support, if you return interfaces instead of the concrete
type each return can be a different implementation. There is no need to have
one giant class. All of those methods in his example should return
IJdkRequest, not JdkRequest.

~~~
Sharlin
That doesn't help at all because all the methods will still have to be
declared in IJdkRequest and implemented by every concrete subtype returned by
the methods.

~~~
snarfy
IJdkRequest would inherit other interfaces, so the other methods would be
declared there. The default implementation could be handled by a base class.

And now that I typed it out, I see how it also doesn't really help. I see your
point. I'm not sure what's worse, one giant class, or the handful of
interfaces and abstractions just to make it work in multiple classes.

------
cromwellian
It seems most of the examples are builder-pattern like, constructing
configuration for the final object. If that's the case, all you really need it
something like @AutoValue or the @Value.Immutable from immutables.org

Most of the boilerplate is removed, especially with Immutables.org's
annotation processor. Plus, you get immutability and other validation.

~~~
specialist
Ya. Builder, Composite, Method Chaining implementations are (can be)
isomorphic.

Thanks for the immutables.org link. Amazing. FWIW, I once fudged a UI DSL for
AWT/Swing using annotations. It sucked. Too many edge cases requiring shims.
Mostly because the underlying UI components were not designed for (clean)
composition.

Like the OP, I went a little nutty with Fluent APIs for a while. Never again.

Now I prefer Builder for small stuff and DSLs for anything interesting.
Composite & Intepreter is a potent combo.

------
stickfigure
This is horrifying. It prioritizes the ease of the library developer's life
over the library user's life. That smart-object API is just simply painful to
use.

Oddly enough, I (independently) developed a very similar opensource http
library with fluent, immutable command objects:

[https://github.com/stickfigure/hattery/](https://github.com/stickfigure/hattery/)

Of all the libraries I've built in my life, this is probably the one I enjoy
using most. I also don't have any problems maintaining it; I still add
features all the time (most recently, preflight and postflight functions) and
it's still fun.

The reason it's easy to maintain, I suspect, is mostly two reasons: 1)
everything is immutable and 2) there's clean separation between the Request
and the Transport.

------
chopin
I prefer to return different _interfaces_ from each (or almost each) method.
This way also a predefined order of calls is imposed which you may want
sometimes. A predefined order also makes similar uses look similar which I
deem a benefit as well.

~~~
jdmichal
I was wondering why this suggestion wasn't included in the OP, as it's exactly
the route that the JAX-RS client specs took. There are several interfaces, and
some of the fluent calls are basically terminal by leading to one of the other
interfaces.

It starts here with the `Client` interface:

[https://docs.oracle.com/javaee/7/api/javax/ws/rs/client/Clie...](https://docs.oracle.com/javaee/7/api/javax/ws/rs/client/Client.html)

Which then leads to `WebTarget`, then `Invocation.Builder`, then `Invocation`.
`Invocation` objects can then be executed to generate `Response` objects, or
typed responses, depending on the overload used.

------
overgard
Honestly I think ease of use of a library is way more important than
implementation elegance. How many people are ever going to look at your
libraries source code? .001% of your users? I use the C++ stl every day, but
if you look at the implementation it's basically horrifying, but I don't
really care because the interface is straightforward and does its job.

My main issue with fluent interfaces though is you can end up with objects in
weird half initialized states, and there's generally combinations of
parameters that are incompatible which the type system can't help you with.
The point of constructors is you don't end up with semi-initialized objects

~~~
jlg23
> Honestly I think ease of use of a library is way more important than
> implementation elegance. How many people are ever going to look at your
> libraries source code?

Poor idiots like me who get hired to help get the hard, last 10% done.

Given the small percentage of projects that actually have a lifespan of more
than a few months, I'd still chose usability (by devs) over elegance for
prototypes or MVPs, but one should never forget that this can incur a huge
technical debt.

PS: I do not consider the article's proposed solution elegant - quite the
contrary.

~~~
overgard
We probably don't disagree here, because "clever" interfaces generally annoy
me, I just think in terms of priorities interface matters more than
implementation elegance. A confusing implementation might waste a few people's
time, but a bad interface can waste hundreds\thousands of peoples time. (if
you ever worked with COM or Win32 apis you probably feel my pain)

------
wiradikusuma
As a lazy user of libraries, I disagree.

Maybe there can be a middle ground, eg a fluid interface generator ala Lombok.

~~~
jpitz
They (Lombok) are already doing so:

[https://projectlombok.org/features/experimental/Accessors](https://projectlombok.org/features/experimental/Accessors)

>> "Experimental because: We may want to roll these features into a more
complete property support concept. New feature – community feedback requested.
Current status: positive - Currently we feel this feature may move out of
experimental status with no or minor changes soon."

------
crabl
I like the "pipeable operators" approach that RxJS takes in order to prevent
this. Each instance of an Observable has a ".pipe" method which can transform
the observable stream in some way and returns an Observable
([https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-o...](https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-
operators.md)).

It would also be nice to have an operator like the proposed "pipeline"
operator, à la F# ([https://github.com/tc39/proposal-pipeline-
operator](https://github.com/tc39/proposal-pipeline-operator)).

------
jlg23
I am not sure I want:

* HTTP-requests done in constructors

* Assertion classes that wrap objects of interest which in turn must be extracted from their wrapper

* Specific methods that assert that one very specific attribute has an equally specific integer value.

~~~
mcguire
The before state may or may not be bad, but the after state is hideous.
Multiply nested constructors?

