What I Love and Hate About Flow

Thanos
8 min readSep 14, 2017

--

I’m not talking about incontinence, that’s flomax!

I mean this flow:
https://flow.org/

TLDR;
The Good:
* Eliminate a whole class of errors
* Easier refactoring
* Helps document things

The Bad:
* A false sense of security from the misconception that this will have any effect at runtime
* More damn errors to fix before your pull request gets merged in
* It’s a big time investment

The Verdict: Use Flow (or Typescript) but don’t half-ass it. Go all out and require it in all files.

Javascript Sucks Okay

But it doesn’t matter, I love it. I have an addictive personality, but thankfully, coffee is my main vice these days. As much as I’ve tried to kick the JS habit, I always find myself relapsing. Like some kind of virtual DOM crackhead..

I read a lot about functional programming. And Haskell. I find all kinds of things that I like.

Advanced pattern matching, advanced (and strong) type systems, miraculous compilers, auto-currying, immutability enforced everywhere, compose and pipe operators. Those things are so nice that they make me want to cry.

Yet I think that I am in the slightly unhinged class of people that prefer the overall syntax of Javascript. To me it’s so familiar. I’ve been writing Javascript (with varying degrees of success) since high school. That adventure kicked off around 1998-ish.

The first language I learned was C. And Javascript syntax is identical to C in many cases. So it seems the most natural to me, curly braces and all.

So, I’ve been writing Javascript for almost 20 years. Although, back then I had a completely different take on it. `document.write` didn’t just seem like the shittiest thing ever, it actually was!

But after document.write I leveled up to jQuery. Then node. Then Backbone and underscore. Then we had a regression with Angular 1. Then another leap forward with React, Redux, eslint, mocha, TDD and BDD and every DD you can think of. And webpack (which I can’t stand configuring but still use every time).

Then there’s Flow. It adds types to Javascript. Big whoop. We’ve been listening to Anders Heilsjrbord give Typescript interviews for 5 years now. (To be clear, the guy is a genius.)

I don’t really care about the argument of Typescript vs Flow because I don’t really care. To me they’re different and the same and have their individual quirks. The more interesting argument is ES6 with types vs ES6 without types.

Notice that I said ES6 there. Because as far as ES5 is concerned I’m —

Credit: document.write

I will configure webpack until my eyeballs bleed, if it means I can have the goodness of ES6 when I wake up in the morning. Along with a dark and decadent cup of Folgers.

This isn’t my first time people. I’ve been around the block, I originally started as a .NET programmer. I’ve worked in the statically typed salt mines with the rest of you.

Similar to my stance on ES6 though, I don’t really want to go back to Javascript without types again, if given the choice.

Eliminate a whole class of errors

You tried to call .map on an `object` doofus! Those days are gone my friend. Flow will complain OH will it complain.

There is no naem property on the PersonType. You’re right Flow, there’s not. But there is a name property. Thanks for pointing that out special buddy.

Oh you updated a function signature with new argument types, changed all the call sites but forgot to change the calls in your tests? Don’t worry, Flow will pop out of the bushes like Clippy!

FML

Even if ESLint and your unit tests miss an opportunity to take shots at your ideas, Flow never will. It will nitpick you until you’re just tired of winning.

Create a new class of errors (if you’re not careful)

WAKE UP!

This is my biggest concern when using Flow. Heed my warning or risk turning Flow into the old foot-gun.

How you code
How I code

Okay so here’s the deal. Flow is kind of like the United Nations. It has great and just ideas. It wants everyone to live in harmony and makes suggestions all the time about what’s right and good.

But Flow doesn’t have an army, that is to say, Flow has no power at runtime. None. Whatsoever.

You could call an API and give the return value a type like this:

type SeussType = { 
thing1: string,
thing2: string
};
// or even bettertype ThingType = string;type SeussType = {
thing1: ThingType,
thing2: ThingType
};
// and thentype ApiResponseType = SeussType;

Notice that in that last line, `SeussType` is not ?SeussType, meaning we are NOT expecting a null or undefined value here.

Which will work great when our API, or some other team’s API, or some other company’s API returns null. Or undefined. Or “potato”. Or “Not found”. Or whatever other silly thing.

