Slide 1

Slide 1 text

A presentation by @stuherbert
 for @GanbaroDigital Type Integrity The Software Engineering Behind Stricter Typing

Slide 2

Slide 2 text

@GanbaroDigital This is a follow-up to the January 2020 talks @PHPSW by Rob Allen and Dave Liddament

Slide 3

Slide 3 text

@GanbaroDigital Although the examples are in PHP, the underlying principles apply to all programming languages.

Slide 4

Slide 4 text

@GanbaroDigital What's The Problem?

Slide 5

Slide 5 text

@GanbaroDigital A Worked Example

Slide 6

Slide 6 text

@GanbaroDigital A very simple example, that calculates the sales tax / VAT owed.

Slide 7

Slide 7 text

@GanbaroDigital function calculateVat($amount, $rate) { return ($amount/100) * $rate; }

Slide 8

Slide 8 text

@GanbaroDigital calculateVat() is not robust and not always correct.

Slide 9

Slide 9 text

@GanbaroDigital function calculateVat($amount, $rate) { return ($amount/100) * $rate; }

Slide 10

Slide 10 text

@GanbaroDigital function calculateVat($amount, $rate) { if (!is_int($amount)) { throw new Exception(...); } return ($amount/100) * $rate; }

Slide 11

Slide 11 text

@GanbaroDigital function calculateVat($amount, $rate) { if (!is_int($amount)) { throw new Exception(...); } if (!is_int($rate)) { throw new Exception(...); } return ($amount/100) * $rate; }

Slide 12

Slide 12 text

@GanbaroDigital We've gone from 1 line of code to 5 lines of code ... ... and we're just getting started!

Slide 13

Slide 13 text

@GanbaroDigital Without type hints, someone has to write every single check.

Slide 14

Slide 14 text

@GanbaroDigital Every check you add is executed every time the function / method is called.

Slide 15

Slide 15 text

@GanbaroDigital Every line of code that you add needs its own unit test.

Slide 16

Slide 16 text

@GanbaroDigital We live in a world where the time it takes to create and ship working code is often the biggest cost for a project / org / business.

Slide 17

Slide 17 text

@GanbaroDigital Some of these runtime checks can be replaced with type-hinting ...

Slide 18

Slide 18 text

@GanbaroDigital function calculateVat($amount, $rate) { if (!is_int($amount)) { throw new Exception(...); } if (!is_int($rate)) { throw new Exception(...); } return ($amount/100) * $rate; }

Slide 19

Slide 19 text

@GanbaroDigital declare(strict_types=1); function calculateVat( int $amount, int $rate ) { return ($amount/100) * $rate; }

Slide 20

Slide 20 text

@GanbaroDigital ... but calculateVat() still isn't robust and still isn't always correct.

Slide 21

Slide 21 text

@GanbaroDigital Remaining Issues Include ... • Generating negative values • Locale-specific rules on rounding up / down • Accepting the wrong currency • Accepting invalid VAT rates

Slide 22

Slide 22 text

@GanbaroDigital Every legal value of $amount and $rate is an integer. Not every integer is a legal value of $amount and $rate.

Slide 23

Slide 23 text

@GanbaroDigital The Engineering

Slide 24

Slide 24 text

@GanbaroDigital Defensive Programming https://flic.kr/p/9SuCos

Slide 25

Slide 25 text

@GanbaroDigital Defensive programming uses runtime checks to prevent "bad" data reaching the business logic.

Slide 26

Slide 26 text

@GanbaroDigital Design By Contract https://unsplash.com/photos/dtZcW9cSot4

Slide 27

Slide 27 text

@GanbaroDigital Design By Contract ™ uses runtime checks to inspect input AND return values.

Slide 28

Slide 28 text

@GanbaroDigital Your Business Logic 
 Has To Wait In Line https://flic.kr/p/LnuA4G

Slide 29

Slide 29 text

@GanbaroDigital “ Code that relies entirely on runtime checks is slower to write, and slower to run.

Slide 30

Slide 30 text

@GanbaroDigital “Defensive programming is like the pandemic lockdown. It only works if it is practiced everywhere and by everyone.

Slide 31

Slide 31 text

@GanbaroDigital Design By Contract ™ is a mid-80s technique to mitigate the risks of Object-Oriented Programming.

Slide 32

Slide 32 text

@GanbaroDigital The Problem Is State https://unsplash.com/photos/IPx7J1n_xUc

Slide 33

Slide 33 text

