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

Building Robust Software (Episode 1)

Building Robust Software (Episode 1)

Presented at https://www.meetkt.org on 22 July 2021

Most of us can build software that works. But, building robust software that can withstand the test of time requires a shift in both mindset and skillset. Often, we have trouble finding out where to start.

This talk aims to shed some light on making that shift in both mindset and skillset. We'll begin by drawing some inspiration from the philosophy behind TCP and venture into functional programming principles to adopt ideas that can help us build expressive and robust programs.

We'll also look into an example for each idea and see how you can adopt it and implement it at work the very next day. We will leverage Kotlin's multi-paradigm abilities and its type system to bring most of these ideas to fruition.

A8ee6ea52b89dfa1388b592a260c60a6?s=128

Ragunath Jawahar

July 22, 2021
Tweet

More Decks by Ragunath Jawahar

Other Decks in Programming

Transcript

  1. Ragunath Jawahar • @ragunathjawahar Building Robust Software Episode 1 Small

    ideas, big impact
  2. Write code that communicates well.

  3. Example 1 API Call / External Systems

  4. interface AccountsApi { fun signup(email: String, password: String): Response? }

    AccountsApi.kt
  5. interface AccountsApi { fun signup(email: String, password: String): Response? }

    AccountsApi.kt
  6. interface AccountsApi { fun signup(email: String, password: String): Response? }

    AccountsApi.kt
  7. interface AccountsApi { fun signup(email: String, password: String): Response? }

    AccountsApi.kt
  8. interface AccountsApi { fun signup(email: String, password: String): Response? }

    AccountsApi.kt
  9. interface AccountsApi { fun signup(email: String, password: String): Response? }

    AccountsApi.kt
  10. interface AccountsApi { fun signup(email: String, password: String): Response? }

    AccountsApi.kt
  11. Response? • Successfully creates a user account and returns a

    Response 🎉 • null • retrofit2.HttpException • java.net.SocketTimeoutException • i.ll.MakeYourLifeMoreInterestingException 🤷
  12. Response? • Successfully creates a user account and returns a

    Response 🎉 • null • retrofit2.HttpException • java.net.SocketTimeoutException • i.ll.MakeYourLifeMoreInterestingException 🤷
  13. Response? • Successfully creates a user account and returns a

    Response 🎉 • null • retrofit2.HttpException • java.net.SocketTimeoutException • i.ll.MakeYourLifeMoreInterestingException 🤷
  14. Response? • Successfully creates a user account and returns a

    Response 🎉 • null • retrofit2.HttpException • java.net.SocketTimeoutException • i.ll.MakeYourLifeMoreInterestingException 🤷
  15. Response? • Successfully creates a user account and returns a

    Response 🎉 • null • retrofit2.HttpException • java.net.SocketTimeoutException • i.ll.MakeYourLifeMoreInterestingException 🤷
  16. 😊 😕 😨 Caller’s emotions

  17. 😊 😕 😨 Caller’s emotions

  18. 😊 😕 😨 Caller’s emotions

  19. 😊 😕 😨 Caller’s emotions

  20. Be conservative in what you send, be liberal in what

    you accept Robustness Principle
  21. Be conservative in what you send, be liberal in what

    you accept Robustness Principle
  22. Robustness Principle Be conservative in what you send, be liberal

    in what you accept
  23. interface AccountsApi { fun signup(email: String, password: String): Response? }

    AccountsApi.kt
  24. interface AccountsApi { fun signup(email: String, password: String): Response? }

    AccountsApi.kt
  25. interface AccountsApi { fun signup(email: String, password: String): Response? }

    AccountsApi.kt
  26. interface AccountsApi { fun signup(email: String, password: String): Response? }

    AccountsApi.kt
  27. interface AccountsApi { fun signup(email: String, password: String): SignupResponse }

    AccountsApi.kt
  28. SignupResponse • Successfully creates a user account • Email already

    registered • Validation errors • Connection errors • 5xx server errors • Unknown errors
  29. SignupResponse • Successfully creates a user account • Email already

    registered • Validation errors • Connection errors • 5xx server errors • Unknown errors
  30. SignupResponse • Successfully creates a user account • Email already

    registered • Validation errors • Connection errors • 5xx server errors • Unknown errors
  31. SignupResponse • Successfully creates a user account • Email already

    registered • Validation errors • Connection errors • 5xx server errors • Unknown errors
  32. SignupResponse • Successfully creates a user account • Email already

    registered • Validation errors • Connection errors • 5xx server errors • Unknown errors
  33. SignupResponse • Successfully creates a user account • Email already

    registered • Validation errors • Connection errors • 5xx server errors • Unknown errors
  34. SignupResponse • Successfully creates a user account • Email already

    registered • Validation errors • Connection errors • 5xx server errors • Unknown errors
  35. SignupResponse • Successfully creates a user account • Email already

    registered • Validation errors • Connection errors • 5xx server errors • Unknown errors
  36. Sealed Classes Sealed classes are used for representing restricted class

    hierarchies, when a value can have one of the types from a limited set, but cannot have any other type.
  37. Sealed Classes Sealed classes are used for representing restricted class

    hierarchies, when a value can have one of the types from a limited set, but cannot have any other type.
  38. Sealed Classes Sealed classes are used for representing restricted class

    hierarchies, when a value can have one of the types from a limited set, but cannot have any other type.
  39. SignupResponse.kt sealed class SignupResponse { data class AccountCreated(val userId: String)

    : SignupResponse() data class EmailAlreadyRegistered(val registrationEmail: String) : SignupResponse() data class InputValidationFailed(val errors: List<ValidationError>) : SignupResponse() object ConnectionError : SignupResponse() data class ServerError(val statusCode: Int) : SignupResponse() data class UnknownError(val statusCode: Int, val responseBody: String?) : SignupResponse() }
  40. SignupResponse.kt sealed class SignupResponse { data class AccountCreated(val userId: String)

    : SignupResponse() data class EmailAlreadyRegistered(val registrationEmail: String) : SignupResponse() data class InputValidationFailed(val errors: List<ValidationError>) : SignupResponse() object ConnectionError : SignupResponse() data class ServerError(val statusCode: Int) : SignupResponse() data class UnknownError(val statusCode: Int, val responseBody: String?) : SignupResponse() }
  41. SignupResponse.kt sealed class SignupResponse { data class AccountCreated(val userId: String)

    : SignupResponse() data class EmailAlreadyRegistered(val registrationEmail: String) : SignupResponse() data class InputValidationFailed(val errors: List<ValidationError>) : SignupResponse() object ConnectionError : SignupResponse() data class ServerError(val statusCode: Int) : SignupResponse() data class UnknownError(val statusCode: Int, val responseBody: String?) : SignupResponse() }
  42. SignupResponse.kt sealed class SignupResponse { data class AccountCreated(val userId: String)

    : SignupResponse() data class EmailAlreadyRegistered(val registrationEmail: String) : SignupResponse() data class InputValidationFailed(val errors: List<ValidationError>) : SignupResponse() object ConnectionError : SignupResponse() data class ServerError(val statusCode: Int) : SignupResponse() data class UnknownError(val statusCode: Int, val responseBody: String?) : SignupResponse() }
  43. SignupResponse.kt sealed class SignupResponse { data class AccountCreated(val userId: String)

    : SignupResponse() data class EmailAlreadyRegistered(val registrationEmail: String) : SignupResponse() data class InputValidationFailed(val errors: List<ValidationError>) : SignupResponse() object ConnectionError : SignupResponse() data class ServerError(val statusCode: Int) : SignupResponse() data class UnknownError(val statusCode: Int, val responseBody: String?) : SignupResponse() }
  44. SignupResponse.kt sealed class SignupResponse { data class AccountCreated(val userId: String)

    : SignupResponse() data class EmailAlreadyRegistered(val registrationEmail: String) : SignupResponse() data class InputValidationFailed(val errors: List<ValidationError>) : SignupResponse() object ConnectionError : SignupResponse() data class ServerError(val statusCode: Int) : SignupResponse() data class UnknownError(val statusCode: Int, val responseBody: String?) : SignupResponse() }
  45. SignupResponse.kt sealed class SignupResponse { data class AccountCreated(val userId: String)

    : SignupResponse() data class EmailAlreadyRegistered(val registrationEmail: String) : SignupResponse() data class InputValidationFailed(val errors: List<ValidationError>) : SignupResponse() object ConnectionError : SignupResponse() data class ServerError(val statusCode: Int) : SignupResponse() data class UnknownError(val statusCode: Int, val responseBody: String?) : SignupResponse() }
  46. SignupResponse.kt sealed class SignupResponse { data class AccountCreated(val userId: String)

    : SignupResponse() data class EmailAlreadyRegistered(val registrationEmail: String) : SignupResponse() data class InputValidationFailed(val errors: List<ValidationError>) : SignupResponse() object ConnectionError : SignupResponse() data class ServerError(val statusCode: Int) : SignupResponse() data class UnknownError(val statusCode: Int, val responseBody: String?) : SignupResponse() }
  47. SignupResponse.kt sealed class SignupResponse { data class AccountCreated(val userId: String)

    : SignupResponse() data class EmailAlreadyRegistered(val registrationEmail: String) : SignupResponse() data class InputValidationFailed(val errors: List<ValidationError>) : SignupResponse() object ConnectionError : SignupResponse() data class ServerError(val statusCode: Int) : SignupResponse() data class UnknownError(val statusCode: Int, val responseBody: String?) : SignupResponse() }
  48. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "john@example.org") when (response) { is AccountCreated -> saveUserId(response.userId) is EmailAlreadyRegistered -> showEmailAlreadyRegistered(response.registrationEmail) is InputValidationFailed -> showValidationErrors(response.errors) ConnectionError -> showCheckConnectionMessage() is ServerError, is UnknownError -> showTryAgainInSometimeMessage() }.exhaustive()
  49. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "john@example.org") when (response) { is AccountCreated -> saveUserId(response.userId) is EmailAlreadyRegistered -> showEmailAlreadyRegistered(response.registrationEmail) is InputValidationFailed -> showValidationErrors(response.errors) ConnectionError -> showCheckConnectionMessage() is ServerError, is UnknownError -> showTryAgainInSometimeMessage() }.exhaustive()
  50. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "john@example.org") when (response) { is AccountCreated -> saveUserId(response.userId) is EmailAlreadyRegistered -> showEmailAlreadyRegistered(response.registrationEmail) is InputValidationFailed -> showValidationErrors(response.errors) ConnectionError -> showCheckConnectionMessage() is ServerError, is UnknownError -> showTryAgainInSometimeMessage() }.exhaustive()
  51. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "john@example.org") when (response) { is AccountCreated -> saveUserId(response.userId) is EmailAlreadyRegistered -> showEmailAlreadyRegistered(response.registrationEmail) is InputValidationFailed -> showValidationErrors(response.errors) ConnectionError -> showCheckConnectionMessage() is ServerError, is UnknownError -> showTryAgainInSometimeMessage() }.exhaustive()
  52. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "john@example.org") when (response) { is AccountCreated -> saveUserId(response.userId) is EmailAlreadyRegistered -> showEmailAlreadyRegistered(response.registrationEmail) is InputValidationFailed -> showValidationErrors(response.errors) ConnectionError -> showCheckConnectionMessage() is ServerError, is UnknownError -> showTryAgainInSometimeMessage() }.exhaustive()
  53. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "john@example.org") when (response) { is AccountCreated -> saveUserId(response.userId) is EmailAlreadyRegistered -> showEmailAlreadyRegistered(response.registrationEmail) is InputValidationFailed -> showValidationErrors(response.errors) ConnectionError -> showCheckConnectionMessage() is ServerError, is UnknownError -> showTryAgainInSometimeMessage() }.exhaustive()
  54. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "john@example.org") when (response) { is AccountCreated -> saveUserId(response.userId) is EmailAlreadyRegistered -> showEmailAlreadyRegistered(response.registrationEmail) is InputValidationFailed -> showValidationErrors(response.errors) ConnectionError -> showCheckConnectionMessage() is ServerError, is UnknownError -> showTryAgainInSometimeMessage() }.exhaustive()
  55. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "john@example.org") when (response) { is AccountCreated -> saveUserId(response.userId) is EmailAlreadyRegistered -> showEmailAlreadyRegistered(response.registrationEmail) is InputValidationFailed -> showValidationErrors(response.errors) ConnectionError -> showCheckConnectionMessage() is ServerError, is UnknownError -> showTryAgainInSometimeMessage() }.exhaustive()
  56. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "john@example.org") when (response) { is AccountCreated -> saveUserId(response.userId) is EmailAlreadyRegistered -> showEmailAlreadyRegistered(response.registrationEmail) is InputValidationFailed -> showValidationErrors(response.errors) ConnectionError -> showCheckConnectionMessage() is ServerError, is UnknownError -> showTryAgainInSometimeMessage() }.exhaustive()
  57. Enforcing exhaustive when statements https://github.com/cashapp/exhaustive (compiler plugin) https://youtrack.jetbrains.com/issue/KT-12380 (Kotlin language

    proposal)
  58. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "john@example.org") when (response) { is AccountCreated -> saveUserId(response.userId) is EmailAlreadyRegistered -> showEmailAlreadyRegistered(response.registrationEmail) is InputValidationFailed -> showValidationErrors(response.errors) ConnectionError -> showCheckConnectionMessage() is ServerError, is UnknownError -> showTryAgainInSometimeMessage() }.exhaustive()
  59. 😊 😌 Caller’s emotions

  60. 😊 😌 Caller’s emotions

  61. 😊 😌 Caller’s emotions

  62. What have we achieved? • Certainty • Convey business ideas

    in code • Improved readability • Represent errors as data
  63. What have we learnt? • Total functions • Algebraic data

    types • Robustness principle
  64. What have we learnt? • Total functions • Algebraic data

    types • Robustness principle
  65. Total Functions Total functions are functions that give you a

    valid return value for every combination of valid arguments.
  66. Partial Functions A partial function is a function that is

    not defined for all possible input values; in some cases returns a value, it may never return at all, throw an exception or simply crash a system.
  67. Partial Functions A partial function is a function that is

    not defined for all possible input values; in some cases returns a value, it may never return at all, throw an exception or simply crash a system.
  68. Partial Functions A partial function is a function that is

    not defined for all possible input values; in some cases returns a value, it may never return at all, throw an exception or simply crash a system.
  69. Partial Functions A partial function is a function that is

    not defined for all possible input values; in some cases returns a value, it may never return at all, throw an exception or simply crash a system.
  70. Partial Functions A partial function is a function that is

    not defined for all possible input values; in some cases returns a value, it may never return at all, throw an exception or simply crash a system.
  71. Partial Functions A partial function is a function that is

    not defined for all possible input values; in some cases returns a value, it may never return at all, throw an exception or simply crash a system.
  72. Partial Functions A partial function is a function that is

    not defined for all possible input values; in some cases returns a value, it may never return at all, throw an exception or simply crash a system.
  73. Partial Functions A partial function is a function that is

    not defined for all possible input values; in some cases returns a value, it may never return at all, throw an exception or simply crash a system.
  74. // Partial function fun signup(email: String, password: String): Response? //

    Total function fun signup(email: String, password: String): SignupResponse Partial vs. Total Functions
  75. // Partial function fun signup(email: String, password: String): Response? //

    Total function fun signup(email: String, password: String): SignupResponse Partial vs. Total Functions
  76. // Partial function fun signup(email: String, password: String): Response? //

    Total function fun signup(email: String, password: String): SignupResponse Partial vs. Total Functions
  77. // Partial function fun signup(email: String, password: String): Response? //

    Total function fun signup(email: String, password: String): SignupResponse Partial vs. Total Functions
  78. // Partial function fun signup(email: String, password: String): Response? //

    Total function fun signup(email: String, password: String): SignupResponse Partial vs. Total Functions
  79. // Partial function fun signup(email: String, password: String): Response? //

    Total function fun signup(email: String, password: String): SignupResponse Partial vs. Total Functions
  80. // Partial function fun signup(email: String, password: String): Response? //

    Total function fun signup(email: String, password: String): SignupResponse Partial vs. Total Functions
  81. What have we learnt? • Total functions • Algebraic data

    types • Robustness principle
  82. What have we learnt? • Total functions • Algebraic data

    types • Robustness principle
  83. Algebraic Data Types An algebraic data type is a kind

    of composite type, i.e., a type formed by combining other types.
  84. SignupResponse.kt sealed class SignupResponse { data class AccountCreated(val userId: String)

    : SignupResponse() data class EmailAlreadyRegistered(val registrationEmail: String) : SignupResponse() data class InputValidationFailed(val errors: List<ValidationError>) : SignupResponse() object ConnectionError : SignupResponse() data class ServerError(val statusCode: Int) : SignupResponse() data class UnknownError(val statusCode: Int, val responseBody: String?) : SignupResponse() }
  85. SignupResponse.kt sealed class SignupResponse { data class AccountCreated(val userId: String)

    : SignupResponse() data class EmailAlreadyRegistered(val registrationEmail: String) : SignupResponse() data class InputValidationFailed(val errors: List<ValidationError>) : SignupResponse() object ConnectionError : SignupResponse() data class ServerError(val statusCode: Int) : SignupResponse() data class UnknownError(val statusCode: Int, val responseBody: String?) : SignupResponse() }
  86. SignupResponse.kt sealed class SignupResponse { data class AccountCreated(val userId: String)

    : SignupResponse() data class EmailAlreadyRegistered(val registrationEmail: String) : SignupResponse() data class InputValidationFailed(val errors: List<ValidationError>) : SignupResponse() object ConnectionError : SignupResponse() data class ServerError(val statusCode: Int) : SignupResponse() data class UnknownError(val statusCode: Int, val responseBody: String?) : SignupResponse() }
  87. SignupResponse.kt sealed class SignupResponse { data class AccountCreated(val userId: String)

    : SignupResponse() data class EmailAlreadyRegistered(val registrationEmail: String) : SignupResponse() data class InputValidationFailed(val errors: List<ValidationError>) : SignupResponse() object ConnectionError : SignupResponse() data class ServerError(val statusCode: Int) : SignupResponse() data class UnknownError(val statusCode: Int, val responseBody: String?) : SignupResponse() }
  88. What have we learnt? • Total functions • Algebraic data

    types • Robustness principle
  89. What have we learnt? • Total functions • Algebraic data

    types • Robustness principle
  90. Where to apply? • Use it heavily on integration points

    with external systems (code that lives outside your own application) • Anti-corruption layers (both internal and external) • Take failure frequency and domain context into account (databases and fi le systems may not be good candidates. Network layer is a good starting point.)
  91. Example 2 Division / Exceptions

  92. fun divide(dividend: Int, divisor: Int): Int { return dividend /

    divisor } Arithmetic.kt
  93. fun divide(dividend: Int, divisor: Int): Int { return dividend /

    divisor } Arithmetic.kt
  94. fun divide(dividend: Int, divisor: Int): Int { return dividend /

    divisor } Arithmetic.kt
  95. fun divide(dividend: Int, divisor: Int): Int { return dividend /

    divisor } Arithmetic.kt
  96. fun divide(dividend: Int, divisor: Int): Int { return dividend /

    divisor } Arithmetic.kt
  97. fun divide(dividend: Int, divisor: Int): Int { return dividend /

    divisor } Arithmetic.kt
  98. fun divide(dividend: Int, divisor: Int): Int { return dividend /

    divisor } Arithmetic.kt
  99. divisor = 0

  100. Exception in thread "main" java.lang.ArithmeticException: / by zero at org.example.ArithmeticKt.divide(Arithmetic.kt:3)

    at org.example.ArithmeticKt.main(Arithmetic.kt:7) at org.example.ArithmeticKt.main(Arithmetic.kt)
  101. Be conservative in what you send, be liberal in what

    you accept
  102. fun divide(dividend: Int, divisor: Int): Quotient { return if (divisor

    == 0) { Infinity } else { Result(dividend / divisor) } } Arithmetic.kt
  103. fun divide(dividend: Int, divisor: Int): Quotient { return if (divisor

    == 0) { Infinity } else { Result(dividend / divisor) } } Arithmetic.kt
  104. fun divide(dividend: Int, divisor: Int): Quotient { return if (divisor

    == 0) { Infinity } else { Result(dividend / divisor) } } Arithmetic.kt
  105. fun divide(dividend: Int, divisor: Int): Quotient { return if (divisor

    == 0) { Infinity } else { Result(dividend / divisor) } } Arithmetic.kt
  106. fun divide(dividend: Int, divisor: Int): Quotient { return if (divisor

    == 0) { Infinity } else { Result(dividend / divisor) } } Arithmetic.kt
  107. fun divide(dividend: Int, divisor: Int): Quotient { return if (divisor

    == 0) { Infinity } else { Result(dividend / divisor) } } Arithmetic.kt
  108. fun divide(dividend: Int, divisor: Int): Quotient { return if (divisor

    == 0) { Infinity } else { Result(dividend / divisor) } } Arithmetic.kt
  109. fun divide(dividend: Int, divisor: Int): Quotient { return if (divisor

    == 0) { Infinity } else { Result(dividend / divisor) } } Arithmetic.kt
  110. fun divide(dividend: Int, divisor: Int): Quotient { return if (divisor

    == 0) { Infinity } else { Result(dividend / divisor) } } Arithmetic.kt
  111. fun divide(dividend: Int, divisor: Int): Quotient { return if (divisor

    == 0) { Infinity } else { Result(dividend / divisor) } } Arithmetic.kt
  112. sealed class Quotient { object Infinity : Quotient() data class

    Result(val value: Double) : Quotient() } Quotient.kt
  113. sealed class Quotient { object Infinity : Quotient() data class

    Result(val value: Double) : Quotient() } Quotient.kt
  114. sealed class Quotient { object Infinity : Quotient() data class

    Result(val value: Double) : Quotient() } Quotient.kt
  115. sealed class Quotient { object Infinity : Quotient() data class

    Result(val value: Double) : Quotient() } Quotient.kt
  116. sealed class Quotient { object Infinity : Quotient() data class

    Result(val value: Double) : Quotient() } Quotient.kt
  117. 🤔

  118. Implications • Places burden on the consumer to handle both

    Infinity and Result types • The consumer may pass the Quotient type downstream. If that happens, downstream consumers should also deal with the added complexity
  119. Be conservative in what you send, be liberal in what

    you accept
  120. Be conservative in what you send, be liberal in what

    you accept conservative
  121. fun divide(dividend: Int, divisor: Integer.NonZero): Int { return dividend /

    divisor.number } Arithmetic.kt
  122. fun divide(dividend: Int, divisor: Integer.NonZero): Int { return dividend /

    divisor.number } Arithmetic.kt
  123. fun divide(dividend: Int, divisor: Integer.NonZero): Int { return dividend /

    divisor.number } Arithmetic.kt
  124. fun divide(dividend: Int, divisor: Integer.NonZero): Int { return dividend /

    divisor.number } Arithmetic.kt
  125. fun divide(dividend: Int, divisor: Integer.NonZero): Int { return dividend /

    divisor.number } Arithmetic.kt
  126. fun divide(dividend: Int, divisor: Integer.NonZero): Int { return dividend /

    divisor.number } Arithmetic.kt
  127. fun divide(dividend: Int, divisor: Integer.NonZero): Int { return dividend /

    divisor.number } Arithmetic.kt
  128. fun divide(dividend: Int, divisor: Integer.NonZero): Int { return dividend /

    divisor.number } Arithmetic.kt
  129. fun divide(dividend: Int, divisor: Integer.NonZero): Int { return dividend /

    divisor.number } Arithmetic.kt
  130. fun divide(dividend: Int, divisor: Integer.NonZero): Int { return dividend /

    divisor.number } Arithmetic.kt
  131. sealed class Integer { object Zero : Integer() data class

    NonZero(val number: Int) : Integer() companion object { fun from(number: Int): Integer = if (number == 0) { Zero } else { NonZero(number) } } } Integer.kt
  132. sealed class Integer { object Zero : Integer() data class

    NonZero(val number: Int) : Integer() companion object { fun from(number: Int): Integer = if (number == 0) { Zero } else { NonZero(number) } } } Integer.kt
  133. sealed class Integer { object Zero : Integer() data class

    NonZero(val number: Int) : Integer() companion object { fun from(number: Int): Integer = if (number == 0) { Zero } else { NonZero(number) } } } Integer.kt
  134. sealed class Integer { object Zero : Integer() data class

    NonZero(val number: Int) : Integer() companion object { fun from(number: Int): Integer = if (number == 0) { Zero } else { NonZero(number) } } } Integer.kt
  135. sealed class Integer { object Zero : Integer() data class

    NonZero(val number: Int) : Integer() companion object { fun from(number: Int): Integer = if (number == 0) { Zero } else { NonZero(number) } } } Integer.kt
  136. sealed class Integer { object Zero : Integer() data class

    NonZero(val number: Int) : Integer() companion object { fun from(number: Int): Integer = if (number == 0) { Zero } else { NonZero(number) } } } Integer.kt
  137. sealed class Integer { object Zero : Integer() data class

    NonZero(val number: Int) : Integer() companion object { fun from(number: Int): Integer = if (number == 0) { Zero } else { NonZero(number) } } } Integer.kt
  138. sealed class Integer { object Zero : Integer() data class

    NonZero(val number: Int) : Integer() companion object { fun from(number: Int): Integer = if (number == 0) { Zero } else { NonZero(number) } } } Integer.kt
  139. sealed class Integer { object Zero : Integer() data class

    NonZero(val number: Int) : Integer() companion object { fun from(number: Int): Integer = if (number == 0) { Zero } else { NonZero(number) } } } Integer.kt
  140. sealed class Integer { object Zero : Integer() data class

    NonZero(val number: Int) : Integer() companion object { fun from(number: Int): Integer = if (number == 0) { Zero } else { NonZero(number) } } } Integer.kt
  141. val divisor = Integer.from(4) if (divisor is NonZero) { val

    quotient = divide(9, divisor) // Do fancy things! } else { // Fail fast… } Usage
  142. val divisor = Integer.from(4) if (divisor is NonZero) { val

    quotient = divide(9, divisor) // Do fancy things! } else { // Fail fast… } Usage
  143. val divisor = Integer.from(4) if (divisor is NonZero) { val

    quotient = divide(9, divisor) // Do fancy things! } else { // Fail fast… } Usage
  144. val divisor = Integer.from(4) if (divisor is NonZero) { val

    quotient = divide(9, divisor) // Do fancy things! } else { // Fail fast… } Usage
  145. val divisor = Integer.from(4) if (divisor is NonZero) { val

    quotient = divide(9, divisor) // Do fancy things! } else { // Fail fast… } Usage
  146. val divisor = Integer.from(4) if (divisor is NonZero) { val

    quotient = divide(9, divisor) // Do fancy things! } else { // Fail fast… } Usage
  147. val divisor = Integer.from(4) if (divisor is NonZero) { val

    quotient = divide(9, divisor) // Do fancy things! } else { // Fail fast… } Usage
  148. val divisor = Integer.from(4) if (divisor is NonZero) { val

    quotient = divide(9, divisor) // Do fancy things! } else { // Fail fast… } Usage
  149. val divisor = Integer.from(4) if (divisor is NonZero) { val

    quotient = divide(9, divisor) // Do fancy things! } else { // Fail fast… } Usage
  150. // Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int): Quotient // Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: Integer.NonZero): Int Original vs. Tweaked
  151. // Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int): Quotient // Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: Integer.NonZero): Int Original vs. Tweaked
  152. // Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int): Quotient // Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: Integer.NonZero): Int Original vs. Tweaked
  153. // Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int): Quotient // Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: Integer.NonZero): Int Original vs. Tweaked
  154. // Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int): Quotient // Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: Integer.NonZero): Int Original vs. Tweaked
  155. // Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int): Quotient // Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: Integer.NonZero): Int Original vs. Tweaked
  156. // Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int): Quotient // Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: Integer.NonZero): Int Original vs. Tweaked
  157. // Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int): Quotient // Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: Integer.NonZero): Int Original vs. Tweaked
  158. // Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int): Quotient // Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: Integer.NonZero): Int Original vs. Tweaked
  159. // Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int): Quotient // Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: Integer.NonZero): Int Original vs. Tweaked
  160. // Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int): Quotient // Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: Integer.NonZero): Int Original vs. Tweaked
  161. // Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int): Quotient // Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: Integer.NonZero): Int Original vs. Tweaked
  162. // Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int): Quotient // Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: Integer.NonZero): Int Original vs. Tweaked
  163. What have we learnt? • Robustness principle (tweaked)

  164. Where to apply? • All layers and components that are

    within your system boundary
  165. Example 3 File / Defensive Programming

  166. File

  167. fun countFiles(directory: File, extension: String): Int { if (!directory.exists() ||

    !directory.isDirectory) { return 0 } return directory .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  168. fun countFiles(directory: File, extension: String): Int { if (!directory.exists() ||

    !directory.isDirectory) { return 0 } return directory .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  169. fun countFiles(directory: File, extension: String): Int { if (!directory.exists() ||

    !directory.isDirectory) { return 0 } return directory .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  170. fun countFiles(directory: File, extension: String): Int { if (!directory.exists() ||

    !directory.isDirectory) { return 0 } return directory .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  171. fun countFiles(directory: File, extension: String): Int { if (!directory.exists() ||

    !directory.isDirectory) { return 0 } return directory .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  172. fun countFiles(directory: File, extension: String): Int { if (!directory.exists() ||

    !directory.isDirectory) { return 0 } return directory .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  173. fun countFiles(directory: File, extension: String): Int { if (!directory.exists() ||

    !directory.isDirectory) { return 0 } return directory .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  174. fun countFiles(directory: File, extension: String): Int { if (!directory.exists() ||

    !directory.isDirectory) { return 0 } return directory .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  175. fun countFiles(directory: File, extension: String): Int { if (!directory.exists() ||

    !directory.isDirectory) { return 0 } return directory .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  176. fun countFiles(directory: File, extension: String): Int { if (!directory.exists() ||

    !directory.isDirectory) { return 0 } return directory .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  177. fun countFiles(directory: File, extension: String): Int { if (!directory.exists() ||

    !directory.isDirectory) { return 0 } return directory .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  178. Be conservative in what you send, be liberal in what

    you accept conservative
  179. fun countFiles(directory: Directory.Valid, extension: String): Int { return directory .location

    .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  180. fun countFiles(directory: Directory.Valid, extension: String): Int { return directory .location

    .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  181. fun countFiles(directory: Directory.Valid, extension: String): Int { return directory .location

    .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  182. fun countFiles(directory: Directory.Valid, extension: String): Int { return directory .location

    .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  183. fun countFiles(directory: Directory.Valid, extension: String): Int { return directory .location

    .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  184. fun countFiles(directory: Directory.Valid, extension: String): Int { return directory .location

    .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  185. fun countFiles(directory: Directory.Valid, extension: String): Int { return directory .location

    .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  186. fun countFiles(directory: Directory.Valid, extension: String): Int { return directory .location

    .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  187. fun countFiles(directory: Directory.Valid, extension: String): Int { return directory .location

    .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  188. fun countFiles(directory: Directory.Valid, extension: String): Int { return directory .location

    .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  189. sealed class Directory { data class Valid(val location: File) :

    Directory() data class Invalid(val rawPath: String) : Directory() companion object { fun from(rawPath: String): Directory { val possibleDirectory = File(rawPath) val isDirectory = possibleDirectory.exists() & & possibleDirectory.isDirectory return if (isDirectory) { Valid(File(rawPath)) } else { Invalid(rawPath) } } } } Directory.kt
  190. sealed class Directory { data class Valid(val location: File) :

    Directory() data class Invalid(val rawPath: String) : Directory() companion object { fun from(rawPath: String): Directory { val possibleDirectory = File(rawPath) val isDirectory = possibleDirectory.exists() & & possibleDirectory.isDirectory return if (isDirectory) { Valid(File(rawPath)) } else { Invalid(rawPath) } } } } Directory.kt
  191. sealed class Directory { data class Valid(val location: File) :

    Directory() data class Invalid(val rawPath: String) : Directory() companion object { fun from(rawPath: String): Directory { val possibleDirectory = File(rawPath) val isDirectory = possibleDirectory.exists() & & possibleDirectory.isDirectory return if (isDirectory) { Valid(File(rawPath)) } else { Invalid(rawPath) } } } } Directory.kt
  192. sealed class Directory { data class Valid(val location: File) :

    Directory() data class Invalid(val rawPath: String) : Directory() companion object { fun from(rawPath: String): Directory { val possibleDirectory = File(rawPath) val isDirectory = possibleDirectory.exists() & & possibleDirectory.isDirectory return if (isDirectory) { Valid(File(rawPath)) } else { Invalid(rawPath) } } } } Directory.kt
  193. sealed class Directory { data class Valid(val location: File) :

    Directory() data class Invalid(val rawPath: String) : Directory() companion object { fun from(rawPath: String): Directory { val possibleDirectory = File(rawPath) val isDirectory = possibleDirectory.exists() & & possibleDirectory.isDirectory return if (isDirectory) { Valid(File(rawPath)) } else { Invalid(rawPath) } } } } Directory.kt
  194. sealed class Directory { data class Valid(val location: File) :

    Directory() data class Invalid(val rawPath: String) : Directory() companion object { fun from(rawPath: String): Directory { val possibleDirectory = File(rawPath) val isDirectory = possibleDirectory.exists() & & possibleDirectory.isDirectory return if (isDirectory) { Valid(File(rawPath)) } else { Invalid(rawPath) } } } } Directory.kt
  195. sealed class Directory { data class Valid(val location: File) :

    Directory() data class Invalid(val rawPath: String) : Directory() companion object { fun from(rawPath: String): Directory { val possibleDirectory = File(rawPath) val isDirectory = possibleDirectory.exists() & & possibleDirectory.isDirectory return if (isDirectory) { Valid(File(rawPath)) } else { Invalid(rawPath) } } } } Directory.kt
  196. sealed class Directory { data class Valid(val location: File) :

    Directory() data class Invalid(val rawPath: String) : Directory() companion object { fun from(rawPath: String): Directory { val possibleDirectory = File(rawPath) val isDirectory = possibleDirectory.exists() & & possibleDirectory.isDirectory return if (isDirectory) { Valid(File(rawPath)) } else { Invalid(rawPath) } } } } Directory.kt
  197. sealed class Directory { data class Valid(val location: File) :

    Directory() data class Invalid(val rawPath: String) : Directory() companion object { fun from(rawPath: String): Directory { val possibleDirectory = File(rawPath) val isDirectory = possibleDirectory.exists() & & possibleDirectory.isDirectory return if (isDirectory) { Valid(File(rawPath)) } else { Invalid(rawPath) } } } } Directory.kt
  198. sealed class Directory { data class Valid(val location: File) :

    Directory() data class Invalid(val rawPath: String) : Directory() companion object { fun from(rawPath: String): Directory { val possibleDirectory = File(rawPath) val isDirectory = possibleDirectory.exists() & & possibleDirectory.isDirectory return if (isDirectory) { Valid(File(rawPath)) } else { Invalid(rawPath) } } } } Directory.kt
  199. sealed class Directory { data class Valid(val location: File) :

    Directory() data class Invalid(val rawPath: String) : Directory() companion object { fun from(rawPath: String): Directory { val possibleDirectory = File(rawPath) val isDirectory = possibleDirectory.exists() & & possibleDirectory.isDirectory return if (isDirectory) { Valid(File(rawPath)) } else { Invalid(rawPath) } } } } Directory.kt
  200. val directory = Directory.from("~/Documents/Finance") if (directory is Directory.Valid) { val

    spreadSheetsCount = countFiles(directory, "xlsx") // Do fancy things! } else { // Fail fast… } Usage
  201. val directory = Directory.from("~/Documents/Finance") if (directory is Directory.Valid) { val

    spreadSheetsCount = countFiles(directory, "xlsx") // Do fancy things! } else { // Fail fast… } Usage
  202. val directory = Directory.from("~/Documents/Finance") if (directory is Directory.Valid) { val

    spreadSheetsCount = countFiles(directory, "xlsx") // Do fancy things! } else { // Fail fast… } Usage
  203. val directory = Directory.from("~/Documents/Finance") if (directory is Directory.Valid) { val

    spreadSheetsCount = countFiles(directory, "xlsx") // Do fancy things! } else { // Fail fast… } Usage
  204. val directory = Directory.from("~/Documents/Finance") if (directory is Directory.Valid) { val

    spreadSheetsCount = countFiles(directory, "xlsx") // Do fancy things! } else { // Fail fast… } Usage
  205. val directory = Directory.from("~/Documents/Finance") if (directory is Directory.Valid) { val

    spreadSheetsCount = countFiles(directory, "xlsx") // Do fancy things! } else { // Fail fast… } Usage
  206. val directory = Directory.from("~/Documents/Finance") if (directory is Directory.Valid) { val

    spreadSheetsCount = countFiles(directory, "xlsx") // Do fancy things! } else { // Fail fast… } Usage
  207. val directory = Directory.from("~/Documents/Finance") if (directory is Directory.Valid) { val

    spreadSheetsCount = countFiles(directory, "xlsx") // Do fancy things! } else { // Fail fast… } Usage
  208. val directory = Directory.from("~/Documents/Finance") if (directory is Directory.Valid) { val

    spreadSheetsCount = countFiles(directory, "xlsx") // Do fancy things! } else { // Fail fast… } Usage
  209. What have we learnt? • Protecting users of a type

    from making unnecessary defensive checks
  210. Where to apply? • All layers and components that are

    within your system boundary
  211. Example 4 Username / Primitive Obsession

  212. / / Failing fast…

  213. View Presenter Model Model - View - Presenter

  214. View Presenter Model Model - View - Presenter

  215. View Presenter Model Model - View - Presenter

  216. View Presenter Model Model - View - Presenter

  217. View Presenter Model Model - View - Presenter

  218. View Presenter Model Model - View - Presenter

  219. View Presenter Model Model - View - Presenter

  220. View Presenter Model Model - View - Presenter

  221. View Presenter Model Model - View - Presenter

  222. View Presenter Model Model - View - Presenter

  223. View Presenter Model Model - View - Presenter

  224. Failure 1 username: String View Presenter Model username username failure

    failure
  225. Failure 1 username: String View Presenter Model username username failure

    failure
  226. Failure 1 username: String View Presenter Model username username failure

    failure
  227. Failure 1 username: String View Presenter Model username username failure

    failure
  228. Failure 1 username: String View Presenter Model username username failure

    failure
  229. Failure 1 username: String View Presenter Model username username failure

    failure
  230. Failure 1 username: String View Presenter Model username username failure

    failure
  231. Data Behavior

  232. Data Behavior

  233. Data Behavior

  234. Data Behavior

  235. Data Behavior

  236. sealed class Username { data class Valid(val text: String) :

    Username() data class Invalid(val reasons: Set<Reason>) : Username() companion object { fun from(possiblyUsername: String): Username { possiblyUsername.find { !it.isLetter() } ?: return Valid(possiblyUsername) return Invalid(setOf(INVALID_CHAR)) } } } Username.kt
  237. sealed class Username { data class Valid(val text: String) :

    Username() data class Invalid(val reasons: Set<Reason>) : Username() companion object { fun from(possiblyUsername: String): Username { possiblyUsername.find { !it.isLetter() } ?: return Valid(possiblyUsername) return Invalid(setOf(INVALID_CHAR)) } } } Username.kt
  238. sealed class Username { data class Valid(val text: String) :

    Username() data class Invalid(val reasons: Set<Reason>) : Username() companion object { fun from(possiblyUsername: String): Username { possiblyUsername.find { !it.isLetter() } ?: return Valid(possiblyUsername) return Invalid(setOf(INVALID_CHAR)) } } } Username.kt
  239. sealed class Username { data class Valid(val text: String) :

    Username() data class Invalid(val reasons: Set<Reason>) : Username() companion object { fun from(possiblyUsername: String): Username { possiblyUsername.find { !it.isLetter() } ?: return Valid(possiblyUsername) return Invalid(setOf(INVALID_CHAR)) } } } Username.kt
  240. sealed class Username { data class Valid(val text: String) :

    Username() data class Invalid(val reasons: Set<Reason>) : Username() companion object { fun from(possiblyUsername: String): Username { possiblyUsername.find { !it.isLetter() } ?: return Valid(possiblyUsername) return Invalid(setOf(INVALID_CHAR)) } } } Username.kt
  241. sealed class Username { data class Valid(val text: String) :

    Username() data class Invalid(val reasons: Set<Reason>) : Username() companion object { fun from(possiblyUsername: String): Username { possiblyUsername.find { !it.isLetter() } ?: return Valid(possiblyUsername) return Invalid(setOf(INVALID_CHAR)) } } } Username.kt
  242. Failure 2 username: Username.Invalid View Presenter Model username failure

  243. Failure 2 username: Username.Invalid View Presenter Model username failure

  244. Failure 2 username: Username.Invalid View Presenter Model username failure

  245. Failure 2 username: Username.Invalid View Presenter Model username failure

  246. View Presenter Model username username failure failure View Presenter Model

    username failure
  247. View Presenter Model username username failure failure View Presenter Model

    username failure
  248. View Presenter Model username username failure failure View Presenter Model

    username failure
  249. Username Algebraic Data Type View Presenter Model username Invalid Valid

  250. Username Algebraic Data Type View Presenter Model username Invalid Valid

  251. Username Algebraic Data Type View Presenter Model username Invalid Valid

  252. Username Algebraic Data Type View Presenter Model username Invalid Valid

  253. What have we learnt? • Enforce control fl ow for

    data between components • Failing fast by reducing data travel between components • Reduce coupling between components
  254. Where to apply? • All layers and components that are

    within your system boundary, unless you have a strong reason not to
  255. Impact on Overall System Design • Enhances certainty on micro

    and macroscopic levels • Errors are treated as fi rst-class citizens • Reduced range of motion for your data from its point of origin • Easy to move towards a functional core and an imperative shell style of architectural pattern • Focus moves towards data over actors • Promotes value boundaries over call boundaries • E ff ective cure against primitive obsession • Reduces defensive programming • Promotes value testing over behaviour testing
  256. Summary • Robustness Principle • Total Functions • Algebraic Data

    Types • Robustness Principle (Tweaked) • Preventing duplication of defensive checks • Shaping program’s control fl ow • Failing fast
  257. Questions? @ragunathjawahar • https: / / ragunath.xyz