Go's Modernizer Movement: Automated Refactoring with go fix and Reliable Concurrency Testing with synctest
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.