@GanbaroDigital Make The Incompatible, Impossible! https://flic.kr/p/8Ws1Sn

Slide 34

Slide 34 text

@GanbaroDigital declare(strict_types=1); function calculateVat( int $amount, int $rate ) { return ($amount/100) * $rate; }

Slide 35

Slide 35 text

@GanbaroDigital declare(strict_types=1); function calculateVat( CartAmountToTax $amount, VatRate $rate ) { return ($amount/100) * $rate; }

Slide 36

Slide 36 text

@GanbaroDigital “ Use type systems to make bad data a bad fit.

Slide 37

Slide 37 text

@GanbaroDigital Using Stricter Typing

Slide 38

Slide 38 text

@GanbaroDigital ?? ?? How do we create stricter types that make our code robust and correct?

Slide 39

Slide 39 text

@GanbaroDigital Type Refinement

Slide 40

Slide 40 text

@GanbaroDigital declare(strict_types=1); function calculateVat( CartAmountToTax $amount, VatRate $rate ) { return ($amount/100) * $rate; }

Slide 41

Slide 41 text

@GanbaroDigital $amount and $rate are values.

Slide 42

Slide 42 text

@GanbaroDigital “ A value is data that has no identity. Two values are the same if their state is identical.

Slide 43

Slide 43 text

@GanbaroDigital “Data that has identity is called an entity. Two entities are the same if their identities are identical.

Slide 44

Slide 44 text

@GanbaroDigital The differences between values and entities aren't important for this talk.

Slide 45

Slide 45 text

@GanbaroDigital declare(strict_types=1); function calculateVat( CartAmountToTax $amount, VatRate $rate ) { return ($amount/100) * $rate; }

Slide 46

Slide 46 text

@GanbaroDigital CartAmountToTax and VatRate are value types.

Slide 47

Slide 47 text

@GanbaroDigital In many languages (including PHP), the only way to define a value type is to define a class.

Slide 48

Slide 48 text

@GanbaroDigital declare(strict_types=1); class CartAmountToTax { public constructor(???) { ... } } class VatRate { public constructor(???) { ... } }

Slide 49

Slide 49 text

@GanbaroDigital ?? ?? What do we need to know to create these values?

Slide 50

Slide 50 text

@GanbaroDigital If you're not sure where to start, start with the primitive types that you are replacing.

Slide 51

Slide 51 text

@GanbaroDigital declare(strict_types=1); class CartAmountToTax { public constructor( int $amount ) { .... } }

Slide 52

Slide 52 text

@GanbaroDigital declare(strict_types=1); class VatRate { public constructor( string $jurisdiction, int $rate ) { ... } }

Slide 53

Slide 53 text

@GanbaroDigital ?? ?? What work will these constructors do?

Slide 54

Slide 54 text

@GanbaroDigital Every legal value of $amount and $rate is an integer. Not every integer is a legal value of $amount and $rate.

Slide 55

Slide 55 text

@GanbaroDigital Type refinement takes a wider data type (like an int) and reduces it to a narrower data type (like a VatRate).

Slide 56

Slide 56 text

@GanbaroDigital Type refinement is done by smart constructors.

Slide 57

Slide 57 text

@GanbaroDigital Smart Constructors

Slide 58

Slide 58 text

@GanbaroDigital ?? ?? What work will these constructors do?

Slide 59

Slide 59 text

@GanbaroDigital Smart constructors enforce the data constraints for their value type.

Slide 60

Slide 60 text

@GanbaroDigital A constraint is a non-negotiable condition that must be met.

Slide 61

Slide 61 text

@GanbaroDigital ?? ?? What are the constraints of the CartAmountToTax value type?

Slide 62

Slide 62 text

@GanbaroDigital ?? ?? What pre-conditions must be met?

Slide 63

Slide 63 text

