Slide 1

Slide 1 text

Code←readability←session←7 Dependency ‐‐

Slide 2

Slide 2 text

Brushup: Coupling 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" by arguments

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

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

Slide 5

Slide 5 text

Dependency direction Keep dependencies acyclic as far as possible Class A Class C Class B cyclic Class A Class C Class B acyclic Dependency > Direction

Slide 6

Slide 6 text

Preferred dependency direction - caller → callee - concrete → abstract - complex → simple - mutable → immutable - unstable → stable - algorithm → data model Dependency > Direction

Slide 7

Slide 7 text

Preferred dependency direction - caller → callee - concrete → abstract - complex → simple Dependency > Direction

Slide 8

Slide 8 text

Preferred dependency direction - caller → callee - concrete → abstract - complex → simple Dependency > Direction > Caller to callee

Slide 9

Slide 9 text

Caller and callee: Bad example 1/2 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

Slide 10

Slide 10 text

Caller and callee: Bad example 2/2 MediaViewPresenter fun getVideoUri VideoPlayerView fun play calls play calls getVideoUri Dependency > Direction > Caller to callee

Slide 11

Slide 11 text

Caller and callee: Bad points Difficult to understand the behavior The call sequence of playVideo is complex Large impact on modification Changes in MediaViewPresenter affect VideoPlayerView Hard to write tests Mock object of VideoPlayerView will be complicated Dependency > Direction > Caller to callee

Slide 12

Slide 12 text

Caller and callee: How to fix Remove unnecessary callback - Pass values as arguments - Extract small classes Dependency > Direction > Caller to callee

Slide 13

Slide 13 text

Caller and callee: How to fix Remove unnecessary callback - Pass values as arguments - Extract small classes Dependency > Direction > Caller to callee

Slide 14

Slide 14 text

Pass values as arguments 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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

Caller and callee: How to fix Remove unnecessary callback - Pass values as arguments - Extract small classes Dependency > Direction > Caller to callee

Slide 17

Slide 17 text

Extract small classes Cannot pass value directly for asynchronous evaluation Extract depended logic as a class Dependency > Direction > Caller to callee

Slide 18

Slide 18 text

Extract small classes: Step 1 Create a named class for the callback MediaViewPresenter VideoPlayerView VideoUriProvider fun getVideoUri Dependency > Direction > Caller to callee

Slide 19

Slide 19 text

Extract small classes: Step 2 Move the named class out of the outer class MediaViewPresenter VideoPlayerView VideoUriProvider fun getVideoUri Dependency > Direction > Caller to callee

Slide 20

Slide 20 text

Caller and callee: How to fix Remove unnecessary callback - Pass values as arguments - Extract small classes Dependency > Direction > Caller to callee

Slide 21

Slide 21 text

Caller and callee: Exception Dependency on a caller may be acceptable for some cases - Asynchronous call: thread, event listener - Higher order function as a utility: List.forEach Dependency > Direction > Caller to callee

Slide 22

Slide 22 text

Mitigating asynchronous calls Limit purpose/lifecycle/scope of a callback - promise pattern, future object Use language features or libraries for asynchronous calls - coroutine, actor, async/await Dependency > Direction > Caller to callee

Slide 23

Slide 23 text

Preferred dependency direction - caller → callee - concrete → abstract - complex → simple Dependency > Direction > Concrete to abstract

Slide 24

Slide 24 text

Concrete and abstract Do not depend on inheritances = Do not check type of this or self Dependency > Direction > Concrete to abstract

Slide 25

Slide 25 text

