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

Code readability

Code readability

Munetoshi Ishikawa

September 09, 2019
Tweet

More Decks by Munetoshi Ishikawa

Other Decks in Programming

Transcript

  1. Code Readability
    Munetoshi Ishikawa

    View Slide

  2. Code readability session 1
    Introduction and Principles

    View Slide

  3. What readable code is
    - Obvious: isVisible rather than flag
    - Simple: isA && isB rather than !(!isA || !isB) && isB
    - Isolated: responsibility and dependency
    - Structured: format, members, interactions, and abstraction layer
    Introduction and Principles > Introduction

    View Slide

  4. Why we need readable code
    Introduction and Principles > Introduction

    View Slide

  5. Why we need readable code
    Sustainable development
    - To extend saturation curve of development
    Introduction and Principles > Introduction

    View Slide

  6. Why we need readable code
    Sustainable development
    - To extend saturation curve of development
    Reading code > Writing code
    - Requesting code review from two or more engineers
    - Complicated bug fix with a few lines
    Introduction and Principles > Introduction

    View Slide

  7. Optimize for sustainable development
    Focus on team productivity
    Introduction and Principles > Introduction

    View Slide

  8. Optimize for sustainable development
    Focus on team productivity
    Your 5 minutes could save 1 hour for others
    Add a comment, write test, refactor
    Introduction and Principles > Introduction

    View Slide

  9. Optimize for sustainable development
    Focus on team productivity
    Your 5 minutes could save 1 hour for others
    Add a comment, write test, refactor
    We may need to update personnel rating criteria
    Don't focus only on short-term speed to implement
    Introduction and Principles > Introduction

    View Slide

  10. Prisoner's dilemma
    Team productivity may decrease if we focus on the personal
    Introduction and Principles > Introduction

    View Slide

  11. Prisoner's dilemma
    Team productivity may decrease if we focus on the personal
    Eng. A ┃ │
    ┃ Write clean code │ Write hacky code
    Eng. B ┃ │
    ━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━
    ┃ 8 │ 10
    Write clean code ┃ │
    ┃ 8 │ 1
    ──────────────────╂──────────────────┼───────────────────
    ┃ 1 │ 2
    Write hacky code ┃ │
    ┃ 10 │ 2
    Introduction and Principles > Introduction

    View Slide

  12. Learn how to write readable code
    Feature implementation itself is easy
    Special training is not required
    Introduction and Principles > Introduction

    View Slide

  13. Learn how to write readable code
    Feature implementation itself is easy
    Special training is not required
    No learning, No readable code
    - Training, lectures
    - Peer code review
    - Self-education
    Introduction and Principles > Introduction

    View Slide

  14. Contents of this lecture
    - Introduction and Principles
    - Natural language: Naming, Comments
    - Inner type structure: State, Procedure
    - Inter type structure: Dependency (two sessions)
    - Follow-up: Review
    Introduction and Principles > Introduction

    View Slide

  15. Contents of this lecture
    - Introduction and Principles
    - Natural language: Naming, Comments
    - Inner type structure: State, Procedure
    - Inter type structure: Dependency (two sessions)
    - Follow-up: Review
    Introduction and Principles > Introduction

    View Slide

  16. Topics
    - Introduction
    - The boy scout rule
    - YAGNI
    - KISS
    - Single responsibility principle
    - Premature optimization is the root of all evil
    Introduction and Principles > Principles

    View Slide

  17. Topics
    - Introduction
    - The boy scout rule
    - YAGNI
    - KISS
    - Single responsibility principle
    - Premature optimization is the root of all evil
    Introduction and Principles > Principles

    View Slide

  18. The boy scout rule
    Try to leave this world a little better than you found it...
    — Robert Baden-Powell
    Introduction and Principles > Principles > The boy scout rule

    View Slide

  19. The boy scout rule
    Try to leave this world a little better than you found it...
    — Robert Baden-Powell
    Introduced to software development by Robert C. Martin1
    Clean code whenever you have touched it
    1 97 Things Every Programmer Should Know: Collective Wisdom from the Experts, Kevlin Henney, 2010
    Introduction and Principles > Principles > The boy scout rule

    View Slide

  20. Dos for the boy scout rule
    - Add: comments, tests
    - Remove: unnecessary dependencies, members, and conditions
    - Rename: types, functions, and values
    - Break: huge types, huge functions, nests, and call sequences
    - Structure: dependencies, abstraction layers, and type hierarchy
    Introduction and Principles > Principles > The boy scout rule

    View Slide

  21. Don'ts for the boy scout rule 1/2
    Don't add an element in a huge structure
    - Sentence in a huge method
    - Case in a huge conditional branch
    - Member in a huge type
    - Callback in a huge call sequence
    - Inheritance in a huge hierarchy
    Introduction and Principles > Principles > The boy scout rule

    View Slide

  22. Don'ts for the boy scout rule 2/2
    Don't add something without thought
    Introduction and Principles > Principles > The boy scout rule

    View Slide

  23. Don'ts for the boy scout rule 2/2
    Don't add something without thought
    Is the code location correct?
    - Consider restructuring before adding a conditional branch
    Introduction and Principles > Principles > The boy scout rule

    View Slide

  24. Don'ts for the boy scout rule 2/2
    Don't add something without thought
    Is the code location correct?
    - Consider restructuring before adding a conditional branch
    Can't you simplify?
    - Merge copied values or conditions
    - Look around where you want to change
    Introduction and Principles > Principles > The boy scout rule

    View Slide

  25. Example of "don'ts" 1/2
    Question: Is it fine to add a new type Z?
    val viewType: ViewType = ... // An enum type
    when (viewType) {
    A -> {
    view1.isVisible = true
    view2.text = "Case A"
    }
    B -> {
    view1.isVisible = false
    view2.text = "Case B"
    }
    ...
    Introduction and Principles > Principles > The boy scout rule

    View Slide

  26. Example of "don'ts" 2/2
    Answer: No
    Introduction and Principles > Principles > The boy scout rule

    View Slide

  27. Example of "don'ts" 2/2
    Answer: No
    Don't add a new condition if there are too many branches
    Introduction and Principles > Principles > The boy scout rule

    View Slide

  28. Example of "don'ts" 2/2
    Answer: No
    Don't add a new condition if there are too many branches
    Solution: Apply strategy pattern
    Introduction and Principles > Principles > The boy scout rule

    View Slide

  29. Example of "dos"
    1: Extract parameters as constructor parameters
    (or computed properties)
    enum class ViewType(val isView1Visible: Boolean, val view2Text: String)
    Introduction and Principles > Principles > The boy scout rule

    View Slide

  30. Example of "dos"
    1: Extract parameters as constructor parameters
    (or computed properties)
    enum class ViewType(val isView1Visible: Boolean, val view2Text: String)
    2: Remove conditional branches with the parameters
    view1.isVisible = viewType.isView1Visible
    view2.text = viewType.view2Text
    Introduction and Principles > Principles > The boy scout rule

    View Slide

  31. Example of "dos"
    1: Extract parameters as constructor parameters
    (or computed properties)
    enum class ViewType(val isView1Visible: Boolean, val view2Text: String)
    2: Remove conditional branches with the parameters
    view1.isVisible = viewType.isView1Visible
    view2.text = viewType.view2Text
    3: Add a new type Z.
    Introduction and Principles > Principles > The boy scout rule

    View Slide

  32. Topics
    - Introduction
    - The boy scout rule
    - YAGNI
    - KISS
    - Single responsibility principle
    - Premature optimization is the root of all evil
    Introduction and Principles > Principles

    View Slide

  33. YAGNI
    You Aren't Gonna Need It
    = Implement it only when you need it
    - 90% of features for the future are not used2
    - Keep structure simple = ready for unexpected change2
    2 http://www.extremeprogramming.org/rules/early.html
    Introduction and Principles > Principles > YAGNI

    View Slide

  34. YAGNI
    You Aren't Gonna Need It
    = Implement it only when you need it
    - 90% of features for the future are not used2
    - Keep structure simple = ready for unexpected change2
    Exception: Public library for external people
    2 http://www.extremeprogramming.org/rules/early.html
    Introduction and Principles > Principles > YAGNI

    View Slide

  35. YAGNI: Example
    - Unused types, procedures and values
    - Interface or abstract class with only one implementation
    - Function parameter for a constant value
    - Public global utility function only for one client code
    - Code commented out
    Introduction and Principles > Principles > YAGNI

    View Slide

  36. Topics
    - Introduction
    - The boy scout rule
    - YAGNI
    - KISS
    - Single responsibility principle
    - Premature optimization is the root of all evil
    Introduction and Principles > Principles > KISS

    View Slide

  37. KISS
    Keep It Simple Stupid
    — Clarence Leonard "Kelly" Johnson
    Choose simpler solution
    - Limit and specify the usage of a library/framework/design
    - Use the default implementation as far as possible
    Introduction and Principles > Principles > KISS

    View Slide

  38. KISS
    Keep It Simple Stupid
    — Clarence Leonard "Kelly" Johnson
    Choose simpler solution
    - Limit and specify the usage of a library/framework/design
    - Use the default implementation as far as possible
    Beautiful code is not always readable
    Introduction and Principles > Principles > KISS

    View Slide

  39. KISS: Good example
    fun getActualDataSingle(): Single> = Single
    .fromCallable(dataProvider::provide)
    .subscribeOn(ioScheduler)
    Introduction and Principles > Principles > KISS

    View Slide

  40. KISS: Good example
    fun getActualDataSingle(): Single> = Single
    .fromCallable(dataProvider::provide)
    .subscribeOn(ioScheduler)
    fun getDummyDataSingle(): Single> =
    Single.just(listOf(1, 10, 100))
    Introduction and Principles > Principles > KISS

    View Slide

  41. KISS: Bad example 1/2
    fun getActualDataSingle(): Single> = Single
    .fromCallable(dataProvider::provide)
    .subscribeOn(ioScheduler)
    fun getDummyDataSingle(): Single> = Single
    .fromCallable { listOf(1, 10, 100) }
    .subscribeOn(ioScheduler)
    Makes the code complex for unnecessary consistency
    Introduction and Principles > Principles > KISS

    View Slide

  42. KISS: Bad example 2/2
    fun getActualDataSingle(): Single> = Single
    .fromCallable(dataProvider::provide)
    .subscribeOn(ioScheduler)
    fun getDummyDataSingle(): Single> = Observable
    .range(1, 2)
    .reduce(listOf(1)) { list, _ -> list + list.last() * 10 }
    .subscribeOn(ioScheduler)
    Uses Rx for unnecessary list creation
    (Rx was used only for changing the thread.)
    Introduction and Principles > Principles > KISS

    View Slide

  43. Topics
    - Introduction
    - The boy scout rule
    - YAGNI
    - KISS
    - Single responsibility principle
    - Premature optimization is the root of all evil
    Introduction and Principles > Principles > KISS

    View Slide

  44. Single method == small responsibility?
    Introduction and Principles > Principles > Single responsibility principles

    View Slide

  45. Single method == small responsibility?
    No, definitely!
    class Alviss {
    // May show a text, may break the device, may launch a rocket,
    // may ...
    fun doEverything(state: UniverseState)
    }
    Introduction and Principles > Principles > Single responsibility principles

    View Slide

  46. Single responsibility principle
    A class should have only one reason to change.
    — Robert C. Martin
    Introduction and Principles > Principles > Single responsibility principles

    View Slide

  47. Single responsibility principle
    A class should have only one reason to change.
    — Robert C. Martin
    We should not mix up two unrelated features
    Introduction and Principles > Principles > Single responsibility principles

    View Slide

  48. Single responsibility principle: Bad example
    class LibraryBookRentalData(
    val bookIds: MutableList,
    val bookNames: MutableList,
    ) {
    ...
    Introduction and Principles > Principles > Single responsibility principles

    View Slide

  49. Single responsibility principle: Bad example
    class LibraryBookRentalData(
    val bookIds: MutableList,
    val bookNames: MutableList,
    val bookIdToRenterNameMap: MutableMap,
    ) {
    ...
    Introduction and Principles > Principles > Single responsibility principles

    View Slide

  50. Single responsibility principle: Bad example
    class LibraryBookRentalData(
    val bookIds: MutableList,
    val bookNames: MutableList,
    val bookIdToRenterNameMap: MutableMap,
    val bookIdToDueDateMap: MutableMap, ...
    ) {
    ...
    Introduction and Principles > Principles > Single responsibility principles

    View Slide

  51. Single responsibility principle: Bad example
    class LibraryBookRentalData(
    val bookIds: MutableList,
    val bookNames: MutableList,
    val bookIdToRenterNameMap: MutableMap,
    val bookIdToDueDateMap: MutableMap, ...
    ) {
    fun findRenterName(bookName: String): String?
    fun findDueDate(bookName: String): Date?
    ...
    Introduction and Principles > Principles > Single responsibility principles

    View Slide

  52. Single responsibility principle: What is wrong
    Introduction and Principles > Principles > Single responsibility principles

    View Slide

  53. Single responsibility principle: What is wrong
    BookRentalData has all data of book, user, and circulation record
    Introduction and Principles > Principles > Single responsibility principles

    View Slide

  54. Single responsibility principle: What is wrong
    BookRentalData has all data of book, user, and circulation record
    Split a model class for each entity
    Introduction and Principles > Principles > Single responsibility principles

    View Slide

  55. Single responsibility principle: Good example
    data class BookData(val id: Int, val name: String, ...)
    data class UserData(val name: String, ...)
    Introduction and Principles > Principles > Single responsibility principles

    View Slide

  56. Single responsibility principle: Good example
    data class BookData(val id: Int, val name: String, ...)
    data class UserData(val name: String, ...)
    class CirculationRecord(
    val onLoanBookEntries: MutableMap
    ) {
    data class Entry(val renter: UserData, val dueDate: Date)
    Introduction and Principles > Principles > Single responsibility principles

    View Slide

  57. Keep responsibility small
    Split types
    - Model for each entity
    - Logic for each layer and component
    - Utility for each target type
    Introduction and Principles > Principles > Single responsibility principles

    View Slide

  58. How to confirm responsibility
    List what the type does, and try to summarize it
    Introduction and Principles > Principles > Single responsibility principles

    View Slide

  59. How to confirm responsibility
    List what the type does, and try to summarize it
    Split the type for each case as follows
    - It's hard to make a summary
    - The summary is too fat compared with the name
    Introduction and Principles > Principles > Single responsibility principles

    View Slide

  60. Topics
    - Introduction
    - The boy scout rule
    - YAGNI
    - KISS
    - Single responsibility principle
    - Premature optimization is the root of all evil
    Introduction and Principles > Principles > KISS

    View Slide

  61. Premature optimization 1/2
    We should forget about small efficiencies,
    say about 97% of the time:
    premature optimization is the root of all evil.
    — Structured Programming with go to Statements, Donald Knuth
    Introduction and Principles > Principles > Premature optimization

    View Slide

  62. Premature optimization 2/2
    Good: if the optimization cleans the code
    Bad: if the optimization makes the code complex
    Introduction and Principles > Principles > Premature optimization

    View Slide

  63. Example of good optimization 1/2
    Before:
    val data = arrayList.firstOrNull { data -> data.key == expectedKey }
    Introduction and Principles > Principles > Premature optimization

    View Slide

  64. Example of good optimization 1/2
    Before:
    val data = arrayList.firstOrNull { data -> data.key == expectedKey }
    After:
    val data = hashMap.getOrNull(key)
    Introduction and Principles > Principles > Premature optimization

    View Slide

  65. Example of good optimization 1/2
    Before:
    val data = arrayList.firstOrNull { data -> data.key == expectedKey }
    After:
    val data = hashMap.getOrNull(key)
    Simplify the code while reducing the calculation cost
    Introduction and Principles > Principles > Premature optimization

    View Slide

  66. Examples of bad optimization
    Don't optimize without profiling or platform support, either
    - Mutable instance reusing
    - Lazy initialization
    - Cache
    - Inline extraction
    - Instance pool
    Introduction and Principles > Principles > Premature optimization

    View Slide

  67. Drawbacks of optimization
    - May obstruct simplification
    - Compiler is sometimes smarter than us
    - May require overhead cost
    - Lazy initialization: (Synchronized) instance check
    - Cache: cache miss ratio * cache access time
    Introduction and Principles > Principles > Premature optimization

    View Slide

  68. Required actions before optimization
    - Ask yourself
    "Do I really need it?" or "Is it easy enough to implement?"
    - Profile
    Target (time, memory), number, rate
    Introduction and Principles > Principles > Premature optimization

    View Slide

  69. Summary
    Focus on sustainable development: Readability
    Introduction and Principles > Summary

    View Slide

  70. Summary
    Focus on sustainable development: Readability
    The boy scout rule: Clean code whenever you have touched it
    Introduction and Principles > Summary

    View Slide

  71. Summary
    Focus on sustainable development: Readability
    The boy scout rule: Clean code whenever you have touched it
    YAGNI: Implement only when you need it
    Introduction and Principles > Summary

    View Slide

  72. Summary
    Focus on sustainable development: Readability
    The boy scout rule: Clean code whenever you have touched it
    YAGNI: Implement only when you need it
    KISS: Use simple method, beautiful != readable
    Introduction and Principles > Summary

    View Slide

  73. Summary
    Focus on sustainable development: Readability
    The boy scout rule: Clean code whenever you have touched it
    YAGNI: Implement only when you need it
    KISS: Use simple method, beautiful != readable
    Single responsibility principle: Make the scope clear
    Introduction and Principles > Summary

    View Slide

  74. Summary
    Focus on sustainable development: Readability
    The boy scout rule: Clean code whenever you have touched it
    YAGNI: Implement only when you need it
    KISS: Use simple method, beautiful != readable
    Single responsibility principle: Make the scope clear
    Premature optimization: Profile or estimate before optimization
    Introduction and Principles > Summary

    View Slide

  75. codeReadabilitySession2
    N_A_M_I_N_G

    View Slide

  76. Contents of this lecture
    - Introduction and Principles
    - Natural language: Naming, Comments
    - Inner type structure: State, Procedure
    - Inter type structure: Dependency (two sessions)
    - Follow-up: Review
    Naming > Introduction

    View Slide

  77. What we name
    - Type: class, interface, enum, struct, protocol, trait
    - Value: property, field, parameter, local value
    - Procedure: function, method, subroutine
    - Scope: package, module, namespace
    - Resource: file, directory, ID
    - etc.
    Naming > Introduction

    View Slide

  78. What a good name is
    - Accurate
    isVisible shouldn't be used for sound
    - Descriptive
    width/height rather than w/h
    - Unambiguous
    imageView/imageBitmap/imageUrl rather than image
    Naming > Introduction

    View Slide

  79. How to create a good name
    - Use correct grammar
    - Describe what rather than who/when
    - Choose unambiguous words
    - Avoid confusing abbreviations
    - Add type/unit suffix
    - Use positive affirmation
    Naming > Introduction

    View Slide

  80. Topics
    - Use correct grammar
    - Describe what rather than who/when
    - Choose unambiguous words
    - Avoid confusing abbreviations
    - Add type/unit suffix
    - Use positive affirmation
    Naming > Introduction

    View Slide

  81. Why grammar is important
    Question 1: What is ListenerEventMessageClickViewText?
    Naming > Use the correct grammar

    View Slide

  82. Why grammar is important
    Question 1: What is ListenerEventMessageClickViewText?
    Answer?: A text of a click view (?) of listener event message (???)
    Naming > Use the correct grammar

    View Slide

  83. Why grammar is important
    Question 1: What is ListenerEventMessageClickViewText?
    Answer?: A text of a click view (?) of listener event message (???)
    Question 2: What is MessageTextViewClickEventListener?
    Naming > Use the correct grammar

    View Slide

  84. Why grammar is important
    Question 1: What is ListenerEventMessageClickViewText?
    Answer?: A text of a click view (?) of listener event message (???)
    Question 2: What is MessageTextViewClickEventListener?
    Answer: A listener of click events on a message text view
    Naming > Use the correct grammar

    View Slide

  85. Use correct grammar
    "Grammar" depends on the language and convention
    Let's look at the case of Java/Kotlin
    Naming > Use the correct grammar

    View Slide

  86. Types of names 1/2
    - Noun: Type, value (including property function)
    imageView, HashSet, indexOf
    - Imperative: Procedure
    findMessage, compareTo
    Naming > Use the correct grammar

    View Slide

  87. Types of names 2/2
    - Adjective, participle: Interface, state type/constant
    Iterable, PLAYING, CONNECTED
    - Interrogative, third-person verb: Boolean value/function
    isTextVisible, contains, equalsTo, may/shouldShow
    - Adverb phrase with preposition: Converter, callback
    toInt, fromMemberId, onNewIntent
    Naming > Use the correct grammar

    View Slide

  88. Grammar: Nouns
    Place the essential words at the last
    (= the word telling what it is)
    Naming > Use the correct grammar

    View Slide

  89. Grammar: Nouns
    Place the essential words at the last
    (= the word telling what it is)
    !
    : MessageEventHandler, buttonHeight
    Naming > Use the correct grammar

    View Slide

  90. Grammar: Nouns
    Place the essential words at the last
    (= the word telling what it is)
    !
    : MessageEventHandler, buttonHeight
    !
    : xyzHeightForPortlait (don't use for a type name)
    Naming > Use the correct grammar

    View Slide

  91. Grammar: Nouns
    Place the essential words at the last
    (= the word telling what it is)
    !
    : MessageEventHandler, buttonHeight
    !
    : xyzHeightForPortlait (don't use for a type name)
    !
    : HandlerMessageEvent (if it's a handler), heightPortlait
    Naming > Use the correct grammar

    View Slide

  92. Grammar: Nouns
    Place the essential words at the last
    (= the word telling what it is)
    !
    : MessageEventHandler, buttonHeight
    !
    : xyzHeightForPortlait (don't use for a type name)
    !
    : HandlerMessageEvent (if it's a handler), heightPortlait
    Exception: Property function with a preposition
    e.g., indexOf(value), maxValueIn(array)
    Naming > Use the correct grammar

    View Slide

  93. Grammar: Imperative
    Place a verb at the first
    Naming > Use the correct grammar

    View Slide

  94. Grammar: Imperative
    Place a verb at the first
    "get X":
    !
    getX,
    "
    xGet
    Naming > Use the correct grammar

    View Slide

  95. Grammar: Imperative
    Place a verb at the first
    "get X":
    !
    getX,
    "
    xGet
    "post Y":
    !
    postY,
    "
    yPost
    Naming > Use the correct grammar

    View Slide

  96. Grammar: Imperative
    Place a verb at the first
    "get X":
    !
    getX,
    "
    xGet
    "post Y":
    !
    postY,
    "
    yPost
    "map to Z":
    !
    mapToZ,
    "
    toZMap, zToMap
    Naming > Use the correct grammar

    View Slide

  97. How a name in a wrong order is created
    class UserActionEvent
    Naming > Use the correct grammar

    View Slide

  98. How a name in a wrong order is created
    class UserActionEvent
    class UserActionEventSwipe: UserActionEvent()
    class UserActionEventClick: UserActionEvent()
    Naming > Use the correct grammar

    View Slide

  99. How a name in a wrong order is created
    class UserActionEvent
    class UserActionEventSwipe: UserActionEvent()
    class UserActionEventClickMessageText: UserActionEvent()
    class UserActionEventClickProfileImage: UserActionEvent()
    Naming > Use the correct grammar

    View Slide

  100. How a name in a wrong order is created
    class UserActionEvent
    class UserActionEventSwipe: UserActionEvent()
    class UserActionEventClickMessageText: UserActionEvent()
    class UserActionEventClickProfileImage: UserActionEvent()
    Don't focus on comprehensiveness and consistency too much
    Imagine how it looks on the caller side
    Naming > Use the correct grammar

    View Slide

  101. Grammar: Summary
    - Types for a name
    Noun, imperative, adjective, interrogative (+ third person), adverb
    - Word order is important
    Imagine how the name looks on the caller side
    Naming > Use the correct grammar

    View Slide

  102. Topics
    - Use the correct grammar
    - Describe what rather than who/when
    - Choose unambiguous words
    - Avoid confusing abbreviations
    - Add type/unit suffix
    - Use positive affirmation
    Naming > Describe what rather than who/when

    View Slide

  103. Describe what rather than who/when
    - A name must answer
    !
    What a type/value is
    !
    What a procedure does
    Naming > Describe what rather than who/when

    View Slide

  104. Describe what rather than who/when
    - A name must answer
    !
    What a type/value is
    !
    What a procedure does
    - A name should not mention to the caller
    "
    Who it calls/uses
    "
    When/Where/Why/How it's called/used
    Naming > Describe what rather than who/when

    View Slide

  105. Function name example: Declaration
    Good
    !
    : Describe what this procedure does
    class MessageRepository {
    fun storeReceivedMessage(data: MessageData) {
    Naming > Describe what rather than who/when

    View Slide

  106. Function name example: Declaration
    Good
    !
    : Describe what this procedure does
    class MessageRepository {
    fun storeReceivedMessage(data: MessageData) {
    Bad
    !
    : Describe when this procedure should be called
    class MessageRepository {
    fun onMessageReceived(data: MessageData) {
    Naming > Describe what rather than who/when

    View Slide

  107. Function name example: Caller code
    Good
    !
    : We can know what happens by the calling code
    repository.storeReceivedMessage(messageData)
    handler.post { presenter.showNewReceivedMessage(messageData) }
    Naming > Describe what rather than who/when

    View Slide

  108. Function name example: Caller code
    Good
    !
    : We can know what happens by the calling code
    repository.storeReceivedMessage(messageData)
    handler.post { presenter.showNewReceivedMessage(messageData) }
    Bad
    !
    : We can't know what happens
    repository.onMessageReceived(messageData)
    handler.post { presenter.onMessageReceived(messageData) }
    Naming > Describe what rather than who/when

    View Slide

  109. Function name example: Another bad reason
    The procedure responsibility is ambiguous
    Naming > Describe what rather than who/when

    View Slide

  110. Function name example: Another bad reason
    The procedure responsibility is ambiguous
    class MessageViewPresenter {
    fun onMessageReceived(data: MessageData) {
    // View presentation code for new message ...
    Naming > Describe what rather than who/when

    View Slide

  111. Function name example: Another bad reason
    The procedure responsibility is ambiguous
    class MessageViewPresenter {
    fun onMessageReceived(data: MessageData) {
    // View presentation code for new message ...
    launch(...) { repository.onMessageReceived(data) } // !!
    Naming > Describe what rather than who/when

    View Slide

  112. Function name example: Another bad reason
    The procedure responsibility is ambiguous
    class MessageViewPresenter {
    fun onMessageReceived(data: MessageData) {
    // View presentation code for new message ...
    launch(...) { repository.onMessageReceived(data) } // !!
    What happens with the following code?
    repository.onMessageReceived(messageData)
    handler.post { presenter.onMessageReceived(messageData) }
    Naming > Describe what rather than who/when

    View Slide

  113. Parameter name example: Declaration
    Good
    !
    : We can know what happens if it's true
    fun showHistory(shouldShowDialogOnError: Boolean)
    Naming > Describe what rather than who/when

    View Slide

  114. Parameter name example: Declaration
    Good
    !
    : We can know what happens if it's true
    fun showHistory(shouldShowDialogOnError: Boolean)
    Bad
    !
    : We can't know what happens if it's true
    fun showHistory(isCalledFromMainActivity: Boolean)
    Naming > Describe what rather than who/when

    View Slide

  115. Parameter name example: Reason of "bad"
    1: The name easily becomes obsolete
    // ... from another activity requiring dialog
    showHistory(isCalledFromMainActivity = true)
    Naming > Describe what rather than who/when

    View Slide

  116. Parameter name example: Reason of "bad"
    1: The name easily becomes obsolete
    // ... from another activity requiring dialog
    showHistory(isCalledFromMainActivity = true)
    2: The parameter will be used for other purposes
    if (isCalledFromMainActivity) { setActivityResult(...)
    Naming > Describe what rather than who/when

    View Slide

  117. Parameter name example: Reason of "bad"
    1: The name easily becomes obsolete
    // ... from another activity requiring dialog
    showHistory(isCalledFromMainActivity = true)
    2: The parameter will be used for other purposes
    if (isCalledFromMainActivity) { setActivityResult(...)
    Causes a bug if 1 and 2 happens at the same time
    Naming > Describe what rather than who/when

    View Slide

  118. Describe what: Exception
    May need to name by how/when for an abstract callback interface
    - e.g., onClicked / onSwiped / onDestroyed
    Naming > Describe what rather than who/when

    View Slide

  119. Describe what: Exception
    May need to name by how/when for an abstract callback interface
    - e.g., onClicked / onSwiped / onDestroyed
    - Because "what" is not decided on the declaration
    Naming > Describe what rather than who/when

    View Slide

  120. Describe what: Exception
    May need to name by how/when for an abstract callback interface
    - e.g., onClicked / onSwiped / onDestroyed
    - Because "what" is not decided on the declaration
    We should name by "what" if possible even for a callback
    abstract fun toggleSelectionState()
    ...
    view.setOnClickListener { toggleSelectionState() }
    Naming > Describe what rather than who/when

    View Slide

  121. Describe what: Summary
    Describe "what" it does/is
    = Should not mention to the caller (who/when/where/why/how)
    Naming > Describe what rather than who/when

    View Slide

  122. Describe what: Summary
    Describe "what" it does/is
    = Should not mention to the caller (who/when/where/why/how)
    - Show code responsibility clearly
    - Make caller code readable
    Naming > Describe what rather than who/when

    View Slide

  123. Describe what: Summary
    Describe "what" it does/is
    = Should not mention to the caller (who/when/where/why/how)
    - Show code responsibility clearly
    - Make caller code readable
    Exception: Abstract callback interface
    Naming > Describe what rather than who/when

    View Slide

  124. Topics
    - Use the correct grammar
    - Describe what rather than who/when
    - Choose unambiguous words
    - Avoid confusing abbreviations
    - Add type/unit suffix
    - Use positive affirmation
    Naming > Choose unambiguous words

    View Slide

  125. Ambiguous words: Example 1/2
    Question: What does initializationFlag represent?
    - shouldInitialize
    - isInitializing
    - is/wasInitialized
    - isInitializable
    - isNotInitialized (!!)
    Naming > Choose unambiguous words

    View Slide

  126. Ambiguous words: Example 1/2
    Question: What does initializationFlag represent?
    - shouldInitialize
    - isInitializing
    - is/wasInitialized
    - isInitializable
    - isNotInitialized (!!)
    Choose one from the above options (Except for the last one)
    Naming > Choose unambiguous words

    View Slide

  127. Ambiguous words: Example 2/2
    Question: What does sizeLimit represent?
    - max or min?
    - height, width, byte, characters, or length?
    Naming > Choose unambiguous words

    View Slide

  128. Ambiguous words: Example 2/2
    Question: What does sizeLimit represent?
    - max or min?
    - height, width, byte, characters, or length?
    Name maxHeight, for example.
    Naming > Choose unambiguous words

    View Slide

  129. Rewording options to consider
    - flag: is, was, should, can, may, will ...
    - check: is, query, verify, measure, filter, notify, update ...
    - good, fine: valid, completed, reliable, secure, satisfies ...
    - old: previous, stored, expired, invalidated, deprecated ...
    - tmp, retval: actual name
    Naming > Choose unambiguous words

    View Slide

  130. Rewording options to consider
    - flag: is, was, should, can, may, will ...
    - check: is, query, verify, measure, filter, notify, update ...
    - good, fine: valid, completed, reliable, secure, satisfies ...
    - old: previous, stored, expired, invalidated, deprecated ...
    - tmp, retval: actual name
    Use dictionary and thesaurus
    Naming > Choose unambiguous words

    View Slide

  131. Choose unambiguous words: Summary
    - Avoid words like flag and check
    - Use a dictionary and thesaurus
    Naming > Choose unambiguous words

    View Slide

  132. Topics
    - Use the correct grammar
    - Describe what rather than who/when
    - Choose unambiguous words
    - Avoid confusing abbreviations
    - Add type/unit suffix
    - Use positive affirmation
    Naming > Avoid confusing abbreviations

    View Slide

  133. Abbreviations: Example 1/2
    Question: What does im stand for?
    Naming > Avoid confusing abbreviations

    View Slide

  134. Abbreviations: Example 1/2
    Question: What does im stand for?
    input method, illegal message, instance manager ...
    Naming > Avoid confusing abbreviations

    View Slide

  135. Abbreviations: Example 1/2
    Question: What does im stand for?
    input method, illegal message, instance manager ...
    Don't use your own acronyms
    - Recognizing is easier than recalling3
    3 100 Things: Every Designer Needs to Know About People, Susan Weinschenk, 2011
    Naming > Avoid confusing abbreviations

    View Slide

  136. Abbreviations: Example 2/2
    Question: What does str stand for?
    Naming > Avoid confusing abbreviations

    View Slide

  137. Abbreviations: Example 2/2
    Question: What does str stand for?
    string, structure, stream, streak, street, sorted transaction record
    Naming > Avoid confusing abbreviations

    View Slide

  138. Abbreviations: Example 2/2
    Question: What does str stand for?
    string, structure, stream, streak, street, sorted transaction record
    Abbreviations may be acceptable if commonly used
    - Abbreviations like URL and TCP are totally fine
    - str for string is acceptable especially for a limited scope
    Naming > Avoid confusing abbreviations

    View Slide

  139. Project-specific abbreviation
    Some abbreviations are used project-wide
    Naming > Avoid confusing abbreviations

    View Slide

  140. Project-specific abbreviation
    Some abbreviations are used project-wide
    Make readable for a new team member
    - Write documentation for type, value, or procedure
    - Prepare glossary
    Naming > Avoid confusing abbreviations

    View Slide

  141. Avoid confusing abbreviations: Summary
    - Don't use your own abbreviations
    - Commonly used abbreviations are acceptable
    - Prepare document or glossary for project-specific abbreviations
    Naming > Avoid confusing abbreviations

    View Slide

  142. Topics
    - Use the correct grammar
    - Describe what rather than who/when
    - Choose unambiguous words
    - Avoid confusing abbreviations
    - Add type/unit suffix
    - Use positive affirmation
    Naming > Add type/unit suffix

    View Slide

  143. Add type/unit suffix
    Add suffix as a supplement type/unit
    - timeout: timeoutMillis, timeoutHours
    - width: widthPoints, widthPx, widthInches
    - color: colorInt, colorResId
    - i, j, k: xxxIndex, row, col
    Naming > Add type/unit suffix

    View Slide

  144. Add type/unit suffix: Aside
    Consider creating a wrapper class of a unit
    Naming > Add type/unit suffix

    View Slide

  145. Add type/unit suffix: Aside
    Consider creating a wrapper class of a unit
    class Inch(val value: Int)
    class Centimeter(val value: Int)
    Naming > Add type/unit suffix

    View Slide

  146. Add type/unit suffix: Aside
    Consider creating a wrapper class of a unit
    class Inch(val value: Int)
    class Centimeter(val value: Int)
    fun setWidth(width: Inch) = ...
    setWidth(Centimeter(10)) // Compile error!
    Naming > Add type/unit suffix

    View Slide

  147. Add type/unit suffix: Aside
    Consider creating a wrapper class of a unit
    class Inch(val value: Int)
    class Centimeter(val value: Int)
    fun setWidth(width: Inch) = ...
    setWidth(Centimeter(10)) // Compile error!
    "value class" (Scala) or "inline class" (Kotlin) may help you
    Naming > Add type/unit suffix

    View Slide

  148. Topics
    - Use the correct grammar
    - Describe what rather than who/when
    - Choose unambiguous words
    - Avoid confusing abbreviations
    - Add type/unit suffix
    - Use positive affirmation
    Naming > Use positive affirmation

    View Slide

  149. Use positive affirmation
    !
    : Positive words, isEnabled
    Naming > Use positive affirmation

    View Slide

  150. Use positive affirmation
    !
    : Positive words, isEnabled
    !
    : Negative words, isDisabled
    Naming > Use positive affirmation

    View Slide

  151. Use positive affirmation
    !
    : Positive words, isEnabled
    !
    : Negative words, isDisabled
    !
    : Positive words with "not", "no", or "non", isNotEnabled
    Naming > Use positive affirmation

    View Slide

  152. Use positive affirmation
    !
    : Positive words, isEnabled
    !
    : Negative words, isDisabled
    !
    : Positive words with "not", "no", or "non", isNotEnabled
    !
    : Negative words with "not", "no", or "non", isNotDisabled
    Naming > Use positive affirmation

    View Slide

  153. Use positive affirmation
    !
    : Positive words, isEnabled
    !
    : Negative words, isDisabled
    !
    : Positive words with "not", "no", or "non", isNotEnabled
    !
    : Negative words with "not", "no", or "non", isNotDisabled
    We can rename isNotDisabled → isEnabled,
    and isNotEnabled → isDisabled without any logic change
    Naming > Use positive affirmation

    View Slide

  154. Topics
    - Use the correct grammar
    - Describe what rather than who/when
    - Choose unambiguous words
    - Avoid confusing abbreviations
    - Add type/unit suffix
    - Use positive affirmation
    Naming > Adhere to language/platform/project conventions

    View Slide

  155. Topics
    - Use the correct grammar
    - Describe what rather than who/when
    - Choose unambiguous words
    - Avoid confusing abbreviations
    - Add type/unit suffix
    - Use positive affirmation
    - Adhere to language/platform/project conventions
    Naming > Adhere to language/platform/project conventions

    View Slide

  156. Language/platform/project conventions
    Adhere to convention/rule/manner of language/platform/project
    Naming > Adhere to language/platform/project conventions

    View Slide

  157. Language/platform/project conventions
    Adhere to convention/rule/manner of language/platform/project
    Some conventions use a get... method, satisfying the following
    requirements
    - Returns a value
    - Finishes immediately and O(1)
    - Has no side-effect
    Naming > Adhere to language/platform/project conventions

    View Slide

  158. Summary
    A name must be accurate, descriptive, and unambiguous
    Naming > Summary

    View Slide

  159. Summary
    A name must be accurate, descriptive, and unambiguous
    Pay attention to
    - describing "what" rather than "who/when"
    - grammar and word choice
    Naming > Summary

    View Slide

  160. /** Code readability session 3 */
    // Comments

    View Slide

  161. Contents of this lecture
    - Introduction and Principles
    - Natural language: Naming, Comments
    - Inner type structure: State, Procedure
    - Inter type structure: Dependency (two sessions)
    - Follow-up: Review
    Comments > Introduction

    View Slide

  162. Why we should write comments
    Readability
    - Convey intent: summarize, explain reasoning
    - Prevent mistakes: note precautions
    - Refactor: rename, simplify
    Comments > Introduction

    View Slide

  163. Why we should write comments
    Readability
    - Convey intent: summarize, explain reasoning
    - Prevent mistakes: note precautions
    - Refactor: rename, simplify
    We don't need a comment if the code is simple enough
    (depending on the convention)
    Comments > Introduction

    View Slide

  164. Why we should write comments
    Readability
    - Convey intent: summarize, explain reasoning
    - Prevent mistakes: note precautions
    - Refactor: rename, simplify
    We don't need a comment if the code is simple enough
    (depending on the convention)
    Comments > Introduction

    View Slide

  165. Refactoring with comments: Example
    /**
    * Adds a new pair of keyword and the definition to this dictionary,
    * can be referenced by [getDefinition(String)].
    *
    * If the keyword is already registered, this registration fails.
    * Then, this returns a boolean representing whether the registration
    * succeeded.
    */
    fun add(newData: Pair): Boolean
    Comments > Introduction

    View Slide

  166. Refactoring with comments: Refactoring plan
    Think about why we need a long comment
    Comments > Introduction

    View Slide

  167. Refactoring with comments: Refactoring plan
    Think about why we need a long comment
    for the parameter, error case, and return value
    - Rename method
    - Break parameters
    - Simplify error cases
    - Remove unnecessary return value
    Comments > Introduction

    View Slide

  168. Refactoring after comment: Refactoring result
    /**
    * Adds or overwrites a definition of a given [keyword].
    * The registered definition is obtained by [getDefinition(String)].
    */
    fun registerDefinition(keyword: String, definitionText: String)
    Comments > Introduction

    View Slide

  169. Types of comments
    - Documentation: Formal comment for type/value/procedure...
    - Inline comment: Informal comment in a code block
    - To do: // TODO: , // FIXME:
    - IDE/compiler support, code generation: // $COVERAGE-IGNORE$
    Comments > Introduction

    View Slide

  170. Topics
    - Documentation
    - Inline comment
    Comments > Introduction

    View Slide

  171. Topics
    - Documentation
    - Inline comment
    Comments > Documentation

    View Slide

  172. What we write documentation for
    - Type: class, struct, interface, protocol, enum ...
    - Value: field, property, constant, parameter ...
    - Procedure: global function, method, extension ...
    - Scope: package, module, namespace ...
    Comments > Documentation

    View Slide

  173. Format of documentation
    Starts with...
    - `/**` for Kotlin, Java, Swift and Objective-C
    - `///` for Swift and Objective-C
    - `/*!` for Objective-C
    Refer to documentation for KDoc, JavaDoc, Swift Markup or
    HeaderDoc for more details
    Comments > Documentation

    View Slide

  174. Contents of documentation
    Let's take a look at anti-patterns first
    Comments > Documentation

    View Slide

  175. Anti-patterns: Auto-generated comment
    /**
    * @param keyword
    * @return
    */
    fun getDescription(keyword: String): String
    Comments > Documentation > Anti-patterns

    View Slide

  176. Anti-patterns: Same to the name
    /**
    * Gets the description for a keyword.
    */
    fun getDescription(keyword: String): String
    Comments > Documentation > Anti-patterns

    View Slide

  177. Anti-patterns: Translation of the code
    /**
    * Calls [doA] if `conditionA` is satisfied.
    * Otherwise, calls [doB] and if ...
    */
    fun getDescription(keyword: String): String {
    if (conditionA) {
    doA()
    } else {
    doB()
    if (...) {...}
    }
    Comments > Documentation > Anti-patterns

    View Slide

  178. Anti-patterns: Referring to private members
    /**
    * Returns a string stored in a private map [dictionary].
    */
    fun getDescription(keyword: String): String
    Comments > Documentation > Anti-patterns

    View Slide

  179. Anti-patterns: No summary
    /**
    * Throws an exception if the given `keyword` is empty.
    */
    fun getDescription(keyword: String): String
    Comments > Documentation > Anti-patterns

    View Slide

  180. Anti-patterns: Mentioning to callers
    /**
    * ...
    * This is called by class [UserProfilePresenter].
    */
    fun getDescription(keyword: String): String
    Comments > Documentation > Anti-patterns

    View Slide

  181. Lessons from anti-patterns
    Write documentation only when explanation is required
    - Fine to skip if type/value/procedure name is enough
    Comments > Documentation > Anti-patterns

    View Slide

  182. Lessons from anti-patterns
    Write documentation only when explanation is required
    - Fine to skip if type/value/procedure name is enough
    Summarize "what it is/does"
    - Without mentioning to callers
    - Without using implementation details
    Comments > Documentation > Anti-patterns

    View Slide

  183. Contents of documentation
    Comments > Documentation > Overview

    View Slide

  184. Contents of documentation
    Short summary (Mandatory)
    "What it is/does" in a phrase/sentence
    Comments > Documentation > Overview

    View Slide

  185. Contents of documentation
    Short summary (Mandatory)
    "What it is/does" in a phrase/sentence
    Details (Optional)
    Additional sentences to explain usages, limitations, and so on
    Comments > Documentation > Overview

    View Slide

  186. Contents of documentation
    Short summary (Mandatory)
    "What it is/does" in a phrase/sentence
    Details (Optional)
    Additional sentences to explain usages, limitations, and so on
    Comments > Documentation > Short summary

    View Slide

  187. Short summary: How to write 1/2
    Find the most important line/block/element of code
    Comments > Documentation > Short summary

    View Slide

  188. Short summary: How to write 1/2
    Find the most important line/block/element of code
    if (!user.isValid) {
    return
    }
    val rawProfileImage = getProfileImage(user.id, ...)
    val roundProfileImage = applyRoundFilter(rawProfileImage, ...)
    profileView.setImage(roundProfileImage)
    Comments > Documentation > Short summary

    View Slide

  189. Short summary: How to write 1/2
    Find the most important line/block/element of code
    if (!user.isValid) {
    return
    }
    val rawProfileImage = getProfileImage(user.id, ...)
    val roundProfileImage = applyRoundFilter(rawProfileImage, ...)
    profileView.setImage(roundProfileImage)
    Comments > Documentation > Short summary

    View Slide

  190. Short summary: How to write 2/2
    Complement information based on the most important point
    Comments > Documentation > Short summary

    View Slide

  191. Short summary: How to write 2/2
    Complement information based on the most important point
    /**
    * Shows ... a profile image
    *
    */
    ...
    profileView.setImage(roundProfileImage)
    Comments > Documentation > Short summary

    View Slide

  192. Short summary: How to write 2/2
    Complement information based on the most important point
    /**
    * Shows a roundly cut profile image of a given [user].
    * ...
    */
    ...
    profileView.setImage(roundProfileImage)
    Comments > Documentation > Short summary

    View Slide

  193. Short summary: How to write 2/2
    Complement information based on the most important point
    /**
    * Shows a roundly cut profile image of a given [user].
    * ...
    */
    ...
    profileView.setImage(roundProfileImage)
    The summary will also be a good hint for naming
    Comments > Documentation > Short summary

    View Slide

  194. Convention of short summary
    Refer to standard library documents Examples: Kotlin, Java, Swift, Objective-C
    - Type, Value: Starts with a noun phrase
    e.g., "A generic ordered collection of elements."4
    - Procedure: Starts with a verb with 3rd-person singular form
    e.g., "Adds a new element at the end of the array."5
    5 https://developer.apple.com/documentation/swift/array/3126937-append
    4 https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/#list
    Comments > Documentation > Short summary

    View Slide

  195. Contents of documentation
    Short summary (Mandatory)
    "What it is/does" in a phrase/sentence
    Details (Optional)
    Additional sentences to explain usages, limitations, and so on
    Comments > Documentation > Details

    View Slide

  196. Contents of "details" in documentation
    - Specification and usage
    - Function return value
    - Limitation and error values/status
    - Examples
    - ...
    Comments > Documentation > Details

    View Slide

  197. Contents of "details" in documentation
    - Specification and usage
    - Function return value
    - Limitation and error values/status
    - Examples
    Comments > Documentation > Details

    View Slide

  198. Specification and usage
    Comment on typical usage and expected behavior
    Comments > Documentation > Details

    View Slide

  199. Specification and usage
    Comment on typical usage and expected behavior
    /**
    * ...
    * To update view components such as message text and sender name,
    * give [MessageData] model object to [bindView].
    */
    class MessageViewPresenter(messageView: View)
    Comments > Documentation > Details

    View Slide

  200. Contents of "details" in documentation
    - Specification and usage
    - Function return value
    - Limitation and error values/status
    - Examples
    Comments > Documentation > Details

    View Slide

  201. Function return value 1/2
    Question: What does the following return value mean?
    fun setSelectedState(isSelected: Boolean): Boolean
    Comments > Documentation > Details

    View Slide

  202. Function return value 1/2
    Question: What does the following return value mean?
    fun setSelectedState(isSelected: Boolean): Boolean
    Possible answer:
    isToggled // true if the state is changed
    Comments > Documentation > Details

    View Slide

  203. Function return value 1/2
    Question: What does the following return value mean?
    fun setSelectedState(isSelected: Boolean): Boolean
    Possible answer:
    isToggled // true if the state is changed
    wasSelected // previous state before the function call
    Comments > Documentation > Details

    View Slide

  204. Function return value 1/2
    Question: What does the following return value mean?
    fun setSelectedState(isSelected: Boolean): Boolean
    Possible answer:
    isToggled // true if the state is changed
    wasSelected // previous state before the function call
    isSuccessfullyUpdated // true if there is no error
    Comments > Documentation > Details

    View Slide

  205. Function return value 1/2
    Question: What does the following return value mean?
    fun setSelectedState(isSelected: Boolean): Boolean
    Possible answer:
    isToggled // true if the state is changed
    wasSelected // previous state before the function call
    isSuccessfullyUpdated // true if there is no error
    isSelected // pass through the given `isSelected` value
    ...
    Comments > Documentation > Details

    View Slide

  206. Function return value 2/2
    Comment on a return value if the function name is not enough
    - has side effects for function call
    - has contract on the return value
    Comments > Documentation > Details

    View Slide

  207. Function return value 2/2
    Comment on a return value if the function name is not enough
    - has side effects for function call
    - has contract on the return value
    /**
    * ...
    * ... returns true if it was selected before this function call.
    */
    fun setSelectedState(isSelected: Boolean): Boolean
    Comments > Documentation > Details

    View Slide

  208. Function return value 2/2
    Comment on a return value if the function name is not enough
    - has side effects for function call
    - has contract on the return value
    /**
    * ...
    * The profile ID is non-negative value.
    */
    fun getProfileId(): Int
    Comments > Documentation > Details

    View Slide

  209. Contents of "details" in documentation
    - Specification and usage
    - Function return value
    - Limitation and error values/status
    - Examples
    Comments > Documentation > Details

    View Slide

  210. Limitation and error values/status
    Comment on the expected precondition and error values/status
    - A required receiver state to call a function
    - Restrictions on the arguments
    Comments > Documentation > Details

    View Slide

  211. Limitation and error values/status
    Comment on the expected precondition and error values/status
    - A required receiver state to call a function
    - Restrictions on the arguments
    /**
    * ...
    * [prepare] must be called before calling [play] or [seek],
    * or this throws [ResourceNotReadyException].
    */
    class VideoPlayer(videoPath: String)
    Comments > Documentation > Details

    View Slide

  212. Limitation and error values/status
    Comment on the expected precondition and error values/status
    - A required receiver state to call a function
    - Restrictions on the arguments
    /**
    * ...
    * Returns `null` if the given `position` is out of the array range.
    */
    fun valueAt(position: Int): T?
    Comments > Documentation > Details

    View Slide

  213. Limitations: Example
    - Arguments and receiver state
    - Instance lifecycle
    - Caller thread
    - Reentrancy, idempotency ...
    - Calculation cost, memory size
    - External environment (e.g., network, database)
    Comments > Documentation > Details

    View Slide

  214. Contents of "details" in documentation
    - Specification and usage
    - Function return value
    - Limitation and error values/status
    - Examples
    Comments > Documentation > Details

    View Slide

  215. Examples
    Make code more understandable with examples
    - Example code for usage
    - Parameter or return value example
    /**
    * ...
    * For example, this returns `arrayOf("a", "bc", "", "d")`
    * for argument `"a, bc ,,d"`
    */
    fun splitByComma(string: String): Array {...
    Comments > Documentation > Details

    View Slide

  216. Documentation: Summary
    Comments > Documentation > Summary

    View Slide

  217. Documentation: Summary
    Target: Type, value, procedure, and scope
    Comments > Documentation > Summary

    View Slide

  218. Documentation: Summary
    Target: Type, value, procedure, and scope
    Contents: Short summary (mandatory), details (optional)
    Comments > Documentation > Summary

    View Slide

  219. Documentation: Summary
    Target: Type, value, procedure, and scope
    Contents: Short summary (mandatory), details (optional)
    Cautions:
    - Write only when the name is insufficient as explanation
    - Don't mention to the caller or the implementation detail
    Comments > Documentation > Summary

    View Slide

  220. Topics
    - Documentation
    - Inline comment
    Comments > Inline comment

    View Slide

  221. What inline comment is: Kotlin case
    Comments > Inline comment

    View Slide

  222. What inline comment is: Kotlin case
    // Function explanation, which won't appear on KDoc
    fun function(param /* Parameter explanation */: Param) {
    // Code block summary
    someMethod(value) // Reason of a statement
    newId = param.id + 123 /* Reason of constant value */ + ...
    ...
    Comments > Inline comment

    View Slide

  223. What inline comment is: Kotlin case
    // Function explanation, which won't appear on KDoc
    fun function(param /* Parameter explanation */: Param) {
    // Code block summary
    someMethod(value) // Reason of a statement
    newId = param.id + 123 /* Reason of constant value */ + ...
    ...
    - Comment with whatever helps readers
    - Summary is not mandatory
    Comments > Inline comment

    View Slide

  224. What requires inline comments
    - Large code: Code blocks with comments
    - Complex/unintuitive code: Summary, reason
    - Workaround: Background, issue link
    Comments > Inline comment

    View Slide

  225. What requires inline comments
    - Large code: Code blocks with comments
    - Complex/unintuitive code: Summary, reason
    - Workaround: Background, issue link
    Comments > Inline comment

    View Slide

  226. Inline comment for large code
    Make code blocks with comments to summarize what they do
    Comments > Inline comment

    View Slide

  227. Inline comment for large code
    Make code blocks with comments to summarize what they do
    ...
    val messageKey = ...
    val messageData = messageCache[messageKey]
    ...
    if (messageData != null || ...) { // <- When this satisfies?
    ... // <- Hard to overview code in a nest
    }
    Comments > Inline comment

    View Slide

  228. Inline comment for large code
    Make code blocks with comments to summarize what they do
    // Get message data cache if it's available
    val messageKey = ...
    val messageData = messageCache[messageKey]
    ...
    // Load message data from DB if there's no cached data.
    if (messageData != null || ...) {
    ...
    }
    Comments > Inline comment

    View Slide

  229. What requires inline comments
    - Large code: Code blocks with comments
    - Complex/unintuitive code: Summary, reason
    - Workaround: Background, issue link
    Comments > Inline comment

    View Slide

  230. Inline comment for unintuitive code
    Explain summary/reason if it's hard to understand
    //
    //
    wordReplacementData.reverse()
    .forEach { (startIndex, endIndex, newText) ->
    stringBuilder.replace(startIndex, endIndex, newText)
    }
    Comments > Inline comment

    View Slide

  231. Inline comment for unintuitive code
    Explain summary/reason if it's hard to understand
    //
    //
    wordReplacementData.reverse()
    .forEach { (startIndex, endIndex, newText) ->
    stringBuilder.replace(startIndex, endIndex, newText)
    }
    Question: Why do we need to call reverse()?
    Comments > Inline comment

    View Slide

  232. Inline comment for unintuitive code
    Explain summary/reason if it's hard to understand
    // Replace texts in the reverse order because `replace()`
    // affects the following indices.
    wordReplacementData.reverse()
    .forEach { (startIndex, endIndex, newText) ->
    stringBuilder.replace(startIndex, endIndex, newText)
    }
    Comments > Inline comment

    View Slide

  233. Inline comment for unintuitive code
    Explain summary/reason if it's hard to understand
    // Replace texts in the reverse order because `replace()`
    // affects the following indices.
    wordReplacementData.reverse()
    .forEach { (startIndex, endIndex, newText) ->
    stringBuilder.replace(startIndex, endIndex, newText)
    }
    Prevent from removing reverse() by incorrect "refactoring"
    Comments > Inline comment

    View Slide

  234. What requires inline comments
    - Large code: Code blocks with comments
    - Complex/unintuitive code: Summary, reason
    - Workaround: Background, issue link
    Comments > Inline comment

    View Slide

  235. Inline comment for workaround
    Explain what a workaround does and explain the reason
    // We restore the previous state here
    // because libraryFunction() may break the receiver state
    Comments > Inline comment

    View Slide

  236. Inline comment for workaround
    Explain what a workaround does and explain the reason
    // We restore the previous state here
    // because libraryFunction() may break the receiver state
    Add links to explain
    // To avoid Device-X specific tinting bug (see, ISSUE-123456)
    Comments > Inline comment

    View Slide

  237. Inline comments: Summary
    Essentially the same with documentation
    - Short summary is not mandatory
    Comments > Inline comment

    View Slide

  238. Inline comments: Summary
    Essentially the same with documentation
    - Short summary is not mandatory
    Explain for large, complex, unintuitive code
    - Code block, summary, reason, and links
    Comments > Inline comment

    View Slide

  239. Summary
    - Comment with whatever readers require
    - Refactor before/after writing comments
    - Documentation: Short summary (mandatory) and details
    (optional)
    - Inline comments: Explanation for large, complex, unintuitive code
    Comments > Summary

    View Slide

  240. const val CODE_READABILITY_SESSION_4
    var State

    View Slide

  241. Contents of this lecture
    - Introduction and Principles
    - Natural language: Naming, Comments
    - Inner type structure: State, Procedure
    - Inter type structure: Dependency (two sessions)
    - Follow-up: Review
    State > Introduction

    View Slide

  242. Object state and readability 1/2
    It's hard to read overly stated code
    State > Introduction

    View Slide

  243. Object state and readability 1/2
    It's hard to read overly stated code
    Question: Reference transparent/immutable == readable?
    State > Introduction

    View Slide

  244. Object state and readability 1/2
    It's hard to read overly stated code
    Question: Reference transparent/immutable == readable?
    No, sometimes it can be easier to read code with side-effect or
    mutable state.
    State > Introduction

    View Slide

  245. Object state and readability 2/2
    Let's look at an example of width first search of a binary tree
    class Node(value: Int, left: Node?, right: Node?)
    State > Introduction

    View Slide

  246. Object state and readability 2/2
    Let's look at an example of width first search of a binary tree
    class Node(value: Int, left: Node?, right: Node?)
    Case1: With a mutable queue
    Case2: With recursive call
    State > Introduction

    View Slide

  247. Case1: With a mutable queue
    fun search(valueToFind: Int, root: Node): Node? {
    val queue = ArrayDeque()
    var node: Node? = root
    while (node != null && node.value != valueToFind) {
    node.left?.let(queue::add)
    node.right?.let(queue::add)
    node = queue.poll()
    }
    return node
    }
    State > Introduction

    View Slide

  248. Case2: With recursive call
    fun search(valueToFind: Int, root: Node): Node? =
    innerSearch(valueToFind, listOf(root))
    tailrec fun innerSearch(valueToFind: Int, queue: List): Node? {
    val node = queue.getOrNull(0)
    if (node == null || node.value == valueToFind) {
    return node
    }
    val nextQueue = queue.subList(1, queue.size) +
    (node.left?.let(::listOf) ?: emptyList()) +
    (node.right?.let(::listOf) ?: emptyList())
    return innerSearch(valueToFind, nextQueue)
    }
    State > Introduction

    View Slide

  249. Problems of the recursive call example
    State > Introduction

    View Slide

  250. Problems of the recursive call example
    More complicated
    - Required a separate public interface from recursive function
    - Complex queue calculation for the next iteration
    State > Introduction

    View Slide

  251. Problems of the recursive call example
    More complicated
    - Required a separate public interface from recursive function
    - Complex queue calculation for the next iteration
    No guarantee that valueToFind is constant
    - Recursive call arguments can be changed for each call
    - A nested function is required to make it constant
    State > Introduction

    View Slide

  252. What we should care about
    State > Introduction

    View Slide

  253. What we should care about
    Fold/recursive call is just a way to make code readable/robust
    - Must not be an objective
    - Apply fold/recursive call only when it makes code readable
    State > Introduction

    View Slide

  254. What we should care about
    Fold/recursive call is just a way to make code readable/robust
    - Must not be an objective
    - Apply fold/recursive call only when it makes code readable
    Focus on actual program state rather than apparent immutability
    - Mutability within a small scope may be fine
    - Fold/recursive call may adversely affect actual state
    State > Introduction

    View Slide

  255. Topics
    How to simplify states for readability
    State > Introduction

    View Slide

  256. Topics
    How to simplify states for readability
    - "Orthogonal" relationship
    - State design strategies
    State > Introduction

    View Slide

  257. Topics
    How to simplify states for readability
    - "Orthogonal" relationship
    - State design strategies
    State > Orthogonality

    View Slide

  258. "Orthogonal" relationship: Definition
    State > Orthogonality

    View Slide

  259. "Orthogonal" relationship: Definition
    For two properties, they are "orthogonal" if one is
    modifiable regardless of the other's value
    State > Orthogonality

    View Slide

  260. "Orthogonal" relationship: Example
    Orthogonal: authorName and layoutVisibility
    Both properties can be updated regardless of the other value
    State > Orthogonality

    View Slide

  261. "Orthogonal" relationship: Example
    Orthogonal: authorName and layoutVisibility
    Both properties can be updated regardless of the other value
    Non-orthogonal: authorId and authorName
    Both properties depend on each other
    authorName must be updated when authorId is also updated
    State > Orthogonality

    View Slide

  262. Why we avoid non-orthogonal relationships
    Inconsistent state might occur
    State > Orthogonality

    View Slide

  263. Why we avoid non-orthogonal relationships
    Inconsistent state might occur
    var coinCount: Int
    var coinText: String
    State > Orthogonality

    View Slide

  264. Why we avoid non-orthogonal relationships
    Inconsistent state might occur
    var coinCount: Int
    var coinText: String
    What does coinCount == 10 && coinText == "0 coin" represent?
    State > Orthogonality

    View Slide

  265. How to remove non-orthogonal relationships
    - Replace with a property getter or function
    - Encapsulate by algebraic data type
    State > Orthogonality

    View Slide

  266. How to remove non-orthogonal relationships
    - Replace with a property getter or function
    - Encapsulate by algebraic data type
    State > Orthogonality

    View Slide

  267. Replace by using getter/function 1/2
    Non-orthogonal properties
    var coinCount: Int
    var coinText: String
    State > Orthogonality > Remove with getter/function

    View Slide

  268. Replace by using getter/function 1/2
    Non-orthogonal properties
    var coinCount: Int
    var coinText: String
    Remove variable by means of a property getter
    State > Orthogonality > Remove with getter/function

    View Slide

  269. Replace by using getter/function 2/2
    Create coinText value by coinCount
    var coinCount: Int
    var coinText: String
    State > Orthogonality > Remove with getter/function

    View Slide

  270. Replace by using getter/function 2/2
    Create coinText value by coinCount
    var coinCount: Int
    val coinText: String
    get() = resource.getQuantityString(..., coinCount)
    State > Orthogonality > Remove with getter/function

    View Slide

  271. Replace by using getter/function 2/2
    Create coinText value by coinCount
    var coinCount: Int
    val coinText: String
    get() = resource.getQuantityString(..., coinCount)
    The modifiable property is only coinCount
    State > Orthogonality > Remove with getter/function

    View Slide

  272. How to remove non-orthogonal relationships
    - Replace with a property getter or function
    - Encapsulate by algebraic data type
    State > Orthogonality

    View Slide

  273. Encapsulate by algebraic data type 1/2
    Example:
    var queryResultText: String?
    var queryErrorCode: Int?
    One of queryResultText or queryErrorCode is non null exclusively
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  274. Encapsulate by algebraic data type 1/2
    Example:
    var queryResultText: String?
    var queryErrorCode: Int?
    One of queryResultText or queryErrorCode is non null exclusively
    A variable cannot be computable from the other
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  275. Encapsulate by algebraic data type 1/2
    Example:
    var queryResultText: String?
    var queryErrorCode: Int?
    One of queryResultText or queryErrorCode is non null exclusively
    A variable cannot be computable from the other
    Use algebraic data type
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  276. Algebraic data type
    Data type to represent direct sum
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  277. Algebraic data type
    Data type to represent direct sum
    Example of binary tree:
    Node = Branch(Node, Node) | Leaf(Int) | Nil
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  278. Algebraic data type
    Data type to represent direct sum
    Example of binary tree:
    Node = Branch(Node, Node) | Leaf(Int) | Nil
    Realized by:
    Tagged union, variant, sealed class, associated value ...
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  279. Encapsulate by algebraic data type 2/2
    sealed class QueryResponse {
    class Result(val resultText: String): QueryResponse()
    class Error(val errorCode: Int): QueryResponse()
    }
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  280. Encapsulate by algebraic data type 2/2
    sealed class QueryResponse {
    class Result(val resultText: String): QueryResponse()
    class Error(val errorCode: Int): QueryResponse()
    }
    QueryResponse has resultText or errorCode exclusively
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  281. How to remove non-orthogonal relationships
    - Replace with a property getter or function
    - Encapsulate by algebraic data type
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  282. How to remove non-orthogonal relationships
    - Replace with a property getter or function
    - Encapsulate by algebraic data type
    - Emulate algebraic data type
    - Use an enum for a special case
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  283. How to remove non-orthogonal relationships
    - Replace with a property getter or function
    - Encapsulate by algebraic data type
    - Emulate algebraic data type
    - Use an enum for a special case
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  284. Emulate algebraic data type 1/2
    Some language does not have algebraic data type (e.g., Java)
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  285. Emulate algebraic data type 1/2
    Some language does not have algebraic data type (e.g., Java)
    Emulate algebraic data type with a small class
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  286. Emulate algebraic data type 2/2
    class QueryResponse {
    @Nullable private final String resultText;
    @Nullable private final Integer errorCode;
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  287. Emulate algebraic data type 2/2
    class QueryResponse {
    @Nullable private final String resultText;
    @Nullable private final Integer errorCode;
    private QueryResponse(...) { ... }
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  288. Emulate algebraic data type 2/2
    class QueryResponse {
    @Nullable private final String resultText;
    @Nullable private final Integer errorCode;
    private QueryResponse(...) { ... }
    @NonNull
    static QueryResponse asResult(@NonNull String ...) { ... }
    @NonNull
    static QueryResponse asError(int errorCode) { ... }
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  289. How to remove non-orthogonal relationships
    - Replace with a property getter or function
    - Encapsulate by algebraic data type
    - Emulate algebraic data type
    - Use an enum for a special case
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  290. Use an enum for a special case
    An enum may be enough to remove a non-orthogonal relationship
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  291. Use an enum for a special case
    An enum may be enough to remove a non-orthogonal relationship
    // isResultViewShown && isErrorViewShown can't happen
    var isResultViewShown: Boolean
    var isErrorViewShown: Boolean
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  292. Use an enum for a special case
    An enum may be enough to remove a non-orthogonal relationship
    // isResultViewShown && isErrorViewShown can't happen
    var isResultViewShown: Boolean
    var isErrorViewShown: Boolean
    An enum can remove (true, true) case
    enum class VisibleViewType { RESULT_VIEW, ERROR_VIEW, NOTHING }
    State > Orthogonality > Encapsulate with an algebraic class

    View Slide

  293. "Orthogonal" relationship: Summary
    State > Orthogonality > Summary

    View Slide

  294. "Orthogonal" relationship: Summary
    Orthogonal relationship
    - Updatable independently
    - Good to remove invalid state
    State > Orthogonality > Summary

    View Slide

  295. "Orthogonal" relationship: Summary
    Orthogonal relationship
    - Updatable independently
    - Good to remove invalid state
    To remove non-orthogonal relationships, use
    - property getter/function
    - algebraic data type, enum, small class
    State > Orthogonality > Summary

    View Slide

  296. Topics
    How to simplify states for readability
    - "Orthogonal" relationship
    - State design strategies
    State > State design strategy

    View Slide

  297. Types of object state transitions
    State > State design strategy

    View Slide

  298. Types of object state transitions
    - Immutable: e.g., constant value
    - Idempotent: e.g., closable, lazy
    - Acyclic (except for self): e.g., resource stream
    - Cyclic: e.g., reusable
    State > State design strategy

    View Slide

  299. Types of object state transitions
    - Immutable: e.g., constant value
    - Idempotent: e.g., closable, lazy
    - Acyclic (except for self): e.g., resource stream
    - Cyclic: e.g., reusable
    State > State design strategy

    View Slide

  300. Immutable object
    All properties won't be changed
    State > State design strategy > Immutable

    View Slide

  301. Immutable object
    All properties won't be changed
    Immutable type example:
    class Immutable(val value: Int)
    class AnotherImmutable(val immutable: Immutable)
    State > State design strategy > Immutable

    View Slide

  302. Immutable object
    All properties won't be changed
    Immutable type example:
    class Immutable(val value: Int)
    class AnotherImmutable(val immutable: Immutable)
    Mutable type example:
    class Mutable(var value: Int)
    class AnotherMutable(var immutable: Immutable)
    class YetAnotherMutable(val mutable: Mutable)
    State > State design strategy > Immutable

    View Slide

  303. Immutable properties
    Make properties immutable if possible
    State > State design strategy > Immutable

    View Slide

  304. Immutable properties
    Make properties immutable if possible
    class ItemListCategoryData(
    // Use val if no need to update
    var itemCategoryName: String,
    State > State design strategy > Immutable

    View Slide

  305. Immutable properties
    Make properties immutable if possible
    class ItemListCategoryData(
    // Use val if no need to update
    var itemCategoryName: String,
    // Even if we need to update,
    // val of MutableList or var of List is enough
    var itemModelList: MutableList
    )
    State > State design strategy > Immutable

    View Slide

  306. Types of object state transitions
    - Immutable: e.g., constant value
    - Idempotent: e.g., closable, lazy
    - Acyclic (except for self): e.g., resource stream
    - Cyclic: e.g., reusable
    State > State design strategy > Idempotent

    View Slide

  307. Idempotency
    The result of multiple operations is the same as single operation
    State > State design strategy > Idempotent

    View Slide

  308. Idempotency
    The result of multiple operations is the same as single operation
    val closable = Closable() // "OPEN" state
    closable.close() // "CLOSED" state
    closable.close() // Valid. Keep "CLOSED" state
    State > State design strategy > Idempotent

    View Slide

  309. Idempotency
    The result of multiple operations is the same as single operation
    val closable = Closable() // "OPEN" state
    closable.close() // "CLOSED" state
    closable.close() // Valid. Keep "CLOSED" state
    The side-effect may be hidden with a wrapper (e.g., Lazy)
    State > State design strategy > Idempotent

    View Slide

  310. State graph of idempotent object
    There are two states and one direction path
    ┌─────┐
    ▼ │
    ┌──────┐ ┌──────┐ │
    │ Open │──────▶│Closed│──┘
    └──────┘ └──────┘
    State > State design strategy > Idempotent

    View Slide

  311. Why idempotency is good
    Encapsulate internal state to call a function
    State > State design strategy > Idempotent

    View Slide

  312. Why idempotency is good
    Encapsulate internal state to call a function
    // We may forget to check `isClosed`
    if (!nonIdempotentClosable.isClosed()) {
    nonIdempotentClosable.close()
    }
    State > State design strategy > Idempotent

    View Slide

  313. Why idempotency is good
    Encapsulate internal state to call a function
    // We may forget to check `isClosed`
    if (!nonIdempotentClosable.isClosed()) {
    nonIdempotentClosable.close()
    }
    // We can simply call `close` for an idempotent instance
    idempotentClosable.close()
    State > State design strategy > Idempotent

    View Slide

  314. Non-idempotent case
    It's NOT idempotent if the first state can remain after the operation
    State > State design strategy > Idempotent

    View Slide

  315. Non-idempotent case
    It's NOT idempotent if the first state can remain after the operation
    fun get(): Result? {
    if (latestResult == null) {
    latestResult = queryToServer() // May return null
    }
    return latestResult
    State > State design strategy > Idempotent

    View Slide

  316. Non-idempotent case
    It's NOT idempotent if the first state can remain after the operation
    fun get(): Result? {
    if (latestResult == null) {
    latestResult = queryToServer() // May return null
    }
    return latestResult
    The return values of the first call and the second can differ
    get() != get() // This may be true!!
    State > State design strategy > Idempotent

    View Slide

  317. Non-idempotent state transition graph
    The first state also has a cycle to itself
    ┌─────┐ ┌─────┐
    ▼ │ ▼ │
    ┌──────┐ │ ┌──────┐ │
    │ Null │──┴───▶│Result│──┘
    └──────┘ └──────┘
    State > State design strategy > Idempotent

    View Slide

  318. Non-idempotent state transition graph
    The first state also has a cycle to itself
    ┌─────┐ ┌─────┐
    ▼ │ ▼ │
    ┌──────┐ │ ┌──────┐ │
    │ Null │──┴───▶│Result│──┘
    └──────┘ └──────┘
    The side-effect should not be hidden
    State > State design strategy > Idempotent

    View Slide

  319. Types of object state transitions
    - Immutable: e.g., constant value
    - Idempotent: e.g., closable, lazy
    - Acyclic (except for self): e.g., resource stream
    - Cyclic: e.g., reusable
    State > State design strategy > Acyclic

    View Slide

  320. Acyclic state
    Instance only used for a specific argument
    - Another instance is created for another argument
    - The performance is not good sometimes
    State > State design strategy > Acyclic

    View Slide

  321. Acyclic state
    Instance only used for a specific argument
    - Another instance is created for another argument
    - The performance is not good sometimes
    // This class instance works only a given `videoPath`.
    class VideoPlayer(videoPath: String) {
    enum class State { LOADING, PLAYING, FINISHED, ERROR }
    ...
    State > State design strategy > Acyclic

    View Slide

  322. State graph of acyclic state
    Partial order like state transition (if we ignore loop for itself)
    ┌────────┐ ┌────────┐ ┌────────┐
    │Loading │─▶│Playing │──▶│Finished│
    └────────┘ └────────┘ └────────┘
    │ │ ┌────────┐
    └───────────┴──────▶│ Error │
    └────────┘
    State > State design strategy > Acyclic

    View Slide

  323. Why acyclic state is good 1/2
    Let's compare with a type with cyclic states
    State > State design strategy > Acyclic

    View Slide

  324. Why acyclic state is good 1/2
    Let's compare with a type with cyclic states
    Cyclic type example:
    class VideoPlayer {
    fun play(videoPath: String) { ... }
    State > State design strategy > Acyclic

    View Slide

  325. Cyclic state transition graph
    Loopback to "Loading" with a new video path
    ┌───────────────────────────────┐
    ▼ │
    ┌────────┐ ┌────────┐ ┌────────┐ │
    │Loading │─▶│Playing │──▶│Finished│──┤
    └────────┘ └────────┘ └────────┘ │
    │ │ ┌────────┐ │
    └───────────┴──────▶│ Error │──┘
    └────────┘
    State > State design strategy > Acyclic

    View Slide

  326. Why acyclic state is good 2/2
    Need to check the current loop argument if there's a cycle
    State > State design strategy > Acyclic

    View Slide

  327. Why acyclic state is good 2/2
    Need to check the current loop argument if there's a cycle
    //
    // Abort playing video after 1000ms
    launch(...) {
    delay(1000)
    videoPlayer.finish()
    State > State design strategy > Acyclic

    View Slide

  328. Why acyclic state is good 2/2
    Need to check the current loop argument if there's a cycle
    val videoPath = videoPlayer.videoPath
    // Abort playing video after 1000ms
    launch(...) {
    delay(1000)
    // Need to check if a new video is not loaded
    if (videoPath == videoPlayer.videoPath) {
    videoPlayer.finish()
    State > State design strategy > Acyclic

    View Slide

  329. Why acyclic state is good 2/2
    Need to check the current loop argument if there's a cycle
    val videoPath = videoPlayer.videoPath
    // Abort playing video after 1000ms
    launch(...) {
    delay(1000)
    // Need to check if a new video is not loaded
    if (videoPath == videoPlayer.videoPath) {
    videoPlayer.finish() // Not thread safe still
    State > State design strategy > Acyclic

    View Slide

  330. Acyclic state VS reality
    A cycle is often required to make a model simply
    - May be overly complex to remove all cycles
    - Example of VideoPlayer: PLAYING <-> PAUSED
    State > State design strategy > Acyclic

    View Slide

  331. Acyclic state VS reality
    A cycle is often required to make a model simply
    - May be overly complex to remove all cycles
    - Example of VideoPlayer: PLAYING <-> PAUSED
    Make the cycle as small as possible
    State > State design strategy > Acyclic

    View Slide

  332. State cycle encapsulation
    Put a cycle in an enclosing state = Don't create large cycle
    ┌────────────────────┐
    ┌────────┐ │┌───────┐─▶┌───────┐│ ┌────────┐
    │Loading │──▶││Playing│ │Paused ││──▶│Finished│
    └────────┘ │└───────┘◀─└───────┘│ └────────┘
    │ └────────────────────┘
    │ │ ┌────────┐
    └──────────────────┴────────────▶│ Error │
    └────────┘
    State > State design strategy > Acyclic

    View Slide

  333. Types of object state transitions
    - Immutable: e.g., constant value
    - Idempotent: e.g., closable, lazy
    - Acyclic (except for self): e.g., resource stream
    - Cyclic: e.g., reusable
    State > State design strategy > Summary

    View Slide

  334. State design strategy: Summary
    - Make properties immutable
    - Idempotency is also good
    - Remove/minimize state cycle
    State > State design strategy > Summary

    View Slide

  335. Summary
    Minimize state for readability and robustness
    - Don't make it an objective
    State > Summary

    View Slide

  336. Summary
    Minimize state for readability and robustness
    - Don't make it an objective
    Remove/encapsulate non-orthogonal relationships
    - Use a property getter/function, an algebraic data type, or enum
    State > Summary

    View Slide

  337. Summary
    Minimize state for readability and robustness
    - Don't make it an objective
    Remove/encapsulate non-orthogonal relationships
    - Use a property getter/function, an algebraic data type, or enum
    Care about state design strategy
    - Immutability, idempotency and cycle
    State > Summary

    View Slide

  338. abstract fun codeReadabilitySession5()
    fun Procedure()

    View Slide

  339. Contents of this lecture
    - Introduction and Principles
    - Natural language: Naming, Comments
    - Inner type structure: State, Procedure
    - Inter type structure: Dependency (two sessions)
    - Follow-up: Review
    Procedure > Introduction

    View Slide

  340. What "procedure" is
    - main/subroutine
    - function
    - method
    - computed property
    - property getter/setter
    - constructor/initialization block
    - ...
    Procedure > Introduction

    View Slide

  341. What a readable procedure is
    Procedure > Introduction

    View Slide

  342. What a readable procedure is
    Predictable
    - Consistent with the name
    - Easy to write documentation
    - Few error cases or limitations
    Procedure > Introduction

    View Slide

  343. Topics
    Make procedure responsibility/flow clear
    Procedure > Introduction

    View Slide

  344. Topics
    - Make responsibility clear
    - Split if it's hard to summarize
    - Split if it has both return value and side-effect
    - Make flow clear
    - Perform definition-based programming
    - Focus on normal cases
    - Split by object, not condition
    Procedure > Introduction

    View Slide

  345. Topics
    - Make responsibility clear
    - Split if it's hard to summarize
    - Split if it has both return value and side-effect
    - Make flow clear
    - Perform definition-based programming
    - Focus on normal cases
    - Split by object, not condition
    Procedure > Responsibility > Check with a short summary

    View Slide

  346. Split procedure if it's hard to summarize
    Try to summarize what the procedure does
    Procedure > Responsibility > Check with a short summary

    View Slide

  347. Procedure summary: Example 1/2
    messageView.text = messageData.contentText
    senderNameView.text = messageData.senderName
    timestampView.text = messageData.sentTimeText
    Procedure > Responsibility > Check with a short summary

    View Slide

  348. Procedure summary: Example 1/2
    messageView.text = messageData.contentText
    senderNameView.text = messageData.senderName
    timestampView.text = messageData.sentTimeText
    We can summarize this as:
    "Bind/update message layout with a new message data"
    Procedure > Responsibility > Check with a short summary

    View Slide

  349. Procedure summary: Example 2/2
    messageView.text = messageData.contentText
    doOnTransaction {
    messageDatabase.insertNewMessage(messageData)
    }
    Procedure > Responsibility > Check with a short summary

    View Slide

  350. Procedure summary: Example 2/2
    messageView.text = messageData.contentText
    doOnTransaction {
    messageDatabase.insertNewMessage(messageData)
    }
    How to summarize this
    Procedure > Responsibility > Check with a short summary

    View Slide

  351. Procedure summary: Example 2/2
    messageView.text = messageData.contentText
    doOnTransaction {
    messageDatabase.insertNewMessage(messageData)
    }
    How to summarize this
    "Bind a new message and save it"
    "Perform actions on a new message received"
    Procedure > Responsibility > Check with a short summary

    View Slide

  352. Procedure responsibility and summary
    Hard to summarize = Typical bad signal
    - Split the procedure into sub-procedures
    Procedure > Responsibility > Check with a short summary

    View Slide

  353. Procedure responsibility and summary
    Hard to summarize = Typical bad signal
    - Split the procedure into sub-procedures
    fun bindMessageViewData(messageData: MessageData) { ...
    fun saveMessageDataToDatabase(messageData: MessageData) { ...
    Procedure > Responsibility > Check with a short summary

    View Slide

  354. Topics
    - Make responsibility clear
    - Split if it's hard to summarize
    - Split if it has both return value and side-effect
    - Make flow clear
    - Perform definition-based programming
    - Focus on normal cases
    - Split by object, not condition
    Procedure > Responsibility > Return value and side-effect

    View Slide

  355. Command-query separation6
    Asking a question should not change the answer 7
    7 Eiffel: a language for software engineering, Meyer Bertrand, 2012
    6 https://martinfowler.com/bliki/CommandQuerySeparation.html
    Procedure > Responsibility > Return value and side-effect

    View Slide

  356. Command-query separation6
    Asking a question should not change the answer 7
    Split procedure by command and query
    - Command: Procedure to modify receiver or parameters
    - Query: Procedure to return without any modification
    7 Eiffel: a language for software engineering, Meyer Bertrand, 2012
    6 https://martinfowler.com/bliki/CommandQuerySeparation.html
    Procedure > Responsibility > Return value and side-effect

    View Slide

  357. Command-query separation: Example 1/2
    class IntList(vararg elements: Int) {
    infix fun append(others: IntList): IntList = ...
    }
    Procedure > Responsibility > Return value and side-effect

    View Slide

  358. Command-query separation: Example 1/2
    class IntList(vararg elements: Int) {
    infix fun append(others: IntList): IntList = ...
    }
    val a = IntList(1, 2)
    val b = IntList(3, 4)
    val c = a append b
    Procedure > Responsibility > Return value and side-effect

    View Slide

  359. Command-query separation: Example 1/2
    class IntList(vararg elements: Int) {
    infix fun append(others: IntList): IntList = ...
    }
    val a = IntList(1, 2)
    val b = IntList(3, 4)
    val c = a append b
    Question: What is the expected value of a, b, and c?
    Procedure > Responsibility > Return value and side-effect

    View Slide

  360. Command-query separation: Example 2/2
    val a = IntList(1, 2)
    val b = IntList(3, 4)
    val c = a append b
    Expected: a={1, 2}, b={3, 4}, c={1, 2, 3, 4}
    Procedure > Responsibility > Return value and side-effect

    View Slide

  361. Command-query separation: Example 2/2
    val a = IntList(1, 2)
    val b = IntList(3, 4)
    val c = a append b
    Expected: a={1, 2}, b={3, 4}, c={1, 2, 3, 4}
    Result with bad code: a={1, 2, 3, 4}, b={3, 4}, c={1, 2, 3, 4}
    Procedure > Responsibility > Return value and side-effect

    View Slide

  362. Command-query separation: Example 2/2
    val a = IntList(1, 2)
    val b = IntList(3, 4)
    val c = a append b
    Expected: a={1, 2}, b={3, 4}, c={1, 2, 3, 4}
    Result with bad code: a={1, 2, 3, 4}, b={3, 4}, c={1, 2, 3, 4}
    Function append must not modify a or b if it returns the result
    Procedure > Responsibility > Return value and side-effect

    View Slide

  363. Command-query separation: Drawbacks 1/3
    Command-query separation is not an objective
    - May cause strong coupling (will explain at the next session)
    - May make an unnecessary state
    Procedure > Responsibility > Return value and side-effect

    View Slide

  364. Command-query separation: Drawbacks 2/3
    Bad code example:
    class UserDataStore {
    // Command
    fun saveUserData(userData: UserData) = ...
    }
    Procedure > Responsibility > Return value and side-effect

    View Slide

  365. Command-query separation: Drawbacks 2/3
    Bad code example:
    class UserDataStore {
    private var latestOperationResult: Result = NO_OPERATION
    // Command
    fun saveUserData(userData: UserData) = ...
    // Query
    val wasLatestOperationSuccessful: Boolean get() = ...
    }
    Procedure > Responsibility > Return value and side-effect

    View Slide

  366. Command-query separation: Drawbacks 2/3
    Bad code example:
    class UserDataStore {
    private var latestOperationResult: Result = NO_OPERATION
    // Command
    fun saveUserData(userData: UserData) = ...
    // Query
    val wasLatestOperationSuccessful: Boolean get() = ...
    }
    State latestOperationResult may become a cause of a bug
    Procedure > Responsibility > Return value and side-effect

    View Slide

  367. Command-query separation: Drawbacks 2/3
    Good code example:
    class UserDataRequester {
    /**
    * Saves a given user data to ...
    * Then, returns true if the operation successful, otherwise false.
    */
    fun saveUserData(userData: UserData): Boolean = ...
    }
    Procedure > Responsibility > Return value and side-effect

    View Slide

  368. Command-query separation: Drawbacks 2/3
    Good code example:
    class UserDataRequester {
    /**
    * Saves a given user data to ...
    * Then, returns true if the operation successful, otherwise false.
    */
    fun saveUserData(userData: UserData): Boolean = ...
    }
    No need to keep the latest result when returning it directly
    Procedure > Responsibility > Return value and side-effect

    View Slide

  369. Responsibility on return value and side-effect
    Don't modify when returning a "main" result
    Procedure > Responsibility > Return value and side-effect

    View Slide

  370. Responsibility on return value and side-effect
    Don't modify when returning a "main" result
    = Acceptable to return a "sub" result with modification
    Procedure > Responsibility > Return value and side-effect

    View Slide

  371. Responsibility on return value and side-effect
    Don't modify when returning a "main" result
    = Acceptable to return a "sub" result with modification
    - Main result: conversion/calculation result, a new instance ...
    - Sub result: error type, metadata of modification (stored size) ...
    (Documentation is mandatory)
    Procedure > Responsibility > Return value and side-effect

    View Slide

  372. Responsibility on return value and side-effect
    Don't modify when returning a "main" result
    = Acceptable to return a "sub" result with modification
    - Main result: conversion/calculation result, a new instance ...
    - Sub result: error type, metadata of modification (stored size) ...
    (Documentation is mandatory)
    Acceptable if the interface is a de facto standard
    e.g., fun Queue.poll(): T
    Procedure > Responsibility > Return value and side-effect

    View Slide

  373. Make responsibility clear: Summary
    Procedure > Responsibility > Summary

    View Slide

  374. Make responsibility clear: Summary
    Split a procedure if it's hard to summarize
    - Try to write documentation anyway
    Procedure > Responsibility > Summary

    View Slide

  375. Make responsibility clear: Summary
    Split a procedure if it's hard to summarize
    - Try to write documentation anyway
    Don't modify when returning a "main" result
    - Fine to return "sub" result with modification
    - Fine if the interface is a de facto standard
    Procedure > Responsibility > Summary

    View Slide

  376. Topics
    - Make responsibility clear
    - Split if it's hard to summarize
    - Split if it has both return value and side-effect
    - Make flow clear
    - Perform definition-based programming
    - Focus on normal cases
    - Split by object, not condition
    Procedure > Flow

    View Slide

  377. How to find if procedure flow is clear
    Procedure > Flow

    View Slide

  378. How to find if procedure flow is clear
    Try to write short summary
    Procedure > Flow

    View Slide

  379. How to find if procedure flow is clear
    Try to write short summary
    - Hard to write when the flow is unclear
    Procedure > Flow

    View Slide

  380. Topics
    - Make responsibility clear
    - Split if it's hard to summarize
    - Split if it has both return value and side-effect
    - Make flow clear
    - Perform definition-based programming
    - Focus on normal cases
    - Split by object, not condition
    Procedure > Flow > Definition-based programming

    View Slide

  381. Definition-based programming
    Prefer to define value/procedure with a name
    Procedure > Flow > Definition-based programming

    View Slide

  382. Definition-based programming
    Prefer to define value/procedure with a name rather than:
    - Nest (parameter, procedure, type, control flow)
    - Anonymous procedure/object literal
    - Chained call
    Procedure > Flow > Definition-based programming

    View Slide

  383. Definition-based programming: Bad example
    Procedure > Flow > Definition-based programming

    View Slide

  384. Definition-based programming: Bad example
    fun startColorChangeAnimation(startColorInt: Int, endColorInt: Int) =
    ColorAnimator(srcColorInt, destinationColorInt)
    .also {
    it.addUpdateListener {
    if (it.colorValue == null) {
    [email protected]
    }
    // Apply the new color to views
    }
    }.start()
    Procedure > Flow > Definition-based programming

    View Slide

  385. What is wrong with the bad example
    Hard to make summary of the procedure
    Procedure > Flow > Definition-based programming

    View Slide

  386. What is wrong with the bad example
    Hard to make summary of the procedure
    - Context and scope because of nested lambdas
    - Which code is called directly, and which asynchronously
    - Receiver caused by overly long call chain
    Procedure > Flow > Definition-based programming

    View Slide

  387. Definition-based programming: How to fix 1/3
    Extract a lambda/parameter/receiver/call-chain
    as a named local value or a named private function
    Procedure > Flow > Definition-based programming

    View Slide

  388. Definition-based programming: How to fix 2/3
    fun startColorChangeAnimation(startColorInt: Int, endColorInt: Int) =
    ColorAnimator(srcColorInt, destinationColorInt)
    .also {
    it.addUpdateListener {
    if (it.colorValue == null) {
    [email protected]
    }
    // Apply the new color to views
    }
    }.start()
    Procedure > Flow > Definition-based programming

    View Slide

  389. Definition-based programming: How to fix 2/3
    fun startColorChangeAnimation(startColorInt: Int, endColorInt: Int) =
    ColorAnimator(srcColorInt, destinationColorInt)
    .also {
    it.addUpdateListener {
    if (it.colorValue == null) {
    [email protected]
    }
    // Apply the new color to views
    }
    }.start()
    Procedure > Flow > Definition-based programming

    View Slide

  390. Definition-based programming: How to fix 2/3
    fun startColorChangeAnimation(startColorInt: Int, endColorInt: Int) =
    ColorAnimator(srcColorInt, destinationColorInt)
    .also {
    it.addUpdateListener { applyColorToViews(it.colorValue) }
    }.start()
    fun applyColorToViews(colorInt: Int?) {
    if (colorInt == null) {
    return
    }
    // Apply the new color to views
    }
    Procedure > Flow > Definition-based programming

    View Slide

  391. Definition-based programming: How to fix 3/3
    fun startColorChangeAnimation(startColorInt: Int, endColorInt: Int) =
    ColorAnimator(srcColorInt, destinationColorInt)
    .also {
    it.addUpdateListener { applyColorToViews(it.colorValue) }
    }.start()
    fun applyColorToViews(colorInt: Int?) {
    if (colorInt == null) {
    return
    }
    // Apply the new color to views
    }
    Procedure > Flow > Definition-based programming

    View Slide

  392. Definition-based programming: How to fix 3/3
    fun startColorChangeAnimation(startColorInt: Int, endColorInt: Int) {
    val animator = ColorAnimator(srcColorInt, destinationColorInt)
    animator.addUpdateListener { applyColorToViews(it.colorValue) }
    animator.start()
    }
    fun applyColorToViews(colorInt: Int?) {
    if (colorInt == null) {
    return
    }
    // Apply the new color to views
    }
    Procedure > Flow > Definition-based programming

    View Slide

  393. Definition-based programming: How to fix 3/3
    fun startColorChangeAnimation(startColorInt: Int, endColorInt: Int) {
    val animator = ColorAnimator(srcColorInt, destinationColorInt)
    animator.addUpdateListener { applyColorToViews(it.colorValue) }
    animator.start()
    }
    fun applyColorToViews(colorInt: Int?) {
    if (colorInt == null) {
    return
    }
    // Apply the new color to views
    }
    Procedure > Flow > Definition-based programming

    View Slide

  394. Definition-based programming: Pitfall
    Extracting may make the code less readable
    Procedure > Flow > Definition-based programming

    View Slide

  395. Definition-based programming: Pitfall
    Extracting may make the code less readable
    - Unnecessary state
    - Unnecessary strong coupling (will explain at the next session)
    Procedure > Flow > Definition-based programming

    View Slide

  396. Pitfalls of extraction: Original code
    val userNameTextView: View = ...
    val profileImageView: View = ...
    init {
    // Complex userNameTextView initialization code
    userNameTextView...
    // Complex profileImageView initialization code
    profileImageView...
    }
    Extract complex initialization code
    Procedure > Flow > Definition-based programming

    View Slide

  397. Pitfalls of extraction: Bad example
    var userNameTextView: View? = null
    var profileImageView: View? = null
    init {
    initializeUserNameTextView()
    initializeProfileImageView()
    }
    private fun initializeUserNameTextView() {
    userNameTextView = ...
    private fun initializeProfileImageView() {
    profileImageView = ...
    Procedure > Flow > Definition-based programming

    View Slide

  398. Pitfalls of extraction: What is wrong
    - Unnecessary mutability and nullability
    - Unsafe to call initialize... methods multiple times
    - Function name initialize... is ambiguous
    Procedure > Flow > Definition-based programming

    View Slide

  399. Pitfalls of extraction: What is wrong
    - Unnecessary mutability and nullability
    - Unsafe to call initialize... methods multiple times
    - Function name initialize... is ambiguous
    Optimize the scope of the extracted methods
    Procedure > Flow > Definition-based programming

    View Slide

  400. Pitfalls of extraction: Good example
    Extract view creation and initialization procedure
    Procedure > Flow > Definition-based programming

    View Slide

  401. Pitfalls of extraction: Good example
    Extract view creation and initialization procedure
    val userNameTextView: View = createUserNameTextView()
    val profileImageView: View = createProfileImageView()
    private fun createUserNameTextView(): TextView =
    ...
    private fun createProfileImageView(): ImageView =
    ...
    Procedure > Flow > Definition-based programming

    View Slide

  402. Topics
    - Make responsibility clear
    - Split if it's hard to summarize
    - Split if it has both return value and side-effect
    - Make flow clear
    - Perform definition-based programming
    - Focus on normal cases
    - Split by object, not condition
    Procedure > Flow > Focus on normal cases

    View Slide

  403. Normal cases and error cases
    Normal case: Achieves the main purpose of the procedure
    Error case: Quits the procedure without achieving the purpose
    Procedure > Flow > Focus on normal cases

    View Slide

  404. Normal cases and error cases: Example
    String.toIntOrNull()
    Procedure > Flow > Focus on normal cases

    View Slide

  405. Normal cases and error cases: Example
    String.toIntOrNull()
    Normal case: Returns an integer value
    e.g, "-1234", "0", "+001", "-2147483648"
    Procedure > Flow > Focus on normal cases

    View Slide

  406. Normal cases and error cases: Example
    String.toIntOrNull()
    Normal case: Returns an integer value
    e.g, "-1234", "0", "+001", "-2147483648"
    Error case: Returns null
    e.g., "--0", "text", "", "2147483648", "1.0"
    Procedure > Flow > Focus on normal cases

    View Slide

  407. Procedure flow and normal/error cases
    Procedure > Flow > Focus on normal cases

    View Slide

  408. Procedure flow and normal/error cases
    Filter error cases to focus on the normal case
    Procedure > Flow > Focus on normal cases

    View Slide

  409. Procedure flow and normal/error cases
    Filter error cases to focus on the normal case
    Apply early return
    Procedure > Flow > Focus on normal cases

    View Slide

  410. Early return: Bad example
    Procedure > Flow > Focus on normal cases

    View Slide

  411. Early return: Bad example
    if (isNetworkAvailable()) {
    val queryResult = queryToServer()
    if (queryResult.isValid) {
    // Do something with queryResult...
    } else {
    showInvalidResponseDialog()
    }
    } else {
    showNetworkUnavailableDialog()
    }
    Procedure > Flow > Focus on normal cases

    View Slide

  412. Early return: What is wrong
    Unclear
    Procedure > Flow > Focus on normal cases

    View Slide

  413. Early return: What is wrong
    Unclear
    - What the main case logic is
    - The relation between error condition and handling logic
    Procedure > Flow > Focus on normal cases

    View Slide

  414. Early return: Good example
    Procedure > Flow > Focus on normal cases

    View Slide

  415. Early return: Good example
    if (!isNetworkAvailable()) {
    showNetworkUnavailableDialog()
    return
    }
    val queryResult = queryToServer()
    if (!queryResult.isValid) {
    showInvalidResponseDialog()
    return
    }
    // Do something with queryResult...
    Procedure > Flow > Focus on normal cases

    View Slide

  416. Benefits of early return
    Easy to find
    - The main flow and purpose of a procedure
    - Relation between error condition and handling logic
    Procedure > Flow > Focus on normal cases

    View Slide

  417. Topics
    - Make responsibility clear
    - Split if it's hard to summarize
    - Split if it has both return value and side-effect
    - Make flow clear
    - Perform definition-based programming
    - Focus on normal cases
    - Split by object, not condition
    Procedure > Flow > Split by object

    View Slide

  418. Two axes to break down procedure
    Multiple conditions and target objects
    Example:
    - Conditions: Two account types (premium, free)
    - Objects: Two views (background, icon)
    Procedure > Flow > Split by object

    View Slide

  419. Two axes to break down procedure
    Multiple conditions and target objects
    Condition ┃ Premium account │ Free account
    Object ┃ │
    ━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━
    Background color ┃ Yellow │ Gray
    ───────────────────╂─────────────────┼────────────────
    Account Icon ┃ [Premium] │ [Free]
    Procedure > Flow > Split by object

    View Slide

  420. Two axes to break down procedure
    Multiple conditions and target objects
    Condition ┃ Premium account │ Free account
    Object ┃ │
    ━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━
    Background color ┃ Yellow │ Gray
    ───────────────────╂─────────────────┼────────────────
    Account Icon ┃ [Premium] │ [Free]
    Two ways to implement this matrix
    Procedure > Flow > Split by object

    View Slide

  421. Bad example of "split by condition first"
    fun bindViewData(accountType: AccountType) {
    when (accountType) {
    PREMIUM -> updateViewsForPremium()
    FREE -> updateViewsForFree()
    }
    }
    Procedure > Flow > Split by object

    View Slide

  422. Bad example of "split by condition first"
    fun bindViewData(accountType: AccountType) {
    when (accountType) {
    PREMIUM -> updateViewsForPremium()
    FREE -> updateViewsForFree()
    }
    }
    private fun updateViewsForPremium() {
    backgroundView.color = PREMIUM_BACKGROUND_COLOR
    accountTypeIcon.image = resources.getImage(PREMIUM_IMAGE_ID)
    }
    Procedure > Flow > Split by object

    View Slide

  423. Split by object: What is wrong
    - Can't summarize what the function does
    /**
    * Updates views depending whether the account type is free or premium.
    */
    Procedure > Flow > Split by object

    View Slide

  424. Split by object: What is wrong
    - Can't summarize what the function does
    /**
    * Updates views depending whether the account type is free or premium.
    */
    - May cause bug because of no guarantee of completeness
    Procedure > Flow > Split by object

    View Slide

  425. Split by object: What is wrong
    - Can't summarize what the function does
    /**
    * Updates views depending whether the account type is free or premium.
    */
    - May cause bug because of no guarantee of completeness
    Make supporting function or code block for each target object first
    Procedure > Flow > Split by object

    View Slide

  426. Split by object: Good example 1/2
    fun bindViewData(accountType: AccountType) {
    updateBackgroundViewColor(accountType)
    updateAccountTypeImage(accountType)
    }
    Procedure > Flow > Split by object

    View Slide

  427. Split by object: Good example 1/2
    fun bindViewData(accountType: AccountType) {
    updateBackgroundViewColor(accountType)
    updateAccountTypeImage(accountType)
    }
    private fun updateBackgroundViewColor(accountType: AccountType) {
    backgroundView.color = when(accountType) {
    PREMIUM -> PREMIUM_BACKGROUND_COLOR
    FREE -> FREE_BACKGROUND_COLOR
    }
    }
    Procedure > Flow > Split by object

    View Slide

  428. Split by object: Good example
    - Easy to write a short summary
    /**
    * Updates background color and icon according to a given account type.
    */
    Procedure > Flow > Split by object

    View Slide

  429. Split by object: Good example
    - Easy to write a short summary
    /**
    * Updates background color and icon according to a given account type.
    */
    - Ensures that all the combinations are covered
    Procedure > Flow > Split by object

    View Slide

  430. Split by object: Good example
    - Easy to write a short summary
    /**
    * Updates background color and icon according to a given account type.
    */
    - Ensures that all the combinations are covered
    - Can conduct further more refactoring
    e.g., Make extracted functions reference transparent
    Procedure > Flow > Split by object

    View Slide

  431. Split by object: More improvement
    fun bindViewData(accountType: AccountType) {
    backgroundView.color = getBackgroundColorInt(accountType)
    accountTypeIcon.image = getAccountTypeIconImage(accountType)
    }
    private fun getBackgroundColorInt(accountType: AccountType): Int =
    when(accountType) {
    PREMIUM -> PREMIUM_BACKGROUND_COLOR
    FREE -> FREE_BACKGROUND_COLOR
    }
    Procedure > Flow > Split by object

    View Slide

  432. Early return VS split by object
    Doesn't "early return" represent "split by condition"?
    Procedure > Flow > Split by object

    View Slide

  433. Early return VS split by object
    Doesn't "early return" represent "split by condition"?
    Strategy 1:
    - Handle an error case as if it's a normal case
    Procedure > Flow > Split by object

    View Slide

  434. Early return VS split by object
    Doesn't "early return" represent "split by condition"?
    Strategy 1:
    - Handle an error case as if it's a normal case
    Strategy 2:
    - Apply early return to error cases
    - Apply "split by object"
    Procedure > Flow > Split by object

    View Slide

  435. Procedure flow: Summary
    Procedure > Flow > Summary

    View Slide

  436. Procedure flow: Summary
    Perform definition-based programming:
    Extract nests/chains to a named value/function
    Procedure > Flow > Summary

    View Slide

  437. Procedure flow: Summary
    Perform definition-based programming:
    Extract nests/chains to a named value/function
    Focus on normal cases:
    Apply early return
    Procedure > Flow > Summary

    View Slide

  438. Procedure flow: Summary
    Perform definition-based programming:
    Extract nests/chains to a named value/function
    Focus on normal cases:
    Apply early return
    Split by object, not condition:
    Make a function or block for each target object
    Procedure > Flow > Summary

    View Slide

  439. Summary
    Check whether it's easy to write a short summary
    Procedure > Summary

    View Slide

  440. Summary
    Check whether it's easy to write a short summary
    Procedure responsibility:
    - Split if required
    - Don't modify when returning a main result
    Procedure > Summary

    View Slide

  441. Summary
    Check whether it's easy to write a short summary
    Procedure responsibility:
    - Split if required
    - Don't modify when returning a main result
    Procedure flow:
    - Apply definition based programing, early return, split by object
    Procedure > Summary

    View Slide

  442. Code→readability→session 6
    Dependency ‐

    View Slide

  443. Contents of this lecture
    - Introduction and Principles
    - Natural language: Naming, Comments
    - Inner type structure: State, Procedure
    - Inter type structure: Dependency (two sessions)
    - Follow-up: Review
    Dependency

    View Slide

  444. What "dependency" is
    Example: Type "X" depends on type "Y"
    - Type X has an instance of Y as a property
    - A method of X takes Y as a parameter or returns Y
    - X calls a method of Y
    - X inherits Y
    Dependency

    View Slide

  445. What "dependency" is
    Example: Type "X" depends on type "Y"
    - Type X has an instance of Y as a property
    - A method of X takes Y as a parameter or returns Y
    - X calls a method of Y
    - X inherits Y
    "X" depends on "Y" if word "Y" appears in "X", roughly speaking
    Dependency

    View Slide

  446. What "dependency" is
    Is the word "dependency" only for class?
    Dependency

    View Slide

  447. What "dependency" is
    Is the word "dependency" only for class?
    No
    - Procedure, module, instance ...
    Dependency

    View Slide

  448. Topics
    First session:
    - Coupling
    Second session:
    - Direction
    - Redundancy
    - Explicitness
    Dependency

    View Slide

  449. Topics
    First session:
    - Coupling
    Second session:
    - Direction
    - Redundancy
    - Explicitness
    Dependency > Coupling

    View Slide

  450. Coupling
    A property to represent how strong the dependency is
    ▲ Strong






    ▼ Weak
    Dependency > Coupling

    View Slide

  451. Coupling
    A property to represent how strong the dependency is
    ▲ Strong
    │ Content coupling
    │ Common coupling
    │ Control coupling
    │ Stamp coupling
    │ Data coupling
    │ Message coupling
    ▼ Weak (Exception: External coupling)
    Dependency > Coupling

    View Slide

  452. Coupling
    Bad/strong coupling: content, common, control
    Fine/weak coupling: stamp, data, message
    Dependency > Coupling

    View Slide

  453. Coupling
    Bad/strong coupling: content, common, control
    Fine/weak coupling: stamp, data, message
    Dependency > Coupling > Content coupling

    View Slide

  454. Content coupling
    Relies on the internal workings
    Dependency > Coupling > Content coupling

    View Slide

  455. Content coupling
    Relies on the internal workings
    Example:
    - Allows illegal usage
    - Shares internal mutable state
    Dependency > Coupling > Content coupling

    View Slide

  456. Content coupling
    Relies on the internal workings
    Example:
    - Allows illegal usage
    - Shares internal mutable state
    Dependency > Coupling > Content coupling

    View Slide

  457. Allows illegal usage: Bad example
    class Caller {
    fun callCalculator() {
    calculator.calculate()
    ...
    Dependency > Coupling > Content coupling

    View Slide

  458. Allows illegal usage: Bad example
    class Caller {
    fun callCalculator() {
    calculator.parameter = 42
    calculator.calculate()
    val result = calculator.result
    ...
    Dependency > Coupling > Content coupling

    View Slide

  459. Allows illegal usage: Bad example
    class Caller {
    fun callCalculator() {
    calculator.parameter = 42
    calculator.prepare()
    calculator.calculate()
    calculator.tearDown()
    val result = calculator.result
    ...
    Dependency > Coupling > Content coupling

    View Slide

  460. Allows illegal usage: Bad points
    Dependency > Coupling > Content coupling

    View Slide

  461. Allows illegal usage: Bad points
    calculator has the following two requirements:
    - Call prepare()/tearDown() before/after calling calculate()
    - Pass/get a value by parameter/result property.
    Dependency > Coupling > Content coupling

    View Slide

  462. Allows illegal usage: Bad points
    calculator has the following two requirements:
    - Call prepare()/tearDown() before/after calling calculate()
    - Pass/get a value by parameter/result property.
    Not robust to wrong usage and implementation change
    Dependency > Coupling > Content coupling

    View Slide

  463. Allows illegal usage: How to fix
    - Encapsulate stateful call sequence
    - Pass/receive data as arguments or a return value
    Dependency > Coupling > Content coupling

    View Slide

  464. Allows illegal usage: Fixed code example
    class Caller {
    fun callCalculator() {
    val result = calculator.calculate(42)
    ...
    Dependency > Coupling > Content coupling

    View Slide

  465. Allows illegal usage: Fixed code example
    class Caller {
    fun callCalculator() {
    val result = calculator.calculate(42)
    ...
    class Calculator {
    fun calculate(type: Int): Int {
    prepare()
    ...
    tearDown()
    return ...
    Dependency > Coupling > Content coupling

    View Slide

  466. Allows illegal usage: What to check
    Dependency > Coupling > Content coupling

    View Slide

  467. Allows illegal usage: What to check
    Don't apply design concept/principal too much
    - e.g., command/query separation
    Dependency > Coupling > Content coupling

    View Slide

  468. Content coupling
    Relies on the internal workings
    Example:
    - Allows illegal usage
    - Shares internal mutable state
    Dependency > Coupling > Content coupling

    View Slide

  469. Shares mutable state: Bad example
    class UserListPresenter(val userList: List) {
    fun refreshViews() { /* ... */ }
    Dependency > Coupling > Content coupling

    View Slide

  470. Shares mutable state: Bad example
    class UserListPresenter(val userList: List) {
    fun refreshViews() { /* ... */ }
    class Caller {
    val userList: List = mutableListOf()
    val presenter = UserListPresenter(userList)
    ...
    Dependency > Coupling > Content coupling

    View Slide

  471. Shares mutable state: Bad example
    class UserListPresenter(val userList: List) {
    fun refreshViews() { /* ... */ }
    class Caller {
    val userList: List = mutableListOf()
    val presenter = UserListPresenter(userList)
    ...
    fun addUser(newUser: UserData) {
    userList += newUser
    presenter.refreshViews()
    Dependency > Coupling > Content coupling

    View Slide

  472. Shares mutable state: Bad points
    Behavior of UserListPresenter depends on external mutable value
    - Causes unexpected state update by other reference holders
    - No limitation of modifying the list
    Dependency > Coupling > Content coupling

    View Slide

  473. Shares mutable state: Bad points
    Behavior of UserListPresenter depends on external mutable value
    - Causes unexpected state update by other reference holders
    - No limitation of modifying the list
    Easy to break the state and hard to find the root cause
    - A reference to the presenter is not required to update it
    Dependency > Coupling > Content coupling

    View Slide

  474. Shares mutable state: How to fix
    - Make values immutable, copy arguments, or apply copy-on-write
    - Limit interfaces to update the state
    Dependency > Coupling > Content coupling

    View Slide

  475. Shares mutable state: Fixed code example
    class UserListPresenter {
    val userList: MutableList = mutableListOf()
    Dependency > Coupling > Content coupling

    View Slide

  476. Shares mutable state: Fixed code example
    class UserListPresenter {
    val userList: MutableList = mutableListOf()
    fun addUsers(newUsers: List) {
    userList += newUsers
    // Update views with `userList`
    }
    Dependency > Coupling > Content coupling

    View Slide

  477. Shares mutable state: Fixed code example
    class UserListPresenter {
    val userList: MutableList = mutableListOf()
    fun addUsers(newUsers: List) {
    userList += newUsers
    // Update views with `userList`
    }
    No need to worry about whether newUsers is mutable or not
    (if addUsers is atomic)
    Dependency > Coupling > Content coupling

    View Slide

  478. Shares mutable state: What to check
    Dependency > Coupling > Content coupling

    View Slide

  479. Shares mutable state: What to check
    Make a mutable value ownership clear
    Dependency > Coupling > Content coupling

    View Slide

  480. Shares mutable state: What to check
    Make a mutable value ownership clear
    Options:
    - Modify only by owner's interface
    - Create manager class of mutable data
    Dependency > Coupling > Content coupling

    View Slide

  481. Content coupling: Summary
    Don't rely on internal working
    - Remove illegal usage
    - Make mutable data ownership clear
    Dependency > Coupling > Content coupling

    View Slide

  482. Coupling
    Bad/strong coupling: content, common, control
    Fine/weak coupling: stamp, data, message
    Dependency > Coupling > Common coupling

    View Slide

  483. Common coupling
    Shares (mutable) global data
    Dependency > Coupling > Common coupling

    View Slide

  484. Common coupling
    Shares (mutable) global data
    - Pass data by global variables
    - Depend on a mutable singleton
    - (Use files / databases / shared resources)
    Dependency > Coupling > Common coupling

    View Slide

  485. Common coupling
    Shares (mutable) global data
    - Pass data by global variables
    - Depend on a mutable singleton
    - (Use files / databases / shared resources)
    Dependency > Coupling > Common coupling

    View Slide

  486. Pass data by global variables: Bad example
    var parameter: Int? = null
    var result: Data? = null
    Dependency > Coupling > Common coupling

    View Slide

  487. Pass data by global variables: Bad example
    var parameter: Int? = null
    var result: Data? = null
    class Calculator {
    fun calculate() {
    result = parameter + ...
    Dependency > Coupling > Common coupling

    View Slide

  488. Pass data by global variables: Bad example
    var parameter: Int? = null
    var result: Data? = null
    class Calculator {
    fun calculate() {
    result = parameter + ...
    fun callCalculator() {
    parameter = 42
    calculator.calculate()
    Dependency > Coupling > Common coupling

    View Slide

  489. Pass data by global variables: Bad points
    No information/limitation of call relationships
    - Breaks other simultaneous calls
    - Tends to cause illegal state
    Dependency > Coupling > Common coupling

    View Slide

  490. Pass data by global variables: How to fix
    Pass as a function parameter or return value
    Dependency > Coupling > Common coupling

    View Slide

  491. Pass data by global variables: Fixed code
    class Calculator {
    fun calculate(parameter: Int): Int =
    parameter + ...
    Dependency > Coupling > Common coupling

    View Slide

  492. Pass data by global variables: Fixed code
    class Calculator {
    fun calculate(parameter: Int): Int =
    parameter + ...
    fun callCalculator() {
    val result = calculator.calculate(42)
    Dependency > Coupling > Common coupling

    View Slide

  493. Common coupling
    Shares (mutable) global data
    - Pass data by global variables
    - Depend on a mutable singleton
    - (Use files / databases / shared resources)
    Dependency > Coupling > Common coupling

    View Slide

  494. Depend on a mutable singleton: Bad example
    val USER_DATA_REPOSITORY = UserDataRepository()
    Dependency > Coupling > Common coupling

    View Slide

  495. Depend on a mutable singleton: Bad example
    val USER_DATA_REPOSITORY = UserDataRepository()
    class UserListUseCase {
    suspend fun invoke(): List = withContext(...) {
    val result = USER_DATA_REPOSITORY.query(...)
    ...
    Dependency > Coupling > Common coupling

    View Slide

  496. Mutable global variables: Bad points
    Singleton is not managed
    - Unmanaged singleton lifecycle, scope, and references
    - Not robust for specification change
    - Bad testability
    Dependency > Coupling > Common coupling

    View Slide

  497. Mutable global variables: How to fix
    Pass an instance as a constructor parameter
    (= Dependency injection)
    The constructor caller can manage the instance
    Dependency > Coupling > Common coupling

    View Slide

  498. Mutable global variables: Fixed code
    class UserListUseCase(
    val userDataRepository: UserDataRepository
    ) {
    suspend fun invoke(): List = withContext(...) {
    val result = userDataRepository.query(...)
    ...
    Dependency > Coupling > Common coupling

    View Slide

  499. Common coupling: Note
    Apply the same discussion to a smaller scope
    - module, package, type ...
    Example: Use a return value instead of instance field
    Dependency > Coupling > Common coupling

    View Slide

  500. Common coupling in a class: Bad example
    class Klass {
    var result: Result? = null
    fun firstFunction() {
    secondFunction()
    /* use `result` */
    ...
    fun secondFunction() {
    result = /* calculate result */
    Dependency > Coupling > Common coupling

    View Slide

  501. Common coupling in a class: Fixed code
    class Klass {
    fun firstFunction() {
    val result = secondFunction()
    /* use `result` */
    ...
    fun secondFunction(): Int =
    /* calculate a return value */
    Dependency > Coupling > Common coupling

    View Slide

  502. Common coupling: Summary
    No more singletons nor global mutable values
    - Unmanageable, not robust, hard to test ...
    Dependency > Coupling > Common coupling

    View Slide

  503. Common coupling: Summary
    No more singletons nor global mutable values
    - Unmanageable, not robust, hard to test ...
    To remove common coupling:
    - Pass value as arguments or a return value
    - Use dependency injection
    Dependency > Coupling > Common coupling

    View Slide

  504. Coupling
    Bad/strong coupling: content, common, control
    Fine/weak coupling: stamp, data, message
    Dependency > Coupling > Control coupling

    View Slide

  505. Control coupling
    Switches logic by passing flag "what to do"
    - Has large conditional branch covering procedure
    - Each branch body is unrelated to the other branches
    Dependency > Coupling > Control coupling

    View Slide

  506. Control coupling example: Boolean flag
    fun updateView(isError: Boolean) {
    if (isError) {
    resultView.isVisible = true
    errorView.isVisible = false
    iconView.image = CROSS_MARK_IMAGE
    } else {
    resultView.isVisible = false
    errorView.isVisible = true
    iconView.image = CHECK_MARK_IMAGE
    }
    }
    Dependency > Coupling > Control coupling

    View Slide

  507. Control coupling example: "To do" type
    fun updateUserView(dataType: DataType) = when(dataType) {
    UserName -> {
    val userName = getUserName()
    userNameView.text = userName
    }
    Birthday -> {
    val birthdayMillis = ...
    birthDayView.text = format(...)
    }
    ProfileImage -> ...
    }
    Dependency > Coupling > Control coupling

    View Slide

  508. Control coupling: Bad points
    Unreadable
    - Hard to summarize what it does
    - Difficult to name and comment
    Dependency > Coupling > Control coupling

    View Slide

  509. Control coupling: Bad points
    Unreadable
    - Hard to summarize what it does
    - Difficult to name and comment
    Not robust
    - Requires the similar conditional branch to the callers
    - Fragile against condition update
    Dependency > Coupling > Control coupling

    View Slide

  510. Ideas for improvement
    - Remove conditional branch
    - Split by object (introduced last session)
    - Apply strategy pattern
    Dependency > Coupling > Control coupling

    View Slide

  511. Ideas for improvement
    - Remove conditional branch
    - Split by object (introduced last session)
    - Apply strategy pattern
    Dependency > Coupling > Control coupling

    View Slide

  512. Remove conditional branch
    Split procedures for each argument
    - Remove parameter from each new procedure
    Dependency > Coupling > Control coupling

    View Slide

  513. Remove conditional branch
    Split procedures for each argument
    - Remove parameter from each new procedure
    Applicable if "what to do" value is different for each caller
    - If not, causes another content coupling on the caller side
    Dependency > Coupling > Control coupling

    View Slide

  514. Remove conditional branch: Example
    fun updateUserView(dataType: DataType) = when(dataType) {
    UserName -> {
    val userName = getUserName()
    userNameView.text = userName
    }
    Birthday -> {
    val birthdayMillis = ...
    birthDayView.text = format(...)
    }
    ProfileImage -> ...
    }
    Dependency > Coupling > Control coupling

    View Slide

  515. Remove conditional branch: Example
    fun updateUserNameView() {
    val userName = getUserName()
    userNameView.text = userName
    }
    fun updateBirthdayView() {
    val birthdayMillis = ...
    birthDayView.text = format(...)
    }
    fun updateProfileImage() { ...
    Dependency > Coupling > Control coupling

    View Slide

  516. Ideas for improvement
    - Remove conditional branch
    - Split by object (introduced last session)
    - Apply strategy pattern
    Dependency > Coupling > Control coupling

    View Slide

  517. Split by object
    Create code block for each target object, not condition
    - Break down content coupling into smaller scope
    Dependency > Coupling > Control coupling

    View Slide

  518. Split by object
    Create code block for each target object, not condition
    - Break down content coupling into smaller scope
    Applicable if target objects are common for each condition
    - doSomethingIf... function may be used
    Dependency > Coupling > Control coupling

    View Slide

  519. Split by object: Example
    fun updateView(isError: Boolean) {
    if (isError) {
    resultView.isVisible = true
    errorView.isVisible = false
    iconView.image = CROSS_MARK_IMAGE
    } else {
    resultView.isVisible = false
    errorView.isVisible = true
    iconView.image = CHECK_MARK_IMAGE
    }
    }
    Dependency > Coupling > Control coupling

    View Slide

  520. Split by object: Example
    fun updateView(isError: Boolean) {
    resultView.isVisible = !isError
    errorView.isVisible = isError
    iconView.image = getIconImage(isError)
    }
    fun getIconImage(isError: Boolean): Image =
    if (!isError) CHECK_MARK_IMAGE else CROSS_MARK_IMAGE
    Dependency > Coupling > Control coupling

    View Slide

  521. Ideas for improvement
    - Remove conditional branch
    - Split by object (introduced last session)
    - Apply strategy pattern
    Dependency > Coupling > Control coupling

    View Slide

  522. Apply strategy pattern
    Create types to decide "what to do"
    - Branch with sub-typing, polymorphism, or parameter
    - Implemented by an enum, an abstract class, or a callback object
    Dependency > Coupling > Control coupling

    View Slide

  523. Apply strategy pattern
    Create types to decide "what to do"
    - Branch with sub-typing, polymorphism, or parameter
    - Implemented by an enum, an abstract class, or a callback object
    Type selection logic appears on the caller side
    Dependency > Coupling > Control coupling

    View Slide

  524. Apply strategy pattern: Example
    enum class Binder(val viewId: ViewId) {
    fun updateView(holder: ViewHolder) =
    holder.getView(viewId).let(::setContent)
    abstract fun setContent(view: View)
    Dependency > Coupling > Control coupling

    View Slide

  525. Apply strategy pattern: Example
    enum class Binder(val viewId: ViewId) {
    USER_NAME(USER_NAME_VIEW_ID) {
    override fun setContent(...) = ...
    }
    BIRTHDAY(BIRTHDAY_VIEW_ID) {
    override fun setContent(...) = ...
    ...
    fun updateView(holder: ViewHolder) =
    holder.getView(viewId).let(::setContent)
    abstract fun setContent(view: View)
    Dependency > Coupling > Control coupling

    View Slide

  526. Control coupling: Summary
    Don't create large conditional branch covering a whole procedure
    Dependency > Coupling > Control coupling

    View Slide

  527. Control coupling: Summary
    Don't create large conditional branch covering a whole procedure
    To lease control coupling:
    - Remove condition parameters
    - Split by target instead of condition
    - Use strategy pattern
    Dependency > Coupling > Control coupling

    View Slide

  528. Coupling
    Bad/strong coupling: content, common, control
    Fine/weak coupling: stamp, data, message
    Dependency > Coupling > Stamp coupling

    View Slide

  529. Stamp coupling
    Passes a structure though some of the data that are not used
    Dependency > Coupling > Stamp coupling

    View Slide

  530. Stamp coupling
    Passes a structure though some of the data that are not used
    Acceptable for some situations:
    - To use sub-typing, structural typing, or strategy pattern
    - To simplify parameter types
    Make parameter type as small as possible
    Dependency > Coupling > Stamp coupling

    View Slide

  531. Stamp coupling: Example
    updateUserView(userData)
    fun updateUserView(userData: UserData) {
    Dependency > Coupling > Stamp coupling

    View Slide

  532. Stamp coupling: Example
    updateUserView(userData)
    fun updateUserView(userData: UserData) {
    // Some property of `userData` are used
    userNameView.text = userData.fullName
    profileImage.setImageUrl(userData.profileImageUrl)
    // `userData.mailAddress` and `.phoneNumber` are not used
    }
    Dependency > Coupling > Stamp coupling

    View Slide

  533. Coupling
    Bad/strong coupling: content, common, control
    Fine/weak coupling: stamp, data, message
    Dependency > Coupling > Data coupling

    View Slide

  534. Data coupling
    Passes a set of elemental data
    - All the data must be used
    fun updateUserView(fullName: String, profileImageUrl: Url) {
    userNameView.text = fullName
    profileImage.setImageUrl(profileImageUrl)
    }
    updateUserView(userData.fullName, userData.profileImageUrl)
    Dependency > Coupling > Data coupling

    View Slide

  535. Coupling
    Bad/strong coupling: content, common, control
    Fine/weak coupling: stamp, data, message
    Dependency > Coupling > Message coupling

    View Slide

  536. Message coupling
    Call a method without any data
    - Hardly happens
    - May cause content coupling in another layer
    Dependency > Coupling > Message coupling

    View Slide

  537. Message coupling
    Call a method without any data
    - Hardly happens
    - May cause content coupling in another layer
    fun notifyUserDataUpdated() {
    userNameView.text = ...
    profileImage.setImageUrl(...)
    }
    notifyUserDataUpdated()
    Dependency > Coupling > Message coupling

    View Slide

  538. Coupling: Summary
    Avoid strong coupling
    - Use stamp/data coupling instead of content/common/control
    Dependency > Coupling > Summary

    View Slide

  539. Coupling: Summary
    Avoid strong coupling
    - Use stamp/data coupling instead of content/common/control
    Content coupling: Relies on internal working
    Dependency > Coupling > Summary

    View Slide

  540. Coupling: Summary
    Avoid strong coupling
    - Use stamp/data coupling instead of content/common/control
    Content coupling: Relies on internal working
    Common coupling: Depends on global singleton/variables
    Dependency > Coupling > Summary

    View Slide

  541. Coupling: Summary
    Avoid strong coupling
    - Use stamp/data coupling instead of content/common/control
    Content coupling: Relies on internal working
    Common coupling: Depends on global singleton/variables
    Control coupling: Has large conditional branch
    Dependency > Coupling > Summary

    View Slide

  542. Code←readability←session 7
    Dependency ‐‐

    View Slide

  543. Contents of this lecture
    - Introduction and Principles
    - Natural language: Naming, Comments
    - Inner type structure: State, Procedure
    - Inter type structure: Dependency (two sessions)
    - Follow-up: Review
    Dependency

    View Slide

  544. Topics
    First session:
    - Coupling
    Second session:
    - Direction
    - Redundancy
    - Explicitness
    Dependency > Direction

    View Slide

  545. Direction
    Keep the dependency in one direction as far as possible
    = It’s good if there’s no cycle
    Dependency > Direction

    View Slide

  546. Preferred dependency direction: Example
    - Caller to callee
    - Detail to abstraction
    - Complex to simple
    - Algorithm to data model
    - Mutable to immutable
    - Frequently updated layer to stable layer
    Dependency > Direction

    View Slide

  547. Preferred dependency direction
    - Caller to callee
    - Detail to abstraction
    - Complex to simple
    Dependency > Direction

    View Slide

  548. Preferred dependency direction
    - Caller to callee
    - Detail to abstraction
    - Complex to simple
    Dependency > Direction

    View Slide

  549. Bad code example
    class MediaViewPresenter {
    fun getVideoUri(): Uri = ...
    fun playVideo() {
    videoPlayerView.play(this)
    ...
    Dependency > Direction > Caller to callee

    View Slide

  550. Bad code example
    class MediaViewPresenter {
    fun getVideoUri(): Uri = ...
    fun playVideo() {
    videoPlayerView.play(this)
    ...
    class VideoPlayerView {
    fun play(presenter: MediaViewPresenter) {
    val uri = presenter.getVideoUri()
    Dependency > Direction > Caller to callee

    View Slide

  551. What is to be checked
    Call stack of the example
    - MediaViewPresenter.playVideo()
    - VideoPlayerView.play(...)
    - MediaViewPresenter.getVideoUri()
    Dependency > Direction > Caller to callee

    View Slide

  552. What is to be checked
    Call stack of the example
    - MediaViewPresenter.playVideo()
    - VideoPlayerView.play(...)
    - MediaViewPresenter.getVideoUri()
    A call stack of A → B → A is a typical bad signal
    Dependency > Direction > Caller to callee

    View Slide

  553. How to remove reference to caller
    Remove unnecessary callback
    - Pass values as parameters
    - Extract small classes
    Dependency > Direction > Caller to callee

    View Slide

  554. How to remove reference to caller
    Remove unnecessary callback
    - Pass values as parameters
    - Extract small classes
    Dependency > Direction > Caller to callee

    View Slide

  555. Pass values as parameters
    class MediaViewPresenter {
    fun getVideoUri(): Uri = ...
    fun playVideo() {
    videoPlayerView.play(this)
    ...
    class VideoPlayerView {
    fun play(presenter: MediaViewPresenter) {
    val uri = presenter.getVideoUri()
    Dependency > Direction > Caller to callee

    View Slide

  556. Pass values as parameters
    class MediaViewPresenter {
    fun getVideoUri(): Uri = ...
    fun playVideo() {
    videoPlayerView.play(getVideoUri())
    ...
    class VideoPlayerView {
    fun play(videoUri: Uri) {
    ...
    Dependency > Direction > Caller to callee

    View Slide

  557. How to remove reference to caller
    Remove unnecessary callback
    - Pass values as parameters
    - Extract small classes
    Dependency > Direction > Caller to callee

    View Slide

  558. Extract small classes
    Can't pass value directly for asynchronous evaluation
    Dependency > Direction > Caller to callee

    View Slide

  559. Extract small classes
    Can't pass value directly for asynchronous evaluation
    ┌────────────────────────┐
    │ MediaViewPresenter │
    │ │───────▶┌──────────────┐
    │ getVideoUri() ◀──────┼────────│ VideoPlayer │
    │ │ └──────────────┘
    └────────────────────────┘
    Dependency > Direction > Caller to callee

    View Slide

  560. Extract small classes
    Can't pass value directly for asynchronous evaluation
    ┌────────────────────────┐
    │ MediaViewPresenter │
    │ │───────▶┌──────────────┐
    │ getVideoUri() ◀──────┼────────│ VideoPlayer │
    │ │ └──────────────┘
    └────────────────────────┘
    Remove callback by extracting depended logic
    Dependency > Direction > Caller to callee

    View Slide

  561. Extract small classes: Step 1
    Create a named class for the callback
    ┌────────────────────────┐
    │ MediaViewPresenter │
    │ │───────▶┌──────────────┐
    │ getVideoUri() ◀──────┼────────│ VideoPlayer │
    │ │ └──────────────┘
    └────────────────────────┘
    Dependency > Direction > Caller to callee

    View Slide

  562. Extract small classes: Step 1
    Create a named class for the callback
    ┌────────────────────────┐
    │ MediaViewPresenter │
    │ ┌──────────────────┐ │───────▶┌──────────────┐
    │ │ VideoUriProvider │◀──┼────────│ VideoPlayer │
    │ └──────────────────┘ │ └──────────────┘
    └────────────────────────┘
    Dependency > Direction > Caller to callee

    View Slide

  563. Extract small classes: Step 2
    Move the named class out of the outer class
    ┌────────────────────────┐
    │ MediaViewPresenter │
    │ ┌──────────────────┐ │───────▶┌──────────────┐
    │ │ VideoUriProvider │◀──┼────────│ VideoPlayer │
    │ └──────────────────┘ │ └──────────────┘
    └────────────────────────┘
    Dependency > Direction > Caller to callee

    View Slide

  564. Extract small classes: Step 2
    Move the named class out of the outer class
    ┌────────────────────┐
    │ MediaViewPresenter │─────┐
    └────────────────────┘ │ ┌──────────────┐
    │ └─────▶│ VideoPlayer │
    │ └──────────────┘
    ▼ │
    ┌──────────────────┐ │
    │ VideoUriProvider │◀────────────────────┘
    └──────────────────┘
    Dependency > Direction > Caller to callee

    View Slide

  565. How to remove reference to caller
    Remove unnecessary callback
    - Pass values as parameters
    - Extract small classes
    Dependency > Direction > Caller to callee

    View Slide

  566. Dependency on caller: Exceptions
    Dependency on a caller may be acceptable for the following:
    Dependency > Direction > Caller to callee

    View Slide

  567. Dependency on caller: Exceptions
    Dependency on a caller may be acceptable for the following:
    - Threads
    - Event listeners
    - Strategy pattern like functions (e.g., forEach)
    Dependency > Direction > Caller to callee

    View Slide

  568. Dependency on caller: Exceptions
    Dependency on a caller may be acceptable for the following:
    - Threads
    - Event listeners
    - Strategy pattern like functions (e.g., forEach)
    Alternative options: Promise pattern, coroutine, reactive...
    Dependency > Direction > Caller to callee

    View Slide

  569. Preferred dependency direction
    - Caller to callee
    - Detail to abstraction
    - Complex to simple
    Dependency > Direction > Detail to abstraction

    View Slide

  570. Dependency on detail
    Don't depend on inheritances
    = Don't check type of this or self
    Exception: Sealed class
    Dependency > Direction > Detail to abstraction

    View Slide

  571. Bad code example
    open class IntList {
    fun addElement(value: Int) {
    if (this is ArrayIntList) {
    ...
    } else {
    ...
    ...
    class ArrayIntList: IntList() {
    ...
    Dependency > Direction > Detail to abstraction

    View Slide

  572. What's wrong with the bad example
    Brakes encapsulation and not robust
    - For inheritance specification change
    - For adding a new inheritance
    Dependency > Direction > Detail to abstraction

    View Slide

  573. Fixed code example
    Remove downcast with overriding
    open class IntList {
    open fun addElement(value: Int) {
    ...
    class ArrayIntList: IntList() {
    override fun addElement(value: Int) {
    ...
    Dependency > Direction > Detail to abstraction

    View Slide

  574. Dependency direction
    - Caller to callee
    - Detail to abstraction
    - Complex to simple
    Dependency > Direction > Complex to simple

    View Slide

  575. Bad code example
    class UserData(
    val userId: UserId,
    ...
    )
    class UserDataRequester {
    fun obtainFromServer(): UserData {
    ...
    Dependency > Direction > Complex to simple

    View Slide

  576. Bad code example
    class UserData(
    val userId: UserId,
    ...
    val requester: UserDataRequester
    )
    class UserDataRequester {
    fun obtainFromServer(): UserData {
    ...
    Dependency > Direction > Complex to simple

    View Slide

  577. What's wrong with the bad example
    Unnecessary dependency may cause a bug
    - May leak requester resource
    - May make inconsistent state if there are multiple requesters
    Dependency > Direction > Complex to simple

    View Slide

  578. What's wrong with the bad example
    Unnecessary dependency may cause a bug
    - May leak requester resource
    - May make inconsistent state if there are multiple requesters
    A simple type may be used widely and passed to other modules
    Dependency > Direction > Complex to simple

    View Slide

  579. How to remove dependency on complex class
    Just remove dependency of "simple to complex"
    - Pass a complex class instance directly if required
    class UserData(
    val userId: UserId,
    ...
    )
    Dependency > Direction > Complex to simple

    View Slide

  580. Exceptions
    It's sometimes necessary to depend on complex types
    - Mediator pattern
    - Adapter or facade
    - Data binder
    Dependency > Direction > Complex to simple

    View Slide

  581. Preferred dependency direction
    - Caller to callee
    - Detail to abstraction
    - Complex to simple
    Dependency > Direction > Summary

    View Slide

  582. Direction: Summary
    Maintain dependency in one direction
    - From caller to callee
    - From complex, detailed, large to simple, abstract, small
    Exceptions
    - Async call, sealed class, mediator pattern
    Dependency > Direction > Summary

    View Slide

  583. Topics
    First session:
    - Coupling
    Second session:
    - Direction
    - Redundancy
    - Explicitness
    Dependency > Redundancy

    View Slide

  584. Redundant dependency
    Cascaded dependency
    - Nested & indirect dependency
    Redundant dependency set
    - N:M dependency
    Dependency > Redundancy

    View Slide

  585. Redundant dependency
    Cascaded dependency
    - Nested & indirect dependency
    Redundant dependency set
    - N:M dependency
    Dependency > Redundancy

    View Slide

  586. Cascaded dependency
    Avoid unnecessary dependency chain
    Dependency > Redundancy > Cascaded dependency

    View Slide

  587. Cascaded dependency
    Avoid unnecessary dependency chain
    ┌──────────────────────┐
    │ MessageTextPresenter │
    └──────────────────────┘
    │ has instance

    ┌──────────────────────┐
    │ TimestampPresenter │
    └──────────────────────┘
    │ has instance

    ┌──────────────────────┐
    │ MessageDataProvider │
    └──────────────────────┘
    Dependency > Redundancy > Cascaded dependency

    View Slide

  588. Cascaded dependency
    Avoid unnecessary dependency chain
    ┌──────────────────────┐ depending indirectly
    │ MessageTextPresenter │─ ─ ─ ─ ─ ─ ─ ─ ┐
    └──────────────────────┘
    │ has instance │
    ▼ (only for MessageDataProvider)
    ┌──────────────────────┐ │
    │ TimestampPresenter │
    └──────────────────────┘ │
    │ has instance
    ▼ │
    ┌──────────────────────┐
    │ MessageDataProvider │◀ ─ ─ ─ ─ ─ ─ ─ ┘
    └──────────────────────┘
    Dependency > Redundancy > Cascaded dependency

    View Slide

  589. Bad cascaded dependency example
    class TimestampPresenter {
    val dataProvider: MessageDataProvider = ...
    fun invalidateViews() {
    // Update timestamp by `dataProvider`
    ...
    Dependency > Redundancy > Cascaded dependency

    View Slide

  590. Bad cascaded dependency example
    class TimestampPresenter {
    val dataProvider: MessageDataProvider = ...
    fun invalidateViews() {
    // Update timestamp by `dataProvider`
    ...
    class MessageTextPresenter {
    val timestampPresenter: TimestampPresenter = ...
    fun invalidateViews() {
    val messageData = timestampPresenter.dataProvider...
    // Update message text view by `messageData`
    Dependency > Redundancy > Cascaded dependency

    View Slide

  591. What's wrong with the bad example
    Unnecessary dependency
    - MessageTextPresenter is unrelated with TimestampPresenter
    Indirect dependency
    - MessageTextPresenter does not have MessageDataProvider
    directly
    Dependency > Redundancy > Cascaded dependency

    View Slide

  592. How to fix cascaded dependency
    Flatten nested dependencies by means of direct dependencies
    ┌──────────────────────┐ depending indirectly
    │ MessageTextPresenter │─ ─ ─ ─ ─ ─ ─ ─ ┐
    └──────────────────────┘
    │ has instance │
    ▼ (only for MessageDataProvider)
    ┌──────────────────────┐ │
    │ TimestampPresenter │
    └──────────────────────┘ │
    │ has instance
    ▼ │
    ┌──────────────────────┐
    │ MessageDataProvider │◀ ─ ─ ─ ─ ─ ─ ─ ┘
    └──────────────────────┘
    Dependency > Redundancy > Cascaded dependency

    View Slide

  593. How to fix cascaded dependency
    Flatten nested dependencies by means of direct dependencies
    ┌──────────────────────┐
    │ MessageTextPresenter │──┐
    └──────────────────────┘ │
    │ has instance ┌──────────────────────┐
    ├──────────────▶│ MessageDataProvider │
    ┌──────────────────────┐ │ └──────────────────────┘
    │ TimestampPresenter │──┘
    └──────────────────────┘
    Dependency > Redundancy > Cascaded dependency

    View Slide

  594. Fixed example of cascaded dependency
    class TimestampPresenter {
    val dataProvider: MessageDataProvider = ...
    fun invalidateViews() {
    // Update timestamp by `dataProvider`
    ...
    class MessageTextPresenter {
    val dataProvider: MessageDataProvider = ...
    fun invalidateViews() {
    // Update message text view by `dataProvider`
    Dependency > Redundancy > Cascaded dependency

    View Slide

  595. Redundant dependency
    Cascaded dependency
    - Nested & indirect dependency
    Redundant dependency set
    - N:M dependency
    Dependency > Redundancy > Dependency set

    View Slide

  596. Redundant dependency set
    Avoid duplication of "depending class set"
    ┌─────────────────────────┐ ┌─────────────────────────┐
    │ MessageTextPresenter │ │ TimestampPresenter │
    └─────────────────────────┘ └─────────────────────────┘
    │ │
    │ │
    │ │
    ├───────────────────────────┘



    ┌─────────────────────────┐
    │ MessageDataProvider │
    └─────────────────────────┘
    Dependency > Redundancy > Dependency set

    View Slide

  597. Redundant dependency set
    Avoid duplication of "depending class set"
    ┌─────────────────────────┐ ┌─────────────────────────┐
    │ MessageTextPresenter │ │ TimestampPresenter │
    └─────────────────────────┘ └─────────────────────────┘
    │ │ │ │
    │ │
    │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ┤
    ├───────────────────────────┘
    │ │

    ▼ ▼
    ┌─────────────────────────┐ ┌─────────────────────────┐
    │LocalMessageDataProvider │ │ServerMessageDataProvider│
    └─────────────────────────┘ └─────────────────────────┘
    Dependency > Redundancy > Dependency set

    View Slide

  598. What's wrong with redundant dependency set?
    Fragile for modification
    - When a new depending/depended type is added
    Duplicates switching logic
    - Provider selection/fallback logic
    Dependency > Redundancy > Dependency set

    View Slide

  599. How to remove redundant dependency set
    Create an intermediate layer to consolidate dependency set
    ┌─────────────────────────┐ ┌─────────────────────────┐
    │ MessageTextPresenter │ │ TimestampPresenter │
    └─────────────────────────┘ └─────────────────────────┘
    │ │ │ │
    │ │
    │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ┤
    ├───────────────────────────┘
    │ │

    ▼ ▼
    ┌─────────────────────────┐ ┌─────────────────────────┐
    │LocalMessageDataProvider │ │ServerMessageDataProvider│
    └─────────────────────────┘ └─────────────────────────┘
    Dependency > Redundancy > Dependency set

    View Slide

  600. How to remove redundant dependency set
    Create an intermediate layer to consolidate dependency set
    ┌─────────────────────────┐ ┌─────────────────────────┐
    │ MessageTextPresenter │ │ TimestampPresenter │
    └─────────────────────────┘ └─────────────────────────┘
    └────────────────┬────────────────┘

    ┌─────────────────────────┐
    │ MessageDataProvider │
    └─────────────────────────┘
    ┌────────────────┴────────────────┐
    ▼ ▼
    ┌─────────────────────────┐ ┌─────────────────────────┐
    │LocalMessageDataProvider │ │ServerMessageDataProvider│
    └─────────────────────────┘ └─────────────────────────┘
    Dependency > Redundancy > Dependency set

    View Slide

  601. Caution to remove redundant dependency
    Don’t create a layer if you don’t need it now
    - Keep "YAGNI" and "KISS" in mind
    Don't provide access to a hidden class
    - Never create MessageDataProvider.serverMessageDataProvider
    Dependency > Redundancy > Dependency set

    View Slide

  602. Redundancy: Summary
    - Remove dependencies only for providing another dependency
    - Add layer to remove dependency set duplication
    Dependency > Redundancy > Summary

    View Slide

  603. Topics
    First session:
    - Coupling
    Second session:
    - Direction
    - Redundancy
    - Explicitness
    Dependency > Explicitness

    View Slide

  604. Implicit dependency
    A dependency does not appear on a class diagram
    Dependency > Explicitness

    View Slide

  605. Implicit dependency
    A dependency does not appear on a class diagram
    Avoid implicit dependencies, use explicit ones instead
    Dependency > Explicitness

    View Slide

  606. Implicit dependency example
    - Implicit data domain for parameters
    - Implicitly expected behavior of subtype
    Dependency > Explicitness

    View Slide

  607. Implicit dependency example
    - Implicit data domain for parameters
    - Implicitly expected behavior of subtype
    Dependency > Explicitness > Implicit data domain

    View Slide

  608. Bad code example
    Question: What's wrong with the following function?
    fun setViewBackgroundColor(colorString: String) {
    val colorCode = when(colorString) {
    "red" -> 0xFF0000
    "green" -> 0x00FF00
    else -> 0x000000
    }
    view.setBackgroundColor(colorCode)
    }
    Dependency > Explicitness > Implicit data domain

    View Slide

  609. Why implicit data domain is bad
    Hard to find which value is accepted
    - "blue", "RED", "FF0000" are invalid
    Dependency > Explicitness > Implicit data domain

    View Slide

  610. Why implicit data domain is bad
    Hard to find which value is accepted
    - "blue", "RED", "FF0000" are invalid
    Color string conversion logic is tightly coupled with the function
    - Easy to break if we add/remove a color
    Dependency > Explicitness > Implicit data domain

    View Slide

  611. How to remove implicit data domain
    Create a data type representing the data domain
    enum class BackgroundColor(val colorCode: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00)
    }
    Dependency > Explicitness > Implicit data domain

    View Slide

  612. How to remove implicit data domain
    Create a data type representing the data domain
    enum class BackgroundColor(val colorCode: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00)
    }
    fun setViewBackgroundColor(color: BackgroundColor) {
    view.setBackgroundColor(color.colorCode)
    }
    Dependency > Explicitness > Implicit data domain

    View Slide

  613. Other options instead of defining a type
    Options to consider for an invalid value
    - Just do nothing
    - Return an error value
    - Throw a runtime error (not recommended)
    Dependency > Explicitness > Implicit data domain

    View Slide

  614. Other options instead of defining a type
    Options to consider for an invalid value
    - Just do nothing
    - Return an error value
    - Throw a runtime error (not recommended)
    Note 1: Documentation is required for any option
    Dependency > Explicitness > Implicit data domain

    View Slide

  615. Other options instead of defining a type
    Options to consider for an invalid value
    - Just do nothing
    - Return an error value
    - Throw a runtime error (not recommended)
    Note 1: Documentation is required for any option
    Note 2: An "error value" may also require another data domain type
    Dependency > Explicitness > Implicit data domain

    View Slide

  616. Implicit dependency example
    - Implicit data domain for parameters
    - Implicitly expected behavior of subtype
    Dependency > Explicitness > Subtype behavior

    View Slide

  617. Implicit dependency by subtype
    A caller depends on the inherited type implicitly
    ┌───────────────────────┐ call ┌───────────────────────┐
    │ ListenerInterface │◀─────────│ Caller │
    └───────────────────────┘ └───────────────────────┘

    │ inherit




    ┌───────────────────────┐
    │ListenerImplementation │
    └───────────────────────┘
    Dependency > Explicitness > Subtype behavior

    View Slide

  618. Implicit dependency by subtype
    A caller depends on the inherited type implicitly
    ┌───────────────────────┐ call ┌───────────────────────┐
    │ ListenerInterface │◀─────────│ Caller │
    └───────────────────────┘ └───────────────────────┘

    │ inherit
    ┌─────┼───────────────────────┐
    │ │ LargeClass │
    │ │ ▲ │
    │ │ │ │
    │ ┌───────────────────────┐ │
    │ │ListenerImplementation │ │
    │ └───────────────────────┘ │
    └─────────────────────────────┘
    Dependency > Explicitness > Subtype behavior

    View Slide

  619. Implicit dependency example by subtype
    interface ListenerInterface { fun queryInt(): Int }
    class LargeClass {
    inner class ListenerImplementation : ListenerInterface {
    ...
    }
    }
    class Caller(listener : ListenerInterface) {
    ...
    }
    Dependency > Explicitness > Subtype behavior

    View Slide

  620. What's wrong with subtype
    Depends on result of the function call on LargeClass implicitly
    - Does not appear on class diagram
    - Hard to find Caller relying on LargeClass implementation detail
    Dependency > Explicitness > Subtype behavior

    View Slide

  621. How to remove implicit dependency
    Remove the interface if there is only one implementation
    ┌───────────────────────┐ call ┌───────────────────────┐
    │ ListenerInterface │◀─────────│ Caller │
    └───────────────────────┘ └───────────────────────┘

    │ inherit
    ┌─────┼───────────────────────┐
    │ │ LargeClass │
    │ │ ▲ │
    │ │ │ │
    │ ┌───────────────────────┐ │
    │ │ListenerImplementation │ │
    │ └───────────────────────┘ │
    └─────────────────────────────┘
    Dependency > Explicitness > Subtype behavior

    View Slide

  622. How to remove implicit dependency
    Remove the interface if there is only one implementation
    call ┌───────────────────────┐
    ┌─────│ Caller │
    │ └───────────────────────┘


    ┌─────────────────────────────┐ │
    │ LargeClass │ │
    │ ▲ │ │
    │ │ │ │
    │ ┌───────────────────────┐ │ │
    │ │ Listener │◀─┼─┘
    │ └───────────────────────┘ │
    └─────────────────────────────┘
    Dependency > Explicitness > Subtype behavior

    View Slide

  623. Fixed code example
    class LargeClass {
    inner class Listener {
    fun ...
    }
    }
    class Caller(listener : Listener) {
    ...
    }
    Dependency > Explicitness > Subtype behavior

    View Slide

  624. Explicitness: Summary
    Make dependencies visible on a class diagram
    - Define types to represent data domain
    - Remove unnecessary interface
    Dependency > Explicitness > Summary

    View Slide

  625. Summary
    - Coupling: Relax content/common/control coupling
    - Direction: Remove cyclic dependency as far as possible
    - Redundancy: Don't cascade or duplicate dependency set
    - Explicitness: Make dependency visible on a class diagram
    Dependency > Summary

    View Slide

  626. Code-Readability / Session-8
    Review #1

    View Slide

  627. Contents of this lecture
    - Introduction and Principles
    - Natural language: Naming, Comments
    - Inner type structure: State, Procedure
    - Inter type structure: Dependency (two sessions)
    - Follow-up: Review
    Review > Introduction

    View Slide

  628. Why do we need code review?
    Review > Introduction

    View Slide

  629. Why do we need code review?
    Most important: Readability
    - Approve code change only when it is clean enough
    Review > Introduction

    View Slide

  630. Why do we need code review?
    Most important: Readability
    - Approve code change only when it is clean enough
    Nice to have: Logic correctness
    - Edge cases, illegal state, race condition, exceptions...
    - Author's responsibility
    Review > Introduction

    View Slide

  631. Topics
    Authors:
    - How to create PR
    - How to address review comment
    Reviewers:
    - Principles
    - Comment contents
    Review > Introduction

    View Slide

  632. Topics
    Authors:
    - How to create PR
    - How to address review comment
    Reviewers:
    - Principles
    - Comment contents
    Review > Create PR

    View Slide

  633. How to create PR
    - Tell your intention with commit structure
    - Keep your PR small
    Review > Create PR

    View Slide

  634. How to create PR
    - Tell your intention with commit structure
    - Keep your PR small
    Review > Create PR

    View Slide

  635. Commit structure
    - Squash unnecessary commits
    - Don't put logical change and syntax change into a commit
    - Think "cut of axis" for commits
    Review > Create PR > Commit structure

    View Slide

  636. Commit structure
    - Squash unnecessary commits
    - Don't put logical change and syntax change into a commit
    - Think "cut of axis" for commits
    Review > Create PR > Commit structure > Squash unnecessary commits

    View Slide

  637. Unnecessary commits
    Remove or squash "reverting commit"
    Review > Create PR > Commit structure > Squash unnecessary commits

    View Slide

  638. Unnecessary commits
    Remove or squash "reverting commit"
    Bad example:
    Commit 1: Add debug log
    Commit 2: Implement a method of feature X
    Commit 3: Remove debug log
    Review > Create PR > Commit structure > Squash unnecessary commits

    View Slide

  639. How to remove unnecessary commits
    Use git rebase -i or similar command
    Review > Create PR > Commit structure > Squash unnecessary commits

    View Slide

  640. How to remove unnecessary commits
    Use git rebase -i or similar command
    Fixed example:
    Commit 2: Implement a method of feature X
    Review > Create PR > Commit structure > Squash unnecessary commits

    View Slide

  641. Commit structure
    - Squash unnecessary commits
    - Don't put logical change and syntax change into a commit
    - Think "cut of axis" for commits
    Review > Create PR > Commit structure > Logical and syntax change

    View Slide

  642. Logical and syntax change in a commit
    Typical anti-patterns
    - IDE auto clean up + actual refactoring
    - Extracting as a function + function body refactoring
    - Renaming + non-trivial parameter type change
    Review > Create PR > Commit structure > Logical and syntax change

    View Slide

  643. Logical and syntax change in a commit
    Typical anti-patterns
    - IDE auto clean up + actual refactoring
    - Extracting as a function + function body refactoring
    - Renaming + non-trivial parameter type change
    If it's hard to read, consider splitting the commit
    Review > Create PR > Commit structure > Logical and syntax change

    View Slide

  644. Commit structure
    - Squash unnecessary commits
    - Don't put logical change and syntax change into a commit
    - Think "cut of axis" for commits
    Review > Create PR > Commit structure > Cut of axis

    View Slide

  645. Example of splitting PR
    class Deprecated {
    fun functionA() { /* ... */ }
    fun functionB() { /* ... */ }
    }
    class New { /* ... */ }
    Review > Create PR > Commit structure > Cut of axis

    View Slide

  646. Example of splitting PR
    class Deprecated {
    fun functionA() { /* ... */ }
    fun functionB() { /* ... */ }
    }
    class New { /* ... */ }
    Objective:
    Remove class Deprecated with moving the functions into class New
    Review > Create PR > Commit structure > Cut of axis

    View Slide

  647. Axis options to consider
    Option A:
    Commit 1: Define New.functionA/functionB
    Commit 2: Replace receivers Deprecated with New
    Commit 3: Remove class Deprecated
    Review > Create PR > Commit structure > Cut of axis

    View Slide

  648. Axis options to consider
    Option A:
    Commit 1: Define New.functionA/functionB
    Commit 2: Replace receivers Deprecated with New
    Commit 3: Remove class Deprecated
    Option B:
    Commit 1: Move Deprecated.functionA to New, and update the callers
    Commit 2: Move Deprecated.functionB to New, and update the callers
    Commit 3: Remove class Deprecated
    Review > Create PR > Commit structure > Cut of axis

    View Slide

  649. Better option for axis
    Review > Create PR > Commit structure > Cut of axis

    View Slide

  650. Better option for axis
    Option B is better than A
    Review > Create PR > Commit structure > Cut of axis

    View Slide

  651. Better option for axis
    Option B is better than A
    Reason:
    - Easy to understand the intention
    - Verifiable that there is no logical change
    - Traceable commit history
    Review > Create PR > Commit structure > Cut of axis

    View Slide

  652. How to create PR
    - Tell your intention with commit structure
    - Keep your PR small
    Review > Create PR > Keep PR small

    View Slide

  653. Keep your PR small
    - Split your daily work into several PRs
    - Create supporting PRs
    - Specify the scope of a PR
    Review > Create PR > Keep PR small

    View Slide

  654. Keep your PR small
    - Split your daily work into several PRs
    - Create supporting PRs
    - Specify the scope of a PR
    Review > Create PR > Keep PR small > Split daily work

    View Slide

  655. Split your daily work into several PRs
    Don't create a large PR like "PR of the week"
    - Hard to read for reviewers
    - Makes review iteration long
    Two ways to split PRs: Top-down, Bottom-up
    Review > Create PR > Keep PR small > Split daily work

    View Slide

  656. Top-down approach
    Fill implementation detail later
    Review > Create PR > Keep PR small > Split daily work

    View Slide

  657. Top-down approach
    Fill implementation detail later
    1. Create skeleton classes to explain the structure
    2. Explain future plan with TODO and PR comments
    Review > Create PR > Keep PR small > Split daily work

    View Slide

  658. Top-down approach
    Fill implementation detail later
    1. Create skeleton classes to explain the structure
    2. Explain future plan with TODO and PR comments
    class UserProfilePresenter(
    val userProfileUseCase: UseCase
    val profileRootView: View
    ) {
    fun showProfileImage() = TODO(...)
    fun addUserTag() = TODO(...)
    Review > Create PR > Keep PR small > Split daily work

    View Slide

  659. Bottom-up approach
    Create small parts first
    Review > Create PR > Keep PR small > Split daily work

    View Slide

  660. Bottom-up approach
    Create small parts first
    1. Implement simple types/procedure without any client code
    2. Explain future plan of client code with PR comments
    Review > Create PR > Keep PR small > Split daily work

    View Slide

  661. Bottom-up approach
    Create small parts first
    1. Implement simple types/procedure without any client code
    2. Explain future plan of client code with PR comments
    data class UserProfileData(val ..., val ...)
    object UserNameStringUtils {
    fun String.normalizeEmoji(): String = ...
    fun isValidUserName(userName: String): Boolean = ...
    Review > Create PR > Keep PR small > Split daily work

    View Slide

  662. Keep your PR small
    - Split your daily work into several PRs
    - Create supporting PRs
    - Specify the scope of a PR
    Review > Create PR > Keep PR small > Create supporting PRs

    View Slide

  663. Create supporting PRs
    Question: How can you make PRs with the following situation?
    1. You made commits of class New
    2. You found New has code duplication with Old
    3. You need a large change to consolidate the logic (>100 LOC)
    - Because there are already a lot of callers of Old
    Review > Create PR > Keep PR small > Create supporting PRs

    View Slide

  664. Worst way to create a PR
    Commit1: Create a class "New"
    Commit2: Extract duplicated code of "New" and "Old" # >100 LOC
    Problem: The PR is large, and has two responsibilities
    Review > Create PR > Keep PR small > Create supporting PRs

    View Slide

  665. Better way to create a PR
    PR1:
    Commit1: Create a class "New"
    PR2 (including PR1):
    Commit2: Extract duplicated code of "New" and "Old" # >100 LOC
    Problem: There is temporary code duplication
    - We may forget to create PR2
    Review > Create PR > Keep PR small > Create supporting PRs

    View Slide

  666. Best way to create a PR 1/3
    Reorder, split, merge commits
    1: Split Commit2 for New and Old
    Commit1: Create a class "New"
    Commit2_New: Extract code of "New"
    Commit2_Old: Extract code of "Old"
    Review > Create PR > Keep PR small > Create supporting PRs

    View Slide

  667. Best way to create a PR 2/3
    2: Move Commit2_Old to the first
    Commit2_Old: Extract code of "Old"
    Commit1: Create a class "New"
    Commit2_New: Extract code of "New"
    Review > Create PR > Keep PR small > Create supporting PRs

    View Slide

  668. Best way to create a PR 3/3
    3: Squash commits Commit1 and Commit2_New
    Commit2_Old: Extract code of "Old"
    Commit1: Create a class "New"
    4: Create PRs for Commit2_Old and Commit1
    Review > Create PR > Keep PR small > Create supporting PRs

    View Slide

  669. Keep your PR small
    - Split your daily work into several PRs
    - Create supporting PRs
    - Specify the scope of a PR
    Review > Create PR > Keep PR small > Specify scope of PR

    View Slide

  670. Conflict between author and reviewer
    Author's responsibility:
    - Keep the PR small
    Review > Create PR > Keep PR small > Specify scope of PR

    View Slide

  671. Conflict between author and reviewer
    Author's responsibility:
    - Keep the PR small
    Reviewer's responsibility:
    - Don't approve if there is room to improve
    Review > Create PR > Keep PR small > Specify scope of PR

    View Slide

  672. Conflict between author and reviewer
    Author's responsibility:
    - Keep the PR small
    Reviewer's responsibility:
    - Don't approve if there is room to improve
    These two responsibilities may conflict with each other
    Review > Create PR > Keep PR small > Specify scope of PR

    View Slide

  673. Describe scope of PR
    Write PR comment and TODO comment to explain:
    - The main purpose
    - Future plan
    - Out of scope
    Review > Create PR > Keep PR small > Specify scope of PR

    View Slide

  674. How to create PR: Summary
    Make readable PR that is structured and small
    - Make the objective of a PR/commits clear
    - Squash, split, reorder commits
    Review > Create PR > Summary

    View Slide

  675. Topics
    Authors:
    - How to create PR
    - How to address review comment
    Reviewers:
    - Principles
    - Comment contents
    Review > Address review comment

    View Slide

  676. Important point in addressing comments
    Review > Address review comment

    View Slide

  677. Important point in addressing comments
    Don't just address comments
    Review > Address review comment

    View Slide

  678. Don't just address comments
    - Think why reviewer asked/misunderstood
    - Understand reviewer's suggestion
    - Consider addressing a comment to other parts
    - Don't make reviewers repeat
    Review > Address review comment

    View Slide

  679. Don't just address comments
    - Think why reviewer asked/misunderstood
    - Understand reviewer's suggestion
    - Consider addressing a comment to other parts
    - Don't make reviewers repeat
    Review > Address review comment

    View Slide

  680. Think why reviewer asked/misunderstood
    Typical signal that the code is unreadable or wrong
    Review > Address review comment

    View Slide

  681. Think why reviewer asked/misunderstood
    Typical signal that the code is unreadable or wrong
    Example of comment "Why do you check null here?":
    - Remove the null check if unnecessary
    - Write inline comment to explain why
    - Extract as a local value to explain (e.g., isUserExpired)
    Review > Address review comment

    View Slide

  682. Don't just address comments
    - Think why reviewer asked/misunderstood
    - Understand reviewer's suggestion
    - Consider addressing a comment to other parts
    - Don't make reviewers repeat
    Review > Address review comment

    View Slide

  683. Understand reviewer's suggestion
    Don't just paste attached code in a review comment
    - Think what is the key idea
    - Consider renaming
    - Confirm it's logically correct: exceptions, race conditions ...
    Review > Address review comment

    View Slide

  684. Don't just address comments
    - Think why reviewer asked/misunderstood
    - Understand reviewer's suggestion
    - Consider addressing a comment to other parts
    - Don't make reviewers repeat
    Review > Address review comment

    View Slide

  685. Consider addressing a comment to other parts
    Find similar code and address it
    Review > Address review comment

    View Slide

  686. Consider addressing a comment to other parts
    Find similar code and address it
    Example of comment "Add @Nullable to this parameter":
    - Apply to other parameters
    - Apply to the return value
    - Apply to other functions
    Review > Address review comment

    View Slide

  687. Don't just address comments
    - Think why reviewer asked/misunderstood
    - Understand reviewer's suggestion
    - Consider addressing a comment to other parts
    - Don't make reviewers repeat
    Review > Address review comment

    View Slide

  688. Don't make reviewers repeat
    Check what you asked previously before sending a review request
    - Coding style and conventions
    - Natural language: word choice, grammar
    - Language/platform idiom
    - Conditional branch structure
    - Testing
    Review > Address review comment

    View Slide

  689. Don't just address comment: Summary
    Understand what is the intention of a review comment
    - Find the key idea and reason of the comment
    - Try to apply it to other parts
    - Relieve reviewers' workload
    Review > Address review comment

    View Slide

  690. Topics
    Authors:
    - How to create PR
    - How to address review comment
    Reviewers:
    - Principles
    - Comment contents
    Review > Reviewer principles

    View Slide

  691. Principles for reviewers
    Don't be too kind
    Review > Reviewer principles

    View Slide

  692. Principles for reviewers
    Don't be too kind
    (but be kind a little)
    Review > Reviewer principles

    View Slide

  693. Principles for reviewers
    - Don't neglect review requests
    - Reject problematic PR
    - Don't guess author's situation
    - Consider other options of "answer"
    Review > Reviewer principles

    View Slide

  694. Principles for reviewers
    - Don't neglect review requests
    - Reject problematic PR
    - Don't guess author's situation
    - Consider other options of "answer"
    Review > Reviewer principles > Don't neglect

    View Slide

  695. Don't neglect review requests
    Define a reply time limit for the project
    - e.g., 24 hours of working days
    Question: What is the worst reaction to a review request?
    Review > Reviewer principles > Don't neglect

    View Slide

  696. Reactions to review requests
    GOOD:
    NOT SO GOOD:
    - Simply ignore
    WORST:
    Review > Reviewer principles > Don't neglect

    View Slide

  697. Reactions to review requests
    GOOD:
    NOT SO GOOD:
    - Simply ignore
    WORST:
    - Reply "I'll review" without review
    Review > Reviewer principles > Don't neglect

    View Slide

  698. Reactions to review requests
    GOOD:
    - Review soon
    - Redirect to other reviewer
    - "I cannot review", "I'll be late"...
    NOT SO GOOD:
    - Simply ignore
    WORST:
    - Reply "I'll review" without review
    Review > Reviewer principles > Don't neglect

    View Slide

  699. When you cannot review
    If you are busy, reply so
    Review > Reviewer principles > Don't neglect

    View Slide

  700. When you cannot review
    If you are busy, reply so
    The author can:
    - Wait for the PR review if it doesn't block other tasks
    - Find other reviewers if it's an urgent PR
    Review > Reviewer principles > Don't neglect

    View Slide

  701. Principles for reviewers
    - Don't neglect review requests
    - Reject problematic PR
    - Don't guess author's situation
    - Consider other options of "answer"
    Review > Reviewer principles > Reject problematic PR

    View Slide

  702. Reject problematic PR
    Don't spend too much time reviewing a problematic PR
    Review > Reviewer principles > Reject problematic PR

    View Slide

  703. Reject problematic PR
    Ask the author to re-create a PR without reading the detail
    Review > Reviewer principles > Reject problematic PR

    View Slide

  704. Reject problematic PR
    Ask the author to re-create a PR without reading the detail
    Too large or hard to read:
    - Ask to split
    Review > Reviewer principles > Reject problematic PR

    View Slide

  705. Reject problematic PR
    Ask the author to re-create a PR without reading the detail
    Too large or hard to read:
    - Ask to split
    Totally wrong on purpose, assumption, or structure:
    - Ask to make skeleton classes or have casual chat
    Review > Reviewer principles > Reject problematic PR

    View Slide

  706. Principles for reviewers
    - Don't neglect review requests
    - Reject problematic PR
    - Don't guess author's situation
    - Consider other options of "answer"
    Review > Reviewer principles > Don't guess author's situation

    View Slide

  707. Don't guess author's situation
    Reviewers should not care about deadline
    - Let them explain if they want to merge soon
    - Need to file issue tickets, and add TODO at least
    Review > Reviewer principles > Don't guess author's situation

    View Slide

  708. Don't guess author's situation
    Reviewers should not care about deadline
    - Let them explain if they want to merge soon
    - Need to file issue tickets, and add TODO at least
    Exception: Major/critical issues on a release branch or hot-fix
    - Tests are still required
    - Reviewers should keep cooler than authors
    Review > Reviewer principles > Don't guess author's situation

    View Slide

  709. Principles for reviewers
    - Don't neglect review requests
    - Reject problematic PR
    - Don't guess author's situation
    - Consider other options of "answer"
    Review > Reviewer principles > Review comment options

    View Slide

  710. Options of a review comment
    The answer is not only showing answer
    Review > Reviewer principles > Review comment options

    View Slide

  711. Options of a review comment
    The answer is not only showing answer
    To save reviewer's time and encourage authors' growth:
    - Ask questions to make the author come up with an idea
    - Suggest options to make the author decide
    - Leave a note to share knowledge
    Review > Reviewer principles > Review comment options

    View Slide

  712. Principles for reviewers: Summary
    Don't be too kind, but help authors
    - Reply if you can't review
    - Fine to refuse problematic PR
    Review > Reviewer principles > Summary

    View Slide

  713. Principles for reviewers: Summary
    Don't be too kind, but help authors
    - Reply if you can't review
    - Fine to refuse problematic PR
    Think what is the best comment
    - including time cost and author's growth
    Review > Reviewer principles > Summary

    View Slide

  714. Topics
    Authors:
    - How to create PR
    - How to address review comment
    Reviewers:
    - Principles
    - Comment contents
    Review > Reviewer principles

    View Slide

  715. What should be commented
    - Styles, conventions, idioms ... (CI can review instead)
    - Tests
    - All in this presentation series
    - Principles, natural language, structure, dependency
    - Any issues you found
    Review > Comment contents

    View Slide

  716. What should be commented
    - Styles, conventions, idioms ... (CI can review instead)
    - Tests
    - All in this presentation series
    - Principles, natural language, structure, dependency
    - Any issues you found
    That's all!
    Review > Comment contents

    View Slide

  717. Code review case study
    Adding a parameter to function
    Review > Comment contents > Case study

    View Slide

  718. Code review case study
    Adding a parameter to function
    fun showPhotoView(
    photoData: PhotoData
    ) {
    if (!photoData.isValid) {
    return
    }
    ...
    Review > Comment contents > Case study

    View Slide

  719. Code review case study
    Adding a parameter to function
    fun showPhotoView(
    photoData: PhotoData,
    isFromEditor: Boolean
    ) {
    if (!photoData.isValid) {
    if (isFromEditor) { showDialog() }
    return
    }
    ...
    Review > Comment contents > Case study

    View Slide

  720. Code review case study
    Naming: Describe what it is rather than how it is used
    isFromEditor -> showsDialogOnError
    Review > Comment contents > Case study

    View Slide

  721. Code review case study
    Naming: Describe what it is rather than how it is used
    fun showPhotoView(
    photoData: PhotoData,
    isFromEditor: Boolean
    ) {
    if (!photoData.isValid) {
    if (isFromEditor) { showDialog() }
    return
    }
    ...
    Review > Comment contents > Case study

    View Slide

  722. Code review case study
    Naming: Describe what it is rather than how it is used
    fun showPhotoView(
    photoData: PhotoData,
    showsDialogOnError: Boolean
    ) {
    if (!photoData.isValid) {
    if (showsDialogOnError) { showDialog() }
    return
    }
    ...
    Review > Comment contents > Case study

    View Slide

  723. Code review case study
    Dependency: Control coupling with a boolean flag
    Extract showDialog call
    Review > Comment contents > Case study

    View Slide

  724. Code review case study
    Dependency: Control coupling with a boolean flag
    Extract showDialog call
    - For editor:
    val isViewShown = showPhotoView(...)
    if (!isViewShown) { showErrorDialog(...) }
    - For other place:
    showPhotoView(...)
    Review > Comment contents > Case study

    View Slide

  725. Code review case study
    Dependency: Control coupling with a boolean flag
    fun showPhotoView(photoData: PhotoData): Boolean {
    if (!photoData.isValid) {
    return false
    }
    ...
    Review > Comment contents > Case study

    View Slide

  726. Code review case study
    Comments:
    Comment if a function has both side-effect and return value
    Add comment to explain the return value
    Review > Comment contents > Case study

    View Slide

  727. Code review case study
    Comments:
    Comment if a function has both side-effect and return value
    /**
    * Shows a photo at the center if the given photo data is ...,
    * and returns true.
    * Otherwise, this returns false without showing the view.
    */
    fun showPhotoView(photoData: PhotoData): Boolean {
    if (!photoData.isValid) {
    return false
    }
    Review > Comment contents > Case study

    View Slide

  728. What we comment: Summary
    Review the following items
    - Styles, conventions, manners, idioms ...
    - Principles, natural language, structure, dependency ...
    Review > Comment contents > Summary

    View Slide

  729. What we comment: Summary
    Review the following items
    - Styles, conventions, manners, idioms ...
    - Principles, natural language, structure, dependency ...
    Read over "code readability" presentation
    Review > Comment contents > Summary

    View Slide

  730. Summary
    Review is for readability
    Review > Summary

    View Slide

  731. Summary
    Review is for readability
    Author:
    - Keep PR small and structured
    - Understand review comments, don't just address
    Review > Summary

    View Slide

  732. Summary
    Review is for readability
    Author:
    - Keep PR small and structured
    - Understand review comments, don't just address
    Reviewer:
    - Don't be too kind, you can ask
    Review > Summary

    View Slide