Slide 1

Slide 1 text

Code→readability→session 6 Dependency ‐

Slide 2

Slide 2 text

Brushup: Function Check whether it is easy to write a short summary Function responsibility: - Do not modify when returning a main result Function flow: - Make code understandable by scanning it - Definition-based programming, early return, split by object

Slide 3

Slide 3 text

Contents of this lecture - Introduction and Principles - Natural language: Naming, Comments - Inner type structure: State, Function - Inter type structure: Dependency I, Dependency II - Follow-up: Review Dependency > Introduction

Slide 4

Slide 4 text

What "dependency" is 1/2 Example: Class X depends on class Y - 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 Y appears in X X depends on Y, roughly speaking Dependency > Introduction

Slide 5

Slide 5 text

What "dependency" is 2/2 Is the word "dependency" only used for classes? No It may also be used for instances, functions, modules Some dependencies are difficult to find at a glance Dependency > Introduction

Slide 6

Slide 6 text

Contents of Dependency I & II Dependency I: - Coupling Dependency II: - Direction - Redundancy - Explicitness Dependency > Introduction

Slide 7

Slide 7 text

Contents of Dependency I & II Dependency I: - Coupling Dependency II: - Direction - Redundancy - Explicitness Dependency > Coupling

Slide 8

Slide 8 text

Coupling 1/2 Indicates strength of dependency A weaker coupling is preferred The original definition7 is for dependencies between functions It is applied to dependencies between classes in this talk 7 Glenford J. Myers. 1975. Reliable software though composite design. Litton Educational Publishing, Inc. Dependency > Coupling

Slide 9

Slide 9 text

Coupling 2/2 Content coupling Common coupling Control coupling Stamp coupling Data coupling Message coupling strong weak attention required acceptable External coupling Dependency > Coupling

Slide 10

Slide 10 text

Topics Content coupling Common coupling Control coupling Stamp coupling Data coupling Message coupling strong weak attention required acceptable External coupling Dependency > Coupling > Content coupling

Slide 11

Slide 11 text

Content coupling Depends on the internal workings - Jump into internal code directly - Modify internal status directly - Overwrite behavior directly It is not easy to do this with modern programming languages However, a similar behavior can be created Dependency > Coupling > Content coupling

Slide 12

Slide 12 text

Equivalent to content coupling - Allows illegal usage - Shares internal mutable state Dependency > Coupling > Content coupling

Slide 13

Slide 13 text

Equivalent to content coupling - Allows illegal usage - Shares internal mutable state Dependency > Coupling > Content coupling

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Allows illegal usage: Bad points Easy to misuse calculator - Call prepare()/tearDown() before/after calling calculate() - Pass/get a value by parameter/result property. Not robust to implementation change Dependency > Coupling > Content coupling

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Allows illegal usage: What to check Do not apply design concepts/principals too much - e.g., command/query separation Dependency > Coupling > Content coupling

Slide 21

Slide 21 text

Equivalent to content coupling - Allows illegal usage - Shares internal mutable state Dependency > Coupling > Content coupling

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

Shares mutable state: Bad points UserListPresenter depends on an external mutable value - Causes unexpected state update by other reference holders - No limitation of modifying the list (e.g., addition/deletion) Easy to create an illegal state and hard to find the root cause - A reference to the presenter is not required to update it Dependency > Coupling > Content coupling

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Topics Content coupling Common coupling Control coupling Stamp coupling Data coupling Message coupling strong weak attention required acceptable External coupling Dependency > Coupling > Common/external coupling

Slide 29

Slide 29 text

Common/external coupling Shares mutable global data - Pass data by global variables - Depend on a mutable singleton - (Use shared resources such as a file system or network) Common coupling: Shares data structure instance External coupling: Shares basic/primitive value Dependency > Coupling > Common/external coupling

Slide 30

Slide 30 text

Common/external coupling Shares mutable global data - Pass data by global variables - Depend on a mutable singleton - (Use shared resources such as a file system or network) Dependency > Coupling > Common/external coupling

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

Pass data by global variables: How to fix Pass/receive values as function arguments or a return value Dependency > Coupling > Common/external coupling

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Common/external coupling Shares mutable global data - Pass data by global variables - Depend on a mutable singleton - (Use shared resources such as a file system or network) Dependency > Coupling > Common/external coupling

Slide 36

Slide 36 text

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/external coupling

Slide 37

Slide 37 text

Depend on a mutable singleton: Bad points Singleton is hard to manage - Unmanaged lifecycle, scope, and references - Low flexibility for specification change - Bad testability Dependency > Coupling > Common/external coupling

Slide 38

Slide 38 text

Depend on a mutable singleton: How to fix Pass an instance as a constructor parameter ( Dependency injection) The constructor caller can manage the instance Dependency > Coupling > Common/external coupling

Slide 39

Slide 39 text

