Immediate Feedback in Programming

Bret Victor's Inventing on Principle

Bret Victor’s talk Inventing on Principle (video, transcript) changed the way I think about computing in 2019. Inventing on Principle is partly about Bret’s guiding principle:

Creators need an immediate connection to what they create. And what I mean by that is when you’re making something, if you make a change, or you make a decision, you need to see the effect of that immediately.”

The Edit-Compile-Run Cycle

Although Bret doesn’t use the term, programmers are deeply familiar with his principle. We’ve all worked with toolchains that introduce significant delay before you can “see” the results of a change, and we know they’re painful. Everyone wants a short edit-compile-run cycle.

But until IoP, I’d assumed that slow cycles wouldn’t materially change the output – you’d eventually get to the same place. This was wrong. I also didn’t appreciate the very small time scales involved; a 5 second delay used to seem trivial to me, but it’s still meaningfully different from a response time measured in milliseconds.

Through some very impressive custom tools, Bret shows how immediate feedback enables exploration, which then gives birth to ideas which would otherwise never see the light of day. This was an epiphany for me. Since IoP I’ve constantly been looking for better ways to code, and re-evaluating my existing processes for shorter feedback cycles. The results:

Rust

My typical Rust development workflow goes something like this:

  1. Write a small function that does roughly what I want
  2. Write a small unit test inline to exercise the function (even if it’s a private function)
  3. Iterate using cargo test until the function is correct
  4. Later, “productionize” the tests if necessary

Rust’s native support for inline unit tests helps a lot here, and the excellent type system catches a lot of issues before I even run cargo test. On the other hand, Rust’s compiler is notoriously slow and that extends to IDE tooling that depends on the Rust Language Server. I’m looking forward to Cranelift for faster debug builds.

JavaScript/TypeScript/Node

I use Nodemon to watch my files and automatically run a JS/TS file as soon as my code changes on disk. With pure JS, the feedback time is nearly instant. For TypeScript using ts-node, it tends to take a couple seconds – not ideal, but still pretty good. It’s a tradeoff; TypeScript catches bugs as I code and enables better IDE tooling, but JS enables immediate execution+feedback for quick iterations. The best choice depends on the nature of the task.

C#/.NET

This is an area where .NET’s recent cross-platform support stumbles a bit.

For .NET developers on Windows, NCrunch is a fantastic enabler of rapid feedback when coding. It’s a sophisticated automatic test runner; you make a code change, and it runs the relevant tests. It works very well, and has a lot of fans among the Domain Driven Design crowd. My only quibble is more to do with C#: private methods can’t be called by unit tests, which is (arguably) justified for production code but a hindrance for rapid exploration+prototyping. Visual Studio for Windows is also great; the tight integration with the Roslyn compiler allows for detailed code feedback before you even save a file.

However, these strengths don’t always extend to .NET development on other platforms. There is no serious equivalent to NCrunch for Visual Studio Code or VS for Mac; the VS Code extensions in that space are pretty unreliable. In theory, OmniSharp provides first-class language support for C# in VS Code; in practice, it needs to be restarted several times a day and I can never quite trust that I’m getting up-to-date feedback on my code as written.

The dotnet watch CLI tool can be quite useful in the same way as nodemon, but it typically takes a few seconds to compile even trivial applications. Hopefully it will get faster.

Golang/Hugo

I’m not an expert Go programmer, but I get a lot of exposure to it from Hugo (the templating engine I used to build this website).

I appreciate how fast the Go compiler is; speed is a primary goal for the Go team, and it shows. I can make a small change to a Hugo template, and see the results of that change in my browser nearly instantly. On the other hand, Hugo tooling in VS Code is poor, so I rarely get useful information from my IDE before saving a file. I intend to spend some time exploring better Hugo tooling.

(Aside: I’m also not convinced that Go is the right language for templating, because the lack of interfaces greatly complicates working with collections of structured data.)

Grab bag

IoP has changed my perspective on Unix scripting; I better appreciate the rapid iteration and observability of chaining multiple independent steps together with pipes. On the other hand, I get frustrated with the lack of typing; when every input is just raw strings, my development environment is unable to give me useful feedback before I run the script. I feel more warm and fuzzy toward PowerShell these days.

VS Code’s remote development feature is superb; it’s as if your IDE was running on a remote server. This eliminates a whole class of “copy the executable elsewhere to run it” delays (like when writing Linux-specific code on macOS or Windows).

I still really like Tailwind CSS, but it takes a few seconds to compile after configuration changes. This is a pain for rapid iteration on UIs, hopefully it gets faster.

headshot

Cities & Code

Top Categories

View all categories