Static Typing: a Personal Journey
Last February my company sent me to the Colorado School of Mines, my alma mater, to recruit computer science grads. Many of my former classmates were there as well, and I started chatting with a guy who was telling me about his job where he wrote Java every day. Positively gushing, he exclaimed, "I just love Java!"
I was happy for him, but let me tell you that was not my experience with the language when I had to write it in college. It felt extremely verbose and laden with unnecessary syntactic cruft. But worse than anything was its type system; it constantly felt like I was appeasing my robot overlord who demanded that I label my new variable as an integer or a string. It certainly did not feel like it was there to help me.
Near the end of my college experience, I happened to take a web apps course where, over the course of a semester, we built out a Ruby on Rails application together as a class. There was one word to describe my experience: exhilarating. Ruby is a very expressive language and I found that I could largely just write what I was thinking and it would usually work. The curly braces, semicolons, and angle brackets were all gone, and there was very little standing between my ideas and a fully functioning app. It was a wonderland.
I think database work, modeling data, and other back-end tasks are interesting, but I truly love front-end work. There's something about the work of building an interface between data and a human being that I can just lose myself in. There's nothing quite like building an app that someone will find genuinely useful, intuitive, and beautiful. When I got my first software engineering job, I knew that was where I wanted to be, but there was just one problem: I'd have to write JavaScript.
When I had first taught myself JavaScript in 9th grade, I picked up a copy of The Book of JavaScript from No Starch Press and struggled my way through the exercises, trying to make them work in Internet Explorer 6. If something didn't work, I would try alert()
ing a variable here or there, but I eventually came to the conclusion that JS was a terribly confusing language with terrible developer tools with terrible cross-browser support.
When I started my first software job, though, I found out that my company was using a cool language called CoffeeScript that brought a lot of the niceties of Ruby to front-end development. Around the same time we were making the switch from Angular 1.x to React, and for the most part, it was a pretty decent setup.
At my next company we were still using React, but the engineers had decided that rather than using CoffeeScript, we'd use this newfangled version of JavaScript called "ES6." I had plenty of doubts, but when I jumped into the work, I was honestly shocked at how decent the language felt. We no longer had to do all these weird var self = this
tricks; everything just pretty much worked the way you would think it should. I was on a project to build an AI chatbot in React which was pretty interesting and fun, but there was one thing that I was finding less and less fun: getting paged when something wasn't working as intended.
"How did I miss this? How did this get through QA? How did my product owner miss this in his final approval?" I fancied myself to be pretty good at avoiding errors with null
and undefined
and NaN
and the dozen other gotchas in JS, but it turns out that when you're working on a team and multiple people are touching the code, it really doesn't matter how good I might happen to be at avoiding those errors, and it doesn't even matter if everyone on the team is good at it; when working with others, things fall through the cracks. Miscommunication happens, and an API you thought would work a certain way doesn't.
I knew I was tired of undefined is not a function
, but I didn't know how to solve that problem other than by just not making mistakes. The problem, as I just highlighted though, is that such a solution wasn't good enough. And when you guarantee to your customers that your app will work and will have a certain uptime, you need to actually meet their expectations.
It was around that time that I stumbled upon Dr. Boolean's Mostly Adequate Guide to Functional Programming in JavaScript, where it finally clicked for me that my long-held hatred for static typing began to give a little. He described the Maybe
type and how it could more accurately express the possibility of missing data in an application, and I knew in my gut that this was what I was looking for to finally slay that dragon, undefined
. Maybe I won't get paged anymore (or at least less often) if I can be more confident in the code I ship! Maybe I will have to tackle fewer bug fix stories!
I began to construct a safety net of sorts around the code I wrote; in addition to automated tests, I started using Facebook's Flowtype and ImmutableJS to have more visibility into and clarity around the data flowing from our database to our users and back again. Recently I switched from Flowtype to TypeScript to have an even more reliable tool in my belt. In addition to these, we use Lodash/fp, True-Myth, and a myriad of other tools designed to keep us honest about the code we write-code which our users are paying us money to use.
This is why we write tests, yes, but trying to force a test suite to provide the sort of granular insight into your code as you're writing it is like trying to force a square peg into a round hole. I like to think I'm a moderately decent programmer, but the number of times TypeScript catches me and says, "Oops, let's take a step back. That's not actually going to work," or, "Looks like you need to add one more prop to your React component before it'll actually work," helps me to realize that I'm just a finite human being and I can't keep the entirety of the code in my mind all at once.
Taking a step back, it's pretty remarkable that someone like me who used to loathe static typing could have had such a change of opinion. I used to think one of the cool things about dynamically typed languages was that they allowed you to quickly prototype an idea without having to cross every T and dot every i. I actually still do think that, but I no longer want to ship prototypes to users unless that's what they're expecting. To put myself in their shoes, if I'm going to pay money to use your software, it better well work or I'll take my money elsewhere. Don't hear that as entitlement; if Netflix has a hiccup with a video and says, "Sorry, try again," I'll try it again and move on with my life. But if the software simply doesn't do what it says it will do, that's a different story.
I love statically typing my JavaScript, and one of the coolest things I like about it is being able to write my UIs heedlessly; for the past few days I've been building out a new feature for our React Native app and I haven't even opened it in my simulator yet. TypeScript is helping me to put the blocks together correctly, and when I open it finally, I expect to just have to do some massage work to style it and lay it out correctly.
What's next? I would say one of the primary downsides to my current tech stack is that I had to bolt it all together myself. That means things don't always line up exactly right, and there's also the infamous node_modules/
cost to all this. I've been evaluating Elm and Reason precisely because a lot of the pieces I've cobbled together come as part of the package in those languages. My ability to write correct code quickly has improved as a result of some of the tools I currently use, but I long to use a language designed around this workflow.
To summarize, I think static typing makes for a terrible master, but an excellent servant. Nobody likes the feeling of having to placate a compiler just to run the code, but static typing doesn't have to feel that way. When I write Elm, it's like I have a copilot pair-programming with me who is always kind, never judgmental, and quick to help me fix my mistakes and stay in the flow. I won't sugarcoat it: sometimes TS or Elm or Reason or many of the other well-designed type systems can still make it feel like you're just placating a compiler, but I don't think that's a reason to reject them out of hand. They're powerful, valuable tools that can help us think clearly about the code we write as individual programmers, but more importantly, as teams of people building software together.