Depend on a mutable singleton: Fixed code example class UserListUseCase( val userDataRepository: UserDataRepository ) { suspend fun invoke(): List = withContext(...) { val result = userDataRepository.query(...) ... Dependency > Coupling > Common/external coupling

Slide 40

Slide 40 text

Common/external coupling: Note Apply the same discussion to a smaller scope - module, package, class... Example: Avoid passing values using instance properties Dependency > Coupling > Common/external coupling

Slide 41

Slide 41 text

Common/external coupling in a class: Bad example class Klass { var parameter: Int? = null fun firstFunction() { parameter = 42 secondFunction() } fun secondFunction() { /* Use `parameter` here... */ } Dependency > Coupling > Common/external coupling

Slide 42

Slide 42 text

Common/external coupling in a class: Fixed code class Klass { fun firstFunction() { val argument = 42 secondFunction(argument) } fun secondFunction(parameter: Int) { /* Use `parameter` here... */ } Dependency > Coupling > Common/external coupling

Slide 43

Slide 43 text

Common/external coupling in a class: Summary Avoid using mutable singletons or global variables - Unmanageable, not robust, hard to test To avoid common/external coupling: - Pass/receive values as arguments or a return value - Use dependency injection Dependency > Coupling > Common/external coupling

Slide 44

Slide 44 text

Topics Content coupling Common coupling Control coupling Stamp coupling Data coupling Message coupling strong weak attention required acceptable External coupling Dependency > Coupling > Control coupling

Slide 45

Slide 45 text

Control coupling Determines "what to do" using arguments Often unavoidable in software development Control coupling should be mitigated in some cases Dependency > Coupling > Control coupling

Slide 46

Slide 46 text

Control coupling to be mitigated - Conditional branches with unnecessarily large scope - How to fix - Conditional branches with little behavior relevance - How to fix 1 - How to fix 2 Dependency > Coupling > Control coupling

Slide 47

Slide 47 text

Control coupling to be mitigated - Conditional branches with unnecessarily large scope - How to fix - Conditional branches with little behavior relevance - How to fix 1 - How to fix 2 Dependency > Coupling > Control coupling

Slide 48

Slide 48 text

Branches with large scope 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

Slide 49

Slide 49 text

Branches with large scope: What is wrong - Need to read all conditional branches to understand the behavior - Not robust to implementation change Dependency > Coupling > Control coupling

Slide 50

Slide 50 text

Control coupling to be mitigated - Conditional branches with unnecessarily large scope - How to fix: Split by object, not condition - Conditional branches with little behavior relevance - How to fix 1 - How to fix 2 Dependency > Coupling > Control coupling

Slide 51

Slide 51 text

Branches with large scope: How to fix Split by object, not condition (introduced last session) - Split scope of control coupling Confirm that target objects are common for each condition - updateIf... may be applicable when partially different Dependency > Coupling > Control coupling

Slide 52

Slide 52 text

Branches with large scope: Fixed code 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

Slide 53

Slide 53 text

Branches with large scope: Fixed code 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

Slide 54

Slide 54 text

Control coupling to be mitigated - Conditional branches with unnecessarily large scope - How to fix: Split by object, not condition - Conditional branches with little behavior relevance - How to fix 1 - How to fix 2 Dependency > Coupling > Control coupling

Slide 55

Slide 55 text

Branches with little relevance: Bad example 1/2 sealed class DataType { class UserName(...) : DataType() class BirthDate(...) : DataType() class ProfileImage(...) : DataType() } Dependency > Coupling > Control coupling

Slide 56

Slide 56 text

Branches with little relevance: Bad example 2/2 fun updateUserView(dataType: DataType) = when(dataType) { is DataType.UserName -> { val userName = getUserName(dataType.userId) userNameView.text = userName } is DataType.Birthday -> { val birthDate = ... birthDayView.text = format(birthDate, ...) } is DataType.ProfileImage -> { ... } } Dependency > Coupling > Control coupling

Slide 57

Slide 57 text

Branches with little relevance: Bad points - Need to read all branches to understand the behavior - Not easy to map caller condition to callee behavior updateUserView(DataType.ProfileImage(...)) fun updateUserView(dataType: DataType) = when(dataType) { is DataType.UserName -> { ... } // Need to check the condition... is DataType.Birthday -> { ... } is DataType.ProfileImage -> { ... } // Finally found Dependency > Coupling > Control coupling

Slide 58

Slide 58 text

Control coupling to be mitigated - Conditional branches with unnecessarily large scope - How to fix: Split by object, not condition - Conditional branches with little behavior relevance - How to fix 1: Remove conditional branches - How to fix 2 Dependency > Coupling > Control coupling

Slide 59

Slide 59 text

Remove conditional branches Split function for each condition Applicable if a condition is statically determined - If not, causes another control coupling on the caller side Dependency > Coupling > Control coupling

Slide 60

Slide 60 text

Remove conditional branches: Applicable example Caller location determines the condition class Caller1 { fun caller1() = presenter.updateUserView(DataType.UserName(...)) } class Caller2 { fun caller2() = presenter.updateUserView(DataType.BirthDate(...)) } Dependency > Coupling > Control coupling

Slide 61

Slide 61 text

Remove conditional branches: Fixed code example fun updateUserView(dataType: DataType) = when(dataType) { is DataType.UserName -> { val userName = getUserName(dataType.userId) userNameView.text = userName } is DataType.Birthday -> { val birthDate = ... birthDayView.text = format(birthDate, ...) } is DataType.ProfileImage -> { ... } } Dependency > Coupling > Control coupling

Slide 62

Slide 62 text

Remove conditional branches: Fixed code example fun updateUserNameView(...) { val userName = getUserName(...) userNameView.text = userName } fun updateBirthdayView(...) { val birthDate = ... birthDayView.text = format(birthDate, ...) } fun updateProfileImage(...) { ... Dependency > Coupling > Control coupling

Slide 63

Slide 63 text

Control coupling to be mitigated - Conditional branches with unnecessarily large scope - How to fix: Split by object, not condition - Conditional branches with little behavior relevance - How to fix 1: Remove conditional branches - How to fix 2: Replace with other structures Dependency > Coupling > Control coupling

Slide 64

Slide 64 text

Replace with other structures Create classes/values to encapsulate the behavior Example: Strategy pattern - Replace branches with polymorphism or parameters - Implement by enum, abstract class, callback object, etc. - Other methods preferred because of the structure complexity Dependency > Coupling > Control coupling

Slide 65

Slide 65 text

Replace with other structures: Fixed code example enum class Binder(val viewId: ViewId) { fun updateView(holder: ViewHolder) = setContent(holder.getView(viewId)) abstract fun setContent(view: View) Dependency > Coupling > Control coupling

Slide 66

Slide 66 text

Replace with other structures: Fixed code 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) = setContent(holder.getView(viewId)) abstract fun setContent(view: View) Dependency > Coupling > Control coupling

Slide 67

Slide 67 text

Replace with other structures: Fixed code example Consider the scope of encapsulation - Do not add feature-specific logic into a widely-used class - Split logic from model classes if required Dependency > Coupling > Control coupling

Slide 68

Slide 68 text

Control coupling: Summary Avoid branches with unnecessarily large scope or little relevance Options to mitigate control coupling - When target objects are common: Split by object, not condition - When condition is statically determined: Split function - Otherwise: Replace with other structures (e.g., strategy pattern) Dependency > Coupling > Control coupling

Slide 69

Slide 69 text

Topics Content coupling Common coupling Control coupling Stamp coupling Data coupling Message coupling strong weak attention required acceptable External coupling Dependency > Coupling > Stamp coupling

Slide 70

Slide 70 text

Stamp coupling Passes/receives instances of data structures May be acceptable even if there are unused properties: - To use polymorphism, strategy pattern, etc - To clarify the meaning or simplify arguments or return values Dependency > Coupling > Stamp coupling

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

Topics Content coupling Common coupling Control coupling Stamp coupling Data coupling Message coupling strong weak attention required acceptable External coupling Dependency > Coupling > Data coupling

Slide 74

Slide 74 text

Data coupling Passes/receives only basic type values8 fun updateUserView(fullName: String, profileImageUrl: String) { userNameView.text = fullName profileImage.setImageUrl(profileImageUrl) } updateUserView(userData.fullName, userData.profileImageUrl) 8 Strictly speaking, String can be considered as a list/array of characters Dependency > Coupling > Data coupling

Slide 75

Slide 75 text

Data coupling: Caution Stamp coupling is sometimes preferred over data coupling - To clarify meaning of parameters - To create parameters or return value type safe fun updateUserView(fullName: String, profileImageUrl: String) = ... updateUserView(imageUrl, fullName) // !!! updateUserView(itemName, itemImageUrl) // !!! updateUserView(userA.fullName, userB.imageUrl) // !!! Dependency > Coupling > Data coupling

Slide 76

Slide 76 text

Topics Content coupling Common coupling Control coupling Stamp coupling Data coupling Message coupling strong weak attention required acceptable External coupling Dependency > Coupling > Message coupling

Slide 77

Slide 77 text

Message coupling Calls a function without any parameters or return value - e.g., calling an idempotent function such as Closable.close - Note the possibility of content coupling at another place fun notifyUserDataUpdated() { userNameView.text = ... profileImage.setImageUrl(...) } notifyUserDataUpdated() Dependency > Coupling > Message coupling

Slide 78

Slide 78 text

Coupling: Summary Use weaker coupling - Stamp coupling or data coupling preferred Coupling to be aware of: - Content coupling: Depends on internal working - Common/external coupling: Shares mutable global data - Control coupling: Determines "what to do" using arguments Dependency > Coupling > Summary