Just because you made your type not-nullable, doesn’t mean it’s correct. Be pessimistic. For the most part, make types from APIs nullable. Or don’t, just be aware of this problem.

If you mark it nullable, then Flow will endlessly agitate you when you don’t handle the “but this could be null” edge case. But at least it will be handled. Or you could get a “Can’t call method zigZag on undefined” error in production. Your choice.

Also, Flow will shit all over this person.naem but will happily approve this `R.prop(‘naem’, person)`, so be careful out there.

Easier refactoring

The more people on your team or the larger the project or the crazier the requirements, the more you will want Flow in your tool belt. Refactoring is the reason.

For instance:

How Trump codes

Just (not) kidding.

Like unit tests, when you want to refactor the code (which you should want to do all the time) Flow will help you catch some of the low-hanging regressions.

Every time you want to change that function signature, or change a type signature or rename something, Flow will be there lighting up the problems. Turning something that can feel daunting in a dynamic language, into something routine.

More damn errors to fix before your pull request gets merged in

It’s true. This will add one more step to your pre-pull request workflow.

Fixed the lint errors? Check.
Tests are passing? Check.
Rebased my insane asylum commit history? Check.
Searched for debugger and console.log and removed them? Check.

Now it’s time to add a new member to the family. Fix those flow errors. Surely you DON’T have 16 Flow errors, buuuuut you might.

Sure it’s more work. It’s more mental energy to maintain this whole voluntary type system. And provide signatures and update them and fix errors.

But sometimes Flow is actually pointing out regressions that you may have missed otherwise.

Helps document things

So you tell me, who wore it better:

// this
const printName = name => ...
// or this
type NameType = {
first: string,
last: string
};
const printNameOhShitItsAnObject = (name: NameType) => ...

Glancing at the first signature, you could be forgiven for thinking that name is a string. You could glance at the code and figure out that it’s not, but that takes a small amount of mental energy. Or it could be very unclear from the function body what the full type looks like.

Across an entire code base this mental bookkeeping adds up. Especially so with larger, more complex types. So like our best friend TDD, type systems document what we’re dealing with. Another clue as to what the expected behavior should be like.

It’s a big time investment

Not this
This

You will spend a lot of time haggling with Flow. Most of the time, you will be wrong and Flow will be right. Sometimes Flow will get a little hot-headed and $FlowFixMe will be the only option.

The point is, it won’t all come easy, especially when you first start out with Flow. You’ll be confused about this or that. You’ll have to google how to do certain things. You’ll debate how you should design a type.

When you refactor you will have all these “annoying” things to fix. But all these annoying errors are all the things that you would have missed, without the type system to guide you.

Conclusion

When I first learned of Flow, the best thing about it was that you could include it one file at a time. You could start small. Build up your type system slowly.

That’s great for existing projects. But the best thing about Flow is also the worst thing.

It doesn’t do much good to have types defined for a function, if you can call the function anywhere with anything you want. If Flow isn’t turned on for that file, you’ll be oblivious to all the errors. So, in that case, what’s the point of using Flow?

It may seem like you could simply turn Flow on in the file with the calling code too. And you can. Then you’ll catch many of the problems there. But then that file has dependencies that also don’t have Flow turned on.

So you have this whole chain of dependencies without types. At any point your type system could break down and you are oblivious.

That’s why I recommend that you go all in when using Flow. The type system can’t work if it’s only in one file. Hence the word “system”.

If you want to use something like prettier, which formats your JS, it’s almost all win, all the time. Prettier has a minimal up-front time investment and virtually no cost after that. It’s an easy decision.

With Flow, there are big gains to be had. But if you want them then you have to pay the cost to be the boss. You have to maintain your investment in Flow and in the type system that you create, which is not a trivial investment.

But for me it‘s an easy decision. What’s a bug worth? $100? $1,000? $1,000,000?

It’s all relative to your company, your business and your customers. Over the lifetime of a project, Flow won’t catch one bug, it will catch many. Even if you think you’re God’s gift to programming, Flow will find many errors.

The question is, what’s that worth to you?

If you found this article enjoyable then you can follow me on Twitter at:
https://twitter.com/halistechnology

Or better yet subscribe to my blog:
http://halistechnology.us6.list-manage2.com/subscribe?u=1b0c0366b2&id=02d93c6730

--

--