The power of static analysis 🤖

A code reviewer that never sleeps

👋 Hello,

First, I went for my first meal at a restaurant in three and a half months on Sunday. It was really refreshing. You never appreciate what you have until it’s gone. With the opening of bars and restaurants, life is becoming a little more normal here in London. That said, be safe.

Second, I apologise for not sending an issue last week. Work has been very busy, and I couldn’t find the time to put it together. I’ll do my best to get back on a weekly cadence from here on out.

Third, if you’re a first time reader — hello, good to have you! The goal of this newsletter is to discuss techniques for building better software and being more effective in high growth companies. I mix in longer form content — think a short blog post that you could read in 5 minutes — with some of my favourite links.

Here’s a couple of my most popular issues if you want to peruse:

Follow me on Twitter 🐦

The power of static analysis

In the previous issue, I mentioned that I think “static analysis is the single most underrated tool for maintaining quality at speed”. I promised to write a whole issue about it soon. Well, readers, I can tell you that soon is now. Please read my love letter to static analysis.

Well, what is static analysis?

Static analysis refers to analysing the code of a program, without needing to run it. The goal of static analysis is to find bugs, inconsistencies, design issues and other quirks within your program. This is different from dynamic analysis - analysis performed whilst the program is running. Unit testing is an example of dynamic analysis.

Static analysis tools run over a portion of your codebase, perform analysis, and report back their findings. Here’s me running Staticcheck on an very old repository of mine - back when Monzo was called Mondo! It found multiple issues with respect to unused variables.

Static analysis tools can be configured to your heart’s desire: including or remove various checks, outputting their results as machine readable formats, -fast modes for quick feedback, and so on. Annotations can be provided in code to tell tools to ignore certain areas or files.

Who is static analysis for?

Static analysis generally has two audiences:

  • Engineers: used to make improvements to the code, and to highlight potential bugs. They do this by hooking into your development workflow, such as an IDE integration, or a pre-commit check.

  • Computers: often used as part of compiling code. Analysis is done for optimisation purposes, like unrolling loops.

In this edition, I focus on static analysis designed for human consumption.

How does it work?

To do their job, static analysis tools must understand the code in a form that is deeper than raw text. To do this, they often transform your code into a representation called an abstract syntax tree.

Abstract syntax trees represent your code as a tree of relations. This allows other programs to understand and traverse your code base. For example, you can programatically understand all the functions that a given function is calling, with which arguments. They then perform a variety of checks against this representation, such as:

  • Naming and formatting

  • Return value analysis, such as returning errors

  • Security vulnerabilities, like SQL injection

  • Assessing for simpler ways to achieve the same result

Why is it so useful?

It’s an automated code reviewer

Here’s a quote from the Staticcheck website:

Enrich your team with a reviewer that never sleeps, never tires, and always has a keen eye for code quality!

There’s a certain class of bugs that are particularly tricky for humans to spot, often get overlooked in code reviews, but are serious errors. In my experience of working with Go, here’s a few of the following:

Yes, yes, I know your favourite programming language may solve any one of the above issues automatically - that isn’t the point. These bugs are common, tricky to spot consistently and are often overlooked. These issues are trivial for a static analysis tool to find repeatedly and consistently. This enforces a known lower bar for the quality of the code.

Automated enforcer of organisational norms

Norms are “rules or expectations that are socially enforced”. Think of them as conventions: the way we do things around here. Strong norms help you achieve consistency. Consistency creates fungibility: the ability to switch between distinct parts of a code base, with things still feeling very familiar. This matters a lot in high growth companies: engineers tend to move around a lot!

Static analysis is an important ally in enforcing consistency. It might be as simple as reminding engineers to use customer_id, instead of user_id. It might be enforcing that HTTP handlers are wired up in the same way all over the codebase. It could be preventing your microservices importing any other service’s package code, apart from the proto files. We have a static analysis tool at Monzo called svcchecker that helps us do a little of this.

Having a tool do this automatically removes the burden of nitpicking from the code reviewer, and ensures a more consistent codebase over time.

Can improve maintainability, not just correctness

