Hacker News new | past | comments | ask | show | jobs | submit login
Fluent Interfaces Are Bad for Maintainability (yegor256.com)
53 points by ingve on Mar 20, 2018 | hide | past | web | favorite | 51 comments



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...


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().


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 :)


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.


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/

2) https://github.com/JulienPalard/Pipe

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

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


FORTH ?KNOW IF HONK! ELSE FORTH LEARN! THEN

My point is that FORTH is point-free!

https://en.wikipedia.org/wiki/Tacit_programming

Tacit programming, also called point-free style, is a programming paradigm in which function definitions do not identify the arguments (or "points") on which they operate. Instead the definitions merely compose other functions, among which are combinators that manipulate the arguments. Tacit programming is of theoretical interest, because the strict use of composition results in programs that are well adapted for equational reasoning. It is also the natural style of certain programming languages, including APL and its derivatives, and concatenative languages such as Forth. The lack of argument naming gives point-free style a reputation of being unnecessarily obscure, hence the epithet "pointless style."

https://en.wikipedia.org/wiki/Concatenative_programming_lang...

A concatenative programming language is a point-free computer programming language in which all expressions denote functions, and the juxtaposition of expressions denotes function composition. Concatenative programming replaces function application, which is common in other programming styles, with function composition as the default way to build subroutines.


This is actually why I love working in Clojure. There are a number of threading macros to make this pipeline stuff work in different scenarios (put the return value in as the first, last, or variable argument; stop and return nil if you ever get a nil to prevent NPEs from happening downstream; only do this step is a condition is met). A simple example:

    (-> id
        get-account-details ;; {:plan "premium"}
        :plan               ;; "premium"
        get-plan-details    ;; {:price "$100.00"}
        :price)             ;; "$100.00"
It's equivalent to something like this in Python:

    account = get_account_details(id)          # {"plan": "premium"}
    plan_name = account.get("plan")            # "premium"
    plan_details = get_plan_details(plan_name) # {"price": "$100.00"}
    return plan_details.get("price")           # "$100.0"


#!/bin/bash -e -x

(

grep stuff file | wc -l &

expensive_task in/ out/&

)

join

ls out/ | parallel reducers > result


Forth, maybe?


    someObject
    |> doThis
    |> doThat Thing
    |> doThat OtherThing
    |> dontForgetToPayTheBill
or

    let getErDone = 
        doThis 
        >> doThat Thing 
        >> doThat OtherThing 
        >> dontForgetToPayTheBill

    someObject |> getErDone
[not just pseudo-code, that can be compiled with the right definitions]


And that in essence is not really any different than an extension method in C#. At least not from maintainability point of view.


The function chaining is a game-changer, if you're talking about maintainability...

The temporary variables are eliminated,and the type-based chaining self-verifies for new modifications, neither of which you get in C#. Also, thanks to better support for higher-order programming, common post-launch activities like performance upgrades and async tweaking happens with stronger guarantees and oodles less code.

In terms of essence: namespace shielding and modular composition gives numerous opportunities for logical encapsulation and complexity management that are not feasible thanks to C#s extension semantics and extension method requirements. All of the above can be defined in one type, for example, instead of multiple... The end product is just wildly more cohesive in F#/OCaml, with more options throughout to produce more maintainable solutions.

F# can also use (a slightly improved), version of C#s extension semantics, as needed. But the functional composition, currying, Discriminated Unions, and exhaustive pattern matching in conjunction with the module syntax yield a final product that is markedly smaller and distinctly more maintainable than it's C# counterpart.

Not to mention: from a syntax and readability perspective this just destroys its Fluent Interface counterpart, in about a third as many LoC. Start looking into things like custom operator support, DSL support, computation expression support, etc, and the maintenance improving possibilities just blow the .NET baseline out of the water.


I completely agree that F# is superior to C#, I use it when I can, but the gain from "fluent extensions methods" in C# is significant, and improves the code quite a bit. The main benefit over F#, is the pool of developers that understand C# is significantly bigger. This is a parameter I need to take in to consideration because of the developers I hire, there is few and far between the one that grok F# and FP in general.


Fluent extension methods are better than no extensions at all, I agree :)

I tend to look for general competence in new devs, because that persists across platforms and pivots. In general it's odd that we expect devs to know every language, tech, and cloud platform under the sun but draw the line at basic mathematical concepts mapped into programming decades ago... Using both sum and product types is fundamental to proper modelling, IMO.

I also think comparison between C# and F# based on "tricky" FP concepts is really odd in modern times...

On the one hand: if a new hire can't grok FP in F#, how can I trust them to do serious work in C#? OOP & FP synergize wonderfully, and the language has been openly trending towards FP ideals for a decade now. Those FP concepts are now modern idiomatic C# and also very important in tricky domains...

On the other hand, if I'm doing work that makes me think correctness is even a little important why would I want to do it in a language almost-as-good that provides no guarantees and requires more complexity? Why bring a footgun to the party if I'm already worried about quality and some rando new hire? Seems like people who don't grok the basics would do better in a stricter environment with less room for f-ups.

