Reviewing Kotlin (Conference for Kotliners 2019)

Reviewing Kotlin (Conference for Kotliners 2019)

I have been teaching our rapidly growing team of Android developers Kotlin for about a year, and for the last few months, I’ve been reviewing tens of thousands of lines of code written by almost a dozen people at times. Here’s what we’ve found out together about learning, teaching, and reviewing Kotlin. I’ll tell you what worked for us and what didn’t, so that you may be more prepared for this path than we were. I’ll also point out some of the issues that most often arose in the code while our developers were getting familiar with the language.

Talk recording: https://www.youtube.com/watch?v=sXwiiTFjczw

4047c64e3a1e2f81addd4ba675ddc451?s=128

Márton Braun

June 07, 2019
Tweet

Transcript

  1. Reviewing Kotlin Márton Braun zsmb.co zsmb13 braun.marton@autsoft.hu

  2. This is not…

  3. How great code reviews are, and what’s the best way

    to do them This is not…
  4. and how to get your management onboard with the idea

    Why you should use Kotlin for Android development… How great code reviews are, and what’s the best way to do them This is not…
  5. and how to get your management onboard with the idea

    Why you should use Kotlin for Android development… All the awesome things that are in the Kotlin standard library How great code reviews are, and what’s the best way to do them This is not…
  6. Life is Great and Everything Will Be Ok, Kotlin is

    Here Christina Lee, Jake Wharton Google I/O '17 Dissecting the stdlib Huyen Tue Dao KotlinConf 2018 Code Review Best Practices Trisha Gee SCLConf 2018 This is not…
  7. None
  8. None
  9. None
  10. None
  11. None
  12. None
  13. 99000 lines of Kotlin code 5700 commits 700 merge requests

  14. None
  15. None
  16. None
  17. None
  18. None
  19. None
  20. None
  21. None
  22. Breaking the habit

  23. Breaking the habit Enums

  24. Breaking the habit Enums Lambdas

  25. Breaking the habit Enums Lambdas Typechecks

  26. Breaking the habit Enums Lambdas Typechecks is as as?

  27. entries: ArrayList<Entry>) { fun updateColors(chart: , } PieChart

  28. entries: ArrayList<Entry>) { fun updateColors(chart: , val colors = ArrayList<Int>()

    } PieChart
  29. entries: ArrayList<Entry>) { fun updateColors(chart: , val colors = ArrayList<Int>()

    entries.forEach { entry -> colors.add(entry.data as Int) } } PieChart
  30. entries: ArrayList<Entry>) { fun updateColors(chart: , val colors = ArrayList<Int>()

    entries.forEach { entry -> colors.add(entry.data as Int) } chart.colors = colors } PieChart
  31. entries: ArrayList<Entry>) { fun updateColors(chart: , val colors = ArrayList<Int>()

    entries.forEach { entry -> colors.add(entry.data as Int) } chart.colors = colors } PieChart
  32. fun PieChart.updateColors(entries: val colors = ArrayList<Int>() entries.forEach { entry ->

    colors.add(entry.data as Int) } this.colors = colors } List<Entry>) { Array
  33. fun PieChart.updateColors(entries: val colors = ArrayList<Int>() entries.forEach { entry ->

    colors.add(entry.data as Int) } this.colors = colors } List<Entry>) {
  34. fun PieChart.updateColors(entries: val colors = ArrayList<Int>() entries.forEach { entry ->

    colors.add(entry.data as Int) } this.colors = colors } List<Entry>) {
  35. fun PieChart.updateColors(entries: List<Entry>) { val colors = mutableListOf<Int>() entries.forEach {

    entry -> colors.add(entry.data as Int) } this.colors = colors }
  36. fun PieChart.updateColors(entries: List<Entry>) { val colors = mutableListOf<Int>() entries.forEach {

    entry -> colors.add(entry.data as Int) } this.colors = colors }
  37. fun PieChart.updateColors(entries: List<Entry>) { val colors = mutableListOf<Int>() entries.map {

    entry -> colors.add( ) } this.colors = colors } entry.data as Int
  38. fun PieChart.updateColors(entries: List<Entry>) { val colors = mutableListOf<Int>() entries.map {

    entry -> } this.colors = colors } entry.data as Int
  39. fun PieChart.updateColors(entries: List<Entry>) { this.colors = colors } entry.data as

    Int } entries.map { entry -> val colors =
  40. fun PieChart.updateColors(entries: List<Entry>) { this.colors = colors } entry.data as

    Int } entries.map { entry -> val colors =
  41. fun PieChart.updateColors(entries: List<Entry>) { this.colors entry.data as Int } }

    = entries.map { entry ->
  42. None
  43. None
  44. fun getExaminationResult(resultId: UUID): ExaminationResult { }

  45. fun getExaminationResult(resultId: UUID): ExaminationResult { return getItemGroups() }

  46. fun getExaminationResult(resultId: UUID): ExaminationResult { return getItemGroups() } .map {

    it.toExaminationResult() }
  47. fun getExaminationResult(resultId: UUID): ExaminationResult { return getItemGroups() } .map {

    it.toExaminationResult() } .first { it.id == resultId }
  48. fun getExaminationResult(resultId: UUID): ExaminationResult { return getItemGroups() } .map {

    it.toExaminationResult() } .first { it.id == resultId }
  49. fun getExaminationResult(resultId: UUID): ExaminationResult { return getItemGroups() } .map {

    it.toExaminationResult() } .first { it.id == resultId }
  50. fun getExaminationResult(resultId: UUID): ExaminationResult { return getItemGroups() } .first {

    it.id == resultId } .map { it } .toExaminationResult()
  51. fun getExaminationResult(resultId: UUID): ExaminationResult { return getItemGroups() } .first {

    it.id == resultId } .toExaminationResult()
  52. val events: List<Event> = getAllEvents() val upcoming = events.filter {

    it.date > } OffsetDateTime.now()
  53. val events: List<Event> = getAllEvents() val upcoming = events.filter {

    it.date > } OffsetDateTime.now() val now = now
  54. fun startBluetoothLeScan() { }

  55. fun startBluetoothLeScan() { val scanner = BluetoothAdapter.getDefaultAdapter() .bluetoothLeScanner }

  56. fun startBluetoothLeScan() { val scanner = BluetoothAdapter.getDefaultAdapter() .bluetoothLeScanner /* ...

    */ }
  57. fun startBluetoothLeScan() { val scanner = BluetoothAdapter.getDefaultAdapter() .bluetoothLeScanner /* ...

    */ scanner } .startScan(/* ... */)
  58. fun startBluetoothLeScan() { val scanner = BluetoothAdapter.getDefaultAdapter() .bluetoothLeScanner /* ...

    */ scanner? } .startScan(/* ... */)
  59. if (scanner != null) { } else { /* ¯\_(ツ)_/¯

    */ } } fun startBluetoothLeScan() { val scanner = BluetoothAdapter.getDefaultAdapter() .bluetoothLeScanner /* ... */ scanner.startScan(/* ... */)
  60. None
  61. None
  62. data class DailyFluidConsumption( )

  63. data class DailyFluidConsumption( val quantity: Double, )

  64. data class DailyFluidConsumption( val quantity: Double, val recommended: Double =

    4.0, )
  65. data class DailyFluidConsumption( val quantity: Double, val recommended: Double =

    4.0, val percentage: Int = ((quantity / recommended) * 100) .roundToInt() .coerceIn(0..100) )
  66. data class DailyFluidConsumption( val quantity: Double, val recommended: Double =

    4.0 ) val percentage: Int = ((quantity / recommended) * 100) .roundToInt() .coerceIn(0..100)
  67. data class DailyFluidConsumption( val quantity: Double, val recommended: Double =

    4.0 ) val DailyFluidConsumption.percentage: Int ((quantity / recommended) * 100) .roundToInt() .coerceIn(0..100)
  68. data class DailyFluidConsumption( val quantity: Double, val recommended: Double =

    4.0 ) val DailyFluidConsumption.percentage: Int get() = ((quantity / recommended) * 100) .roundToInt() .coerceIn(0..100)
  69. zsmb.co/data-classes-arent-that-magical

  70. class Ingredient( val id: UUID = UUID.randomUUID(), val name: String

    = "", val quantity: Double? = null, val unit: String? = null, val imageId: UUID? = null )
  71. class Ingredient( val id: UUID = UUID.randomUUID(), val name: String

    = "", val quantity: Double? = null, val unit: String? = null, val imageId: UUID? = null )
  72. class Ingredient( val id: UUID = UUID.randomUUID(), val name: String

    = "", val quantity: Double? = null, val unit: String? = null, val imageId: UUID? = null )
  73. class Ingredient( val id: UUID = UUID.randomUUID(), val name: String

    = "", val quantity: Double? = null, val unit: String? = null, val imageId: UUID? = null ) Ingredient(id, "Pixie dust", 6.28, "teaspoon")
  74. class Ingredient( val id: UUID = UUID.randomUUID(), val name: String

    = "", val quantity: Double? = null, val unit: String? = null, val imageId: UUID? = null ) Ingredient(id, "Pixie dust", 6.28, "teaspoon")
  75. class Ingredient( val id: UUID = UUID.randomUUID(), val name: String

    = "", val quantity: Double? = null, val unit: String? = null, val imageId: UUID? = null ) Ingredient(id, "Pixie dust", 6.28, "teaspoon")
  76. class Ingredient( val id: UUID, val name: String, val quantity:

    Double?, val unit: String?, val imageId: UUID? ) Ingredient(id, "Pixie dust", 6.28, "teaspoon")
  77. class Ingredient( val id: UUID, val name: String, val quantity:

    Double?, val unit: String?, val imageId: UUID? ) Ingredient(id, "Pixie dust", 6.28, "teaspoon", null)
  78. id = name = quantity = unit = imageId =

    id, 6.28, ) "teaspoon", null Ingredient( "Pixie dust", class Ingredient( val id: UUID, val name: String, val quantity: Double?, val unit: String?, val imageId: UUID? )
  79. suspend fun

  80. suspend fun getValidMeasurements( measurements: List<Measurement> ): List<Measurement> { }

  81. suspend fun getValidMeasurements( measurements: List<Measurement> ): List<Measurement> { return measurements.filter

    { } }
  82. suspend fun getValidMeasurements( measurements: List<Measurement> ): List<Measurement> { return measurements.filter

    { val validator = getValidatorForType(it.type) } }
  83. suspend fun getValidMeasurements( measurements: List<Measurement> ): List<Measurement> { return measurements.filter

    { val validator = getValidatorForType(it.type) it.value in (validator.minValue..validator.maxValue) } }
  84. suspend fun getValidMeasurements( measurements: List<Measurement> ): List<Measurement> { return measurements.filter

    { measurement -> val validator = getValidatorForType(measurement.type) measurement.value in (validator.minValue..validator.maxValue) } }
  85. return measurements.filter { measurement -> val validator = getValidatorForType(measurement.type) measurement.value

    in (validator.minValue..validator.maxValue) } } suspend fun getValidMeasurements( measurements: List<Measurement> ): List<Measurement> {
  86. return measurements.filter { measurement -> val validator = getValidatorForType(measurement.type) measurement.value

    in (validator.minValue..validator.maxValue) } } suspend fun getValidMeasurements( measurements: List<Measurement> ): List<Measurement> {
  87. return measurements.filter { measurement -> val validator = getValidatorForType(measurement.type) measurement.value

    in (validator.minValue..validator.maxValue) } } suspend fun getValidMeasurements( measurements: List<Measurement> ): List<Measurement> {
  88. val validatorsByType = getAllValidators() suspend fun getValidMeasurements( measurements: List<Measurement> ):

    List<Measurement> { } return measurements.filter { measurement -> val validator = getValidatorForType(measurement.type) measurement.value in (validator.minValue..validator.maxValue) }
  89. val validatorsByType = getAllValidators().associateBy { it.type } suspend fun getValidMeasurements(

    measurements: List<Measurement> ): List<Measurement> { } return measurements.filter { measurement -> val validator = getValidatorForType(measurement.type) measurement.value in (validator.minValue..validator.maxValue) }
  90. val validatorsByType = getAllValidators() suspend fun getValidMeasurements( measurements: List<Measurement> ):

    List<Measurement> { } return measurements.filter { measurement -> val validator = validatorsByType.getValue(measurement.type) measurement.value in (validator.minValue..validator.maxValue) } .associateBy { it.type }
  91. None
  92. None
  93. None
  94. None
  95. None
  96. None
  97. None
  98. None
  99. None
  100. None
  101. None
  102. None
  103. None
  104. None
  105. None
  106. None
  107. fun add (x:Int,y: Int) :Int{ return x +y } fun

    main() { println( add(2,3) ) }
  108. fun add(x: Int, y: Int): Int { return x +

    y } fun main() { println(add(2, 3)) }
  109. What did you miss about Java?

  110. Related talks • Code Review Best Practices  Trisha Gee,

    SCLConf 2018  https://www.youtube.com/watch?v=jXi8h44cbQA • Life is Great and Everything Will Be Ok, Kotlin is Here  Christina Lee & Jake Wharton, Google I/O ‘17  https://www.youtube.com/watch?v=fPzxfeDJDzY • Dissecting the stdlib  Huyen Tue Dao, KotlinConf 2018  https://www.youtube.com/watch?v=Fzt_9I733Yg
  111. Learning resources • Kotlin in Action, Dmitry Jemerov and Svetlana

    Isakova  https://www.manning.com/books/kotlin-in-action • Coursera course, Andrey Breslav and Svetlana Isakova  https://www.coursera.org/learn/kotlin-for-java-developers • O’Reilly courses, Hadi Hariri  https://hadihariri.com/2016/11/01/oreilly-kotlin-course/
  112. Learning resources • Kotlin Bootcamp for Programmers  https://eu.udacity.com/course/kotlin-bootcamp-for- programmers--ud9011

    • Developing Android Apps with Kotlin  https://eu.udacity.com/course/developing-android-apps-with- kotlin--ud9012
  113. Further reading • Data classes aren’t (that) magical  https://zsmb.co/data-classes-arent-that-magical/

  114. zsmb13 zsmb.co/talks

  115. Questions? Márton Braun zsmb.co/talks zsmb13 braun.marton@autsoft.hu