From Messy to Masterpiece: How Coding Standards Rescued Our Kotlin App
"The app works, but the codebase feels like a haunted house."
That’s how our junior dev described our project. And honestly, they weren’t wrong.
When we started building our Android app in Kotlin, everything was exciting — fast screens, sleek UI, clever logic. But within a few months, we were buried in bugs, confused by code, and frustrated during every PR review.
The problem?
We had no coding standards. Just a jungle of inconsistent logic.
So we turned things around — not with a rewrite, but with a code clean-up campaign powered by Kotlin’s best practices. Here’s how we fixed our mistakes — and how you can too.
MISTAKE #1: Wild Formatting Wars
No indentation rules. Random semicolons. Files looked like ransom notes.
Before:
fun fetchuser():List<User>{return userlist;}
After (Using Kotlin style guide):
fun fetchUser(): List<User> {
return userList
}
Fix: We adopted 4-space indentation, removed semicolons, and formatted consistently with
ktlint
.
Benefit: Easier code reviews, cleaner diffs, and a more welcoming codebase.
MISTAKE #2: Confusing Naming That Told You Nothing
usrlist1
, getU()
, DataMngr
... what do these even mean?
Before:
val usrlist1 = getU()
After:
val activeUserList = userRepository.getActiveUsers()
Fix: Clear, camelCase naming conventions for variables and methods, PascalCase for classes.
Benefit: Developers could read and understand code without decoding abbreviations.
MISTAKE #3: Ignoring Null Safety — Until It Crashed
Our app crashed more often than it launched. NullPointerException
was a daily visitor.
Before:
val nameLength = user.name.length // Crashes if name is null
After:
val nameLength = user.name?.length ?: 0
Fix: We embraced Kotlin's null safety features:
?.
,?:
, and avoided!!
.
Benefit: Crashes dropped significantly. Code became more predictable and safe.
MISTAKE #4: Writing Java in Kotlin
Our Kotlin files were still clinging to Java habits — verbose, repetitive, and clunky.
Before:
class User {
var name: String = ""
var age: Int = 0
}
After:
data class User(val name: String, val age: Int)
Fix: We used Kotlin-specific features:
data classes
,when
,extension functions
, andscope functions
.
Benefit: Shorter, cleaner code that actually looked and felt like Kotlin.
MISTAKE #5: Logic All Over the Place
Business logic lived in Fragments. API calls happened in click listeners. Total chaos.
Before:
button.setOnClickListener {
val user = apiService.getUser() // Blocking call
textView.text = user.name
}
After (Using MVVM):
viewModel.userLiveData.observe(viewLifecycleOwner) {
textView.text = it.name
}
Fix: We moved to MVVM, separating UI, logic, and data.
Benefit: Code was modular, testable, and less prone to spaghetti messes.
MISTAKE #6: Manual Dependency Management
We passed objects manually or relied on global singletons. Testing? Impossible.
Before:
val repo = UserRepository(ApiClient())
After (Using Hilt):
@HiltViewModel
class UserViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel()
Fix: Adopted Hilt for dependency injection.
Benefit: Cleaner setup, easier testing, no more “why is this null?” moments.
MISTAKE #7: Logging? More Like Guessing
We used Log.d
everywhere. No tags. No structure. And we caught exceptions… but did nothing.
Before:
try {
fetchUser()
} catch (e: Exception) {
Log.d("TAG", "Oops")
}
After:
try {
fetchUser()
} catch (e: IOException) {
Timber.e(e, "Failed to fetch user from server")
}
Fix: Replaced logs with Timber and added proper error handling.
Benefit: Debugging became a breeze. No more guessing games in production logs.
MISTAKE #8: Async Madness
We mixed threads, callbacks, and random GlobalScope.launch
like a chaotic soup.
Before:
GlobalScope.launch {
val data = api.fetchData()
}
After (Using coroutines properly):
viewModelScope.launch {
val data = repository.getData()
}
Fix: Standardized coroutine usage with proper scopes and dispatchers.
Benefit: Smooth performance. No more ANRs or weird async bugs.
MISTAKE #9: Everything Was a Code Smell
Code smells went unchecked — long methods, unused variables, confusing logic.
Fix: Introduced
ktlint
anddetekt
in our CI/CD pipeline.
Benefit: The codebase started cleaning itself. Review time shrunk. Dev confidence grew.
MISTAKE #10: Too Many Comments. Or None at All.
We either overexplained the obvious or left mysteries in silence.
Before:
// this function gets user
fun getUser() { ... }
After:
/**
* Fetches user from cache or API depending on freshness
*/
fun fetchUser(): User { ... }
Fix: We wrote meaningful KDoc only where necessary.
Benefit: Future devs (including us) actually understood the why — not just the what.
The Results: Cleaner Code, Happier Devs, Fewer Bugs
After applying coding standards:
-
Crashes dropped by 40%
-
Code reviews became faster and friendlier
-
New team members were productive by Day 2
-
We actually enjoyed working in the codebase
Final Take: Clean Code is Kind Code
The biggest lesson?
Bad code isn’t written by bad devs — it’s written by rushed, unsynced teams.
Coding standards gave us structure. Structure gave us sanity. And sanity gave us speed.
If you're drowning in spaghetti code, start small:
-
Pick one mistake from above
-
Apply its fix today
-
Watch your app (and team) get better overnight
Clean code isn’t about perfection. It’s about respect — for your future self, your teammates, and your users.
Let your codebase tell a story worth reading.
Comments
Post a Comment