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, and scope 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 and detekt 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

Popular posts from this blog

Jetpack Compose based Android interview and questions

Kotlin Some important unsorted interview questions and answers

Null safety based Kotlin interview questions and answers