Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Building Robust Apps (Swift Edition)

Building Robust Apps (Swift Edition)

Ragunath Jawahar

October 10, 2020
Tweet

More Decks by Ragunath Jawahar

Other Decks in Programming

Transcript

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

    Response 🎉 • nil • NSURLErrorCannotConnectToHost • NSURLErrorCannotFindHost • NSURLError* "
  2. Response? • Successfully creates a user account and returns a

    Response 🎉 • nil • NSURLErrorCannotConnectToHost • NSURLErrorCannotFindHost • NSURLError* "
  3. Response? • Successfully creates a user account and returns a

    Response 🎉 • nil • NSURLErrorCannotConnectToHost • NSURLErrorCannotFindHost • NSURLError* "
  4. Response? • Successfully creates a user account and returns a

    Response 🎉 • nil • NSURLErrorCannotConnectToHost • NSURLErrorCannotFindHost • NSURLError* "
  5. Response? • Successfully creates a user account and returns a

    Response 🎉 • nil • NSURLErrorCannotConnectToHost • NSURLErrorCannotFindHost • NSURLError* "
  6. Be conservative in what you send, be liberal in what

    you accept Robustness Principle / Postel’s Law
  7. Be conservative in what you send, be liberal in what

    you accept Robustness Principle / Postel’s Law
  8. Response • Successfully creates a user account • Email already

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

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

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

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

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

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

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

    registered • Validation errors • Connection errors • 5xx server errors • Unknown errors
  16. Enumerations An enumeration defines a common type for a group

    of related values and enables you to work with those values in a type-safe way within your code. Alternatively, enumeration cases can specify associated values of any type to be stored along with each different case value, much as unions or variants do in other languages.
  17. Enumerations An enumeration defines a common type for a group

    of related values and enables you to work with those values in a type-safe way within your code. Alternatively, enumeration cases can specify associated values of any type to be stored along with each different case value, much as unions or variants do in other languages.
  18. Enumerations An enumeration defines a common type for a group

    of related values and enables you to work with those values in a type-safe way within your code. Alternatively, enumeration cases can specify associated values of any type to be stored along with each different case value, much as unions or variants do in other languages.
  19. Enumerations An enumeration defines a common type for a group

    of related values and enables you to work with those values in a type-safe way within your code. Alternatively, enumeration cases can specify associated values of any type to be stored along with each different case value, much as unions or variants do in other languages.
  20. Usage - Enum Exhaustiveness val response = accountsApi.signup(with: "John Doe",

    and: "[email protected]") switch response { case .accountCreated(let userId): saveUserId(userId) case .emailAlreadyRegistered(let registrationEmail): showEmailAlreadyRegistered(registrationEmail) case .inputValidationFailed(let errors): showValidationErrors(errors) case .connectionError: showCheckConnectionMessage() case .serverError(_), .unknownError(_, _): showTryAgainInSometimeMessage() }
  21. Usage - Enum Exhaustiveness val response = accountsApi.signup(with: "John Doe",

    and: "[email protected]") switch response { case .accountCreated(let userId): saveUserId(userId) case .emailAlreadyRegistered(let registrationEmail): showEmailAlreadyRegistered(registrationEmail) case .inputValidationFailed(let errors): showValidationErrors(errors) case .connectionError: showCheckConnectionMessage() case .serverError(_), .unknownError(_, _): showTryAgainInSometimeMessage() }
  22. Usage - Enum Exhaustiveness val response = accountsApi.signup(with: "John Doe",

    and: "[email protected]") switch response { case .accountCreated(let userId): saveUserId(userId) case .emailAlreadyRegistered(let registrationEmail): showEmailAlreadyRegistered(registrationEmail) case .inputValidationFailed(let errors): showValidationErrors(errors) case .connectionError: showCheckConnectionMessage() case .serverError(_), .unknownError(_, _): showTryAgainInSometimeMessage() }
  23. Usage - Enum Exhaustiveness val response = accountsApi.signup(with: "John Doe",

    and: "[email protected]") switch response { case .accountCreated(let userId): saveUserId(userId) case .emailAlreadyRegistered(let registrationEmail): showEmailAlreadyRegistered(registrationEmail) case .inputValidationFailed(let errors): showValidationErrors(errors) case .connectionError: showCheckConnectionMessage() case .serverError(_), .unknownError(_, _): showTryAgainInSometimeMessage() }
  24. Usage - Enum Exhaustiveness val response = accountsApi.signup(with: "John Doe",

    and: "[email protected]") switch response { case .accountCreated(let userId): saveUserId(userId) case .emailAlreadyRegistered(let registrationEmail): showEmailAlreadyRegistered(registrationEmail) case .inputValidationFailed(let errors): showValidationErrors(errors) case .connectionError: showCheckConnectionMessage() case .serverError(_), .unknownError(_, _): showTryAgainInSometimeMessage() }
  25. Usage - Enum Exhaustiveness val response = accountsApi.signup(with: "John Doe",

    and: "[email protected]") switch response { case .accountCreated(let userId): saveUserId(userId) case .emailAlreadyRegistered(let registrationEmail): showEmailAlreadyRegistered(registrationEmail) case .inputValidationFailed(let errors): showValidationErrors(errors) case .connectionError: showCheckConnectionMessage() case .serverError(_), .unknownError(_, _): showTryAgainInSometimeMessage() }
  26. Usage - Enum Exhaustiveness val response = accountsApi.signup(with: "John Doe",

    and: "[email protected]") switch response { case .accountCreated(let userId): saveUserId(userId) case .emailAlreadyRegistered(let registrationEmail): showEmailAlreadyRegistered(registrationEmail) case .inputValidationFailed(let errors): showValidationErrors(errors) case .connectionError: showCheckConnectionMessage() case .serverError(_), .unknownError(_, _): showTryAgainInSometimeMessage() }
  27. Usage - Enum Exhaustiveness val response = accountsApi.signup(with: "John Doe",

    and: "[email protected]") switch response { case .accountCreated(let userId): saveUserId(userId) case .emailAlreadyRegistered(let registrationEmail): showEmailAlreadyRegistered(registrationEmail) case .inputValidationFailed(let errors): showValidationErrors(errors) case .connectionError: showCheckConnectionMessage() case .serverError(_), .unknownError(_, _): showTryAgainInSometimeMessage() }
  28. Usage - Enum Exhaustiveness val response = accountsApi.signup(with: "John Doe",

    and: "[email protected]") switch response { case .accountCreated(let userId): saveUserId(userId) case .emailAlreadyRegistered(let registrationEmail): showEmailAlreadyRegistered(registrationEmail) case .inputValidationFailed(let errors): showValidationErrors(errors) case .connectionError: showCheckConnectionMessage() case .serverError(_), .unknownError(_, _): showTryAgainInSometimeMessage() }
  29. What have we achieved? • Certainty • Convey business ideas

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

    valid return value for every combination of valid arguments.
  31. 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.
  32. 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.
  33. 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.
  34. 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.
  35. 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.
  36. 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.
  37. 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.
  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 function func signup(with email: String, and password: String)

    #-> Response? !// Total function func signup(with email: String, and password: String) #-> Response Partial vs. Total Functions
  40. !// Partial function func signup(with email: String, and password: String)

    #-> Response? !// Total function func signup(with email: String, and password: String) #-> Response Partial vs. Total Functions
  41. !// Partial function func signup(with email: String, and password: String)

    #-> Response? !// Total function func signup(with email: String, and password: String) #-> Response Partial vs. Total Functions
  42. !// Partial function func signup(with email: String, and password: String)

    #-> Response? !// Total function func signup(with email: String, and password: String) #-> Response Partial vs. Total Functions
  43. !// Partial function func signup(with email: String, and password: String)

    #-> Response? !// Total function func signup(with email: String, and password: String) #-> Response Partial vs. Total Functions
  44. !// Partial function func signup(with email: String, and password: String)

    #-> Response? !// Total function func signup(with email: String, and password: String) #-> Response Partial vs. Total Functions
  45. !// Partial function func signup(with email: String, and password: String)

    #-> Response? !// Total function func signup(with email: String, and password: String) #-> Response Partial vs. Total Functions
  46. Algebraic Data Types An algebraic data type is a kind

    of composite type, i.e., a type formed by combining other types.
  47. 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 into account (databases and file systems may not be good candidates. Network layer is a good starting point.)
  48. ERROR : divide by zero error: Execution was interrupted, reason:

    EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0). Fatal error: Division by zero: file Swift/x86_64-apple-ios- simulator.swiftinterface, line 32420
  49. func divide(dividend: Int, divisor: Int) #-> Quotient { return divisor

    #== 0 ? .infinity : .result(dividend / divisor) } Arithmetic.swift
  50. func divide(dividend: Int, divisor: Int) #-> Quotient { return divisor

    #== 0 ? .infinity : .result(dividend / divisor) } Arithmetic.swift
  51. func divide(dividend: Int, divisor: Int) #-> Quotient { return divisor

    #== 0 ? .infinity : .result(dividend / divisor) } Arithmetic.swift
  52. func divide(dividend: Int, divisor: Int) #-> Quotient { return divisor

    #== 0 ? .infinity : .result(dividend / divisor) } Arithmetic.swift
  53. func divide(dividend: Int, divisor: Int) #-> Quotient { return divisor

    #== 0 ? .infinity : .result(dividend / divisor) } Arithmetic.swift
  54. func divide(dividend: Int, divisor: Int) #-> Quotient { return divisor

    #== 0 ? .infinity : .result(dividend / divisor) } Arithmetic.swift
  55. func divide(dividend: Int, divisor: Int) #-> Quotient { return divisor

    #== 0 ? .infinity : .result(dividend / divisor) } Arithmetic.swift
  56. func divide(dividend: Int, divisor: Int) #-> Quotient { return divisor

    #== 0 ? .infinity : .result(dividend / divisor) } Arithmetic.swift
  57. func divide(dividend: Int, divisor: Int) #-> Quotient { return divisor

    #== 0 ? .infinity : .result(dividend / divisor) } Arithmetic.swift
  58. func divide(dividend: Int, divisor: Int) #-> Quotient { return divisor

    #== 0 ? .infinity : .result(dividend / divisor) } Arithmetic.swift
  59. func divide(dividend: Int, divisor: Int) #-> Quotient { return divisor

    #== 0 ? .infinity : .result(dividend / divisor) } Arithmetic.swift
  60. 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
  61. class NaturalNumber { class Zero: NaturalNumber { !/* empty !*/

    } class NonZero: NaturalNumber { let value: Int fileprivate init(_ value: Int) { self.value = value } } !//!!... } NaturalNumber.swift
  62. class NaturalNumber { class Zero: NaturalNumber { !/* empty !*/

    } class NonZero: NaturalNumber { let value: Int fileprivate init(_ value: Int) { self.value = value } } !//!!... } NaturalNumber.swift
  63. class NaturalNumber { class Zero: NaturalNumber { !/* empty !*/

    } class NonZero: NaturalNumber { let value: Int fileprivate init(_ value: Int) { self.value = value } } !//!!... } NaturalNumber.swift
  64. class NaturalNumber { class Zero: NaturalNumber { !/* empty !*/

    } class NonZero: NaturalNumber { let value: Int fileprivate init(_ value: Int) { self.value = value } } !//!!... } NaturalNumber.swift
  65. class NaturalNumber { class Zero: NaturalNumber { !/* empty !*/

    } class NonZero: NaturalNumber { let value: Int fileprivate init(_ value: Int) { self.value = value } } !//!!... } NaturalNumber.swift
  66. class NaturalNumber { class Zero: NaturalNumber { !/* empty !*/

    } class NonZero: NaturalNumber { let value: Int fileprivate init(_ value: Int) { self.value = value } } !//!!... } NaturalNumber.swift
  67. class NaturalNumber { class Zero: NaturalNumber { !/* empty !*/

    } class NonZero: NaturalNumber { let value: Int fileprivate init(_ value: Int) { self.value = value } } !//!!... } NaturalNumber.swift
  68. class NaturalNumber { class Zero: NaturalNumber { !/* empty !*/

    } class NonZero: NaturalNumber { let value: Int fileprivate init(_ value: Int) { self.value = value } } !//!!... } NaturalNumber.swift
  69. class NaturalNumber { class Zero: NaturalNumber { !/* empty !*/

    } class NonZero: NaturalNumber { let value: Int fileprivate init(_ value: Int) { self.value = value } } !//!!... } NaturalNumber.swift
  70. class NaturalNumber { class Zero: NaturalNumber { !/* empty !*/

    } class NonZero: NaturalNumber { let value: Int fileprivate init(_ value: Int) { self.value = value } } !//!!... } NaturalNumber.swift
  71. class NaturalNumber { !//!!... class func from(_ value: Int) #->

    NaturalNumber { return value #== 0 ? Zero() : NonZero(value) } } NaturalNumber.swift
  72. class NaturalNumber { !//!!... class func from(_ value: Int) #->

    NaturalNumber { return value #== 0 ? Zero() : NonZero(value) } } NaturalNumber.swift
  73. class NaturalNumber { !//!!... class func from(_ value: Int) #->

    NaturalNumber { return value #== 0 ? Zero() : NonZero(value) } } NaturalNumber.swift
  74. class NaturalNumber { !//!!... class func from(_ value: Int) #->

    NaturalNumber { return value #== 0 ? Zero() : NonZero(value) } } NaturalNumber.swift
  75. class NaturalNumber { !//!!... class func from(_ value: Int) #->

    NaturalNumber { return value #== 0 ? Zero() : NonZero(value) } } NaturalNumber.swift
  76. class NaturalNumber { !//!!... class func from(_ value: Int) #->

    NaturalNumber { return value #== 0 ? Zero() : NonZero(value) } } NaturalNumber.swift
  77. let divisor = NaturalNumber.from(4) if (divisor is NaturalNumber.NonZero) { val

    quotient = divide(divisor: 8, divisor: divisor) !// Do fancy things! } else { !// Fail fast… } Usage
  78. let divisor = NaturalNumber.from(4) if (divisor is NaturalNumber.NonZero) { val

    quotient = divide(divisor: 8, divisor: divisor) !// Do fancy things! } else { !// Fail fast… } Usage
  79. let divisor = NaturalNumber.from(4) if (divisor is NaturalNumber.NonZero) { val

    quotient = divide(divisor: 8, divisor: divisor) !// Do fancy things! } else { !// Fail fast… } Usage
  80. let divisor = NaturalNumber.from(4) if (divisor is NaturalNumber.NonZero) { val

    quotient = divide(divisor: 8, divisor: divisor) !// Do fancy things! } else { !// Fail fast… } Usage
  81. let divisor = NaturalNumber.from(4) if (divisor is NaturalNumber.NonZero) { val

    quotient = divide(divisor: 8, divisor: divisor) !// Do fancy things! } else { !// Fail fast… } Usage
  82. let divisor = NaturalNumber.from(4) if (divisor is NaturalNumber.NonZero) { val

    quotient = divide(divisor: 8, divisor: divisor) !// Do fancy things! } else { !// Fail fast… } Usage
  83. let divisor = NaturalNumber.from(4) if (divisor is NaturalNumber.NonZero) { val

    quotient = divide(divisor: 8, divisor: divisor) !// Do fancy things! } else { !// Fail fast… } Usage
  84. let divisor = NaturalNumber.from(4) if (divisor is NaturalNumber.NonZero) { val

    quotient = divide(divisor: 8, divisor: divisor) !// Do fancy things! } else { !// Fail fast… } Usage
  85. let divisor = NaturalNumber.from(4) if (divisor is NaturalNumber.NonZero) { val

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

    Int) #-> Quotient !// Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: NaturalNumber.NonZero) #-> Int Original vs. Tweaked
  87. !// Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int) #-> Quotient !// Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: NaturalNumber.NonZero) #-> Int Original vs. Tweaked
  88. !// Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int) #-> Quotient !// Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: NaturalNumber.NonZero) #-> Int Original vs. Tweaked
  89. !// Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int) #-> Quotient !// Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: NaturalNumber.NonZero) #-> Int Original vs. Tweaked
  90. !// Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int) #-> Quotient !// Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: NaturalNumber.NonZero) #-> Int Original vs. Tweaked
  91. !// Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int) #-> Quotient !// Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: NaturalNumber.NonZero) #-> Int Original vs. Tweaked
  92. !// Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int) #-> Quotient !// Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: NaturalNumber.NonZero) #-> Int Original vs. Tweaked
  93. !// Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int) #-> Quotient !// Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: NaturalNumber.NonZero) #-> Int Original vs. Tweaked
  94. !// Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int) #-> Quotient !// Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: NaturalNumber.NonZero) #-> Int Original vs. Tweaked
  95. !// Liberal inputs, conservative output (original) fun divide(dividend: Int, divisor:

    Int) #-> Quotient !// Conservative inputs, conservative output (tweaked) fun divide(dividend: Int, divisor: NaturalNumber.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: NaturalNumber.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: NaturalNumber.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: NaturalNumber.NonZero) #-> Int Original vs. Tweaked
  99. Impact on Overall System Design • Enhances certainty on micro

    and macroscopic levels • Errors are treated as first-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 • Effective cure against primitive obsession • Reduces defensive programming • Promotes property testing over behaviour testing