The Secret Life of Go: Interface Pollution

 

The Secret Life of Go: Interface Pollution

Accept interfaces, return structs, and don't abstract before you need to

#Go #Interfaces #Structs #InterfacePollution




Eleanor is a senior software engineer. Ethan is her junior colleague. They work in a beautiful beaux arts library in Lower Manhattan — the kind of place where coding languages are discussed like poetry.

Episode 40

The library was peaceful this morning, bathed in the soft glow of the reading lamps. Ethan was deep into the architecture of his new metadata indexer, his screen filled with a meticulously organized hierarchy of files.

"I’ve been thinking a lot about the scale of this project, Eleanor," Ethan said, turning his chair to face her. "I’ve abstracted the core components into interfaces—a Provider, a Storer, and a Formatter. I wanted to make sure that as the library grows, we’re ready for whatever new requirements come our way."

Eleanor looked up from her notes and smiled. She walked over to his station, looking not at the complexity, but at the intent behind it. "I love the foresight, Ethan. You’re thinking like someone who wants to build something that lasts. But let's look at what these abstractions are costing you right now."

The Hidden Weight of Abstraction

She pulled a chair alongside him. "In Go, we have a saying: 'Focus on the concrete first.' When we create an interface before we have at least two different things that need to behave the same way, we’re actually adding a bit of a 'fog' between us and our code."

She pointed to his DataStorer interface. "Right now, you only have one way to store data. By wrapping it in an interface this early, you’re asking the compiler and your future self to work through a layer of indirection. It makes the code a little harder to navigate and a little slower to execute, without a present-day benefit."

The Architect’s Rule: Accept Interfaces, Return Structs

Ethan looked at his code, his brow furrowed. "I thought I was helping the next developer by giving them a clean contract to follow."

"The best gift you can give another developer is clarity," Eleanor said gently. "In Go, we prefer to discover our interfaces rather than plan them. It’s like the way this library was curated; we didn't build the shelves based on a guess. We saw the books we had, and we built the shelves to fit them."

She tapped a few keys, showing him a simpler path.

// Ethan's initial approach
func NewProcessor() ProcessorInterface {
    return &concreteProcessor{}
}

// The Go-centric approach
func NewProcessor() *Processor {
    return &Processor{}
}

"When you return a concrete struct," Eleanor explained, "you’re giving the caller everything you have to offer. You aren't locking them into a contract yet. If they eventually need to mock your Processor for a test, they can define their own small interface that covers only what they need. You’re giving them the power to choose their own abstraction."

Clarity Over Complexity

Ethan watched as they collapsed the unnecessary interfaces back into simple, direct structs. The code felt lighter, and the intent of each function became immediately clear.

"It’s much easier to read this way," Ethan admitted. "I guess I was so worried about the future that I was making the present more complicated than it needed to be."

"It’s a common path for talented engineers," Eleanor said, returning to her desk. "We want to build for every possibility. But in Go, an interface is like a bridge. You don't need to build a bridge over an open field; you wait until you find a river that needs crossing. Until then, just enjoy the walk across the grass."

Ethan deleted the redundant "Provider" files, feeling a sense of relief. The architecture was no longer a puzzle to solve; it was a clear, high-performance tool ready to work.


Key Concepts

Interface Pollution
This occurs when interfaces are created prematurely. In Go, "pre-abstraction" can lead to a more fragmented codebase and unnecessary indirection. Keeping code concrete until an abstraction is truly required keeps the project easy to maintain and navigate.

Accept Interfaces, Return Structs
By returning concrete types, you provide the most utility to the user of your code. Because Go interfaces are satisfied implicitly, the caller can always define their own interface later if they need to decouple your logic for testing or other implementations.

Discovery over Design
In the Go philosophy, interfaces are often "discovered" by looking at existing concrete types and finding their commonalities. This ensures that the interfaces you do create are grounded in actual requirements rather than theoretical future needs.


Aaron Rose is a software engineer and technology writer at tech-reader.blog

Catch up on the latest explainer videos, podcasts, and industry discussions below.


Comments

Popular posts from this blog

Insight: The Great Minimal OS Showdown—DietPi vs Raspberry Pi OS Lite

The New ChatGPT Reason Feature: What It Is and Why You Should Use It

Running AI Models on Raspberry Pi 5 (8GB RAM): What Works and What Doesn't