Simple, concrete explanations of web technologies.

March 15, 2017

Some nice people on Reddit pointed out that in this article I got the definition of framework wrong. Sorry about that! Just imagine every time I say "framework" that I actually say "thing" instead, and you should be good. :)

As a web developer, probably the question that keeps me up at night with the highest frequency is: what does this new-fangled framework do? Their webpages don't give you any clues. They all say exactly the same thing - "Foo Framework is designed to make your coding experience fun again, by making your code more easy to reason about and simpler to understand!" (Seriously, you could probably paste that into one of webpages of the frameworks du jour and no one would ever notice. Except the "Foo Framework" thing, that's a bit of a giveaway.)

Or maybe the website does give a clue, but it's written in a way that's hard to understand until you already understand the framework, in which case it's pointless. A good example is React. (Sorry, not picking on React! It's a great framework, and every framework has this problem!) React's website says:

"Declarative views make your code more predictable and easier to debug."

This claim is 100% factually true, but unless you're already using React, you probably have no idea what it means to have a 'declarative view'. So it really doesn't help anyone who wants to figure out whether they should use React.

Here's the problem. Like React, some fraction of these frameworks actually do exactly what they say. They really will make your code easier to understand and reason about. It's great, and it's totally something worth exploring and understanding! But since you can't learn just by reading the webpage, what you really need is someone to suffer through understanding them until they can explain cogently how they can help you on a day-to-day basis.

Well, you are in luck, because that person is me.

Simple, concrete examples are the best ways of understanding things. In each case, I'll give you an example of what sort of problem the framework will help you solve. If you have that problem, you should consider using the framework! If you don't, it may not be for you yet, but keep it in mind in case the situation changes! I don't advocate using every framework or library unless you understand why you need it and have deemed it necessary. If you toss a bunch of frameworks into your project, you're going to be adding a lot of complexity to a project where it may not be necessary.

Immutable.js:

This one's easy - and yet curiously I've never seen my explanation for why immutability is necessary anywhere. People always start talking about data races and concurrency, and while it's true that immutability helps with threaded environments, lots of people use immutable data structures in single-threaded environments. Like, for example, JAVASCRIPT - you know, that language that has a super popular immutability library called immutable.js. Why do people care about immutability, even in single-threaded languages?

Say you have a mutable set of points: { { x: 1, y: 2 }, { x: 1, y: 3 }, { x: 2, y: 2} }. Now, we mutate the first point to be { x: 2, y: 2 }. Do you see the problem? We've mutated point such that now the set has two points with the same value in it. But the set has no clue that anything has changed. How could it? We never touched the set at all, just some of it's internals, so it has no idea anything changed. And expecting point.x = 2 to have side effects on a Set data structure seems totally insane.

What went wrong?

Imagine if the points were immutable. How would we change the first point? Well, we can't mutate it any more, so we have to remove it and insert a new point. But hey, as soon as we add in that new point, the set knows that something is changed because we called a set method.

So that's one strong advantage of immutability over mutability: we can't change the internals of an object without informing the object.

React:

React is necessary because when you're using a non-React library, you have a ton of code all over the place that updates the DOM. React does a lot, but to me the biggest advantage of React is organization. It takes all that DOM-updating code, de-duplicates it, and puts it all in one place. Now instead of wondering which function set the class of that div, you know exactly where it happened: the render function. Because all DOM updates happen in the render function.

Example. Say you have up and down buttons for adjusting brightness. In both of those buttons you'd have to do $('brightness-div').adjustBrightness(newValue). Not so bad. Oh wait, you also have a button which automatically sets the brightness somewhere else, so you need more for that guy too: $('brightness-div').adjustBrightness(getAutomaticValue()).

This is OK, but now what if you want to not only adjust the brightness-div, but also the brightness-span and the brightness-textbox whenever the brightness changes? You now have to hunt through your code in n different places to update the code. In the example, it's not *awful*, but in practice you're looking at updating O(n) different places in the worst case, where n is the number of functions that change the DOM. That sucks. Imagine if it could be O(1)!

Much better is the React way, which would replace all the direct div access with an update of a shared model. Now you'd just go this.model.brightness = newValue in each of the 3 places. React will provide you with a render() method which you use to create the entire view. This function gets called whenever you update the model. In that function, you'd set the brightness of your div appropriately. And when the DOM changes and you need to update more elements, all you have to do is update the render() method to set the brightness of the span as well.

