Slide 1

Slide 1 text

Magda Miu @magdamiu Squad Lead Developer at YOXO.ro (Orange) Android Google Developer Expert Clean Code with Kotlin

Slide 2

Slide 2 text

A. An app that works perfectly but is very difficult to add new features or to change. What would you prefer to have: B. An app that does not contain all the features required by stakeholders but it is easy to extend and change.

Slide 3

Slide 3 text

What is Clean Code? “Express your intentions clearly to the reader of the code. Unreadable code isn’t clever.” Venkat Subramaniam

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Code quality “measure” Few WTFs Developer Many WTFs Developer WTF/min WTF = What a Terrible Feature

Slide 7

Slide 7 text

MEANINGFUL NAMES

Slide 8

Slide 8 text

A name should reveal intent and should answer at these questions: ● Why it exists? ● What it does? ● How it is used? Use intention-revealing names

Slide 9

Slide 9 text

Examples Types Names Classes and Objects Customer, Account, WikiPage Methods postPayment, deleteAccount, displayPage Solution domain names AccountVisitor Problem domain names churnPerMonth

Slide 10

Slide 10 text

“UNCLEAN” CODE CLEAN CODE

Slide 11

Slide 11 text

if (t % 5 == 0) { val w = t / 5 println("$w week(s)") } else { println("$t days") } TODO: display the number of days/weeks until an offer will expire

Slide 12

Slide 12 text

if (daysUntilOfferEnds % numberOfDaysInAWeek == 0) { val numberOfWeeks = daysUntilOfferEnds / numberOfDaysInAWeek println("$numberOfWeeks week(s)") } else { println("$daysUntilOfferEnds days") } TODO: display the number of days/weeks until an offer will expire

Slide 13

Slide 13 text

class Address { var addressCountry: String? = null var addressCity: String? = null var addressStreety: String? = null var addressStreetNo: Int = 0 } TODO: define a class for geo addresses

Slide 14

Slide 14 text

class Address { var country: String? = null var city: String? = null var street: String? = null var streetNumber: Int = 0 } TODO: define a class for geo addresses

Slide 15

Slide 15 text

users.filter{ it.job == Job.Developer } .map{ it.birthDate.dayOfMonth } .filter{ it <= 10 } .min() Warning: Use explicit argument names and avoid using too often “it”

Slide 16

Slide 16 text

users.filter{ user -> user.job == Job.Developer } .map{ developer -> developer.birthDate.dayOfMonth } .filter { birthDay -> birthDay <= 10 } .min() Warning: Use explicit argument names and avoid using too often “it”

Slide 17

Slide 17 text

FUNCTIONS

Slide 18

Slide 18 text

Functions are the verbs of the language, and classes are the nouns.

Slide 19

Slide 19 text

Basic Rules: ➔ The functions should be small ➔ The functions should be smaller than that Make it smaller

Slide 20

Slide 20 text

Extra Rules: ➔ No many arguments ➔ No side effects ➔ Indent level max 2 Make it smaller

Slide 21

Slide 21 text

fun parseProduct(response: Response?): Product? { if (response == null) { throw ClientException("Response is null") } val code: Int = response.code() if (code == 200 || code == 201) { return mapToDTO(response.body()) } if (code >= 400 && code <= 499) { throw ClientException("Invalid request") } if (code >= 500 && code <= 599) { throw ClientException("Server error") } throw ClientException("Error $code") }

Slide 22

Slide 22 text

fun parseProduct(response: Response?) = when (response?.code()){ null -> throw ClientException("Response is null") 200, 201 -> mapToDTO(response.body()) in 400..499 -> throw ClientException("Invalid request") in 500..599 -> throw ClientException("Server error") else -> throw ClientException("Error ${response.code()}") }

Slide 23

Slide 23 text

for (user in users) { if(user.subscriptions != null) { if (user.subscriptions.size > 0) { var isYoungerThan30 = user.isYoungerThan30() if (isYoungerThan30) { countUsers++ } } } } WARNING: keep the abstraction level consistent and avoid nested code

Slide 24

Slide 24 text

var countUsersYoungerThan30WithSubscriptions = 0 for (user in users) { if (user.isYoungerThan30WithSubscriptions) { countUsersYoungerThan30WithSubscriptions++; } } WARNING: keep the abstraction level consistent and avoid nested code

Slide 25

Slide 25 text

