Your Code Works, But That's No Reason Not to Touch It
You wrote an app, it runs, clients are happy, builds don't crash. It seems like you can breathe easy and work on new features. But inside the codebase is a Frankenstein monster: 300-line methods, copy-paste, tangled logic, and magic numbers scattered throughout the project. If you've ever caught yourself thinking "better not touch this class, or everything will break" — refactoring Swift code is not just a recommendation for you, but a vital necessity.
In this article, we'll break down when it's really worth tackling refactoring, and when it's better to leave the code alone. I'll show you specific examples in Swift that you can apply today. And yes, no fluff — only practice.
What Refactoring Is and What It Is Not
Let's start with the basics. Refactoring Swift code is the process of changing the internal structure of a program without changing its external behavior. You are not adding new features, not fixing bugs (although you often find them along the way). You are simply making the code cleaner, clearer, and easier to maintain.
It's important to understand: refactoring ≠ rewriting from scratch. If you want to throw out the entire project and start over in SwiftUI with the TCA architecture — that's not refactoring, that's rewriting. And rewriting, as experience shows, ends in failure 80% of the time.
When to Refactor Swift Code: Three Sure Signals
1. The Rule of Three
If you copy the same piece of code for the third time — that's a signal. Not the second time, but the third. The first time is okay, you solved the problem. The second time is already questionable, but can be blamed on haste. The third time — that's it, write a generalized solution.
// Bad: copy-pastefunc formatUserName(_ name: String) -> String { return name.trimmingCharacters(in: .whitespaces).capitalized}
func formatProductName(_ name: String) -> String { return name.trimmingCharacters(in: .whitespaces).capitalized}
// Good: unified methodfunc formatDisplayName(_ name: String) -> String { return name.trimmingCharacters(in: .whitespaces).capitalized}2. The "Smell" of Long Methods
If a method doesn't fit on a phone screen — that's a problem. In the Swift community, it's common to aim for 15-20 lines per method. Of course, there are exceptions (complex calculations), but in 95% of cases, a long method is a sign that it's doing too much.
3. Testing Turns into a Nightmare
When testing a single function requires mocking 5 services, setting up a database, and reading a file from disk — that's a red flag. Refactoring Swift code should make your codebase more testable. If writing tests is painful right now, it means the architecture is asking for change.
Practical Examples of Swift Refactoring
Let's look at real situations every iOS developer faces.
Example 1: Getting Rid of Magic Numbers
Magic numbers are numeric literals whose meaning is unclear without context. They make code fragile and unreadable.
// Before refactoringfunc calculateDiscount(price: Double) -> Double { if price > 1000 { return price * 0.15 } else if price > 500 { return price * 0.1 } else { return price * 0.05 }}
// After refactoringprivate struct DiscountConstants { static let premiumThreshold: Double = 1000 static let standardThreshold: Double = 500 static let premiumRate: Double = 0.15 static let standardRate: Double = 0.10 static let basicRate: Double = 0.05}
func calculateDiscount(price: Double) -> Double { switch price { case _ where price > DiscountConstants.premiumThreshold: return price * DiscountConstants.premiumRate case _ where price > DiscountConstants.standardThreshold: return price * DiscountConstants.s