For my money I'll readily take a mild bump in on-boarding costs for productivity, production, and correctness guarantees forever. Not to mention the improved caliber of candidate one gets from finding self-selected A-grade techies and advertising an aggressive dev environment.


In the current situation, getting a competent dev that seems to be able to grasp FP naturally, does not require a mild bump in on-boarding cost, but a significantly higher salary.

Right now I'm hiring developers right out of school and put them through a mentor program, hoping that they turn out good.


Kotlin solves the problem elegantly with the 'apply' and 'run' extension functions.

  fun foo() =
    Runtime.getRuntime().apply {
        addShutdownHook(Thread { println("Bye!") })
        traceMethodCalls(true)
        exit(404)
    }


Which is awfully similar to JavaScript's much maligned 'with' statement.

Never understood the FUD and 'considered harmful' screeds against it. Yes I'm aware of the performance/optimization concerns, but honestly, the whole javascript language is a massive performance/optimization concern and we've managed pretty well.

Simple things like changing style properties on DOM elements:

   with(element.style) {
       display = "block";
       marginLeft = "5px";
       background = "green";
   }
And of course with 'with', everything is a fluid interface, and with the flexibility of being able to enter code between the function calls.


Kotlins `apply` seems exactly like the much maligned `with` statement from JavaScript, which has been deprecated.

Why is it elegant here, but deprecated there?


i suspect some or many of the reasons it's discouraged in js do not apply to kotlin. as we all know, js has... unique constraints in it's design. why is it deprecated in js?


Took the words right out of my mouth (literally, and 5 minutes faster).

Why? I'd say because Douglas Crockford.


With `Arrow`, a Kotlin FP library you can do that

"foo" pipe capitalize pipe toUpperCase pipe reverse


> While I don’t like Fluent interfaces at all, I don’t like the alternative presented much either

If you pull at this sweater thread the end result is working in a language powerful enough to model the 'human readable' bit as structured code, and let mini-DSLs flourish in code bases and interfaces... This may entail leaving OOP partially behind, but it's highly achievable (Scala, F#, Haskell, LISP, etc).


Many times programmers do not even know that their language contains something akin or better than VB's `with` statement. For example, in Ruby `instance_eval` or the even more powerful `instance_exec` could be a drop in replacement for half the stupid fluent interface libraries out there.

The one exception I'll make is query builders like ActiveRecord, as the submitted post mentions. It's hard to figure out how else you can dynamically pass `User.where(age > 18)` to a controller that has the filter to only include men and to paginate by 10 records at a time. I'm not saying it's not possible, it just seems like the one case where the fluent interface is worth the pain it brings. Even so, I really hate them even if I love Rails. Also, scopes are so brittle I avoid them whenever possible. They seem clean and elegant at first, but they're hard to combine and that combining is really only needed as the application gets more and more complicated.


How do you feel about LINQ[0] (specifically, the query expression syntax) as an alternative to the query builders that you mention? From reading this page[1], I'd guess that the query expression syntax compiles down to the same code that the method-based syntax does.

I think it looks great, but I've never had a chance to actually use it. None of the languages that I use regularly have anything even close to LINQ's query expression syntax.

[0] https://docs.microsoft.com/en-us/dotnet/csharp/programming-g...

[1] https://docs.microsoft.com/en-us/dotnet/csharp/programming-g...


Haven't used it but it looks like a syntactic improvement even if some of the underlying issues like difficult debugging may remain persistent. Though really, I shouldn't really answer with a gut opinion. I haven't even toyed around with it.


That feature is, of course, much older than Visual Basic. Pascal had "with" a couple decades earlier.


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.


> 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?


Personally I like fluent interfaces because they enable IDE users to write correct code quickly. That's a big win in languages like Java.

The changes you make to enable fluent interfaces underneath are just boilerplate code that is unlikely to have errors or require much maintenance. At least that's my experience.


Yeah. It's a tool, use it in moderation when most appropriate and you should be ok.

Yegor often seems to take an extreme position, which makes for a good discussion especially as there's usually some truth in what he says but I suspect there are no silver bullets to find here.


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.


> 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.


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.


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.


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.


Came to say the same thing. In Rust, you can also have fluent interfaces that return different types as you build, which means that you end up being more typesafe throughout the construction process.


Even in Java: if you want your fluent builder to be extensible, that's easy enough to achieve with a base class that returns a generic self type in the fluent methods. If lack of extensibility is the strongest criticism of fluency, then fluency must be great.


I use extension methods extensively, and it keeps the code closer to where it's used and the classes they operate on clean. It's a huge win for maintainability.


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.


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.


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/

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.


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.


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...

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.


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


> 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.


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)


As a lazy user of libraries, I disagree.

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


They (Lombok) are already doing so:

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."


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...).

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


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.


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




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: