Tighten Up Your Feedback Loops
- 6 minutes read - 1210 wordsTighten Up Your Feedback Loops
On Friday I shared a Makefile for building Go programs and promised to share how I integrate this into my workflow.
High Level Shape of My Workflow
Before we get to specifics, here’s an overview of what my dev environment looks like so that you can have the context of where these integrations fit into the big picture.
I write all of my code in vim. It’s not a religious thing, it’s mainly a combination of (a) I do a lot of editing on remote machines via ssh and vim is the most consistent powerful editor I’ve found to work reliably over a remote connection and (b) I’ve spent enough effort customizing my environment and learning semi-advanced commands that I haven’t seen any sufficiently compelling alternatives to make me want to switch. If you don’t share my constraints, other editors/IDEs may work better for you.
As noted above, I do a lot of coding on machines that are not my laptop. So I’m usually on an SSH connection. Except for a web browser I’m almost always working in a terminal. I use tmux to maintain my terminal state across sessions.
With multiple monitors I’ll have one monitor with a fullscreen terminal and vim with several splits. On the other monitor I’ll have another fullscreen terminal with several shells open in tmux splits. (If I’m just on my laptop it’s typically a tmux split with vim on the right and a shell on the left.)
As I make edits in one window, I can either run commands to build/test/etc in the other window or – as you’ll see below – I can have it automatically run commands so that I can get live feedback every time I save a file.
Note that I didn’t invent any of this – it’s all adapted from other environments I’ve seen or used. I’ve just fit it into the workflows that have been the best fit for me.
Finally, while this blog is mostly about Go, this will fit any other language that allows you to run build/test/etc from the command line. I’ve used these techniques with Go, C, Python, Rust, and maybe others I’m forgetting about.
Instant Feedback in the Editor
I resisted this for way too long. It just seemed like a hassle to set up (it was a hassle, but the payoff has been worth the investment).
If your editor isn’t giving you at least hints when you’ve got syntax errors in your code – whether it’s a red squiggle underline, or a marker in the margin – then you should seriously look into how to turn this on, or find a better editor.
And once that integration is in place: learn the keystrokes to quickly navigate to errors. It’s a big time savings – and a big attention saver too.
I’ve heard objections to this line of thinking before – “I don’t spend that much time actually coding, I don’t need to be hyper-efficient in the editor.” And while this may be true, I find that every extra bit of seamlessness I can have while I’m editing keeps me in flow so that I don’t even have to context switch from editor to shell and back.
Using entr
to Rebuild on Every Write
If you work in the shell on a regular basis, and you aren’t using entr
yet, you’re missing out. I use it all the time. (I just checked, and there
are currently two instances running on my laptop and one on my home
server.)
I posted this git alias a couple weeks ago:
my new favorite git alias:
— Brian St. Pierre (@bstpierre) February 16, 2022
wm="!f() { git ls-files | entr -c -s \"make $@\"; }; f"
that feeds "entr" with all of my project files, and runs "make" any time I change one of them, so "git wm cover" monitors all of my git files and reruns lint/tests/etc every time I save from vim
In my ~/.gitconfig, that’s:
[alias]
wm="!f() { git ls-files | entr -r -c -s \"make $@\"; }; f"
The abundance of syntax here is unfortunate, but here’s the breakdown.
wm
names the alias – everything after the = is the value of the alias.
Running git wm
will run that shell command for me. Mnemonic: “wm” ==
“watch & make”.
The !
tells git to run the shell command that comes afterward at the top
level of my git repo.
Having the shell commands wrapped in f() { ... }
creates a function f.
And the ; f
runs that function right afterward. This function trick gets
around some issues and makes it possible to pass arguments to the alias.
(See this article for more
explanation.)
Inside the function we get to the core of the alias. First, use git ls-files
to feed all of the files in the repo to entr
. Then tell entr:
- Run
make
any time one of those files changes, passing it whatever arguments were given as part of the alias. -r
says that if the command is still running when another file changes, kill it and rerun. This allows me to run a command likemake serve
that runs my Go program to serve http, and then have it killed, rebuilt, and restarted on every edit.-c
clears the screen on each rebuild. I find this makes it easier to find status output and errors when I’m using it to run tests.-s
just says that the command is to be run in a shell instead of directly byentr
.
I will often run git wm cover
to get entr
to run make cover
on every file change.
Or maybe I’m tightly iterating on fixing a failing test. Say the test is
TestSetupDatabase
. I would use git wm 'check TESTFLAGS="-run TestSetupDatabase"'
. And then every time I save a file it reruns that test.
Note that entr
can’t reload the list of files if you add a new file to
git. That’s fairly infrequent so I haven’t tried to optimize this – just
hit q
and entr
will exit, and then you can up-arrow to rerun the
command and it will get the new list of files.
Other Tricks?
If you’ve got other workflow tweaks like this – or tools I should check out – please share! Send me an email or @bstpierre.
Next Week: More On Feedback
Linting, testing, etc. are all about feedback. I posted this idea of a “feedback pyramid” with a quick & dirty sketch:
I wonder if "Feedback Pyramid" is a better way to think about the testing pyramid? pic.twitter.com/4GUr2an1Ez
— Brian St. Pierre (@bstpierre) March 2, 2022
Next week I’ll have more than a half-baked tweet on the feedback pyramid, including some ideas about how we can move blocks up & down the pyramid to dial in the right amount: faster/weaker feedback or slower/stronger feedback.
On Friday we’ll add some better tests for our book list app.