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

Fixing the Billion Dollar Mistake (in C#)

Richard
November 19, 2021

Fixing the Billion Dollar Mistake (in C#)

Delivered at Build Stuff 2021

Sir Anthony Hoare calls the null reference his billion dollar mistake because it has led to "innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years." Whether the order of magnitude is true remains to be proven by the scientific community but I think it's fair to say that null references are one of the leading causes of sleep deprivation for developers about to release. Thankfully most programming languages have been extended with alternate ways to handle "the absence of value" that lead to more expressive code, less room for errors and better sleep.

In this talk we will explore the Nullability possibilities that C#8 and 9 provide, the alternate ways that other languages like Python, Kotlin or F# have picked and techniques such as Railway-Oriented Programming or the Null-Object-Pattern that can help if the language of choice does not provide an option. Finally we'll look at migration options for older code bases.

Richard

November 19, 2021
Tweet

More Decks by Richard

Other Decks in Programming

Transcript

  1. Slides by @arghrich Fixing the Billion Dollar Mistake 19.11.21 richargh.de/

    speakerdeck.com/richargh Richard Gross pronoun.is/he Works at maibornwolff.de/en
  2. Slides by @arghrich 5 € Dealing with “nothing” Effective “nothing”

    Fixing the Mistake in C#
  3. Slides by @arghrich What is the most dangerous error programmers

    make? 6 Slides by @arghrich
  4. Slides by @arghrich 2021 CWE Top 20 Most Dangerous* Software

    Weaknesses 7 *„most common and impactful issues experienced over the previous two calendar years.” https://cwe.mitre.org/top25/archive/2021/2021_cwe_top25.html & Comment by Jeff Atwood #15 Null Pointer Dereference Input Validation › ‚Cross-Site Scripting‘ › ‚OS Command Injection‘ Auth › Missing Authentication / Authorization Manual Memory › Out-of-bounds read/write #12 Int Overflow / Wraparound 1. // this morning in the Keynote 2. binarySearch(int[] a, int key){ 3. int low = 0; 4. int high = a.length - 1; 5. 6. while (low <= high) { 7. // Bug in JDK for 20 years 8. int mid = (low + high) / 2; 9. int midVal = a[mid]; 10. // …
  5. Slides by @arghrich Handling null is important but not the

    most dangerous mistake 8 Slides by @arghrich
  6. Slides by @arghrich Still … 9 Slides by @arghrich

  7. Slides by @arghrich 97% of Logged Errors are Caused by

    10 Unique Errors 10 https://www.overops.com/blog/we-crunched-1-billion-java-logged-errors-heres-what-causes-97-of-them-2/ https://www.overops.com/blog/the-top-10-exceptions-types-in-production-java-applications-based-on-1b-events/ 97% 3% Logged Errors 10 Unique Errors Other Errors 97% of Logged Errors by Frequency 1. NullPointerException 2. NumberFormatException 3. IllegalArgumentException 4. …
  8. Slides by @arghrich 11 I call it my billion-dollar mistake.

    It was the invention of the null reference in 1965 [ALGOL]. […] This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years. Tony Hoare Null References: The Billion Dollar Mistake 2009 InnoQ Did not design C#, Java, or Python though. J
  9. Slides by @arghrich 13 € Dealing with “nothing” Effective “nothing”

    Fixing the Mistake in C#
  10. Slides by @arghrich Designing a Dictionary 1. public class Addresses

    2. { 3. private readonly Dictionary<PersonId, Address> _addresses = new(); 4. 5. public Address Find(PersonId personId) 6. { 7. return _addresses[personId]; 8. } 9. // … 14 Slides by @arghrich Applying the Principle of Least Surprise: What should this return when personId is unknown?
  11. Slides by @arghrich Modelling “Nothing” 15 1. public class Addresses

    2. { 3. private readonly Dictionary<PersonId, Address> _addresses = new(); 4. 5. public Address Find(PersonId personId) 6. { 7. return _addresses[personId]; 8. } 9. // … Implicit Throw Exception C#, Python throw KeyNotFoundException Implicit Return null Java return null; Implicit Return “default” return Address.NULL; Explicit Require a default return GetOrDefault( personId, defaultAddress); Explicit Model absence Scala, Kotlin, F#, … return ???;
  12. Slides by @arghrich Explicit abscence 1. const address = addresses.Find(personId);

    16 Slides by @arghrich 2. // cannot be ignored 3. log(`Street is ${address.Street}`); 4. // has to be checked before use 5. if(address != null) 6. log(…) Compile error Cannot access without explicit check
  13. Slides by @arghrich Wouldn‘t it be nice if C# had

    explicit absence? 17 Slides by @arghrich
  14. Slides by @arghrich 20 More secure languages by taking away

    options „Goto considered harmful“ * If, else for While return Manual memory considered harmful Garbage Collection, Reference Counting Ownership (Rust J) Implicit null considered harmful Optional<T> Maybe T Nullable? *Edsger Dijkstra „A Case Against the Goto Statement“ aka Goto Statement Considered Harmful.
  15. Slides by @arghrich F# 2005 F# empowers everyone to write

    succinct, robust and performant code Has null but you cannot assign it to anything. 21 Rust 2010 A language empowering everyone to build reliable and efficient software. Doesn‘t even have null. Kotlin 2011 Modern, concise and safe programming language /* Get rid of those pesky NullPointerExceptions, you know, The Billion Dollar Mistake*/ Swift 2014 write software that is incredibly fast and safe by design. Has nil for Objective-C interop. There is a trend here https://en.wikipedia.org/wiki/Void_safety
  16. Slides by @arghrich Luckily, languages evolve 22 Slides by @arghrich

  17. Slides by @arghrich Everyone tries to deal with null 23

    Python Java C# https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history ‘94 ‘96 ‘02 ‘05 ‘14 ‘15 ‘19 ‘20 ‘22 C# 02 › Generics › Nullable Value Types › Null-coalescing operator ?? C# 06 › Null-propagator ?. and ?[] C# 08 .NET Core 3+ › Nullable reference types C# 09 .NET 5+ › Generic nullable improvements › Records › Init-only setters Java 8 › Optional<T> instead of nullable Python 3.5 › Type hints › Optional[T]
  18. Slides by @arghrich 24 € Dealing with “nothing” Effective “nothing”

    Fixing the Mistake in C#
  19. Slides by @arghrich Where’s the error? 25 Slides by @arghrich

    public string FindNotebookMaker(EmployeeId id) { var employee = _company.FindById(id); if (employee is null) return "Employee does not exist"; /* ... */ return employee.Notebook.Maker; }
  20. Slides by @arghrich Why is this an error? public string

    FindNotebookMaker(EmployeeId id) { var employee = _company.FindById(id); if (employee is null) return "Employee does not exist"; /* ... */ return employee.Notebook.Maker; } 26 Slides by @arghrich But, but, but, an Employee always has a notebook!!1! I designed it that way! System.NullReferenceException at FindNotebookMaker(EmployeeId id) in UsesLegacyCode.cs:line 22
  21. Slides by @arghrich The error is somewhere else public void

    StartRepairNotebook(EmployeeId id) { var employee = _company.FindById(id); if (employee is null) return; employee.Notebook = null; _company.Put(employee); } 27 Slides by @arghrich Under certain conditions (f.ex. notebook is being repaired), an employee does not have a notebook. This feature was added later, or maybe you just forgot. The type in employee does not reflect this. public class Employee { public EmployeeId Id {get;} public string Name {get;} public Notebook Notebook {get; set;} /*…*/ }
  22. Slides by @arghrich Stack traces are useless for NullReferenceExceptions 28

    Slides by @arghrich
  23. Slides by @arghrich Enable nullable reference types 1. // per

    *.csproj file: 2. <Project Sdk="Microsoft.NET.Sdk"> 3. <PropertyGroup> 4. <Nullable> 5. enable 6. </Nullable> 7. <LangVersion> 8. 8.0 9. </LangVersion> 10. <WarningsAsErrors> 11. nullable 12. </WarningsAsErrors> 13. <!– if you migrate, better start without WarningsAsErrors --> 14. </PropertyGroup> 29 Slides by @arghrich https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/nullable-reference-types 1. // per *.cs file: 2. #nullable enable 3. namespace <yournamespace> 4. { 5. public class <yourclass> 6. { 7. // can now signal null 8. static string? Name() 9. { /* … */} 10. static <otherclass>? Data() 11. { /* … */} 12.// disable wherever you want 13.#nullable disable OR
  24. Slides by @arghrich After Enable (1) public void StartRepairNotebook(EmployeeId id)

    { var employee = _company.FindById(id); if (employee is null) return; employee.Notebook = null; _company.Put(employee); } 30 Slides by @arghrich Compile error
  25. Slides by @arghrich (2) Mandatory nullability modelling 31 Slides by

    @arghrich public class Employee { public EmployeeId Id {get;} public string Name {get;} public Notebook? Notebook {get; set;} /*…*/ } Mark it as nullable
  26. Slides by @arghrich (3) Cascading compile error(s) 32 Slides by

    @arghrich Compile error public string FindNotebookMaker(EmployeeId id) { var employee = _company.FindById(id); if (employee is null) return "Employee does not exist"; /* ... */ return employee.Notebook.Maker; }
  27. Slides by @arghrich The compiler guides you through the code

    base 33 Slides by @arghrich
  28. Slides by @arghrich We fixed the root cause, not a

    symptom 34 Slides by @arghrich public string FindNotebookMaker(EmployeeId id) { var employee = _company.FindById(id); if (employee is null) return "Employee does not exist"; /* ... */ return employee.Notebook?.Maker; } public class Employee { public EmployeeId Id {get;} public string Name {get;} public Notebook? Notebook {get; set;} /*…*/ } public void StartRepairNotebook(EmployeeId id) { var employee = _company.FindById(id); if (employee is null) return; employee.Notebook = null; _company.Put(employee); }
  29. Slides by @arghrich Nullable Reference Types help us find&fix the

    root cause of bugs 35 Slides by @arghrich
  30. Slides by @arghrich 37 € Dealing with “nothing” Effective “nothing”

    Fixing the Mistake in C#
  31. Slides by @arghrich Recap: Modelling “Nothing” 38 1. public class

    Addresses 2. { 3. private readonly Dictionary<PersonId, Address> _addresses = new(); 4. 5. public Address Find(PersonId personId) 6. { 7. return _addresses[personId]; 8. } 9. // … Implicit Throw Exception C#, Python throw KeyNotFoundException Implicit Return null Java return null; Implicit Return “default” return Address.NULL; Explicit Require a default return GetOrDefault( personId, defaultAddress); Explicit Model absence Scala, Kotlin, F#, … return ???;
  32. Slides by @arghrich Modelling Absence in C#8 39 1. public

    class Addresses 2. { 3. private readonly Dictionary<PersonId, Address> _addresses = new(); 4. 5. public Address? FindAddress(PersonId personId) 6. { 7. return _addresses.GetValueOrDefault(personId); 8. } 9. // … Explicit Model absence Scala, Kotlin, F#, C#
  33. Slides by @arghrich Dealing with Absence in C#8 40 1.

    // bad, does not compile 2. string? address = FindAddress(personId).Street; 3. // good, uses null-propagating operator 4. string? address = FindAddress(personId)?.Street; Explicit Model absence Scala, Kotlin, F#, C# https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/null-checking-preferences 5. // maybe better, uses null-coalescing operator for default 6. string address = FindAddress(personId)?.Street ?? "Konstitucijos Av. 20”; 7. // maybe better, uses null-coalescing assignment operator for default 8. string? address = FindAddress(personId)?.Street; 9. address ??= "Konstitucijos Av. 20”; 10.// good, use pattern matching is null check 11.if(address is null) {/*…*/} 12.if(address is not null) {/*…*/}
  34. Slides by @arghrich Nullable Reference Types more important than introduction

    of Generics 41 Slides by @arghrich
  35. Slides by @arghrich Migration is worth it. You‘ll catch so

    many bugs 42 Slides by @arghrich
  36. Slides by @arghrich Suppose your system looks like this 43

    … UI Logic Data Adapter Common.Lang csproj Edge Elements Common.Error Common…. UI Logic Data Adapter UI Logic Data Adapter UI Logic Data Adapter Migrate a simple proj first More, Focused steps: migrate one at a time If on C#9+, common proj(s) next Don’t trust the edge
  37. Slides by @arghrich Use nullable reference types effectively 44 Slides

    by @arghrich
  38. Slides by @arghrich 45 #4 Always specify when returning null

    1. Address? FindAddress(PersonId pId) 2. { 3. return _addresses.GetValueOrDefault(pId); 4. } 1
  39. Slides by @arghrich 46 #5 Return empty collections or arrays,

    not nulls 1. // not like this 2. IList<Address>? RecentAddresses() 3. { 4. return _employee.TrackingAllowed 5. ? _recentAddresses 6. : null; 7. } 1. // like this 2. IList<Address> RecentAddresses() 3. { 4. return _employee.TrackingAllowed 5. ? _recentAddresses 6. : new List<Address>(); 7. } Effective Java 3rd Edition Item 54 Makes Api more difficult to use for all callers. 2
  40. Slides by @arghrich 48 Slides by @arghrich #5 Use init-setters

    for configuration with known defaults 3 1. class MyConfiguration 2. { 3. // known defaults 4. int Port {get; init;} = 7070; 5. string? AppName {get; init;} 6. } 1. // instantiate like this 2. var address = new MyConfiguration { 3. Port = “Trakai”, 4. AppName = “Billion” 5. } 6. // not settable, does not compile 7. address.Port = 9999;
  41. Slides by @arghrich Init-setters are useless for domain types, where

    default values are unknown 1. public class Address 2. { 3. public string City {get; init;} 4. public string? Street {get; init;} 5. // does not compile, Compiler can’t know that City is non-null 6. } 49 Slides by @arghrich 1. public class Address 2. { 3. public string City { get; } 4. public string? Street { get; } 5. 6. // tedious but compile-safe 7. public Address( 8. string city, string? street) 9. { 10. City = city; 11. Street = street; 12. } 13.}
  42. Slides by @arghrich 50 Slides by @arghrich #5 Use C#9

    records for domain types 4 1. public class Address 2. { 3. public string City {get; init;} 4. public string? Street {get; init;} 5. // does not compile, Compiler can’t know that City of Address is actually set 6. } 1. public record Address( 2. string City, 3. string? Street 4. ); 1. // we can now init like this: 2. var address = new Address( 3. City: “Trakai”, 4. Street: null 5. )
  43. Slides by @arghrich C#9 records provide value-equality and non-destructive mutation

    1. public record Address( 2. string City, 3. string? Street 4. ); 51 Slides by @arghrich 5. // value equality 6. var address1 = new Address( 7. City: “Vilnius”, 8. Street: "Konstitucijos Av. 20" 9. ); 10. var address2 = new Address( 11. City: “Vilnius”, 12. Street: "Konstitucijos Av. 20" 13. ); 14. 15. address1 == address2 // true https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record 16.// non-destructive mutation 17. var address3 = address2 with { 18. Street = “Ozo g. 18” 19. } 20. address2 == address3 // false
  44. Slides by @arghrich 52 Slides by @arghrich #5 Don’t trust

    types from the edge of your system 5 1. public record EmployeeDto( 2. string Id, 3. string Name, 4. string? Email) 1. var json = @"{ ""foo"": ""bar"” }"; 2. var result = JsonConvert.DeserializeObject<EmployeeDto?>(json); 3. // result = EmployeeDto { Id=, Name=, Email= } 4. result.Id is null // true which should not be possible
  45. Slides by @arghrich 54 € Dealing with “nothing” Effective “nothing”

    Fixing the Mistake in C# One more thing …
  46. Slides by @arghrich 55 Slides by @arghrich I‘ve enabled nullable

    reference types but now I‘ve got null-checks everywhere!!11!eleven
  47. Slides by @arghrich Great, you‘ve fixed the root cause of

    many bugs at compile time 56 Slides by @arghrich
  48. Slides by @arghrich 1. public IResponse CreateEmployee(Request request) 2. {

    3. var employee = EmployeeFromBody(request.Path); 4. CheckEmailIsUnique(employee); 5. StoreEmployee(employee); 6. EmailEmployee(employee); 7. return new OkResponse(200); 8. } 57 Slides by @arghrich
  49. Slides by @arghrich 1. public IResponse CreateEmployee(Request request) 2. {

    3. var employee = EmployeeFromBody(request.Path); 4. if(employee is null) 5. { 6. return new BadResponse(400, “Employee invalid”); 7. } 8. if(EmailIsNotUnique(employee)) 9. { 10. return new BadResponse(400, “Email must be unique”); 11. } 12. StoreEmployee(employee); 13. try { 14. EmailEmployee(employee); 15. } 16. catch(ServerUnreachableException) 17. { 18. return new BadResponse(500, “could not send email”); 19. } 20. return new OkResponse(200); 21.} 58 Slides by @arghrich Where did my happy path go? Only one null- check to keep example short
  50. Slides by @arghrich Can we clarify the business rules again?

    59 Slides by @arghrich
  51. Slides by @arghrich A shallow dive into Railway-Oriented Programming* 60

    Slides by @arghrich *By Scott Wlaschin https://fsharpforfunandprofit.com/rop/
  52. Slides by @arghrich Happy Path without error handling 61 1.

    public IResponse CreateEmployee(Request request) 2. { 3. var employee = EmployeeFromBody(request.Path); 4. CheckEmailIsUnique(employee); 5. StoreEmployee(employee); 6. EmailEmployee(employee); 7. return new OkResponse(200); 8. }
  53. Slides by @arghrich ROP Path with error handling 62 1.

    public IResponse CreateEmployee(Request request) 2. { 3. EmployeeFromBody(request.Path).AsResult(ifnull: “invalid employee passed”) 4. .ThenTry(CheckEmailIsUnique) 5. .Then(StoreEmployee) 6. .ThenTry(EmailEmployee) 7. .Then(_ => new OkResponse(200)); 8. } I can see my business logic again
  54. Slides by @arghrich Focus on Happy Path, Result does the

    rest 64 J L thenTry then thenTryFix thenFix 1. Result<TOut> Then<TIn, TOut>( 2. Func<TIn, TOut> onOk){ /*…*/ } 3. Result<TOut> ThenTry<TIn, TOut>( 4. Func<TIn, Result<Tout>> onOk){ /*…*/ }
  55. Slides by @arghrich How to get on the rail 65

    J L Ok(sth) Fail(“why”) OfResult(sth?)
  56. Slides by @arghrich Against Railway-Oriented Programming* 66 Slides by @arghrich

    *Also Scott Wlaschin https://fsharpforfunandprofit.com/posts/against-railway-oriented-programming/ https://eiriktsarpalis.wordpress.com/2017/02/19/youre-better-off-using-exceptions/
  57. Slides by @arghrich Don‘t wrap all exceptions with Results 67

    Slides by @arghrich *Also Scott Wlaschin https://fsharpforfunandprofit.com/posts/against-railway-oriented-programming/ https://eiriktsarpalis.wordpress.com/2017/02/19/youre-better-off-using-exceptions/
  58. Slides by @arghrich Use ROP only for Domain Errors 68

    Slides by @arghrich *Also Scott Wlaschin https://fsharpforfunandprofit.com/posts/against-railway-oriented-programming/ https://eiriktsarpalis.wordpress.com/2017/02/19/youre-better-off-using-exceptions/
  59. Slides by @arghrich How to get started? 69 Slides by

    @arghrich Railway Oriented Programming [Original, Scott Wlaschin 2014] Against Railway-Oriented Programming [Scott Wlaschin 2019] Railway Oriented Programming: C# Edition [Tama Waddell 2019] Switch to F# J Or Copy/Code your C# Result class Watch a deep- dive talk J
  60. Slides by @arghrich Now we‘re done J 70 Slides by

    @arghrich
  61. Slides by @arghrich Examples available on Github • https://github.com/Richargh/fixing-the-billion-dollar-mistake •

    Includes Snippets for C#, F#, Java, Python, Scala • Includes Railway-Oriented Programming Examples for C# 71 Slides by @arghrich
  62. Slides by @arghrich I‘m not the first to talk about

    this :) • Null References: The Billion Dollar Mistake [Tony Hoare 2009] • Nullable Reference Types in C# 8 • [Jon Skeet GOTO 2019] 72 Slides by @arghrich
  63. Slides by @arghrich I‘m not the first to talk about

    ROP :) • Railway Oriented Programming [Original, Scott Wlaschin 2014] • Against Railway-Oriented Programming [Scott Wlaschin 2019] • Railway Oriented Programming: C# Edition [Tama Waddell 2019] • Railway-Oriented Programming in C# [Marcus Denny 2017] • Railway-oriented programming in Java [Tom Johnson 2021] 73 Slides by @arghrich
  64. Slides by @arghrich Done richargh.de/ speakerdeck.com/richargh Code at https://github.com/Richargh/fixing-the-billion-dollar-mistake Small

    Icons by Fontawesome Richard Gross pronoun.is/he Works at maibornwolff.de/en Slides by @arghrich Null: the billion dollar mistake Try to migrate one csproj at a time (1) Always specify when returning null (5) Don’t trust types from the edge of your system Many languages are trying to fix it Fix the root cause of bugs (2) Return empty collections or arrays, not nulls You can fix it in C# 8+.NET Core 3+ Nullable ref. types more important than Generics (3) Only use init- setters where defaults are known Try out ROP to clarify business rules <Nullable>Enable <WarningsAsErrors>nullable (4) Use records for domain types Use ROP only for Domain Errors
  65. Slides by @arghrich Backup

  66. Slides by @arghrich 2021 CWE Top 20 Most Dangerous Software

    Weaknesses 1. Out-of-bounds Write 2. ‘Cross-site Scripting’ 3. Out-of-bounds Read 4. Improper Input Validation 5. ‘OS Command Injection’ 6. ‘SQL Injection’ 7. Use After Free 8. ‘Path Traversal’ 9. Cross-Site Request Forgery 10. Unrestricted File Upload with ‘Evil’ Type 11. Missing Auth. for Critical Function 12. Integer Overflow or Wraparound 13. Deserialization of Untrusted Data 14. Improper Authentication 15. NULL Pointer Dereference 16. Use of Hard-Coded Credentials 17. Improper Restriction … of Memory Bounds 18. Missing Authorizations 19. Incorrect Default Permissions 20. Exposure of Sensitive Information … 76 https://cwe.mitre.org/top25/archive/2021/2021_cwe_top25.html & Comment by Jeff Atwood Rough categories Manual Memory Input Validation Auth
  67. Slides by @arghrich UseCase: Change Employee Address 77 EmployeeId From

    Request Path Find Employee Address from Path Change Address Store changed Employee Greet Employee with new Email
  68. Slides by @arghrich 1. public IResponse ChangeAddress(Request request) 2. {

    3. var employeeId = EmployeeIdFromPath(request.Path); 4. var employee = _employees.FindById(employeeId); 5. var address = AddressFromBody(request.Body); 6. employee = employee.ChangeAddress(address); 7. _employees.Store(employee); 8. GreetEmployee(employee.Email); 9. return new OkResponse(200); 10.} 78 Slides by @arghrich What about DbExceptions? Does not compile. All nullable.
  69. Slides by @arghrich 1. public IResponse ChangeAddress(Request request) 2. {

    3. var employeeId = EmployeeIdFromPath(request.Path); 4. if(employeeId is null) 5. { 6. return new BadResponse(400, “Employee Id invalid”); 7. } 8. var employee = _employees.FindById(employeeId); 9. if(employee is null) 10. { 11. return new BadResponse(400, “Employee not found”); 12. } 13. var address = AddressFromBody(request.Body); 14. if(address is null) 15. { 16. return new BadResponse(400, “address invalid”); 17. } 18. employee = employee.ChangeAddress(address); 19. try { 20. _employees.Store(employee); 21. } 22. catch(MyDbException ex) 23. { 24. return new BadResponse(500, “could not change email”); 79 Slides by @arghrich Where did my happy path go?
  70. Slides by @arghrich ROP Path with error handling 80 1.

    public IResponse ChangeAddress(Request request) 2. { 3. EmployeeIdFromPath(request.Path) 4. .ThenTry(_employees.FindById) 5. .ThenTryToPair(_ => AddressFromBody(request.Body)) 6. .ThenTry((employee, address) => employee.ChangeAddress(address)) 7. .ThenTry(_employees.Store) 8. .ThenTry(employee => GreetEmployee(employee.Email)) 9. .Then(_ => new OkResponse(200)); 10.} I can see my business logic again
  71. Slides by @arghrich UseCase: Rent NotebookType 81 Employee doesn’t have

    a notebook Employee has enough budget? Notebook with desired type is available? Rent notebook for employee
  72. Slides by @arghrich 1. public IResponse Rent(NotebookType type, EmployeeId eId)

    2. { 3. var employee = _employees.FindById(eId); 4. if (AlreadyHasANotebook(employee)) 5. { 6. return Bad(”Already has a notebook); 7. } 8. var notebook = _inventory 9. .FindNotebooksByType(type) 10. .FirstOrDefault(IsAvailable); 11. 12. var budget = _budget.FindById(eId); 13. if (HasNotEnoughBudget(budget, notebook)) 14. { 15. return Bad(”Not enough budget”); 16. } 17. var remainingBudget = RentNotebook( employee, budget, notebook); 18. NotifyOfRent( employee, notebook, remainingBudget); 19. return Ok(notebook); 20.} 82 Slides by @arghrich Employee doesn’t have a notebook Employee has enough budget? Notebook with desired type is available? Rent notebook for employee Notify Employee Does not compile What about DbExceptions? Negative framing
  73. Slides by @arghrich 1. public IResponse Rent(NotebookType type, EmployeeId eId)

    2. { 3. var employee = _employees.FindById(eId); 4. if (employee is null) 5. { 6. return Bad("Employee does not exist); 7. } 8. if (AlreadyHasANotebook(employee)) 9. { 10. return Bad(”Already has a notebook); 11. } 12. var notebook = _inventory 13. .FindNotebooksByType(type) 14. .FirstOrDefault(IsAvailable); 15. if (notebook is null) 16. { 17. return Bad("Notebook does not exist); 18. } 19. var budget = _budget.FindById(eId); 20. if (budget is null) 21. { 22. return Bad("Employee has no budget); 23. } 24. 25. if (HasNotEnoughBudget(budget, notebook)) 26. { 27. return Bad(”Not enough budget”); 28. } 29. 30. try 31. { 32. var remainingBudget = RentNotebook( employee, budget, notebook); 33. NotifyOfRent( employee, notebook, remainingBudget); 34. return Ok(notebook); 35. } 36. catch (MyDbException e) 37. { 38. Console.WriteLine(e); 39. return Bad("Could not notebook"); 40. } 41.} 83 Slides by @arghrich Where did all my business rules go?
  74. Slides by @arghrich 84 Slides by @arghrich Goto considered very

    harmful, so please never use this in C# 1. [Fact(DisplayName = "We should never ever use the goto statement, it is very harmful, but note that in C# you can")] 2. void ComplexGotoExample() 3. { 4. _output.WriteLine("I'd wager this is hard to understand"); 5. var isDone = false; 6. var repeatCount = 0; 7. 8. start: 9. if (isDone) 10. goto end; 11. _output.WriteLine("First"); 12. 13. 14. 15. 16. repeat: 17. if(repeatCount > 3) 18. { 19. isDone = true; 20. goto start; 21. } 22. _output.WriteLine("Repeat"); 23. repeatCount++; 24. goto repeat; 25. 26. end: 27. _output.WriteLine("End"); 28.}