Many existing tools we have focus on correctness: does the system do what we want it to? But there is another dimension — maintainability: can we change the system easily over time? There’s a beautiful quote by Russ Cox that sums up the difference between programming, and software engineering:

Software engineering is what happens to programming when you add time and other programmers.

To me, the central aspect of software engineering is maintainability. Whilst writing this piece, it struck me: we don’t really have many tools that help us understand how maintainable a piece of code is. Most tools in our toolbox focus on correctness. The maintainability of any single piece of code is usually based on human judgement. We’re still some way from automatically assessing how maintainable a system is, but static analysis can help consistently achieve small improvements here.

For example, simplicity is highly correlated with maintainability. In Staticcheck, there’s plenty of checks that help you achieve simpler code:

If you aggregate these small gains, you’ll end up with a far more maintainable codebase.

It shortens the feedback loop

How long does it take you to know whether what you did was right? Often, the feedback loop in engineering is frustratingly slow. I bet you’ve all been in the situation where you couldn’t run the tests locally, so you needed to push to GitHub, wait 5 minutes for CI checks to run, only for the tests to fail. This sucks.

There’s something joyous about getting instant feedback on the code you just wrote: it allows you to work at the speed of thought. There are non-linear effects at play here: the longer you have to wait for something, the more likely you are to lose your attention and go back to Slack.

Static analysis tightens this feedback loop, giving you near instant feedback on the code you just wrote. When hooked into your IDE, it’s fast enough to run on save. This means that you’re constantly improving based on feedback.

Applying this

Enough of the abstract. Here’s some practical tips you can use to start applying static analysis.

Find a good tool for the language you use

Depending on what language you use, there’s usually a great static analysis tool available. Here’s a couple that I’ve used in the past, or had good recommendations about:

Get these hooked into your IDE!

Adding it to continuous integration

I’d recommend adding static analysis as part of your continuous integration check pipeline. Here’s some tips for rolling it out.

First, run it only on the files changed in the pull request. As you introduce a tool to an existing code base, you’re like to find many existing issues. You’ll annoy your fellow engineers if you flag issues that aren’t related to changes they made. You can then slowly work through existing issues separate pull requests.

Second, be deliberate about whether static analysis checks should be advisory, or should fail builds. To start with, I wouldn’t recommend adding it as a required status check for merging unless you’re a real purist. Often a big red X on a pull request is enough to prompt the engineer to figure it out themselves. Secondly, there’s some legitimate edge cases that apply: e.g. generating unused constants from a documentation file that may be used later on. You don’t want to block engineers in these cases.

Developing your own

If you don’t manage to find something you like, you can always roll your own! You can use this to sculpt a tool exactly for your needs. A few examples below:

  • Enforcing your own naming conventions within your company

  • Forgetting to thread through a context value down the call stack

  • Ensuring that money types are always used for money values, not floats 😬

As mentioned earlier, we’ve done this internally at Monzo, building a tool called svcchecker. Let me know if you end up doing this — I’d be super interested to find out!

Best of the internet 🔗

Every week, I collate some of my favourite links to share. Found something cool, or built something great? Send it to me by replying to this email and I might include it in the next edition. 📧

Using go/analysis to write a custom linter - Fatih Arslan

Fatih - a stalwart in the Go community - takes us through writing a custom static analysis tool for Go. This is a great place to start if you use Go, and want to hack on some of the ideas discussed in this edition.

Internet Famous - Patrick McKenzie and David Perell

This was a great chat, mostly about the power of writing online.

Creative Selection: Inside Apple's Design Process During the Golden Age of Steve Jobs - Ken Kocienda

I read this book recently. It’s a great dive into a golden era at Apple. One of the key stories in the book is about how the iPhone keyboard was designed and developed. It’s a masterclass in showing how hard it is to achieve simplicity in design — they went through so many prototypes before they achieved the final product.

Abstract Syntax Trees (ASTs) in Go - Eleni Fragkiadaki

This is a nice walkthrough of using the AST helper packages in Go.

That’s all from this week’s High Growth Engineering. If you enjoyed it, I’d really appreciate it if you could do one of the following:

  • Share it with a friend that would find it useful.

  • Follow me on Twitter: @sjwhitworth

  • Subscribe: just hit the button below.

All the best,