Clean Code Lessons I Wish I Knew as a Junior Android Developer” — Part 01
This article shares essential clean code lessons I’ve learned as an Android developer working with Kotlin over the past few years. These aren’t strict rules, but proven best practices to help you and your team read, maintain, and create better code for Android applications. Whether you’re a junior Android developer or transitioning to mid-level, these Kotlin clean code principles will improve your development workflow.
Many of these concepts align with principles from Robert C. Martin’s “Clean Code: A Handbook of Agile Software Craftsmanship” (or “Código Limpo: Habilidades Práticas do Agile Software” in Portuguese), which helped me understand the why behind these practices.
Indentation: The Foundation of Readable Code
At the beginning of my career, one of the first challenges I faced was indentation. My main concern was just to produce functional code, and not worrying about indentation made me take more time to understand and finish features.
If we think about it, even when we write in our native language, we still follow rules like paragraphs, commas, etc. With code, it’s similar, but we can get help from our IDE. In Android Studio, for example, we have shortcuts to fix indentation and make our lives easier.
Before (Poor Indentation):
class MainActivity:AppCompatActivity(){
override fun onCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button=findViewById<Button>(R.id.button)
button.setOnClickListener{
Toast.makeText(this,"Button clicked",Toast.LENGTH_SHORT).show()
}}}After (Proper Indentation):
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
Toast.makeText(this, "Button clicked", Toast.LENGTH_SHORT).show()
}
}
}With proper indentation, we can easily see where each function starts and ends.
Meaningful Names: Code That Tells a Story
As time passes and your application grows, you’ll realize that a single class can have multiple methods, calling other classes and changing many variables. If we don’t name methods or variables thinking about their purpose, we create a big mess.
This principle is extensively covered in Chapter 2 of Clean Code, which emphasizes that names should reveal intent. Let’s use a calculator example:
❌ Bad Example:
fun doThing1(x: Double, y: Double): Double {
return x + y
}
fun doThing2(x: Double, y: Double): Double {
return x - y
}
fun doThing3(x: Double, y: Double): Double {
return x * y
}
fun doThing4(x: Double, y: Double): Double {
if (y == 0.0) {
throw IllegalArgumentException("Can't divide by zero")
}
return x / y
}
fun main() {
val a = 10.0
val b = 5.0
val r1 = doThing1(a, b)
val r2 = doThing2(a, b)
val r3 = doThing3(a, b)
val r4 = doThing4(a, b)
println("Result 1: $r1")
println("Result 2: $r2")
println("Result 3: $r3")
println("Result 4: $r4")
}To discover what each method does, you have to read each scope. In a big class with many methods, this takes time. Besides that, if we have any problem during execution, we’ll need debugging tools, which could take a while to discover what’s wrong.
✅ Good Example:
fun add(a: Double, b: Double): Double {
return a + b
}
fun subtract(a: Double, b: Double): Double {
return a - b
}
fun multiply(a: Double, b: Double): Double {
return a * b
}
fun divide(a: Double, b: Double): Double {
if (b == 0.0) {
throw IllegalArgumentException("Can't divide by zero")
}
return a / b
}
fun main() {
val num1 = 10.0
val num2 = 5.0
val sum = add(num1, num2)
val difference = subtract(num1, num2)
val product = multiply(num1, num2)
val quotient = divide(num1, num2)
println("Sum: $sum")
println("Difference: $difference")
println("Product: $product")
println("Quotient: $quotient")
}When we name each function according to its purpose, you can read the class and understand it immediately, just by reading each function name.
Pro tip: Use searchable names! In a big project, if you want to quickly find a method responsible for multiplying values, you can easily search “multiply” and find it.
Notice that we used verbs for each function since they process values. You can also have methods that get, delete, save, or set values, like getUserAddress( ) or saveUserPreferences( ).
The Stepdown Rule: Code That Flows Like a Story
At school, we learned that all stories have a beginning, middle, and end. When we write code, it’s similar. It’s easier to read a class when each function is followed by its next call.
This concept, discussed in Chapter 5 of Clean Code, suggests that code should read like a top-down narrative. For example, let’s look at a `CoffeeMaker` class:
class CoffeeMaker {
fun makeCoffee() {
grindBeans()
heatWater()
brew()
pourIntoCup()
addExtras()
}
private fun grindBeans() {
println("Grinding coffee beans...")
}
private fun heatWater() {
println("Heating water...")
}
private fun brew() {
println("Brewing the coffee...")
}
private fun pourIntoCup() {
println("Pouring coffee into the cup...")
}
private fun addExtras() {
println("Adding sugar and milk...")
}
}
fun main() {
val coffeeMaker = CoffeeMaker()
coffeeMaker.makeCoffee()
}This organization makes the code read like a story, flowing naturally from one step to the next.
Comments: When Are They Really Needed?
When we start programming, it’s common to add comments to our code. It makes it easier to understand what we wrote, the purpose, and sometimes it’s useful for reminding us to code something later (like TODOs).
Get Lurian Oriane’s stories in your inbox
Join Medium for free to get updates from this writer.
However, over time, you’ll realize that comments often compensate for bad code. Chapter 4 of Clean Code explains that good code mostly documents itself. Everything we discussed before helps developers understand what your code does, which is why it’s so important to think clearly about function names, variables, and classes.
❌ Bad Example (Comments compensating for unclear code):
// This method does thing1 with two numbers
fun doThing1(x: Double, y: Double): Double {
return x + y // Adding the numbers
}✅ Good Example (Self-documenting code):
fun calculateTotalPrice(basePrice: Double, tax: Double): Double {
return basePrice + tax
}When comments ARE useful:
- API Documentation: When working with design systems or libraries
- Complex algorithms: When the business logic is inherently complex
- Warnings: Deprecated methods or important constraints
- TODOs: Temporary reminders for future improvements
/**
* Calculates the user's final score based on multiple factors
* @param baseScore The initial score from the game
* @param bonusMultiplier Applied for special achievements
* @param timePenalty Deducted for slow completion
* @return The final calculated score
* @deprecated Use calculateScoreV2() instead. This method will be removed in v2.0
*/
fun calculateScore(baseScore: Int, bonusMultiplier: Double, timePenalty: Int): Int {
// TODO: Add validation for negative values
return (baseScore * bonusMultiplier - timePenalty).toInt()
}Conclusion
Clean code is not just about following rules — it’s about empathy for your future self and your teammates. Every line of code you write today will be read multiple times in the future, whether for debugging, adding features, or refactoring.
As developers, we have additional responsibilities:
- Performance matters: Clean code often leads to better performance
- Team collaboration: Multiple developers work on the same codebase
- Maintenance: Android apps need frequent updates and bug fixes
Remember: Code is written once but read many times. Make it count! 🚀
## Key Takeaways
- ✅ Use proper indentation (let your IDE help you)
- ✅ Choose meaningful, searchable names
- ✅ Organize methods in a logical flow (stepdown rule)
- ✅ Write self-documenting code instead of relying on comments
- ✅ When comments are needed, make them valuable
What clean code practices have helped you the most in your Android development journey? Share your experiences!