fun sumUpUserPoints(): Int { var sumAllPoints = 0 for (user in users) { sumAllPoints += user.points sendEmail(user) } return sumAllPoints } No side effects Side effect

Slide 26

Slide 26 text

Command Query Separation Command Query Output System System System Changed state Output (result) Changed state Output (result)

Slide 27

Slide 27 text

override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putString(KEY_USER_NAME, username) } Command

Slide 28

Slide 28 text

fun getProfileInfo(account: SignInAccount): UserProfile { var userProfile = UserProfile() if (account != null) { userProfile.name = account.displayName userProfile.email = account.email userProfile.id = account.id } return userProfile } Query

Slide 29

Slide 29 text

CLASSES Dog Cat Baby Dog Pet

Slide 30

Slide 30 text

Class organization ● A class should be smaller than small. ● It should have only one responsibility and a single reason to change. ● A class collaborates with a few others to achieve the desired system behaviors.

Slide 31

Slide 31 text

The newspaper metaphor

Slide 32

Slide 32 text

// var and val control the variable itself // not the object instance that is assigned val year = 2019 year = 2020 var age = 18 age = 19 Immutable References

Slide 33

Slide 33 text

val listOfYears = listOf(2017, 2018, 2019, 2020) listOfYears.add(2021) Read-only collections

Slide 34

Slide 34 text

data class Movie( val name: String, val studio: String, val rating: Float, val releaseYear: Int = 2020 ) Immutable Data Classes Constructor (assign args to props) Only getters toString() hashCode(), equals() copy() final Default arguments

Slide 35

Slide 35 text

Cohesion ● Cohesion is a measure of the degree to which the elements of the class/ module are functionally related. ● When classes lose cohesion, split them.

Slide 36

Slide 36 text

Cohesion

Slide 37

Slide 37 text

Coupling ● Coupling is the measure of the degree of interdependence between the modules. ● This isolation makes it easier to understand each element of the system.

Slide 38

Slide 38 text

Coupling

Slide 39

Slide 39 text

Best case scenario Cohesion Coupling

Slide 40

Slide 40 text

Principles 1. DRY 2. KISS 3. YAGNI 4. SOLID

Slide 41

Slide 41 text

● Don’t Repeat Yourself ● Applicable whenever we copy / paste a piece of code D.R.Y.

Slide 42

Slide 42 text

● Keep It Simple and Stupid ● Whenever we want to implement a method to do all things K.I.S.S.

Slide 43

Slide 43 text

● You Ain’t Gonna Need It ● Don’t write code which is not yet necessary Y.A.G.N.I.

Slide 44

Slide 44 text

● Single responsibility (SRP) ● Open-closed (OCP) ● Liskov substitution (LSP) ● Interface segregation (ISP) ● Dependency inversion (DIP) S.O.L.I.D.

Slide 45

Slide 45 text

OBJECTS AND DATA STRUCTURES

Slide 46

Slide 46 text

OBJECTS DATA STRUCTURES ➔ hide their data behind abstractions ➔ expose functions to operate on their data ➔ expose their data ➔ have no meaningful functions Data/object anti-symmetry

Slide 47

Slide 47 text

class Company { var name: String = "" var address: String = "" } DATA STRUCTURE

Slide 48

Slide 48 text

class Company { var name: String = "" get() { if (!field.isEmpty()) { return field.toUpperCase() } return field } var address: String = "" } OBJECT

Slide 49

Slide 49 text

Train wrecks ● Chains of calls are generally considered to be sloppy style and should be avoided. ● Object interactions spliced into each other can be confusing.

Slide 50

Slide 50 text

fun getColorOfFolder(folder: Folder): String? { return folder.feature.tag.color.name }

Slide 51

Slide 51 text

fun getColorOfFolder(folder: Folder): String? { return tagColorName(tagOfFeatureFolder(folder)) } fun getTagColorName(tag: Tag): String? { return tag.color.name } fun getTagOfFeatureFolder(folder: Folder): Tag { return folder.feature.tag }

Slide 52

Slide 52 text

The law of Demeter A module should not know about the internals of the objects it interacts with. Demeter Goddess of the Harvest

Slide 53

Slide 53 text

The law of Demeter Only talk to your friends who share your concerns Demeter Goddess of the Harvest

Slide 54

Slide 54 text

A method f of a class C should only call the methods of these: 1. C 2. An object created by f 3. An object passed as an argument to f 4. An object held in an instance variable of C LoD - Formal definition