Concrete and abstract: Bad example open class IntList { fun addElement(value: Int) { if (this is ArrayIntList) { ... } else { ... ... class ArrayIntList: IntList() { ... Dependency > Direction > Concrete to abstract

Slide 26

Slide 26 text

Concrete and abstract: Bad points Ambiguous scope of the responsibility Not robust against modification - For inheritance specification change - To add a new inheritance Dependency > Direction > Concrete to abstract

Slide 27

Slide 27 text

Concrete and abstract: How to fix Remove downcast with overriding open class IntList { open fun addElement(value: Int) { ... class ArrayIntList: IntList() { override fun addElement(value: Int) { ... Dependency > Direction > Concrete to abstract

Slide 28

Slide 28 text

Preferred dependency direction - caller → callee - concrete → abstract - complex → simple Dependency > Direction > Complex to simple

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

Complex and simple: Bad points Hard to manage scope/lifecycle/references of complex code - Simple: Scope/lifecycle can be _small to large - Complex: Scope/lifecycle must be managed strictly Dependency on complex code may cause: - Resource leaks - implicit preconditions for simple code Dependency > Direction > Complex to simple

Slide 32

Slide 32 text

Complex and simple: How to fix 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

Slide 33

Slide 33 text

Complex and simple: Exceptions It is sometimes necessary to depend on complex types - Mediator pattern - Adapter/facade - Data binder Limit objective/scope as far as possible Dependency > Direction > Complex to simple

Slide 34

Slide 34 text

Preferred dependency direction - caller → callee - concrete → abstract - complex → simple Dependency > Direction > Summary

Slide 35

Slide 35 text

Dependency direction: Summary Maintain acyclic dependency - caller → callee - complex, concrete, mutable → simple, abstract, immutable Exceptions: asynchronous calls, mediator patterns, etc. Limit the objective/scope, even for the exceptions Dependency > Direction > Summary

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Cascaded dependency Do not depend on unrelated code only for getting a reference MessageTextPresenter TimestampPresenter has instance (only for MessageDataProvider) MessageDataProvider has instance depends indirectly Dependency > Redundancy > Cascaded dependency

Slide 40

Slide 40 text

Cascaded dependency: Bad 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

Slide 41

Slide 41 text

Cascaded dependency: Bad points Unnecessary dependency MessageTextPresenter is unrelated with TimestampPresenter Indirect dependency MessageTextPresenter does not have MessageDataProvider directly Dependency > Redundancy > Cascaded dependency

Slide 42

Slide 42 text

Cascaded dependency: How to fix Flatten nested dependencies by means of direct dependency MessageTextPresenter TimestampPresenter MessageDataProvider has instance Dependency > Redundancy > Cascaded dependency

Slide 43

Slide 43 text

Cascaded dependency: Fixed code example 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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

Redundant dependency set Avoid duplication of "depending class set" MessageTextPresenter TimestampPresenter LocalMessageDataProvider RemoteMessageDataProvider Dependency > Redundancy > Dependency set

Slide 46

Slide 46 text

Redundant dependency set: Bad points Fragile for modification - When a new depending/depended class is added - Because of code duplication Dependency > Redundancy > Dependency set

Slide 47

Slide 47 text

Redundant dependency set: How to fix Create an intermediate layer to consolidate the dependency set MessageTextPresenter TimestampPresenter LocalMessageDataProvider RemoteMessageDataProvider MessageDataProvider Dependency > Redundancy > Dependency set

Slide 48

Slide 48 text

Redundant dependency set: Note Do not create a layer if you do not need it now Keep "YAGNI" and "KISS" in mind Do not provide access to a hidden layer Never make a public property serverMessageDataProvider in MessageDataProvider Dependency > Redundancy > Dependency set

Slide 49

Slide 49 text

Redundant dependency set: Summary - Do not depend on unrelated code only for getting a reference - Create an intermediate layer to consolidate the dependency set Dependency > Redundancy > Summary

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

Implicit dependency 1/3 Dependency that is not discoverable only by class definitions interface Interface class Implementation : Interface class Caller(val interface: Interface) Caller may be affected by modification in Implementation Caller(Implementation()) Dependency > Explicitness

Slide 52

Slide 52 text

Implicit dependency 2/3 Not discoverable by class diagram Caller Interface Implementation has instance inherits Dependency > Explicitness

Slide 53

Slide 53 text

Implicit dependency 2/3 Use explicit dependencies instead of implicit dependencies To clarify affected scope of modification Different from "weak coupling" - Trying to use weak coupling may cause implicit dependency Dependency > Explicitness

Slide 54

Slide 54 text

Implicit dependency: Example - Unnecessary abstraction - Implicit domain of variable Dependency > Explicitness

Slide 55

Slide 55 text

Implicit dependency: Example - Unnecessary abstraction - Implicit domain of variable Dependency > Explicitness > Unnecessary abstraction

Slide 56

Slide 56 text

Unnecessary abstraction Caller may depend on a child class implicitly ProfilePresenter MessagePresenter StringProvider ProfileDataRepository MessageDataRepository Dependency > Explicitness > Unnecessary abstraction

Slide 57

Slide 57 text

Unnecessary abstraction: Bad example interface StringProvider { fun queryString(id: Int): String } class ProfileDataRepository : StringProvider { /** Returns user name of the given [userId] */ override fun queryString(userId: Int): String = ... } class ProfilePresenter(val nameProvider: StringProvider, ...) { fun updateProfileView(userId: Int) { userNameView.text = nameProvider.queryString(userId) ... Dependency > Explicitness > Unnecessary abstraction

Slide 58

Slide 58 text

Unnecessary abstraction: Bad points Time-consuming to track the call sequence Requires checking the constructor caller A wrong instance may be passed Compiles even if MessageDataRepository is passed Dependency > Explicitness > Unnecessary abstraction

Slide 59

Slide 59 text

Unnecessary abstraction: How to fix Remove unnecessary subtyping Use child class directly with removing the parent/interface ProfilePresenter MessagePresenter ProfileDataRepository MessageDataRepository Dependency > Explicitness > Unnecessary abstraction

Slide 60

Slide 60 text

Unnecessary abstraction: Fixed code example class ProfileDataRepository { fun queryUserName(userId: Int): String = ... } class ProfilePresenter(val repository: ProfileDataRepository, ...) { fun updateProfileView(userId: Int) { userNameView.text = repository.queryUserName(userId) ... Dependency > Explicitness > Unnecessary abstraction

Slide 61

Slide 61 text

Unnecessary abstraction: Note 1/2 Clarify objective of subtyping - Polymorphism - Sum type: sealed class - Dependency inversion, implement separation: module/library Dependency > Explicitness > Unnecessary abstraction

Slide 62

Slide 62 text

Unnecessary abstraction: Note 2/2 Avoid using subtyping only to make code common - Consider using aggregation or composition instead Consider using another method to achieve polymorphism - Parametric: generics, etc. - Adhoc: overloading, type-class, etc. Dependency > Explicitness > Unnecessary abstraction

Slide 63

Slide 63 text

Implicit dependency: Example - Unnecessary abstraction - Implicit domain of variable Dependency > Explicitness > Implicit domain of variable

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

Implicit domain of variable: Bad points An implicit constraint between the caller and callee Need to know which value is accepted "blue", "RED", "FF0000" are invalid Fragile for modification Need to update the caller when a color is added/removed Dependency > Explicitness > Implicit domain of variable

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

Dependency explicitness: Summary Use explicit dependency - Make dependency visible on class definition/diagram - Do not be confused with weak coupling To make dependency explicit - Remove unnecessary abstraction layer - Create a model type representing the domain Dependency > Explicitness > Summary

Slide 68

Slide 68 text

Summary ɹ Coupling: Mitigate content/common/external/control coupling Direction: Make dependency acyclic or encapsulate the cycle Redundancy: Avoid duplication of dependency Explicitness: Make dependency visible on the definition/diagram Dependency > Summary