On naming, structure, error states, and defaults—the unglamorous work that keeps apps stable and understandable
They come from the boring parts.
The parts no one writes blog posts about.
The parts that feel obvious until they aren’t.
The parts that quietly determine whether an app is understandable six months later.
This article is about those parts—and why they matter more than we like to admit.
Naming: The First Line of Documentation
Names are the most frequently read part of your code. Long before anyone opens a README or a design doc, they read your variable names, type names, and function signatures.
Good naming reduces the need for comments. Bad naming multiplies confusion.
What good naming looks like
Good names describe intent, not implementation. They reflect the problem domain, not the mechanics of how something works.
// ❌ Poor naming
let dataManager = UserManager()
dataManager.load()
// ✅ Better naming
let userRepository = UserRepository()
userRepository.fetchCurrentUser()
In the second example, we know:
- What kind of data is involved
- Where it comes from conceptually
- What the operation actually does
Common iOS naming pitfalls
Some names should make you pause immediately:
class NetworkManager { }
class DataHelper { }
class Utils { }
These names grow endlessly because they communicate nothing about responsibility. They become dumping grounds.
A good litmus test:
If you can’t describe what a type does without using the word “stuff,” the name is wrong.
Renaming is not free—but neither is letting confusion compound indefinitely.
Structure: Code Organization as a Communication Tool
Code structure is not about aesthetics. It’s about how fast someone can answer questions:
- Where does this logic live?
- What happens when this screen loads?
- What breaks if I change this?
Well-structured code answers those questions without searching.
A structural smell: the god view controller
class ProfileViewController: UIViewController {
func viewDidLoad() {
fetchUser()
configureUI()
trackAnalytics()
validateSession()
handleErrors()
}
}
This controller is doing everything—and therefore explaining nothing.
A more communicative structure
class ProfileViewController: UIViewController {
private let viewModel: ProfileViewModel
func viewDidLoad() {
super.viewDidLoad()
viewModel.load()
}
}
Now responsibility is obvious:
- The view controller displays
- The view model coordinates behavior
- Side effects have a clear home
Structure becomes a map. When the map is predictable, onboarding is faster and refactors are safer.
Error States: Designing for the App You Don’t Want to See
Most apps are designed for the happy path. Real users do not live on that path.
They lose connectivity.
They log in on old devices.
They receive incomplete data.
Ignoring error states does1 not make them disappear—it just makes them surface as crashes or silent failures.
The silent failure problem
func loadProfile() {
api.fetchProfile { profile in
self.nameLabel.text = profile.name
}
}
What happens if the request fails?
What happens if profile is nil?
Nothing—and that’s the problem.
Making errors explicit and visible1
func loadProfile() {
api.fetchProfile { result in
switch result {
case .success(let profile):
self.render(profile)
case .failure(let error):
self.showErrorState(error)
}
}
}
Now failure is part of the design—not an afterthought.
Well-designed error states:
- Explain what went wrong
- Offer recovery when possible
- Fail loudly during development
An app that handles failure gracefully feels reliable, even when things go wrong.
Defaults: Decisions You Make Even When You Don’t
Defaults are unavoidable. Every optional, every configuration value, every initial state has one—even if you never wrote it down.
The danger lies in implicit defaults.
Implicit defaults cause invisible bugs
struct FeatureFlags {
var isNewOnboardingEnabled: Bool?
}
if featureFlags.isNewOnboardingEnabled == true {
showNewOnboarding()
}
What happens when the flag is nil?
The behavior is undefined—and likely undocumented.
Explicit defaults communicate intent
struct FeatureFlags {
let isNewOnboardingEnabled: Bool
init(isNewOnboardingEnabled: Bool = false) {
self.isNewOnboardingEnabled = isNewOnboardingEnabled
}
}
Now the system has:
- A predictable baseline
- A documented decision
- Fewer edge cases
Defaults should be conservative. When in doubt, choose the option that fails safely and visibly.
The Compound Effect of “Boring” Decisions
None of these practices are impressive in isolation. No one applauds a well-named variable.
But together, they compound:
- Clear naming reduces mental overhead
- Good structure limits blast radius
- Explicit error handling prevents cascading failures
- Thoughtful defaults eliminate ambiguity
Over time, these decisions determine whether a codebase accelerates—or collapses under its own weight.
Conclusion: Professionalism Over Novelty
Boring code is not a lack of skill.
It is the result of discipline.
The best production iOS apps are not clever—they are understandable. They favor clarity over novelty and stability over shortcuts.
When something breaks, the cause is obvious.
When something changes, the impact is contained.
When someone new joins the team, they can find their footing quickly.
These qualities are invisible when present—and painfully obvious when absent.
The boring parts are not optional.
They are the foundation.