Refactoring in Go — The Complete Series Guide (Based on 'Dive Into Refactoring')
A comprehensive introduction to the Refactoring series: what refactoring is, why it matters, and a 10-part roadmap covering Code Smells & Refactoring Techniques with Go examples.
Have you ever opened a codebase you wrote just six months ago and found yourself thinking: “Who wrote this garbage?” only to realize it was you? Or perhaps you’ve heard the dreaded words “don’t touch that module, it’ll break everything” during a sprint planning meeting. If this sounds familiar, you are not alone. Bad code isn’t usually the result of lazy developers; it is born from deadline pressure, changing requirements, and short-term hacks that gradually pile up into a massive mountain of technical debt. This is where Refactoring comes in—not as a magic wand, but as a disciplined, systematic way to clean up your code without changing its external behavior.
This 10-part series is heavily inspired by Alexander Shvets’ masterpiece, "Dive Into Refactoring"—one of the most practical and comprehensive guides on the subject. We will explore each concept using Go (Golang) as our primary language, showing you how to transform cluttered code into clean, maintainable, and idiomatic Go.
🎯 Takeaway
After reading this series, you will:
- ✅ Understand the core philosophy of refactoring and when (and when not) to do it
- ✅ Recognize 22 common Code Smells that signal your Go code needs help
- ✅ Master time-tested refactoring techniques to cleanly restructure your logic
- ✅ Apply all these concepts practically to real-world Go code
- ✅ Gain the confidence to refactor legacy codebases without fear of breaking them
🗺️ Refactoring Series Mind Map
Here is the visual roadmap of the entire series, divided into Code Smells (identifying the problems) and Refactoring Techniques (applying the solutions):
mindmap
root((Refactoring\nin Go))
Code Smells
Bloaters
Long Method
Large Class
Primitive Obsession
Long Parameter List
Data Clumps
OO Abusers
Switch Statements
Temporary Field
Refused Bequest
Alternative Classes
Change Preventers
Divergent Change
Shotgun Surgery
Parallel Inheritance
Dispensables
Excessive Comments
Duplicate Code
Lazy Class
Data Class
Dead Code
Speculative Generality
Couplers
Feature Envy
Inappropriate Intimacy
Message Chains
Middle Man
Refactoring Techniques
Composing Methods
Extract Method
Inline Method
Replace Temp with Query
Substitute Algorithm
Moving Features
Move Method
Move Field
Extract Class
Hide Delegate
Simplify Conditionals
Decompose Conditional
Consolidate Conditional
Replace Conditional with Polymorphism
Simplify Method Calls
Rename Method
Add Parameter
Introduce Parameter Object
Remove Setting Method
Dealing with Generalization
Pull Up Field
Pull Up Method
Extract Interface
Collapse Hierarchy
🔍 What is Refactoring?
"Refactoring is the process of changing a software system in such a way that it does not alter the external behavior of the code yet improves its internal structure." — Martin Fowler
At its core, refactoring is a disciplined technique for cleaning up code that minimizes the chance of introducing bugs. When refactoring, you make a series of small, behavior-preserving transformations (often referred to as "micro-refactorings"). Individually, these changes might seem too trivial to matter, but their cumulative effect can lead to dramatic improvements in design, readability, and maintenance.
What Refactoring is NOT
To understand refactoring, it is equally important to understand what it is not:
| Not Refactoring ❌ | Refactoring ✅ |
|---|---|
| Rewriting code from scratch | Improving the structure of existing code |
| Adding new features or API endpoints | Modifying only how existing features are implemented |
| Fixing bugs or changing output | Keeping external behavior exactly the same |
| Optimizing performance | (Although often a side effect, it is not the primary goal) |
Refactoring vs. Clean Code
If Clean Code is about how to write good code from the beginning, then Refactoring is about how to fix existing code. They are two sides of the same coin. Understanding code smells and refactoring techniques equips you with the vocabulary to explain why a piece of code is hard to work with and how to systematically clean it up.
🤔 Why Refactoring Matters
Without a disciplined approach to refactoring, codebases naturally deteriorate over time. This phenomenon is often called software rot or code entropy. Every new feature, rushed bug fix, and tight deadline adds another layer of complexity.
graph LR
A["🆕 New Code\n(Clean & Simple)"] -->|Time + Pressure| B["📈 Technical Debt\nAccumulates"]
B -->|Without Refactoring| C["💀 Legacy Code\n(Untouchable)"]
B -->|With Refactoring| D["✨ Healthy Code\n(Sustainable)"]
C --> E["🔥 Total Rewrite\n(Expensive & Risky)"]
D --> F["🚀 New Features\nDelivered Faster"]
Three Key Benefits of Refactoring
- 🧹 Improves Software Design: Without regular refactoring, the design of a program drifts and decays. As people change code to meet short-term goals, they often do so without fully understanding the overall architecture. Refactoring helps keep the code structured and clean.
- 🔎 Makes Software Easier to Understand: Code is read far more often than it is written. When you refactor, you actively make the code more readable and self-documenting for your teammates (and your future self).
- 🐛 Helps Find Bugs Faster: Cleaning up code is like shining a light into a dark, dusty attic. As you untangle the structure of a program, you will often find hidden bugs, edge cases, and flawed assumptions that had been buried under layers of spaghetti code.
⚠️ When to Refactor?
The best time to refactor is not during a dedicated "refactoring sprint" (which product managers rarely approve), but rather as a natural part of your daily development workflow. Shvets recommends using the Rule of Three:
- When you do something for the first time, just get it done.
- When you do something similar for the second time, wince at the duplication but do it anyway.
- When you do something for the third time, start refactoring.
Technical Debt in the Wild: A Go Example
Let’s look at a typical example of bad code in Go. This is a "God Function" that is plagued by multiple code smells: it is too long, takes too many parameters, has nested conditionals, uses magic values, and violates the Single Responsibility Principle.
Bad Code Example (❌)
package main
import "fmt"
// ❌ BAD: This is a "God Function" that does too many things at once.
// Immediately noticeable code smells:
// 1. Vague function name (process)
// 2. Too many primitive parameters (Long Parameter List)
// 3. Nested if-else logic (Complexity)
// 4. Magic numbers (0.1, 0.2, 100, 50, 15)
// 5. Comments explaining "what" the code does instead of "why"
func process(t string, a float64, q int, m bool, code string, uid int) float64 {
var result float64
// Calculate base price
if t == "A" {
result = a * float64(q)
} else if t == "B" {
result = a * float64(q) * 0.9 // 10% discount
} else if t == "C" {
result = a * float64(q) * 0.8 // 20% discount
} else {
result = a * float64(q)
}
// Apply membership discount
if m {
if result > 100 {
result = result * 0.95 // 5% member discount
}
}
// Check voucher code
if code == "HEMAT10" {
result = result - (result * 0.1)
} else if code == "HEMAT20" {
result = result - (result * 0.2)
}
// Minimum order checks
if result < 50 {
result = result + 15 // Shipping fee
}
// Log transaction (but uses fmt instead of a proper logger)
fmt.Printf("uid=%d type=%s total=%.2f\n", uid, t, result)
return result
}
If you need to add a new product type “D”, or support a new discount coupon, or change the shipping rules, this function will grow even longer and more fragile.
Refactored Fix (✅)
To resolve these issues, we can apply several refactoring techniques:
- Extract Method/Function to split each responsibility into a small, descriptive helper.
- Introduce Parameter Object to combine the messy parameters into a structured order request.
- Replace Magic Numbers with Constants to make variables self-documenting.
- Replace Conditional with Switch where appropriate.
Here is the refactored, clean Go version:
package main
import (
"log"
)
// Define custom domain types to prevent primitive obsession.
type ProductType string
const (
ProductTypeA ProductType = "A"
ProductTypeB ProductType = "B"
ProductTypeC ProductType = "C"
)
// Replace magic numbers with meaningful constants.
const (
MinOrderForFreeShipping = 50.0
ShippingFee = 15.0
MemberDiscountThreshold = 100.0
MemberDiscountRate = 0.05
)
// OrderRequest groups related fields into a single struct (Introduce Parameter Object).
type OrderRequest struct {
ProductType ProductType
UnitPrice float64
Quantity int
IsMember bool
VoucherCode string
UserID int
}
// ✅ GOOD: A highly readable function delegating specific responsibilities to helper functions.
func CalculateOrderTotal(req OrderRequest) float64 {
total := calculateBasePrice(req.ProductType, req.UnitPrice, req.Quantity)
total = applyMemberDiscount(total, req.IsMember)
total = applyVoucherDiscount(total, req.VoucherCode)
total = applyShippingFee(total)
logTransaction(req.UserID, req.ProductType, total)
return total
}
func calculateBasePrice(t ProductType, price float64, qty int) float64 {
base := price * float64(qty)
switch t {
case ProductTypeB:
return base * 0.90 // 10% discount
case ProductTypeC:
return base * 0.80 // 20% discount
default:
return base
}
}
func applyMemberDiscount(currentTotal float64, isMember bool) float64 {
if isMember && currentTotal > MemberDiscountThreshold {
return currentTotal * (1.0 - MemberDiscountRate)
}
return currentTotal
}
func applyVoucherDiscount(currentTotal float64, voucherCode string) float64 {
switch voucherCode {
case "HEMAT10":
return currentTotal * 0.90
case "HEMAT20":
return currentTotal * 0.80
default:
return currentTotal
}
}
func applyShippingFee(currentTotal float64) float64 {
if currentTotal < MinOrderForFreeShipping {
return currentTotal + ShippingFee
}
return currentTotal
}
func logTransaction(userID int, productType ProductType, total float64) {
// Uses a proper logger rather than printing directly to stdout
log.Printf("Transaction processed: UserID=%d, Type=%s, Total=%.2f", userID, productType, total)
}
🧬 The Two Pillars of This Series
To help you master refactoring, this series is organized into two primary pillars:
Pillar 1: Code Smells (Parts 1–5)
A Code Smell is not a bug or compile error. It is a structural pattern in your code that indicates a deeper design problem. Just like a bad smell from a refrigerator suggests food is spoiling, a code smell indicates that your code is becoming harder to maintain.
We will cover 5 categories of Code Smells:
graph TD
CS["🦨 Code Smells"] --> B["📦 Bloaters\nCode that has grown too large"]
CS --> OO["🔀 OO Abusers\nIncorrect application of OOP"]
CS --> CP["🔒 Change Preventers\nStructures that make changes hard"]
CS --> D["🗑️ Dispensables\nUnnecessary or redundant code"]
CS --> CU["🔗 Couplers\nExcessive coupling between classes/packages"]
Pillar 2: Refactoring Techniques (Parts 6–10)
Once you know what is wrong (the smell), you need to know how to fix it. We will cover 5 categories of Refactoring Techniques:
graph TD
T["🛠️ Refactoring Techniques"] --> CM["✂️ Composing Methods\nExtracting and organizing methods"]
T --> MF["🚚 Moving Features\nMoving behavior to the right place"]
T --> SC["🌿 Simplifying Conditionals\nTaming branching logic"]
T --> SM["📞 Simplifying Method Calls\nDesigning cleaner interfaces"]
T --> DG["🧬 Dealing with Generalization\nManaging class/struct hierarchies"]
📚 Complete Series Roadmap
| Part | Title | Category | Link |
|---|---|---|---|
| 1 | Code Smells: Bloaters | Code Smells | Read → |
| 2 | Code Smells: Object-Orientation Abusers | Code Smells | Read → |
| 3 | Code Smells: Change Preventers | Code Smells | Read → |
| 4 | Code Smells: Dispensables | Code Smells | Read → |
| 5 | Code Smells: Couplers | Code Smells | Read → |
| 6 | Refactoring Techniques: Composing Methods | Refactoring Techniques | Read → |
| 7 | Refactoring Techniques: Moving Features Between Objects | Refactoring Techniques | Read → |
| 8 | Refactoring Techniques: Simplifying Conditional Expressions | Refactoring Techniques | Read → |
| 9 | Refactoring Techniques: Simplifying Method Calls | Refactoring Techniques | Read → |
| 10 | Refactoring Techniques: Dealing with Generalization | Refactoring Techniques | Read → |
🏁 Best Way to Follow This Series
This series is designed to be read in order, but each post also stands alone as a quick reference guide.
- For Beginners: Start from Part 1 and go in sequence. You will build a solid understanding of why certain patterns are problematic before learning how to fix them.
- For Experienced Developers: Use the table above as a index. If you are reviewing code and feel like something is wrong but cannot name it, browse the Code Smells section. If you already know the problem and need a recipe to resolve it, jump straight to the Refactoring Techniques section.
Practical Workflow:
- 📖 Read the concept.
- 🔍 Identify similar patterns in your current codebase.
- 🛠️ Apply the refactoring technique—using existing unit tests as a safety net.
- 🔁 Repeat as a habit.
💡 Important Note: Before refactoring, ensure you have sufficient test coverage. Refactoring without tests is like renovating a house without a blueprint—you won’t know if you’ve accidentally knocked down a load-bearing wall until the roof collapses.
📝 Summary
Refactoring is not a luxury or a chore; it is a professional necessity for any developer who cares about the longevity of their work. Keep these key takeaways in mind:
- 🦨 Code Smells are signals, not rules — they point you to where code might need attention.
- 🛠️ Refactoring Techniques are tools — they provide proven, structured steps to safely transform your design.
- 🧪 Tests are your safety net — never refactor code that lacks reliable tests.
- ⏱️ Start small — clean up one function, one struct, or one package at a time. Consistency beats intensity.
- 📈 Long-term investment — spending an hour refactoring today saves days of debugging and frustration tomorrow.
This series is designed to be your guide on your journey to writing Go code that is not just functional, but also clean, elegant, and ready to evolve with your business needs.
Welcome to the refactoring journey! 🚀
| 🇮🇩 Versi Indonesia | 🇬🇧 English Version |