1 of 59

From messy to tidy

Jarosław Michalik, Droidcon Berlin 2023

2 of 59

What is Code? </>

3 of 59

What is Code? </>

set of instructions for computer program

4 of 59

Code is a tool

5 of 59

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?

6 of 59

Mars Climate Orbiter

What went wrong?

  • NASA team: kilograms / Newtons
  • contractors: Pounds

7 of 59

Mars Climate Orbiter

What went wrong?

  • NASA team: kilograms / Newtons
  • contractors: Pounds

8 of 59

How do we know if our code will work?

How can we be sure?

9 of 59

Our toolkit

  • compiler
  • automated tests
  • code review

  • user feedback
  • logs from prod

10 of 59

How to detect errors early on?

11 of 59

Coding is not a rocket science.

so let’s take weightlifting as an example

12 of 59

How much bar weights?

13 of 59

How much bar weights?

14 of 59

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

15 of 59

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

16 of 59

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

17 of 59

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

18 of 59

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)

19 of 59

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?

20 of 59

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)

21 of 59

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)

22 of 59

issue #2:�invoking Kilograms(20)

@JvmInline

value class Kilograms(val value: Int)

val barKg = Kilograms(20)

val plateKg = Kilograms(20)

23 of 59

solution #2:�extension property / function

@JvmInline

value class Kilograms(val value: Int)

private val Int.kg: Kilograms

get() = Kilograms(this)

val weight = 20.kg

24 of 59

how to add 20.kg + 30.kg?

val weight: Kilograms = 20.kg + 30.kg

25 of 59

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)

}

}

26 of 59

Is it clean code?

27 of 59

Is it clean code?

IT DEPENDS!

28 of 59

Is it clean code?

  • #1 value class for type safe primitives operations
  • #2 typealias for increasing readability without adding type
  • #3 extension function for building DSL
  • #4 operator fun – more DSL

val weight: Kilograms = 20.kg + 30.kg

29 of 59

Is it clean code?

  • Keep it simple (it’s not simple, it’s magic!)
  • Don’t repeat yourself (function for addition)
  • You ain’t gonna need it ??? - only “+” defined

val weight: Kilograms = 20.kg + 30.kg

30 of 59

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()

31 of 59

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)

32 of 59

Forget the math. Let’s do more real-life example

33 of 59

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("***")

34 of 59

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

35 of 59

Don’t Repeat Yourself

unless…

36 of 59

Don’t Repeat Yourself

unless… it make your code more readable.

37 of 59

Readability > Syntax Sugar

38 of 59

Is it readable?

github.com/chrisbanes/tivi/

39 of 59

Usage example

github.com/chrisbanes/tivi/

40 of 59

Is it clean?

Don’t repeat yourself ✅

Single Responsibility ✅

You ain’t gonna need it ❌

Keep it simple ❌

Closed for modifications ✅

Open for extensions? 🤔

41 of 59

Is it clean?

Don’t repeat yourself ✅

Single Responsibility ✅

You ain’t gonna need it ❌

Keep it simple ❌

Closed for modifications ✅

Open for extensions? 🤔

42 of 59

Everything at once

operator fun :(

executeSync() is… suspend?

This abstraction is actually quite good.

Probably it’s not applicable to your projects.

43 of 59

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()

}

}

}

44 of 59

Wrong abstraction will cost you more than no abstractions at all.

45 of 59

From messy to tidy

46 of 59

The Process

47 of 59

The Process

  1. Identify high-risk areas (box with tools)

48 of 59

The Process

  • Identify high-risk areas (box with tools)
  • Create characterization test (describe existing state of the box without modifying it’s content)

49 of 59

The Process

  • Identify high-risk areas (box with tools)
  • Create characterization test (describe existing state of the box without modifying it’s content)
  • Refactor with “baby steps” (organize tools on the floor)

50 of 59

The Process

  • Identify high-risk areas (box with tools)
  • Create characterization test (describe existing state of the box without modifying it’s content)
  • Refactor with “baby steps” (organize tools on the floor)
  • Write unit tests & refactor (organize tools into a new box)

51 of 59

Clean Code is not “set of rules”

52 of 59

Clean code is not about…

  • Always writing multi-layered architecture
  • Adding interface for every use case
  • Adhering to SOLID / DRY / YAGNI / KISS

53 of 59

Clean code is about…

  • Readability
  • User (Dev) Experience
  • Communication
  • Maintaining system in the long term

54 of 59

🤔 “don’t write comments, code should be self explanatory”

55 of 59

  • We are using meters, Newtons, etc. - it’s obvious!

  • Wasn’t obvious for contractor team :)

Would comment save them?

56 of 59

Guidelines

  • SOLID
  • DRY
  • YAGNI
  • KISS

tl;dr:

communicate intent clearly and don’t create a mess.

57 of 59

Our toolkit

  • comments
  • automated tests
  • SOLID guidelines
  • refactoring
  • language features & syntax sugar

58 of 59

Code is read more often than it is written.

Code should always be written in �a way that promotes readability.

59 of 59

Jarosław Michalik

Slides and materials:

michalik.tech/dcberlin23/

Twitter:

@rozkminia

LinkedIn:�/in/jaroslawmichalik