React is rather mind-bendy for those who have never used it before, because they have to go from "the DOM is a thing which I will modify as I need with lots of different functions" to "I will handle every single state the DOM could possibly get to in a single function." However, it turns out that the second ends up to be much better. The two advantages are:

  1. All your view-touching code is now grouped in one place. I'm a strong believer that if you have two bits of code that are sometimes changed at the same time, and they can't be unified somehow, that they should at least be close in proximity to the codebase. It saves you the time of having to go hunt down that other chunk of code - it's right there. (A good example of what I'm talking about would be the views for a tool, and the view for the properties of that tool. There's nothing not DRY about having both, but if you change one, odds are you'll end up changing the other as well.)
  2. It's much much easier reason about the state of the view in your app. It's very difficult for people to trace the flow of code through a bunch of different functions scattered around the codebase that may or may not be called. It's much easier to trace the flow of a single function.

A lot of people say that the hip new frameworks don't carry any revelatory ideas with them, and won't really change the way you code. To anyone who hasn't encountered declarative programming before, "React.js" is a great 1-word reply to this nonsense. (Of course, then they'll go and look at the website and get confused. Sigh.)

Typescript (or Flow):

I know I claimed that tools should be used to solve problems, and they shouldn't be used until you've encountered that problem.

I lied.

Literally everyone using JavaScript should be using TypeScript (or flow).

"Wow, that's superlative. Do you really-" Yes.

"But even if-" Yes.

"But what about-" Yes, even your dog.

Why?

