Go
Go's Modernizer Movement: Automated Refactoring with go fix and Reliable Concurrency Testing with synctest
Published:
•
Duration: 5:57
0:00
0:00
Transcript
Host: Hey everyone, welcome back to Allur, the show where we break down what’s actually happening in the world of PHP, Laravel, Go, and mobile dev. I’m your host, Alex Chan.
Host: I am super excited to be joined today by Marcus Thorne. Marcus is a Principal Engineer over at CloudScale and a long-time contributor to the Go ecosystem. He’s spent a lot of time thinking about developer productivity and how we can stop doing the "boring" parts of coding manually. Marcus, welcome to Allur!
Guest: Thanks, Alex! It’s great to be here. Honestly, I’m just happy to talk about anything that lets me delete a `time.Sleep` from a test file. It’s a bit of a personal mission at this point.
Host: Oh, I think you’re speaking to everyone’s soul right now. So, let’s jump right in. We’re hearing this term "Modernizer movement" being thrown around. What does that actually look like in the day-to-day for a Go developer?
Guest: Yeah, so, Go has always been about backward compatibility, right? The Go 1 promise is legendary. But the downside is that the language *does* evolve—we get new idioms, better ways to do things—but our old code just stays... old. The Modernizer movement is essentially the Go team saying, "We don’t want you to just have old code that still runs; we want you to have modern code that’s easy to read." The centerpiece of this is the revamped `go fix`.
Host: I remember `go fix` from way back—mostly for when we moved from Go 1.x to 1.y. But it’s changed, right? It’s not just for breaking changes anymore?
Guest: Exactly. It’s much more proactive now. It’s becoming this intelligent refactoring engine. Like, here’s a tiny example that actually makes a big difference in a huge codebase: the way we handle pointers to structs. For years, we’ve all written `p := &MyStruct{}`. It works fine. But now, the idiomatic preference is shifting toward `new(MyStruct)` in certain contexts because it’s cleaner and more explicit about what's happening.
Host: Interesting! So, does `go fix` just... find those and swap them?
Guest: Yeah, precisely. You run the command, and it scans your project and says, "Hey, I can modernize this." It turns that `&MyStruct{}` into `new(MyStruct)` automatically. And while that sounds like a small thing, when you multiply that across a hundred thousand lines of code, the consistency it brings is huge. It reduces that "visual noise" and technical debt that just kind of accumulates like dust in a house.
Host: I love that. It’s like a Roomba for your codebase. But, okay, let’s get to the meat of this, because I know people are really curious about the testing side. You mentioned `time.Sleep` earlier. Why is concurrency testing in Go traditionally such a headache?
Guest: Oh man, where do I start? The problem is that Go’s scheduler is non-deterministic. If you start a goroutine, you have no real guarantee exactly when it’s going to run. So, as developers, we get lazy—or desperate—and we write a test, see it fail because the goroutine didn't finish fast enough, and we go: "Okay, I’ll just add `time.Sleep(100 * millisecond)`. That should be plenty of time."
Host: [Laughs] We’ve all done it. And it works... until it doesn’t.
Guest: Exactly! Then you run it on a busy CI server, the CPU spikes, that 100 milliseconds isn't enough, and *boom*—flaky test. You’ve wasted twenty minutes of your life debugging a "phantom" bug that doesn't actually exist. It’s a huge drain on trust. If your tests are flaky, you stop believing them.
Host: So, how does the new `synctest` package change that? I’ve heard it uses a "fake clock," which sounds a bit like magic.
Guest: It kind of is! So, instead of your test running against the actual system clock—the one that keeps ticking while your CPU is busy—`synctest` lets you run your code in an isolated bubble with a controlled clock. It intercepts all those standard library calls like `time.After` or `time.Sleep`.
Host: Wait, so the code *thinks* time is passing, but it’s actually not?
Guest: Exactly. The clock only moves when *you* tell it to. So, instead of waiting 100 milliseconds of "real" time, you just call `clock.Advance(100 * time.Millisecond)` in your test. The internal state of the goroutines updates immediately as if that time had passed, but it happens in nanoseconds of real time.
Host: Wow. So, does that mean tests that used to take seconds to run because of sleeps could now run... instantly?
Guest: Literally instantly. We’ve seen test suites go from taking three minutes down to ten seconds because we removed all that "idle" waiting. But the real "aha moment" for me was the determinism. Because you control the clock, the test will behave *exactly* the same way every single time, whether you're on your laptop or a cheap CI runner. No more flakiness.
Host: That sounds like a dream for anyone doing high-concurrency stuff, like message brokers or complex state machines. Was it hard to implement? Like, do I have to rewrite all my code to use this "fake clock"?
Guest: That’s the best part—you don’t. Because it intercepts the standard library calls, your actual production code doesn't need to know it's being tested. You just wrap your test logic in a `synctest` environment. It’s a bit of a shift in how you write the *test*, but the *code* stays clean.
Host: Honestly, Marcus, this feels like one of those things where, in two years, we’re going to look back and wonder how we ever survived without it.
Guest: I think so too. It’s taking the "guesswork" out of Go. We’ve always had the performance, but now we’re getting the tooling to make that performance reliable and maintainable.
Host: So, if someone is listening to this and they’ve got a massive Go repo at work, what’s the first step? Do they just run `go fix` and hope for the best?
Guest: [Laughs] Well, maybe start on a branch! But honestly, yeah. Start by exploring the new `go fix` flags. See what it suggests. And for `synctest`, wait for the official release in the newer Go versions—it's currently in that experimental/emerging phase—but start looking at your flakiest tests now. Identify the ones that rely on `time.Sleep`. Those are your first targets for a `synctest` makeover.
Host: This has been so eye-opening. It really feels like Go is entering a "grown-up" phase where the focus is on the long-term health of these massive projects we’re all building. Marcus, thank you so much for coming on and sharing your expertise. Where can people follow you or see what you’re working on?
Guest: Thanks for having me, Alex! You can find me on GitHub at m-thorne or over on X at @marcusthorne_dev. I’m always posting about Go internals and tooling experiments.
Host: Awesome. And to everyone listening, if you’re tired of chasing flaky tests, definitely go check out the docs on `synctest`. It’s going to be a game-changer.
Tags
Go
Golang
software engineering
testing
modernization
concurrency