Slide 1

Slide 1 text

Magda Miu @magdamiu Squad Lead Developer at Orange Android Google Developer Expert Clean Code with Kotlin

Slide 2

Slide 2 text

A. A big software system that works perfectly but is very difficult to change. What would you prefer to have: B. A big software system with some missing features that is very easy to change.

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

What is Clean Code? “Clean code is readable. It tells a story.” Uncle Bob, Clean Code

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

MEANINGFUL NAMES

Slide 9

Slide 9 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 10

Slide 10 text

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

Slide 11

Slide 11 text

“UNCLEAN” CODE CLEAN CODE

Slide 12

Slide 12 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 13

Slide 13 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 14

Slide 14 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 15

Slide 15 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 16

Slide 16 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 17

Slide 17 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 18

Slide 18 text

FUNCTIONS

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Uncle Bob Functions should do one thing. They should do it well. They should do it only.

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 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 24

Slide 24 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 25

Slide 25 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 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 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 31

Slide 31 text

CLASSES Dog Cat Baby Dog Pet

Slide 32

Slide 32 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 33

Slide 33 text

The newspaper metaphor

Slide 34

Slide 34 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 35

Slide 35 text

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

Slide 36

Slide 36 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 37

Slide 37 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 38

Slide 38 text

No content

Slide 39

Slide 39 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 40

Slide 40 text

No content

Slide 41

Slide 41 text

Best case scenario Cohesion Coupling

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

OBJECTS AND DATA STRUCTURES

Slide 48

Slide 48 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 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 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 52

Slide 52 text

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

Slide 53

Slide 53 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 54

Slide 54 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 55

Slide 55 text

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

Slide 56

Slide 56 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 57

Slide 57 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 58

Slide 58 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 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 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 63

Slide 63 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 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

ERROR HANDLING

Slide 68

Slide 68 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 69

Slide 69 text

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

Slide 70

Slide 70 text

Nullability in Kotlin String? “Clean Code” null String “Clean Code”

Slide 71

Slide 71 text

val name: String = if (nullableValue == null) "default" else nullableValue Smart-cast

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 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 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

CODE REVIEW

Slide 80

Slide 80 text

CODE REVIEW

Slide 81

Slide 81 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 82

Slide 82 text

Reviewer Author

Slide 83

Slide 83 text

Author

Slide 84

Slide 84 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 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 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 88

Slide 88 text

Reviewer

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 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 93

Slide 93 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 94

Slide 94 text

Coding is not a sprint It’s a marathon

Slide 95

Slide 95 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 96

Slide 96 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...