Short answer: it resolves a bunch of serious problems with JavaScript as a language, it will save you inordinate amounts of time on large projects, it takes practically no time to learn (It's Just JavaScript!), and it adds practically no overhead. The quantity and scope of problems it solves is so vast that it's impossible you haven't already encountered them. (Yes, I even use it on 20-line files. It takes me like 10 seconds to type tsc --init, and I make bugs even in 20 line files that TypeScript (or flow) would find.)

But I promised concrete examples, so I'll give you some.

How do you refactor your code? Say you're at work, you have tens of thousands of lines of code, and you want to change the name of a method. How do you do it?

You probably do something like open up Sublime's "Find in Files" functionality, search for your method name, and then meticulously change every single one. Take a moment to think about this process. In how many ways could it undetectably go wrong and break production?

  1. You made a typo in the new method name.
  2. You accidentally changed the wrong method.
  3. You aliased the method to another name somewhere, and Find in Files didn't catch it.
  4. You accessed the method with bracket notation and strings somewhere, and Find in Files didn't catch it.
  5. It's 12:31 A.M. Overcome with exhaustion after changing the 134th method name, your head hits the keyboard, backspacing the "dont" from a "dontBurnDownServerRoom" method call. You wake up to the smell of smoke an hour and a half later. Strange, it's as if... you muse, before seeing your computer screen. Panic overtakes you. You attempt to dial 911, but unfortunately you had recently installed the node.js operating system onto your ph...

How many times have you done one of the above? (Hopefully not #5?) If the answer is "never", you're either very new to programming (which is fine!), or you're not self aware. (You could also be an incredible programmer who never makes mistakes. I am unfortunately mortal.) It's very easy to develop a blind spot around the bugs that you encounter. I'd challenge you to keep a journal of all the bugs you encounter over the next month, and then ask yourself or another programmer how many of them could have been prevented with static typing. This exercise can be quite illuminating.

The process of refactoring JavaScript so fraught with errors, it's insane that any of us deals with it at all. And in fact, many of us don't. I've worked jobs that, in production, never refactored JavaScript, because the chances of making a bug were so high. Take a moment to think about how totally insane that is! (If it doesn't sound insane, that's because you've been brainwashed. I'm sorry you had to find out this way.) Sane languages are not like this.

Some will say they solve the problem of refactoring with TDD, which catches all errors. Alright, not knocking on TDD or something, it's great, but it has its uses, and being an ad-hoc type checker is not one of them. While you wrote 5 tests to ensure you didn't misspell that method name, I hovered over my function to observe that TypeScript's type inference was working correctly - as it always is - and then went to have a beer.

We are programmers, and programmers make tools. When we run into the same problem over and over again, we don't avoid it or repeatedly solve it over and over again. We make a tool.

I'll tell you how I solve this problem it in TypeScript: I right click and choose refactor, and I'm done. Even if change wasn't a right-click away, I'd just change the method name, then look at the output from the TypeScript compiler to lead me where I need to go. Yes, it's smart enough to detect aliases, and even indexing! If I make a typo, it'd let me know. If I changed the wrong method, it'd let me know, because the parameter types wouldn't match.

I could go on and on with different examples. How do you add a parameter to a method in JavaScript? How do you find the definition of a function? How do you determine the keys of an opaque parameter object while you're coding? How do you rename a parameter? How do you ensure that you didn't make a typo in an object access? How do you make sure you handled null return values properly? How do you find all the methods of a class?

The JavaScript answers: you struggle with ctrl-f, you struggle hoping ".prototype.[function name]" disambiguates your function name enough to use ctrl-f, you struggle with trying to trace all the code paths into a function, you struggle with find in files, you struggle with naming your method in a meaningful way that indicates it has a null case and hoping that other programmers understand this, you struggle with trying to find the class definition.

In short? You struggle.

The TypeScript answers: right click, cmd click, mouse hover, right click, red squiggly underline, optional types, autocomplete.

If it's not obvious yet, this is something I think is really important.

Redux:

Redux solves the problem of deeply nested dependencies.

This is why a lot of people don't get Redux - they don't have deeply nested dependencies, so it doesn't really change their code in any way, except to add boilerplate. But if you have reached the size where you do, it's a life changer. Seriously.

Say you have a bunch of nested objects in your project - your App has a Menu Bar, which has a Menu Item, which has a Label. Now, the App has the filename, but the Label needs it, because it's a Save "Filename" label. The traditional way would be to pass down the file name from App to Menu Bar to Menu Item to Label, which *works*, sure, but it's kinda crappy. You have to update 4 classes just to pass some data around. And if you want to refactor your Menu Bar, or move the Label somewhere else, you have to remember to pass the data a different way now.

The best way to understand how Redux handles deeply nested dependencies is that it gives you a giant global variable of all the state in your app. ALL OF IT.

You now know exactly what problem Redux solves. If that sounds good to you, feel free to skip to the next section. If you want to learn more about how it works, keep reading.


Still here? Alright, cool.

You may be thinking, "handling nested dependencies by turning literally every variable into a global sounds like 'handling' the problem of ants in your bedroom by nuking your house. It may solve your problem, but it introduces a number of worse problems."

But wait just a sec. Maybe we can make global variables safe. But to do that, we need to understand what's wrong with them in the first place.

What's really wrong with global variables?

Let me answer with a concrete example, as is the norm. Say you have a global variable NumberOfTodoItems in your Todo App. (The old joke being web developers only have enough time to learn a framework well enough to make a todo app before the next framework comes out.) Every time someone adds a todo, you increment the variable by one. You read from the variable to display to the user the number of todos. All is well!

Now, you make a button to delete todos. You decrement the variable. All is still pretty much well. Hey, who said that global variables were bad? They're making your life so much easier.

Now you make a button to delete all todos with a tag. Huh, gotta change that variable again.

Now you make a feature to automatically add todos from your email (hey, this is a fancy todo app). Gotta update that variable again.

You add a bunch more features that adjust the value of the variable.

Now, having done all of the above, you start using your todo app, and after a while you notice - hey, the label showing the number of todos you have is wrong! What gives? The last question in this chain of questions is:

What function set that variable to be the wrong thing?

And the answer is, you have no freaking clue. It could have been ANYONE, across your entire app. If it would have been a local variable, you would have a great clue to start finding your bug, because you'd know that only the guys who share scope with the variable could have screwed it up. If it was only in one class, you'd go straight to that class and start hunting. That would be awesome, because instead of your massive code base, you'd only have to look through one file.

That is why global variables are bad. If they get the wrong value, you have no idea where to start looking.

Let's get back to Redux. Redux gives you the magic of being able to use global variables. But it gives them to you in such a way that you can use them safely.

The reason it works in Redux is two-fold. First, components rarely deal with the entire state. Rather, you slice it up and give each component only the pieces it needs. The Label can tap into the global variable, grab the file name, and be done. This is mostly just for sanity, though. No bug has ever come out of reading from a variable. It's always the setting part that gets us into trouble - because we set it to the wrong thing. Hey, maybe those immutable.js guys have something going for them after all...

Second, it gives you some regimented ways of updating the global variables. It forces you to update them only from within things called Reducers. Just like we agreed that React clustering all your view updates together was a good idea, Redux clusters all your state updates together, and it turns out to be a good idea too. Your Reducers will only operate over slices of your global state, so when you're hunting down the "who changed my variable" problem, you'll have a great place to start. (Redux gives a bunch of other useful ways to track down how your state got updated, all made possible by this architecture.)

Webpack:

This one's the easiest of all. Eventually, you're going to need modules. So, you install Webpack.

THE END. :D

Back


As you may have noticed, I don't have comments on my blog. Instead, I do coffee-comments! Email me at johnfn@gmail.com and ask to meet up for coffee and discussion. Coffee is on me for the first 5 emails that convert into meetups. :)