@GanbaroDigital class CartAmountToTax { public constructor( int $amount ) { // robustness! if ($amount < 0) { throw new Exception(...); } } }

Slide 64

Slide 64 text

@GanbaroDigital class VatRate { public constructor( string $jurisdiction, int $rate ) { // robustness! if (!this->isValidVatRate(...)) { throw new Exception(...); } } }

Slide 65

Slide 65 text

@GanbaroDigital Smart constructors prevent the creation of illegal values.

Slide 66

Slide 66 text

@GanbaroDigital ?? ?? Haven't we simply added defensive programming to our class constructors?

Slide 67

Slide 67 text

@GanbaroDigital Values built by smart constructors are guaranteed to be legal.

Slide 68

Slide 68 text

@GanbaroDigital We move defensive programming from everywhere we use data to everywhere we create typed data.

Slide 69

Slide 69 text

@GanbaroDigital Data Guards

Slide 70

Slide 70 text

@GanbaroDigital class VatRate { public constructor( string $jurisdiction, int $rate ) { // robustness! if (!this->isValidVatRate(...)) { throw new Exception(...); } } }

Slide 71

Slide 71 text

@GanbaroDigital class VatRate { public function isValidVatRate(...): boolean { // inspect params here } }

Slide 72

Slide 72 text

@GanbaroDigital Data guards are functions / methods that return TRUE if a data constraint has been met.

Slide 73

Slide 73 text

@GanbaroDigital Each data guard checks one data constraint, and ONLY one.

Slide 74

Slide 74 text

@GanbaroDigital A specification is a data guard that calls other data guards.

Slide 75

Slide 75 text

@GanbaroDigital Data guards never throw Exceptions.

Slide 76

Slide 76 text

@GanbaroDigital Data Guarantees

Slide 77

Slide 77 text

@GanbaroDigital class VatRate { public constructor( string $jurisdiction, int $rate ) { // robustness! if (!this->isValidVatRate(...)) { throw new Exception(...); } } }

Slide 78

Slide 78 text

@GanbaroDigital ?? ?? Who checks the return values from function / method calls all the time?

Slide 79

Slide 79 text

@GanbaroDigital You can't rely on developers checking return values from function calls. Never use return values to report an error.

Slide 80

Slide 80 text

@GanbaroDigital class VatRate { public constructor( string $jurisdiction, int $rate ) { // robustness! mustBeValidVatRate(...); } }

Slide 81

Slide 81 text

@GanbaroDigital function mustBeValidVatRate(...) { if (isValidVatRate(...)) { return; } throw new Exception(...); }

Slide 82

Slide 82 text

@GanbaroDigital function mustBeValidVatRate(...) { if (isValidVatRate(...)) { return; } throw new Exception(...); }

Slide 83

Slide 83 text

@GanbaroDigital Data guarantees are built from data guards.

Slide 84

Slide 84 text

@GanbaroDigital We don't have to repeat the unit tests, because we are not repeating the code (the input validation).

Slide 85

Slide 85 text

@GanbaroDigital function mustBeValidVatRate(...) { if (isValidVatRate(...)) { return; } throw new Exception(...); }

Slide 86

Slide 86 text

@GanbaroDigital Data guarantees throw Exceptions if a data constraint has not been met.

Slide 87

Slide 87 text

@GanbaroDigital ?? ?? Should data guarantees and data checks be methods, or functions?

Slide 88

Slide 88 text

@GanbaroDigital Data guards and guarantees are more of a modular programming style than pure OOP.

Slide 89

Slide 89 text

@GanbaroDigital ?? ?? Why did we call it CartAmountToTax and not something like Currency or Money?

Slide 90

Slide 90 text

@GanbaroDigital Robustness

Slide 91

Slide 91 text

@GanbaroDigital declare(strict_types=1); function calculateVat( CartAmountToTax $amount, VatRate $rate ) { return ($amount->value/100) * $rate->value; }

Slide 92

Slide 92 text

@GanbaroDigital declare(strict_types=1); function calculateVat( Money $amount, VatRate $rate ) { return ($amount->value/100) * $rate->value; }

Slide 93

Slide 93 text

@GanbaroDigital ?? ?? Are the data constraints for Money the same as for CartAmountToTax?

Slide 94

Slide 94 text

@GanbaroDigital Every legal value of $amount and $rate is an integer. Not every integer is a legal value of $amount and $rate.

Slide 95

Slide 95 text

@GanbaroDigital Every legal value of $amount is Money. Not every value of Money is a legal value of $amount.

Slide 96

Slide 96 text

@GanbaroDigital (The right!) stricter types improve the robustness of our code.

Slide 97

Slide 97 text

@GanbaroDigital Robustness is a measure of how well your code detects and rejects invalid input.

Slide 98

Slide 98 text

@GanbaroDigital Robust code actively prevents bad inputs reaching your business logic.

Slide 99

Slide 99 text

@GanbaroDigital Stricter types represent and reflect the problem domain's language / jargon.

Slide 100

Slide 100 text

@GanbaroDigital declare(strict_types=1); function calculateVat( CartAmountToTax $amount, VatRate $rate ) { return ($amount->value/100) * $rate->value; }

Slide 101

Slide 101 text

@GanbaroDigital declare(strict_types=1); function calculateVat( CartAmountToTax $amount, VatRate $rate ) { return ($amount->value/100) * $rate->value; } ✗

Slide 102

Slide 102 text

@GanbaroDigital declare(strict_types=1); function calculateVatForCart( CartAmountToTax $amount, VatRate $rate ) { return ($amount->value/100) * $rate->value; }

Slide 103

Slide 103 text

@GanbaroDigital class Cart { function calculateVat( CartAmountToTax $amount, VatRate $rate ) { return ($amount->value/100) * $rate->value; } }

Slide 104

Slide 104 text

@GanbaroDigital calculateVat() is not robust and not always correct.

Slide 105

Slide 105 text

@GanbaroDigital ?? ?? calculateVat() is now robust. Is it always correct?

Slide 106

Slide 106 text

@GanbaroDigital Correctness

Slide 107

Slide 107 text

@GanbaroDigital class Cart { function calculateVat( CartAmountToTax $amount, VatRate $rate ) { return ($amount->value/100) * $rate->value; } }

Slide 108

Slide 108 text

@GanbaroDigital ?? ?? Is that calculation correct for every tax jurisdiction?

Slide 109

Slide 109 text

@GanbaroDigital ?? ?? Why don't we ask the VatRate value object for the correct calculation?

Slide 110

Slide 110 text

@GanbaroDigital class Cart { function calculateVat( CartAmountToTax $amount, VatRate $rate ) { return ($amount->value/100) * $rate->value; } }

Slide 111

Slide 111 text

@GanbaroDigital class Cart { function calculateVat( CartAmountToTax $amount, VatRate $rate ) { return $rate->calculateVat( $amount ); } }

Slide 112

Slide 112 text

@GanbaroDigital By themselves, stricter types do not guarantee that your code is now correct.

Slide 113

Slide 113 text

@GanbaroDigital Stricter types allow us to plug the correct behaviour into our business logic.

Slide 114

Slide 114 text

@GanbaroDigital Code is correct if it always produces the expected legal output for each given legal input.

Slide 115

Slide 115 text

@GanbaroDigital Correct code is always deterministic.

Slide 116

Slide 116 text

@GanbaroDigital In Summary ...

Slide 117

Slide 117 text

@GanbaroDigital “ Code that relies entirely on runtime checks is slower to write, and slower to run.

Slide 118

Slide 118 text

@GanbaroDigital We live in a world where the time it takes to create and ship working code is often the biggest cost for a project / org / business.

Slide 119

Slide 119 text

@GanbaroDigital ?? ?? How do we create stricter types that make our code robust and correct?

Slide 120

Slide 120 text

@GanbaroDigital We move defensive programming from everywhere we use data to everywhere we create data.

Slide 121

Slide 121 text

@GanbaroDigital Type refinement takes a wider data type (like an int) and reduces it to a narrower data type (like a VatRate).

Slide 122

Slide 122 text

@GanbaroDigital Smart constructors prevent the creation of illegal values.

Slide 123

Slide 123 text

@GanbaroDigital Data guards are functions / methods that return TRUE if a data constraint has been met.

Slide 124

Slide 124 text

@GanbaroDigital Each data guard checks one data constraint, and ONLY one.

Slide 125

Slide 125 text

@GanbaroDigital A specification is a data guard that calls other data guards.

Slide 126

Slide 126 text

@GanbaroDigital Data guards never throw Exceptions.

Slide 127

Slide 127 text

@GanbaroDigital You can't rely on developers checking return values from function calls. Never use return values to report an error.

Slide 128

Slide 128 text

@GanbaroDigital Data guarantees throw Exceptions if a data constraint has not been met.

Slide 129

Slide 129 text

@GanbaroDigital Data guarantees are built from data guards.

Slide 130

Slide 130 text

@GanbaroDigital (The right!) stricter types improve the robustness of our code.

Slide 131

Slide 131 text

@GanbaroDigital Stricter types represent and reflect the problem domain's language / jargon.

Slide 132

Slide 132 text

@GanbaroDigital By themselves, stricter types do not guarantee that your code is now correct.

Slide 133

Slide 133 text

@GanbaroDigital Stricter types allow us to plug the correct behaviour into our business logic.

Slide 134

Slide 134 text

Thank You How Can We Help You? A presentation by @stuherbert
 for @GanbaroDigital