Slide 1

Slide 1 text

Munetoshi Ishikawa, LINE Corporation Kotlin language features and readability Մಡੑ͔ΒݟͨKotlinͷݴޠػೳ
 -ʮ࢖͍͍ͨʯͷɺͦͷઌ΁ɻ

Slide 2

Slide 2 text

Kotlin's language features Examples: null safety, data classes, extensions...
 Helps writing code - Removes boiler plates - Keeps code type-safe - Makes code concise and robust

Slide 3

Slide 3 text

Drawbacks of language features "Feature overusing" may cause unreadable and non-robust code - Need to make the objective clear - Shouldn't mistake the means and the ends This talk demonstrates anti-patterns and the solutions

Slide 4

Slide 4 text

Contents of this talk - Null safety - Scope function - Data class - Extension - Default argument - Sealed class

Slide 5

Slide 5 text

Contents of this talk - Null safety - Scope function - Data class - Extension - Default argument - Sealed class

Slide 6

Slide 6 text

Null safety Nullable/Non-null type is statically checked - cf. https://kotlinlang.org/docs/null-safety.html var nonNullInt: Int = 1 nonNullInt = null // compile error Introduce 1 anti-pattern example var nullableInt: Int? = 1 nullableInt + 1 // compile error

Slide 7

Slide 7 text

Null safety: Anti-pattern example

Slide 8

Slide 8 text

Null safety: Anti-pattern example Function to return indices matching a condition fun indicesIncludingText(list: List?, searchText: String?): Set? { What’s wrong with this code? if (searchText == null) { return null } return list.orEmpty().asSequence() .withIndex() .filter { (_, value) -> value.contains(searchText) } .map { (index, _) -> index } .toSet() }

Slide 9

Slide 9 text

