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.

Ragunath Jawahar

July 22, 2021
Tweet

More Decks by Ragunath Jawahar

Other Decks in Programming

Transcript

  1. Response? • Successfully creates a user account and returns a

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

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

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

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

    Response 🎉 • null • retrofit2.HttpException • java.net.SocketTimeoutException • i.ll.MakeYourLifeMoreInterestingException 🤷
  6. SignupResponse • Successfully creates a user account • Email already

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

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

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

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

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

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

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

    registered • Validation errors • Connection errors • 5xx server errors • Unknown errors
  14. 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.
  15. 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.
  16. 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.
  17. 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() }
  18. 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() }
  19. 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() }
  20. 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() }
  21. 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() }
  22. 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() }
  23. 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() }
  24. 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() }
  25. 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() }
  26. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "[email protected]") 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()
  27. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "[email protected]") 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()
  28. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "[email protected]") 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()
  29. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "[email protected]") 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()
  30. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "[email protected]") 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()
  31. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "[email protected]") 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()
  32. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "[email protected]") 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()
  33. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "[email protected]") 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()
  34. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "[email protected]") 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()
  35. Usage - Sealed Class Exhaustiveness val response = accountsApi.signup("John Doe",

    "[email protected]") 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()
  36. What have we achieved? • Certainty • Convey business ideas

    in code • Improved readability • Represent errors as data
  37. Total Functions Total functions are functions that give you a

    valid return value for every combination of valid arguments.
  38. 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.
  39. 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.
  40. 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.
  41. 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.
  42. 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.
  43. 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.
  44. 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.
  45. 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.
  46. // Partial function fun signup(email: String, password: String): Response? //

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

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

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

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

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

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

    Total function fun signup(email: String, password: String): SignupResponse Partial vs. Total Functions
  53. Algebraic Data Types An algebraic data type is a kind

    of composite type, i.e., a type formed by combining other types.
  54. 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() }
  55. 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() }
  56. 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() }
  57. 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() }
  58. 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.)
  59. 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)
  60. fun divide(dividend: Int, divisor: Int): Quotient { return if (divisor

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Result(val value: Double) : Quotient() } Quotient.kt
  75. 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
  76. 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
  77. 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
  78. 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
  79. 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
  80. 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
  81. 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
  82. 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
  83. 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
  84. 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
  85. 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
  86. val divisor = Integer.from(4) if (divisor is NonZero) { val

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

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

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

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

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

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

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

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

    quotient = divide(9, divisor) // Do fancy things! } else { // Fail fast… } Usage
  95. // 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
  96. // 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
  97. // 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
  98. // 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
  99. // 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
  100. // 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
  101. // 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
  102. // 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
  103. // 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
  104. // 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
  105. // 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
  106. // 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
  107. // 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
  108. 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
  109. 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
  110. 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
  111. 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
  112. 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
  113. 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
  114. 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
  115. 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
  116. 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
  117. 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
  118. 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
  119. fun countFiles(directory: Directory.Valid, extension: String): Int { return directory .location

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

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

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

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

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

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

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

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

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

    .listFiles() ?. filter { it.name.endsWith(".$extension") } ?. count() ?: 0 } FileStats.kt
  129. 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
  130. 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
  131. 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
  132. 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
  133. 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
  134. 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
  135. 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
  136. 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
  137. 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
  138. 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
  139. 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
  140. val directory = Directory.from("~/Documents/Finance") if (directory is Directory.Valid) { val

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

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

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

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

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

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

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

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

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

    from making unnecessary defensive checks
  150. 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
  151. 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
  152. 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
  153. 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
  154. 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
  155. 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
  156. What have we learnt? • Enforce control fl ow for

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

    within your system boundary, unless you have a strong reason not to
  158. 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
  159. Summary • Robustness Principle • Total Functions • Algebraic Data

    Types • Robustness Principle (Tweaked) • Preventing duplication of defensive checks • Shaping program’s control fl ow • Failing fast