From messy to tidy
Jarosław Michalik, Droidcon Berlin 2023
What is Code? </>
What is Code? </>
set of instructions for computer program
Code is a tool
Code as a tool
Tool… to build an app
Tool… to integrate systems
Tool… to process data
Tool… to create tools
Tool… to launch rockets?
Mars Climate Orbiter
What went wrong?
Mars Climate Orbiter
What went wrong?
How do we know if our code will work?
How can we be sure?
Our toolkit
How to detect errors early on?
Coding is not a rocket science.
so let’s take weightlifting as an example
How much bar weights?
How much bar weights?
kg vs pounds
Let’s add some weight to the bar.
Simple addition?
// europeans
val bar: Int = 20 //kg
val plate: Int = 20//kg
// americans
val bar: Int = 45 //lbs
val plate: Int = 45 //lbs
kg vs pounds
lvl 1.
add unit name to variable name
// europeans
val barKg: Int = 20
val plateKg: Int = 20
// americans
val barLbs: Int = 45
val plateLbs: Int = 45
kg vs pounds
lvl 1
still possible to add pounds to kilograms :(
// europeans
val barKg: Int = 20
val plateKg: Int = 20
// americans
val barLbs: Int = 45
val plateLbs: Int = 45
�// skompiluje się
val sum = barKg + plateLbs
kg vs pounds
lvl 2
typealias
typealias Kilograms = Int
typealias Pounds = Int
val a: Kilograms = 12
val b: Pounds = 14
��// what’s the type???�// who knows the answer?
val c = a + b
kg vs pounds
lvl 3
wrapper class
class Kilograms(val value: Int)
class Pounds(val value: Int)
val barKg = Kilograms(20)
val plateKg = Kilograms(20)
val barLbs = Pounds(45)
val plateLbs = Pounds(45)
kg vs pounds
lvl 3
wrapper class
class Kilograms(val value: Int)
class Pounds(val value: Int)
val barKg = Kilograms(20)
val plateKg = Kilograms(20)
val barLbs = Pounds(45)
val plateLbs = Pounds(45)
// addition no longer possible!
What’s the issue?
issue #1 - object allocation
class Kilograms(val value: Int)
class Pounds(val value: Int)
val barKg = Kilograms(20)
val plateKg = Kilograms(20)
val barLbs = Pounds(45)
val plateLbs = Pounds(45)
solution #1:�value (inline) class
@JvmInline
value class Kilograms(val value: Int)
�@JvmInline
value class Pounds(val value: Int)
val barKg = Kilograms(20)
val plateKg = Kilograms(20)
val barLbs = Pounds(45)
val plateLbs = Pounds(45)
issue #2:�invoking Kilograms(20)
@JvmInline
value class Kilograms(val value: Int)
val barKg = Kilograms(20)
val plateKg = Kilograms(20)
solution #2:�extension property / function
@JvmInline
value class Kilograms(val value: Int)
private val Int.kg: Kilograms
get() = Kilograms(this)
val weight = 20.kg
how to add 20.kg + 30.kg?
val weight: Kilograms = 20.kg + 30.kg
operator fun
val weight: Kilograms = 20.kg + 30.kg��@JvmInline
value class Kilograms(val value: Int) {
operator fun plus(kg: Kilograms): Kilograms {
return Kilograms(this.value + kg.value)
}
}
Is it clean code?
Is it clean code?
IT DEPENDS!
Is it clean code?
val weight: Kilograms = 20.kg + 30.kg
Is it clean code?
val weight: Kilograms = 20.kg + 30.kg
spot the mistake
operator fun Kilograms.plus(kilograms: Kilograms): Kilograms = TODO()
operator fun Kilograms.minus(kilograms: Kilograms): Kilograms = TODO()
operator fun Kilograms.times(kilograms: Kilograms): Kilograms = TODO()
operator fun Kilograms.div(kilograms: Kilograms): Kilograms = TODO()
spot the mistake
operator fun Kilograms.plus(kilograms: Kilograms): Kilograms = TODO()
operator fun Kilograms.minus(kilograms: Kilograms): Kilograms = TODO()
operator fun Kilograms.times(kilograms: Kilograms): Kilograms = TODO()
operator fun Kilograms.div(kilograms: Kilograms): Kilograms = TODO()
kg * kg = kg^2��kg / kg = kg^0 = 1 (no unit)
Forget the math. Let’s do more real-life example
wrapper class for Email, Password (could be even value class)
class Email(val value: String)
class Password(val value: String)
fun loginUser(email: Email, password: Password) {
// now we can check
email.value.contains("@")
}
loginUser(Email("jarek@michalik.tech"), Password("***")
Class delegation
class Email(val value: String): CharSequence by value
class Password(val value: String): CharSequence by value
fun loginUser(email: Email, password: Password) {
// now we can check
email.contains("@")
}
loginUser(Email("jarek@michalik.tech"), Password("***")
now use “email” as a CharSequence while keeping type safety outside
Don’t Repeat Yourself
unless…
Don’t Repeat Yourself
unless… it make your code more readable.
Readability > Syntax Sugar
Is it readable?
github.com/chrisbanes/tivi/
Usage example
github.com/chrisbanes/tivi/
Is it clean?
Don’t repeat yourself ✅
Single Responsibility ✅
You ain’t gonna need it ❌
Keep it simple ❌
Closed for modifications ✅
Open for extensions? 🤔
Is it clean?
Don’t repeat yourself ✅
Single Responsibility ✅
You ain’t gonna need it ❌
Keep it simple ❌
Closed for modifications ✅
Open for extensions? 🤔
Everything at once
operator fun :(
executeSync() is… suspend?
This abstraction is actually quite good.
Probably it’s not applicable to your projects.
Simpler alternative
No Flow.
No “executeSync”
No artificial “Unit” param
Interface - what it should do?
Implementation - what exactly it should do?
interface ConfigUpdater {
suspend fun update()
}
class TmdbConfigUpdater(
val tmdbManager: TmdbManager,
val dispatchers: Dispatchers
) : ConfigUpdater {
override suspend fun update() {
withContext(dispatchers.io){
tmdbManager.refreshConfiguration()
}
}
}
Wrong abstraction will cost you more than no abstractions at all.
From messy to tidy
The Process
The Process
The Process
The Process
The Process
Clean Code is not “set of rules”
Clean code is not about…
Clean code is about…
🤔 “don’t write comments, code should be self explanatory”
Would comment save them?
Guidelines
tl;dr:
communicate intent clearly and don’t create a mess.
Our toolkit
Code is read more often than it is written.
Code should always be written in �a way that promotes readability.
Jarosław Michalik
Slides and materials:
michalik.tech/dcberlin23/
Twitter:
@rozkminia
LinkedIn:�/in/jaroslawmichalik