Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Clean Code with Kotlin

44a168e6578c2cc83aaf54a38458ade9?s=47 Magda Miu
February 03, 2021

Clean Code with Kotlin

Abstract:
With Kotlin we are able to write concise, expressive, and safe code. Sounds like clean code, doesn’t it?
During this presentation, we will recap what clean code is, we will highlight the importance of defining meaningful names and how to write clean functions and classes.
Finally, we will be able to learn more about the advantages of immutability and how to handle the errors in Kotlin.
By the end of the session, you will have a better understanding of what clean code means and you will learn a series of tips and tricks ready to be applied in your code.
“Coding is not a sprint, is a marathon” so join my session and let’s exercise together with our clean code skills.

44a168e6578c2cc83aaf54a38458ade9?s=128

Magda Miu

February 03, 2021
Tweet

Transcript

  1. Clean Code with Kotlin

  2. What is Clean Code? “Express your intentions clearly to the

    reader of the code. Unreadable code isn’t clever.” Venkat Subramaniam
  3. None
  4. None
  5. MEANINGFUL NAMES

  6. 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
  7. Examples Types Names Classes and Objects , , Methods Solution

    domain names Problem domain names
  8. “UNCLEAN” CODE CLEAN CODE

  9. 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
  10. 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
  11. 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
  12. 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
  13. 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”
  14. 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”
  15. Say YES to immutability • Prefer to use val over

    var • Prefer to use read-only properties over mutable properties • Prefer to use read-only collections over mutable collections • Prefer to use data classes that give us copy()
  16. FUNCTIONS

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

    the nouns.
  18. Basic Rules: ➔ The functions should be small ➔ The

    functions should be smaller than that Make it smaller
  19. Extra Rules: ➔ No many arguments ➔ No side effects

    ➔ Indent level max 2 Make it smaller
  20. 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") }
  21. 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()}") }
  22. 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
  23. var countUsersYoungerThan30WithSubscriptions = 0 for (user in users) { if

    (user.isYoungerThan30WithSubscriptions) { countUsersYoungerThan30WithSubscriptions++; } } WARNING: keep the abstraction level consistent and avoid nested code
  24. Command Query Separation Command Query Output System System System Changed

    state Output (result) Changed state Output (result)
  25. override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putString(KEY_USER_NAME, username) } Command

  26. 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
  27. CLASSES Dog Cat Baby Dog Pet

  28. 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.
  29. The newspaper metaphor

  30. 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.
  31. Cohesion

  32. 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.
  33. Coupling

  34. Best case scenario Cohesion Coupling

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

  36. • Don’t Repeat Yourself • Applicable whenever we copy /

    paste a piece of code D.R.Y.
  37. • Keep It Simple and Stupid • Whenever we want

    to implement a method to do all things K.I.S.S.
  38. • You Ain’t Gonna Need It • Don’t write code

    which is not yet necessary Y.A.G.N.I.
  39. • Single responsibility (SRP) • Open-closed (OCP) • Liskov substitution

    (LSP) • Interface segregation (ISP) • Dependency inversion (DIP) S.O.L.I.D.
  40. ERROR HANDLING

  41. • 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
  42. • IndexOutOfBoundsException • NoSuchElementException • IllegalArgumentException • ... Say YES

    to standard exceptions
  43. fun reportError(): Nothing { throw RuntimeException("Nothing is here") } Nothing

    is something...
  44. fun computeSqrt(number: Double): Double { if(number >= 0) { return

    Math.sqrt(number) } else { throw RuntimeException("No negative please") } } Nothing is something...
  45. fun getMovie(id: Int): Movie { val movie = movieRepository.findMovie(id) return

    movie ?: throw RuntimeException("Movie not found") } Throw exceptions
  46. sealed class MovieSearchResult data class MovieFound(val movie: Movie) : MovieSearchResult()

    object MovieNotFound : MovieSearchResult() object DatabaseOffline : MovieSearchResult() fun displayMovieResult(movieResult: MovieSearchResult) { when(movieResult) { is MovieFound -> println("yey, we found the movie") is MovieNotFound -> TODO() } } Return result class Missing branches?!
  47. val <T> T.exhaustive: T get() = this fun displayMovieResult(movieResult: MovieSearchResult)

    { when(movieResult) { is MovieFound -> println("yey, we found the movie") is MovieNotFound -> TODO() }.exhaustive } Return result class Compilation error = 'when' expression must be exhaustive, add necessary 'DatabaseOffline' branch or 'else' branch instead (do NOT add else)
  48. inputStream.use { outputStream.use { // do something with the streams

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

    } “try-with-resources” in Kotlin - improved solution
  50. private inline fun <T : Closeable?> Array<T>.use(block: ()->Unit) { //

    implementation } use implementation
  51. COMMENTS We are best friends forever...

  52. Don’t add comments on the bad code, just rewrite it

    until it’s self-explanatory Comments rule
  53. // Check to see if the employee is eligible for

    full benefits if (employee.rate.equalsIgnoreCase("hours") && employee.objectivesDone > 3) if (employee.isEligibleForFullBenefits())
  54. /** Copyright (c) <year> <copyright holders> 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
  55. 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
  56. 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
  57. fun sendEmail(employee: Employee) { //TODO send email } TODO comments

  58. fun updateUserPoints(user: User, points: Int) { try { user.points +=

    points } catch (e: java.lang.Exception) { // User not found } } Mumbling
  59. 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
  60. class Log { /** The data. */ var data: String

    = "" /** The number of data. */ var numberOfData = 0 /** * Secondary constructor. */ constructor(data: String) { } } Noise and scary noise comments
  61. Every time you write a comment, a cat is scared

    somewhere...
  62. CODE REVIEW

  63. 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
  64. Reviewer Author

  65. Author

  66. • 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
  67. The boy scout rule Leave the campground cleaner than you

    found it.
  68. • Add relevant commit comments • Send pull requests often

    • Have minimum 2 reviewers (one is senior) Before the code review
  69. • Be humble • You are on the same side

    with your reviewer(s) • Know when to unlearn the old habits After the code review
  70. Reviewer

  71. • I think… • I would… • I believe… •

    I suggest... Use I… comments
  72. • Have you consider using… ? • What do you

    think about… ? • Have you tried to… ? Ask questions
  73. • This code… • This function… • This line of

    code... It’s about the code, not about the coder
  74. 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
  75. 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
  76. Coding is not a sprint It’s a marathon