Null safety: Problem Inappropriate nullable parameters 
 
 Unpredictable behavior for null argument - Null list: Returns empty set - Null searchText: Returns a null set fun indicesIncludingText(list: List?, searchText: String?): Set? {

Slide 10

Slide 10 text

Null safety: Solution Options: 1. Use "standard/default/initial value" instead of null 2. Write documentation for null argument 3. Create a null object explicitly

Slide 11

Slide 11 text

Null safety: Solution Options: 1. Use "standard/default/initial value" instead of null 2. Write documentation for null argument 3. Create a null object explicitly

Slide 12

Slide 12 text

Null safety: Solution, option 1 Use "standard/default/initial value" instead of null - empty(): Collection - "": String - 0: adder, size, count - 1: multiplier, exponent - MIN_VALUE: timestamp, max value selection, lower bound - INVALID, INITIAL: model, state

Slide 13

Slide 13 text

Null safety: Solution, option 1 (cont.) Select a standard/default/initial value carefully Some values might not be appropriate - 0 for invalid UInt/ULong value (ID, age, ...) - -1 or size for an invalid index - "" for invalid UUID string 
 Keep null if there is a special meaning

Slide 14

Slide 14 text

Null safety: Solution Options: 1. Use "standard/default/initial value" instead of null 2. Write documentation for null argument 3. Create a null object explicitly

Slide 15

Slide 15 text

Null safety: Solution, option 2 Write documentation for null argument class TextUiElement { fun setText(text: String?) { ... } } /** * Shows [text] in this UI element. * * This UI element is hidden with a null argument * while an empty string `""` is shown explicitly. */

Slide 16

Slide 16 text

Null safety: Solution Options: 1. Use "standard/default/initial value" instead of null 2. Write documentation for null argument 3. Create a null object explicitly

Slide 17

Slide 17 text

Null safety: Solution, option 3 Create a null object explicitly sealed class TextUiElementState { class Visible(text: String) : TextUiElementState() object Hidden : TextUiElementState() } Note: Avoid over-engineering class TextUiElement { fun setState(state: TextUiElementState) { ... } }

Slide 18

Slide 18 text

Null safety: Summary

Slide 19

Slide 19 text

Null safety: Summary Don't overuse null value - Replace with "default value" - Write documentation or create a null object

Slide 20

Slide 20 text

Contents of this talk - Null safety - Scope function - Data class - Extension - Default argument - Sealed class

Slide 21

Slide 21 text

Scope function Executes a given function within the context of an object - cf. https://kotlinlang.org/docs/scope-functions.html - 5 functions: let, run, with, apply, and also Typical purposes: - Nullable value handling - Function call grouping/chaining Introduce 1 anti-pattern example

Slide 22

Slide 22 text

Scope function: Anti-pattern example

Slide 23

Slide 23 text

Scope function: Anti-pattern example Cache layer for query/response class ResponseValueCache(...) { private val cachedValues: MutableMap = mutableMapOf() } What’s wrong with this code? fun getCacheOrQuery(queryParams: Q): V? = cachedValues[query] ?: queryClient.query(queryParams) .let { when (it) { is Response.Success -> it.value is Response.Error -> null } }?.also { cachedValues[query] = it }

Slide 24

Slide 24 text

Scope function: Problem Scope functions just for chaining .let { when (it) { is Response.Success -> it.value is Response.Error -> null } }?.also { cachedValues[query] = it }

Slide 25

Slide 25 text

Scope function: Problem Scope functions just for chaining - Not easy to find the receiver .let { when (it) { is Response.Success -> it.value is Response.Error -> null } }?.also { cachedValues[query] = it }

Slide 26

Slide 26 text

Scope function: Problem Scope functions just for chaining - Not easy to find the receiver - The important side-effect is hidden .let { when (it) { is Response.Success -> it.value is Response.Error -> null } }?.also { cachedValues[query] = it }

Slide 27

Slide 27 text

Scope function: Problem Scope functions just for chaining - Not easy to find the receiver - The important side-effect is hidden .let { when (it) { is Response.Success -> it.value is Response.Error -> null } }?.also { cachedValues[query] = it }

Slide 28

Slide 28 text

Scope function: Solution Options: 1. Simplify the chain 2. Break the chain

Slide 29

Slide 29 text

Scope function: Solution Options: 1. Simplify the chain: Extract as private functions or extensions 2. Break the chain .let { when (it) { is Response.Success -> it.value is Response.Error -> null } } queryClient.query(queryParams)

Slide 30

Slide 30 text

Scope function: Solution Options: 1. Simplify the chain: Extract as private functions or extensions 2. Break the chain queryClient.query(queryParams).valueOrNull private val Response.valueOrNull: R? get() = when(this) { ... }

Slide 31

Slide 31 text

Scope function: Solution Options: 1. Simplify the chain: Extract as private functions or extensions 2. Break the chain: Define local values private val Response.valueOrNull: R? get() = when(this) { ... } queryClient.query(queryParams).valueOrNull

Slide 32

Slide 32 text

Scope function: Solution Options: 1. Simplify the chain: Extract as private functions or extensions 2. Break the chain: Define local values val responseValue = queryClient.query(queryParams).valueOrNull if (responseValue != null) { cachedValues[query] = responseValue } return responseValue private val Response.valueOrNull: R? get() = when(this) { ... }

Slide 33

Slide 33 text

Scope function: Solution Options: 1. Simplify the chain: Extract as private functions or extensions 2. Break the chain: Define local values val responseValue = queryClient.query(queryParams).valueOrNull if (responseValue != null) { cachedValues[query] = responseValue } return responseValue private val Response.valueOrNull: R? get() = when(this) { ... }

Slide 34

Slide 34 text

Scope function: Other anti-patterns

Slide 35

Slide 35 text

Scope function: Other anti-patterns Nested scope functions
 
 
 
 ?.let for not important value receiver.apply { property = MutableValue().apply { ... } anotherProperty = MutableValue().apply { ... } } option?.let { repository.store(data, it) } if (option != null) { repository.store(data, option) }

Slide 36

Slide 36 text

Scope function: Summary

Slide 37

Slide 37 text

Scope function: Summary Don't use scope functions just to make a chain - Make the objective of the chain clear - Simplify or break the chain if possible

Slide 38

Slide 38 text

Contents of this talk - Null safety - Scope function - Data class - Extension - Default argument - Sealed class

Slide 39

Slide 39 text

Data class A class to represent a plain product data type - cf. https://kotlinlang.org/docs/data-classes.html - Some functions (equals, toString, ...) are automatically derived Similar to case class, record, and tuple at other languages Introduce 2 anti-pattern examples

Slide 40

Slide 40 text

Data class 1/2: Anti-pattern example

Slide 41

Slide 41 text

Data class 1/2: Anti-pattern example A data class with - A readonly property of a mutable object - A non read-only property of an immutable object data class FooModel( val mutableElement: MutableElement, var immutableElement: ImmutableElement ) What’s wrong with this code?

Slide 42

Slide 42 text

Data class 1/2: Problem Multiple ways to update the properties - Create a new instance - Modify a property directly 
 
 
 Data class can be shared and have a long lifecycle - The affected scope of modification is ambiguous fooModel.immutableElement = ImmutableElement(0) fooModel.copy(immutableElement = ImmutableElement(0))

Slide 43

Slide 43 text

Data class 1/2: Solution Make data classes immutable - Use read-only variables - Use immutable property type instances data class FooModel(val immutableElement: ImmutableElement)

Slide 44

Slide 44 text

Data class 2/2: Anti-pattern example

Slide 45

Slide 45 text

Data class 2/2: Anti-pattern example A data class of UI layout attributes data class FooLayoutParameters( val titleText: String, val backgroundColorInt: Int, val buttonText: String, val onButtonClickListener: () -> Unit ) What’s wrong with this code?

Slide 46

Slide 46 text

Data class 2/2: Problem Ambiguous equivalency data class FooLayoutParameters( val titleText: String, val backgroundColorInt: Int, val buttonText: String, val onButtonClickListener: () -> Unit )

Slide 47

Slide 47 text

Data class 2/2: Problem Ambiguous equivalency // false FooLayoutParameters("", 0, "") { privateFunction() } == FooLayoutParameters("", 0, "") { privateFunction() } // true FooLayoutParameters("", 0, "", ::privateFunction) == FooLayoutParameters("", 0, "", ::privateFunction) // Java code // false new FooLayoutAttributes("", 0, "", JavaClass::staticMethod).equals( new FooLayoutAttributes("", 0, "", JavaClass::staticMethod) )

Slide 48

Slide 48 text

Data class 2/2: Solution Confirm every property is data class friendly If not... - Use non-data class - Split properties into two classes class FooLayoutAttributes( val titleText: String, val backgroundColorInt: Int, val buttonText: String, val onButtonClickListener: () -> Unit )

Slide 49

Slide 49 text

Data class: Other anti-patterns

Slide 50

Slide 50 text

Data class: Other anti-patterns Non-product data model
 
 
 
 Special case: Should be replaced with a sealed class // An invalid instance is easily created by `copy`. data class CoinState(val coinCount: Int, val coinText: String) { constructor(coinCount: Int): this(coinCount, "Coins: $coinCount") } data class Response(val result: R? = null, val error: E? = null)

Slide 51

Slide 51 text

Data class: Summary

Slide 52

Slide 52 text

Data class: Summary Confirm whether a data class behaves as a plain product data type - Immutability - Equivalency

Slide 53

Slide 53 text

Contents of this talk - Null safety - Scope function - Data class - Extension - Default argument - Sealed class

Slide 54

Slide 54 text

Extension Defines a function of an existing receiver type - Adds a method without inheriting or decorator - cf. https://kotlinlang.org/docs/extensions.html Introduce 3 anti-pattern examples

Slide 55

Slide 55 text

Extension 1/3: Anti-pattern example

Slide 56

Slide 56 text

Extension 1/3: Anti-pattern example Parses the receiver and creates a UserModel instance - At the top-level of a Kotlin file fun String.toUserModel(): UserModel? { val userId = ... ?: return null val userName = ... ?: return null ... return UserModel(userId, userName, ...) } What’s wrong with this code?

Slide 57

Slide 57 text

Extension 1/3: Problem Feature specific public top-level extension - Causes too many IDE suggestions

Slide 58

Slide 58 text

Extension 1/3: Solution Avoid "public" or "extension" for feature specific code - Use internal or private - Define as a normal function in an object 
 Confirm if it's natural to define the method in the receiver class class String: ... { fun toFeatureSpecificModel(): FeatureSpecificModel = ...

Slide 59

Slide 59 text

Extension 2/3: Anti-pattern example

Slide 60

Slide 60 text

Extension 2/3: Anti-pattern example An extension which modifies an instance member open class FooClass { private val registeredElements: MutableSet = mutableSetOf() protected fun List.register() { registeredElements += this } } What’s wrong with this code?

Slide 61

Slide 61 text

Extension 2/3: Problem The implicit receiver hides modification
 register modifies the parent property class BarClass(...): FooClass() { fun someFunction() { ... someDataList.register() } }

Slide 62

Slide 62 text

Extension 2/3: Root cause How do we guess a function behavior 1. Has an important return value: No modification 2. Has a receiver: Modify the receiver 3. Has a parameter: Modify the parameter 
 someDataList.register() looks modifying the receiver

Slide 63

Slide 63 text

Extension 2/3: Solution Remove implicit receiver modification - An instance method is enough open class FooClass { protected fun register(list: Collection) { registeredElements += list } } ... val listToRegister = dataList.map...filter... register(listToRegister) - A receiver-less function implies that this can be the receiver

Slide 64

Slide 64 text

Extension 3/3: Anti-pattern example

Slide 65

Slide 65 text

Extension 3/3: Anti-pattern example Background: - Observable instance is observed while ObserverScope is alive interface Observable { /** * Calls `action` when this instance is updated and `scope` is alive... */ fun observe(scope: ObserverScope, action: (T) -> Unit) }

Slide 66

Slide 66 text

Extension 3/3: Anti-pattern example (cont.) An extension allows to pass ObserverScope as the receiver fun ObserverScope.observe(observable: Observable, action: (T) -> Unit) = observable.observe(this, action) What’s wrong with this code? class FooObserver(private val observerScope: ObserverScope) { init { ... observerScope.observe(observable) { /* do something */ } observerScope.observe(anotherObservable) { /* do another thing */ }

Slide 67

Slide 67 text

Extension 3/3: Problem Non obvious prioritization of function calls - Example: A class inheriting both ObserverScope and Observable class BadClass: ObserverScope(), Observable { ... } fun function(badInstance1: BadClass, badInstance2: BadClass) { // As `Observable.observe` badInstance1.observe(badInstance2) { ... } // As `ObserverScope.observe` (badInstance1 as ObserverScope).observe(badInstance2) { ... }

Slide 68

Slide 68 text

Extension 3/3: Root cause An extension is similar to an overloading method
 
 "Overloaded functions flipping the parameter order" is confusing fun observe(scope: ObserverScope, observable: Observable) { ... } fun observe(observable: Observable, scope: ObserverScope) { ... }

Slide 69

Slide 69 text

Extension 3/3: Solution Options: 1. Remove extensions flipping the receiver and the parameter 2. Give different names for such extensions fun ObserverScope.observe(observable: Observable) { ... } fun Observable.observedDuring(scope: ObserverScope) { ... }

Slide 70

Slide 70 text

Extension: Other anti-patterns

Slide 71

Slide 71 text

Extension: Other anti-patterns With inheritance 
 (cf., https://kotlinlang.org/docs/extensions.html#extensions-are-resolved-statically)
 
 
 
 With down-casting for non-sealed class fun Base.getName() = "Base" fun Derived.getName() = "Derived" val base: Base = Derived() base.getName() // "Base" val RpcFrameworkException.errorCode: ErrorCode get() = when (this) { is FooException -> fooErrorCode is BarException -> barErrorCode else -> ErrorCode.UNKNOWN }

Slide 72

Slide 72 text

Extension: Summary

Slide 73

Slide 73 text

Extension: Summary Extension behavior should look as natural as a normal function - For feature specific logic - For the receiver - For sub-typing

Slide 74

Slide 74 text

Contents of this talk - Null safety - Scope function - Data class - Extension - Default argument - Sealed class

Slide 75

Slide 75 text

Default argument Allows skipping arguments without boiler plates for overloading - cf. https://kotlinlang.org/docs/functions.html#default-arguments Introduce 2 anti-pattern examples

Slide 76

Slide 76 text

Default argument 1/2: Anti-pattern example

Slide 77

Slide 77 text

Default argument 1/2: Anti-pattern example An extension filtering even/odd numbers What’s wrong with this code? fun List.filterParity(takesEven: Boolean = false): List { val expectedRemainder = if (takesEven) 0 else 1 return filter { it % 2 == expectedRemainder } } - true: Returns even elements - false: Returns odd elements

Slide 78

Slide 78 text

Default argument 1/2: Problem The default value is not predictable on the caller side
 
 
 Some booleans might be predictable - View.isEnabled: true? - CheckBox.isChecked: false? - isEven: ??? listOf(1, 2, 3, 4).filterParity() // even or odd?

Slide 79

Slide 79 text

Default argument 1/2: Solution Use well-known or predictable default values - 0, 1, MIN_VALUE, empty(), null, null object - "Initial value" can be a hint 
 Simply, remove default argument

Slide 80

Slide 80 text

Default argument 2/2: Anti-pattern example

Slide 81

Slide 81 text

Default argument 2/2: Anti-pattern example Changing the logic depending on which parameter is given fun setBackground(colorInt: Int? = null, themeType: ThemeType? = null) { when { colorInt != null -> ... themeType != null -> ... } } What’s wrong with this code? view.setBackground(colorInt = Color.WHITE) view.setBackground(themeType = ThemeType.PRIMARY)

Slide 82

Slide 82 text

Default argument 2/2: Problem The number of arguments might not be one - The behavior is not predictable // This may apply to both arguments. // Or, one of the arguments takes the priority. view.setColor(colorInt = Color.WHITE, themeType = ThemeType.PRIMARY) // This may do nothing, or may throw an exception. view.setColor()

Slide 83

Slide 83 text

Default argument 2/2: Solution Options: 1. Split functions 2. Implement a function for type conversion 3. Define a sealed class of the parameter

Slide 84

Slide 84 text

Default argument 2/2: Solution Options: 1. Split functions 2. Implement a function for type conversion 3. Define a sealed class of the parameter fun setBackgroundColor(colorInt: Int) { ... } fun setBackgroundTheme(themeType: ThemeType) { ... }

Slide 85

Slide 85 text

Default argument 2/2: Solution Options: 1. Split functions 2. Implement a function for type conversion 3. Define a sealed class of the parameter fun setBackgroundColor(colorInt: Int) { ... } enum class ThemeType { ... fun toColorInt(): Int { ... } }

Slide 86

Slide 86 text

Default argument 2/2: Solution Options: 1. Split functions 2. Implement a function for type conversion 3. Define a sealed class of the parameter fun setBackground(model: BackgroundModel) { ... } sealed class BackgroundModel { class Color(val colorInt: Int) : BackgroundModel() class Theme(val themeType: ThemeType) : BackgroundModel() }

Slide 87

Slide 87 text

Default argument: Other anti-patterns

Slide 88

Slide 88 text

Default argument: Other anti-patterns With vararg
 
 
 
 
 
 Pass the child class constructor parameter fun getGeneralizedMean(vararg values: Double, exponent: Double = 1.0): Double = ... // Executed as getGeneralizedMean(2.0, 4.0, 1.0, exponent = 1.0) // instead of getGeneralizedMean(2.0, 4.0, exponent = 1.0) getGeneralizedMean(*doubleArrayOf(2.0, 4.0), 1.0) abstract class Layout(themeType: ThemeType = ThemeType.LIGHT) class FooLayout(themeType: ThemeType): Layout(themeType)

Slide 89

Slide 89 text

Default argument: Summary

Slide 90

Slide 90 text

Default argument: Summary Use default argument just for "default" - Default value must be predictable - Don't use for any other purpose - Consider to remove the default argument

Slide 91

Slide 91 text

Contents of this talk - Null safety - Scope function - Data class - Extension - Default argument - Sealed class

Slide 92

Slide 92 text

Sealed class A class cannot be overridden externally - cf. https://kotlinlang.org/docs/functions.html#default-arguments - Used to represent a sum data type, basically Introduce 2 anti-pattern examples

Slide 93

Slide 93 text

Sealed class 1/2: Anti-pattern example

Slide 94

Slide 94 text

Sealed class 1/2: Anti-pattern example A sealed class Color with child data classes: Rgb, Cmy, and Hsl sealed class Color { data class Rgb(val red: UByte, val green: UByte, val blue: UByte) : Color() data class Cmy(val cyan: UByte, val magenta: UByte, val yellow: UByte) : Color() data class Hsl(val hue: AngleInt, val saturation: PercentInt, ...) : Color() } What’s wrong with this code?

Slide 95

Slide 95 text

Sealed class 1/2: Problem Ambiguity on equivalency val RED_1: Color = Color.Rgb(red = 255u, green = 0u, blue = 0u) val RED_2: Color = Color.Cmy(cyan = 0u, magenta = 255u, yellow = 255u) RED_1 == RED_2 // Can be false!

Slide 96

Slide 96 text

Sealed class 1/2: Solution 1 Choose a "main" unit - Create constructors/factories to convert to the "main" unit data class Color(val red: UByte, val green: UByte, val blue: UByte) { companion object { fun fromCmy(cyan: UByte, magenta: UByte, yellow: UByte) : Color = Color((UByte.MAX_VALUE - cyan).toUByte(), ...) fun fromHsl(hue: AngleInt, ...) : Color = Color(...)

Slide 97

Slide 97 text

Sealed class 1/2: Note of solution 1 Resolution and value domain may differ - Rounding error - HSL color has "hue" even for black or white val redHue = AngleInt.of(0) val blackColor = Color.fromHsl( hue = redHue, saturation = PercentInt.MIN, lightness = PercentInt.MIN ) blackColor.hue != redHue // May be true

Slide 98

Slide 98 text

Sealed class 1/2: Solution 2 Implement equals function for every child class combination
 
 Can be too complex - Number of combinations: (n2) / 2 - hashCode must be implemented too

Slide 99

Slide 99 text

Sealed class 1/2: Solution 3 Define as "representation" instead of "value" - Consider to use non-data class sealed class ColorRepresentation { class Rgb(val red: UByte, ...) : ColorRepresentation() class Cmy(val cyan: UByte, ...) : ColorRepresentation() class Hsl(val hue: AngleInt, ...) : ColorRepresentation() }

Slide 100

Slide 100 text

Sealed class 2/2: Anti-pattern example

Slide 101

Slide 101 text

Sealed class 2/2: Anti-pattern example Data model of "comment" for a specific location sealed class GeoLocationComment { } What's wrong with this code? class AtLatLon( val commentText: String, val latitude: Long, val longitude: Long ) : GeoLocationComment() class AtPlace( val commentText: String, val placeId: Long ) : GeoLocationComment()

Slide 102

Slide 102 text

Sealed class 2/2: Problem Unnecessary downcast - Required to take a common property val commentText = when(locationComment) { is GeoLocationComment.AtLatLon -> locationComment.commentText is GeoLocationComment.AtPlace -> locationComment.commentText }

Slide 103

Slide 103 text

Sealed class 2/2: Solution? Extract the common property to the parent sealed class GeoLocationComment { class AtLatLon( val commentText: String, val latitude: Long, val longitude: Long ) : GeoLocationComment() class AtPlace( val commentText: String, val placeId: Long ) : GeoLocationComment() }

Slide 104

Slide 104 text

Sealed class 2/2: Solution? Extract the common property to the parent sealed class GeoLocationComment { class AtLatLon( commentText: String, val latitude: Long, val longitude: Long ) : GeoLocationComment() class AtPlace( commentText: String, val placeId: Long ) : GeoLocationComment() }

Slide 105

Slide 105 text

Sealed class 2/2: Solution? Extract the common property to the parent sealed class GeoLocationComment(val commentText: String) { class AtLatLon( commentText: String, val latitude: Long, val longitude: Long ) : GeoLocationComment(commentText) class AtPlace( commentText: String, val placeId: Long ) : GeoLocationComment(commentText) }

Slide 106

Slide 106 text

Sealed class 2/2: Solution? Extract the common property to the parent sealed class GeoLocationComment(val commentText: String) { class AtLatLon( commentText: String, val latitude: Long, val longitude: Long ) : GeoLocationComment(commentText) class AtPlace( commentText: String, val placeId: Long ) : GeoLocationComment(commentText) }

Slide 107

Slide 107 text

Sealed class 2/2: Other problems 1. Need to update all the children to add a new property 2. open val is required to make a data class

Slide 108

Slide 108 text

Sealed class 2/2: Other problems 1. Need to update all the children to add a new property 2. open val is required to make a data class sealed class GeoLocationComment( val commentText: String, val commentAuthorId: Long ) { class AtLatLon( commentText: String, commentAuthorId: Long, ... ) : GeoLocationComment(commentText, commentAuthorId) ...

Slide 109

Slide 109 text

Sealed class 2/2: Other problems 1. Need to update all the children to add a new property 2. open val is required to make a data class sealed class GeoLocationComment( open val commentText: String, open val commentAuthorId: Long ) { data lass AtLatLon( override val commentText: String, override val commentAuthorId: Long, ... ) : GeoLocationComment(commentText, commentAuthorId) ...

Slide 110

Slide 110 text

Sealed class 2/2: Solution Minimize the sealed class responsibility - Split the sealed class of location type from GeoLocationComment sealed class GeoLocation { class LatLon(val latitude: Long, val longitude: Long) : GeoLocation() class Place(val placeId: Long) : GeoLocation() } class GeoLocationComment( val commentText: String, val location: GeoLocation )

Slide 111

Slide 111 text

Sealed class: Summary

Slide 112

Slide 112 text

Sealed class: Summary Keep sealed class responsibility minimized - Make unrelated properties accessible without downcast - Try to split if there is a "common" property

Slide 113

Slide 113 text

Contents of this talk - Null safety - Scope function - Data class - Extension - Default argument - Sealed class Sealed class

Slide 114

Slide 114 text

Aside

Slide 115

Slide 115 text

Aside 1. How to share the knowledge 2. Non-Kotlin specific topic 3. Something is missing

Slide 116

Slide 116 text

Aside 1. How to share the knowledge
 
 
 
 
 
 
 
 2. Non-Kotlin specific topic 3. Something is missing Watch a presentation at DroidKaigi 2021
 
 ௕͘ੜ͖Δίʔυϕʔεͷʮ඼࣭ʯ໰୊ʹ޲͖߹͏
 https://droidkaigi.jp/2021/timetable/276957/?day=2

Slide 117

Slide 117 text

Aside 1. How to share the knowledge 2. Non-Kotlin specific topic
 
 
 
 
 
 
 
 3. Something is missing Presentation "code readability"ɻ
 https://speakerdeck.com/munetoshi/code-readability Book "ಡΈ΍͍͢ίʔυͷΨΠυϥΠϯ
 -࣋ଓՄೳͳιϑτ΢ΣΞ։ൃͷͨΊʹ"
 https://gihyo.jp/book/2022/978-4-297-13036-7

Slide 118

Slide 118 text

Aside 1. How to share the knowledge 2. Non-Kotlin specific topic 3. Something is missing
 


Slide 119

Slide 119 text

Aside 1. How to share the knowledge 2. Non-Kotlin specific topic 3. Something is missing
 


Slide 120

Slide 120 text

Aside 1. How to share the knowledge 2. Non-Kotlin specific topic 3. Something is missing
 
 Coroutines? Listen to the next presentation!


Slide 121

Slide 121 text

Summary

Slide 122

Slide 122 text

Summary Kotlin's language feature is convenient, but needs attentions - Make the objective clear - Don't mistake the means and the ends
 Introduced anti-patterns and the solutions - For null safety, scope functions, data classes, extensions...