Static Types are Non-Negotiable

September 30, 2020

The choice between using a statically typed or dynamically typed language faces every engineer designing a new system. This is a well-worn debate and one that reasonable people can disagree on. The correct choice frequently depends on the constraints of the project. However, having recently chosen Typescript for our latest project and used it for a few weeks I realized I've decisively come down on one side.

Statically typed languages are superior for any project larger than a script.

What follows is my highly opinionated argument for why.

Developer Experience

The developer experience is immeasurably better with static types.

IDE Features are much more powerful. Auto-refactors like "Rename" work reliably. The IDE will auto-complete variable and methods for you. The IDE will provide inline documentation when you are using an external library. It will tell you when you're using a method with the incorrect variables. You can confidently find everywhere a method is used with "Find References."

Compilation gives you free tests. Untyped languages like Ruby require more tests. This might seem good, but by necessity many of these tests check behavior that in statically typed languages the compiler gives you for free. In Ruby you have to write tests to confirm basic facts like:

  1. The class that I'm instantiating and the method that I'm calling exist
  2. I'm calling that method with the correct types and arity of arguments
  3. The method I'm calling returns the type I'm expecting

With STLs if the code compiles you can be sure of such truths with complete coverage. All without having written a single test. This belies the common idea that UTLs let you move more quickly. That might be true if you're not writing any tests, but if you are then I'd rather write type annotations than an entire acceptance test suite.

Further, compiling is much faster than running the test suite; typically fast enough for your IDE to highlight your errors in real-time. This means that developers can deal with such bugs immediately, while still in the headspace of the code they're working on, rather than later after running the test suite.

Code Quality

One can write shitty or beautiful code in any language. But STLs encourage better coding practices. By necessity they force developrs to think more about their interfaces. When you write poor code you have to stare it in the face.

Developers using UTLs like Python, Ruby, and Javascript often write methods that accept a single large dictionaries/hashes/objects as an input. Values are then pulled from this object by their keys. Sometimes there are options that affect the flow of the code. Sometimes this entire object is then passed to another method. And so on.

I can't understate how horrible this pattern is. This makes the code almost impossible to reason about.

With an STL you could use this anti-pattern. But you would be forced to type that monstrosity into a struct. Once you're staring that beast in the face you are almost certain to re-write the code. Which is why you rarely see this anti-pattern in STLs. But if you did, at the very least have documentation in the form of the type.

STLs encourage modularity. I'm not sure whether or not this is orthogonal to being dynamically typed, but UTLs like Ruby, Python, and Javascript do not have true private methods and variables. Typescript, Java, and Go do. This means the UTLs have no way to enforce the interface for a given abstraction; callers can always reach into the internals of the code. STLs have this mechanism. Further, static analysis in IDEs will suggest that members that can be private be made so. This leads to less coupled code in STLs.

STLs also promote cohesion. A given module might operate over a set of types. Some of these should be exported to consumers of the module, but most should be internal to the module and invisible to the caller. The classes that need to know about those types should be grouped together in the module - cohesion. If too many types are being exported you will see this issue: lots of imports outside the module, lots of exports within it. This code should be refactored. In UTLs you are just dealing with an increasingly diverse set of hashes. This problem is far less visible.

Again: one can write shitty or beautiful code in any language. UTLs can be written with succinct interfaces, low coupling, and high cohesion. However only STLs actively encourage this behavior.

Documentation

How and how much to document your code is another great, ongoing debate. Generally we agree we need more documentation, but that documentation, once written, tends to get quickly out date as the code changes and is forgotten.

STLs have solved this problem. Types are documentation that live in-code and never get out of sync. If you have types and well-named methods and arguments I believe you have all the documentation you need.

Putting it Together

All of this leads to developers working faster and more confidently. This is not a minor point. Developer productivity is the main driver of success for a technology company. Increasingly developer productivity should be the primary goal of any director of engineering.

I'd also argue this all leads to developers being happier. No one enjoys writing tests. No one enjoys accidentally releasing, reverting, and retroing a bug. No one enjoys having to spend a day understanding someone else's shitty code or interface. In my experience these are some of the primary drivers of burnout. The cost of losing a productive engineer is massive. But if you do, having self-documented and well-organized code will make the ramp up time for a new engineer much shorter.

Finally, when you're scaling a large engineering team the name of the game becomes encouraging best practices. You might have great coding instincts, but imparting those on a team of 50 or 100 engineers is impossible without support from the language and your tools. STLs are, simply, the best way to encourage good coding at scale.