Slide 55

Slide 55 text

Law of Demeter A B B is a friend of A C C is a friend of B Note: A friend of a friend is a stranger Messages from A to B are OK Messages from A to C are discouraged

Slide 56

Slide 56 text

fun displayInfoAboutOwner(car: Car) { var city = car.owner.address.city println("${car.owner.name} lives in $city") } Car Owner Owner is a friend of Car Address Address is a friend of Owner

Slide 57

Slide 57 text

App “architecture” Model Room Remote Data source Retrofit SQLite REST API Activity / Fragment

Slide 58

Slide 58 text

Recommended app architecture Activity / Fragment Model Room Remote Data source Retrofit SQLite REST API ViewModel LiveData Repository

Slide 59

Slide 59 text

Room.databaseBuilder(context.getApplicationContext(), UserDatabase::class.java, DATABASE_NAME) .addMigrations(MIGRATION_1_2, MIGRATION_2_3) .fallbackToDestructiveMigrationFrom(4) .build() Fluent APIs

Slide 60

Slide 60 text

“Tell, Don't Ask” Principle (4) ● Object-orientation is about bundling data with the functions that operate on that data. ● Rather than asking an object for data and acting on that data, we should instead tell an object what to do.

Slide 61

Slide 61 text

class Balance(var value: Double) class Client { fun alertService(balances: List) { for (balance in balances) { if (balance.value == 0.0) { // alert } else { // everything is ok } } } }

Slide 62

Slide 62 text

class Balance(var value: Double, val alertThreshold: Int) { val isExceedsThreshold: Boolean get() = value >= alertThreshold }

Slide 63

Slide 63 text

class Client { fun alertService(balances: List) { for (balance in balances) { if (balance.isExceedsThreshold) { // alert } else { // everything is ok } } } }

Slide 64

Slide 64 text

Objects vs Data Tell, don’t ask. Don’t talk to strangers. Dealing with Objects? Dealing with Data?

Slide 65

Slide 65 text

ERROR HANDLING

Slide 66

Slide 66 text

● Prefer exceptions to returning error codes. ● Error handling is important, but if it obscures logic, it’s wrong. ● In Kotlin we have only unchecked exceptions. ● Define dedicated exception classes. ● Don’t return or pass null. Error handling

Slide 67

Slide 67 text

Types/ Nullable Types (recap) @Nullable Type @NotNull Type Java Type Type? Type Kotlin ?

Slide 68

Slide 68 text

val view = activity!!.findViewById(R.id.menu_add) kotlin.KotlinNullPointerException Not null assertion operator !! OR Bang! Bang! Boom! operator

Slide 69

Slide 69 text

fun computeSqrt(number: Double): Double { if(number >= 0) { return Math.sqrt(number) } else { throw RuntimeException("No negative please") } } Nothing is something...

Slide 70

Slide 70 text

fun getMovie(id: Int): Movie { val movie = movieRepository.findMovie(id) return movie ?: throw RuntimeException("Movie not found") } Throw exceptions

Slide 71

Slide 71 text

sealed class MovieSearchResult data class MovieFound(val movie: Movie) : MovieSearchResult() object MovieNotFound : MovieSearchResult() object DatabaseOffline : MovieSearchResult() fun getMovie(id: Int): MovieSearchResult { val movie = movieRepository.findMovie(id) return if (movie == null) { MovieNotFound } else { MovieFound(movie) } } Return result class

Slide 72

Slide 72 text

inputStream.use { outputStream.use { // do something with the streams outputStream.write(inputStream.read()) } } “try-with-resources” in Kotlin - initial solution

Slide 73

Slide 73 text

arrayOf(inputStream, outputStream).use { // do something with the streams outputStream.write(inputStream.read()) } “try-with-resources” in Kotlin - improved solution

Slide 74

Slide 74 text

private inline fun Array.use(block: ()->Unit) { // implementation } use implementation

Slide 75

Slide 75 text

COMMENTS We are best friends forever...

Slide 76

Slide 76 text

Don’t add comments on the bad code, just rewrite it until it’s self-explanatory Comments rule

Slide 77

Slide 77 text

// Check to see if the employee is eligible for full benefits if (employee.rate.equalsIgnoreCase("hours") && employee.objectivesDone > 3) if (employee.isEligibleForFullBenefits())

Slide 78

Slide 78 text

/** Copyright (c) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell */ Legal comments

Slide 79

Slide 79 text

fun compareTo(o: Any): Int { if (o is Employee) { val employee: Employee = o as Employee val username: String = employee.username val argumentUsername: String = (o as Employee).username return username.compareTo(argumentUsername) } return 1 // we are greater because we are the right type } Explanation of intent

Slide 80

Slide 80 text

fun makeStandardDateFormat(): SimpleDateFormat? { //SimpleDateFormat is not thread safe, //so we need to create each instance independently. val df = SimpleDateFormat("EEE, dd MMM yyyy HH:mm: ss z") df.timeZone = TimeZone.getTimeZone("GMT") return df } Warning of consequences

Slide 81

Slide 81 text

fun sendEmail(employee: Employee) { //TODO send email } TODO comments

Slide 82

Slide 82 text

fun updateUserPoints(user: User, points: Int) { try { user.points += points } catch (e: java.lang.Exception) { // User not found } } Mumbling

Slide 83

Slide 83 text

fun updateUserPoints(user: User, points: Int) { try { user.points += points } // try catch (e: java.lang.Exception) { // User not found println("User not found") } // catch } Redundant & closing brace comments

Slide 84

Slide 84 text

class Log { /** The data. */ var data: String = "" /** The number of data. */ var numberOfData = 0 /** * Secondary constructor. */ constructor(data: String) { } } Noise and scary noise comments

Slide 85

Slide 85 text

Every time you write a comment, a cat is scared somewhere...

Slide 86

Slide 86 text

CODE REVIEW

Slide 87

Slide 87 text

CODE REVIEW

Slide 88

Slide 88 text

1. Help to improve the quality and consistency of the code 2. Exchange of knowledge and best practices 3. Learn the code base 4. New perspective(s) on your code 5. Learn new tips and tricks about writing code Code Review Advantages

Slide 89

Slide 89 text

Reviewer Author

Slide 90

Slide 90 text

Author

Slide 91

Slide 91 text

● Make sure you understand your task ● Refactor the code if it’s unreadable ● Write tests and follow the team conventions ● Format your code before commit it Writing the code

Slide 92

Slide 92 text

The boy scout rule Leave the campground cleaner than you found it.

Slide 93

Slide 93 text

● Add relevant commit comments ● Send pull requests often ● Have minimum 2 reviewers (one is senior) Before the code review

Slide 94

Slide 94 text

● Be humble ● You are on the same side with your reviewer(s) ● Know when to unlearn the old habits After the code review

Slide 95

Slide 95 text

Reviewer

Slide 96

Slide 96 text

● I think… ● I would… ● I believe… ● I suggest... Use I… comments

Slide 97

Slide 97 text

● Have you consider using… ? ● What do you think about… ? ● Have you tried to… ? Ask questions

Slide 98

Slide 98 text

● This code… ● This function… ● This line of code... It’s about the code, not about the coder

Slide 99

Slide 99 text

Feedback equation* Observation of a behavior Impact of the behavior Question or Request I observed this function has 60 lines. This makes it difficult for me to understand the logic. I suggest extracting a part of the code into other functions and give them relevant names. * Defined by Lara Hogan

Slide 100

Slide 100 text

1. Define with your team a set of conventions 2. Justify technology use 3. Enforce good practices (XP) 4. Question until you understand 5. Criticize ideas, not people 6. Testing, testing, testing 7. Integrate early, integrate often 8. Emphasize collective ownership of code 9. Prioritize and actively evaluate trade-offs 10. Listen to users My summary

Slide 101

Slide 101 text

Coding is not a sprint It’s a marathon

Slide 102

Slide 102 text

CREDITS: This presentation template was created by Slidesgo, including icons by Flaticon, and infographics & images by Freepik THANKS Do you have any questions? @magdamiu

Slide 103

Slide 103 text

1. Article “CommandQuerySeparation” by Martin Fowler 2. “Law of Demeter: Principle of Least Knowledge” by Professor Karl, Northeastern University 3. Article “Breaking the Law of Demeter is Like Looking for a Needle in the Haystack” by Miško Hevery 4. Article “TellDontAsk” by Martin Fowler 5. Article “One Assertion Per Test” by Dave Astels 6. The Tragedy Of Checked Exceptions by Howard Lewis Ship 7. Exceptions are Bad by Jed Wesley-Smith 8. Checked exceptions I love you, but you have to go by Miško Hevery If you want to learn more...