Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Learn Common Go Design Anti-Patterns | Section
Struct-Oriented Design in Go

bookCommon Go Design Anti-Patterns

Swipe to show menu

In Go, struct and interface design can suffer from several recurring anti-patterns that make code difficult to maintain, test, and extend. The most common issues are god structs, interface pollution, and the overuse of exported fields.

A god struct is a struct that tries to do too much. It collects unrelated fields and methods, often becoming the central hub for many responsibilities in an application. This results in code that is tightly coupled, hard to test, and difficult to change without breaking other parts of the system.

Interface pollution occurs when interfaces are designed to be too broad or when interfaces are introduced prematurely. This leads to unnecessary abstraction and forces implementations to satisfy methods they do not need, reducing clarity and flexibility.

The overuse of exported fields happens when struct fields are made public without a clear reason. This exposes implementation details, making it hard to change struct internals later and often breaking encapsulation, which is a key principle of robust software design.

main.go

main.go

copy
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
package main import "fmt" // Anti-pattern: God struct that tries to do everything type App struct { DBConnection string Logger string Cache map[string]string // ... many unrelated fields } func (a *App) SaveUser(name string) { fmt.Println("Saving user to DB:", name) // uses a.DBConnection } func (a *App) Log(message string) { fmt.Println("Log:", message) // uses a.Logger } func (a *App) CacheUser(name string) { a.Cache[name] = "cached" fmt.Println("User cached:", name) // uses a.Cache } // Refactored: Use composition and interfaces type Storage interface { Save(name string) } type Logger interface { Log(message string) } type Cache interface { Set(key, value string) } type DB struct { Connection string } func (db *DB) Save(name string) { fmt.Println("Saving user to DB:", name) } type ConsoleLogger struct{} func (cl *ConsoleLogger) Log(message string) { fmt.Println("Log:", message) } type MemoryCache struct { store map[string]string } func (mc *MemoryCache) Set(key, value string) { mc.store[key] = value fmt.Println("User cached:", key) } type UserService struct { storage Storage logger Logger cache Cache } func (us *UserService) RegisterUser(name string) { us.storage.Save(name) us.logger.Log("User registered: " + name) us.cache.Set(name, "cached") } func main() { // Using god struct (anti-pattern) app := &App{ DBConnection: "localhost:5432", Logger: "console", Cache: make(map[string]string), } app.SaveUser("alice") app.Log("User saved") app.CacheUser("alice") // Using composition and interfaces (idiomatic) db := &DB{Connection: "localhost:5432"} logger := &ConsoleLogger{} cache := &MemoryCache{store: make(map[string]string)} userService := &UserService{ storage: db, logger: logger, cache: cache, } userService.RegisterUser("bob") }

These anti-patterns make Go code harder to maintain and evolve. God structs combine too many responsibilities, making them fragile and difficult to test. When every part of the application depends on a single, bloated struct, even small changes can have wide-reaching side effects. Interface pollution causes confusion and unnecessary complexity, as developers must implement methods that do not fit their use case, which can obscure the intent of the code. Overusing exported fields exposes internal state, breaking encapsulation and making future refactoring risky and error-prone.

To refactor these issues, break up god structs by splitting responsibilities into smaller structs and use composition to assemble them. Design interfaces to be minimal and focused on client needs, not on hypothetical future requirements. Keep struct fields unexported unless external access is truly necessary, and provide methods to interact with the data instead. These practices lead to code that is easier to test, extend, and understand.

question mark

What is a significant risk of using a "god struct" in a Go backend project?

Select the correct answer

Everything was clear?

How can we improve it?

Thanks for your feedback!

Sectionย 1. Chapterย 12

Ask AI

expand

Ask AI

ChatGPT

Ask anything or try one of the suggested questions to begin our chat

Sectionย 1. Chapterย 12
some-alt