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

Fixing the Billion Dollar Mistake - Java Lens (...

Richard
March 10, 2023

Fixing the Billion Dollar Mistake - Java Lens (v1) 🇬🇧 @JavaLand 2023

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 Java provide, the alternate ways that other languages like Python, Kotlin or C# 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

March 10, 2023
Tweet

More Decks by Richard

Other Decks in Programming

Transcript

  1. 22.03.23 Fixing the Billion Dollar Mistake (Java Lens) Richard Gross

    (he/him) IT-Tailor Archaeologist Auditor richargh.de/ speakerdeck.com/richargh @arghrich
  2. Slides by richargh.de and 2022 CWE Top 20 Most Dangerous1

    Software Weaknesses 7 1„most common and impactful issues experienced over the previous two calendar years.” https://cwe.mitre.org/top25/archive/2022/2022_cwe_top25.html & Comment by Jeff Atwood #11 Null Pointer Dereference Input Validation › ‚Cross-Site Scripting‘ › ‚SQL Injection‘ Auth › Missing Authentication / Authorization Manual Memory › Out-of-bounds read/write › Use after Free
  3. Slides by richargh.de and 97% of All Logged Errors are

    Caused by only 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% All Logged Errors 10 Unique Errors Other Errors 97% of Logged Errors by Frequency 1. NullPointerException 2. NumberFormatException 3. IllegalArgumentException 4. …
  4. Slides by richargh.de and 12 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
  5. Slides by richargh.de and Designing a Map 1. public class

    Addresses { 2. private final Map<PersonId, Address> all = new HashMap<>(); 3. 4. public Address getById(PersonId personId) { 5. return all.get(personId); 6. } 7. /* … */ 15 Applying the Principle of Least Surprise: What should this return when person is unknown?
  6. Slides by richargh.de and Modelling “Nothing” 16 1. public class

    Addresses { 2. private final Map<PersonId, Address> all = new HashMap<>(); 3. 4. public Address getById(PersonId personId) { 5. return all.get(personId); 6. } 7. /* … */ 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 ???;
  7. Slides by richargh.de and Explicit abscence 1. const address =

    addresses.find(personId); 17 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
  8. Slides by richargh.de and 19 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.
  9. Slides by richargh.de and F# 2005 F# empowers everyone to

    write succinct, robust and performant code Has null but you cannot assign it to anything. 20 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
  10. Slides by richargh.de and Everyone tries to deal with null

    22 Python Java C# https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history JSR-305 https://jcp.org/en/jsr/detail?id=305 FindBugs is dead: https://github.com/spotbugs/spotbugs.github.io/issues/24 Nullaway Paper https://www.uber.com/blog/research/nullaway-practical-type-based-null-safety-for-java/ ‘94 ‘96 ‘02 ‘05 ‘06 ‘14 ‘15 ‘19 C# 02 › Nullable Value Types à int? › Null-coalescing operator ?? C# 06 › Null-propagator ?. and ?[] C# 08 .NET Core 3+ › Nullable reference types à User? Java 8 › Optional<T> instead of return null; Python 3.5 › Type hints › Optional[T] JSR 305 › @Nullable › FindBugs initiative › Dormant since ‘12 Nullaway › Compile-time @Nullable check
  11. Slides by richargh.de and Where’s the error? 24 1. public

    String findNotebookMaker(EmployeeId id) { 2. var employee = company.getById(id); 3. if (employee == null) 4. return "Employee does not exist"; 5. /* ... */ 6. 7. return employee.notebook().maker(); 8. }
  12. Slides by richargh.de and Why is this an error? 1.

    public String findNotebookMaker(EmployeeId id) { 2. var employee = company.getById(id); 3. if (employee == null) 4. return "Employee does not exist"; 5. /* ... */ 6. 7. return employee.notebook().maker(); 8. } 25 But, but, but, an Employee always has a notebook!!1! I designed it that way! java.lang.NullPointerException: Cannot invoke "Notebook.maker()" because the return value of "Employee.notebook()" is null
  13. Slides by richargh.de and The error is somewhere else 26

    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. 1. public void startNotebookRepair(EmployeeId id) { 2. var employee = company.getById(id); 3. if (employee == null) 4. return; 5. /* ... */ 6. 7. company.put(id, employee.withNotebook(null)); 8. } 1. public record Employee( 2. EmployeeId id, 3. String name, 4. Notebook notebook) { 5. }
  14. Slides by richargh.de and Add compile-time null checks with Nullaway

    1. // add processor to pom.xml (or .gradle): 2. <build><plugins> 3. <plugin> 4. <artifactId>maven-compiler-plugin</> 5. <configuration> 6. 7. <compilerArgs><arg> 8. -Xplugin:ErrorProne 9. -Xep:NullAway:ERROR 10. -XepOpt:NullAway 11. :AnnotatedPackages=de.richargh</></> 12. 13. <annotationProcessorPaths> 14. <path> 15. <groupId>com.google.errorprone</></> 16. <path> 17. <groupId>com.uber.nullaway</></> 28 Nullaway has instructions for Maven, Gradle, … https://github.com/uber/NullAway 1. // add annotation to *.java file: 2. public class <yourclass> { 3. // can now signal null 4. private @Nullable String text1; 5. { /* … */} 6. private @Nullable <otherclass> data; 7. { /* … */} 8. // this is not nullable 9. private String text2; 10.}
  15. Slides by richargh.de and After Enable (1) 29 Compile error

    1. public void startNotebookRepair(EmployeeId id) { 2. var employee = company.getById(id); 3. if (employee == null) 4. return; 5. /* ... */ 6. 7. company.put(id, employee.withNotebook(null)); 8. } [NullAway] passing @Nullable parameter 'null' where @NonNull is required
  16. Slides by richargh.de and (2) Mandatory nullability modelling 30 Mark

    it as nullable 1. public record Employee( 2. EmployeeId id, 3. String name, 4. @Nullable Notebook notebook) { 5. }
  17. Slides by richargh.de and (3) Cascading compile error(s) 31 1.

    public String findNotebookMaker(EmployeeId id) { 2. var employee = company.getById(id); 3. if (employee == null) 4. return "Employee does not exist"; 5. /* ... */ 6. 7. return employee.notebook().maker(); 8. } Compile error [NullAway] dereferenced expression employee.notebook() is @Nullable
  18. Slides by richargh.de and We fixed the root cause, not

    a symptom 33 1. public void startNotebookRepair(EmployeeId id) { 2. var employee = company.getById(id); 3. if (employee == null) 4. return; 5. /* ... */ 6. 7. company.put(id, employee.withoutNotebook()); 8. } 1. public record Employee( 2. EmployeeId id, 3. String name, 4. @Nullable Notebook notebook) { 5. /* withoutNotebook() … */ 6. } 1. public String findNotebookMaker(EmployeeId id) { 2. var employee = company.getById(id); 3. if (employee == null) 4. return "Employee does not exist"; 5. /* ... */ 6. 7. return employee.notebook() != null 8. ? employee.notebook().maker() 9. : EMPLOYEE_DOES_NOT_HAVE_A_NOTEBOOK; 10.}
  19. Slides by richargh.de and Suppose your system looks like this

    38 … UI Logic Data Adapter commons.lang jar Edge Elements commons.error commons…. UI Logic Data Adapter UI Logic Data Adapter UI Logic Data Adapter More, Focused steps: migrate one at a time Common jar(s) next Don’t trust the edge Migrate a simple jar first AnnotatedPackages + UnannotatedSubPackages
  20. Slides by richargh.de and 39 € Dealing with “nothing” Effective

    “nothing” Fixing the Mistake in Java What about Optional?
  21. Slides by richargh.de and It’s actually OptionalReturn<T> and useful for

    for fluent apis 41 Optional is not for optional fields Optional is not for optional method parameters Optional does not eliminate NullPointerExceptions
  22. Slides by richargh.de and While drafting the fluent Java 8

    Streams API they realized a problem2: 1. String employeeNameById(EmployeeId id){ 2. return employees.stream() 3. .search(it -> it.id() == id) 4. .name(); 5. } It’s really OptionalReturn<T>1 as in method return. 42 1 https://mail.openjdk.org/pipermail/lambda-dev/2012-September/005952.html 2 Optional - The Mother of All Bikesheds https://www.youtube.com/watch?v=Ej0sss6cq14 In the actual Stream Apithey added a limited mechanism to represent “no result” and keep the Api fluent2: 1. String employeeNameById(EmployeeId id){ 2. return employees.stream() 3. .filter(it -> it.id() == id) 4. .findFirst() 5. // now we have an Optional 6. .map(Employee::name) 7. .orElse(“UNKNOWN”); 8. } Non-obvious NullPointerException Will only call .name() when non-null
  23. Slides by richargh.de and Optional does not eliminate NullPointerExceptions 43

    1. // this is valid without Nullaway 2. Optional<String> employeeName = null; 7. // and improper use still results in Exceptions 8. employees.stream() 9. .filter(it -> it.id() == id) 10. .findFirst() 11. .get(); // potential NoSuchElementException 3. // so is this 4. var person = new Person(null); 5. record Person( 6. Optional<EmployerId> employer) {}
  24. Slides by richargh.de and Optional for optional fields: error-prone and

    cluttered 44 Optional, rather inconvenient if you don’t want fluidity 1. if(sth.a().isEmpty() 2. && sth.b().isPresent() 3. && sth.b().get().bb().isPresent()){ 4. 5. aMethod(sth.b().get(), sth.c().get()); 6. } With @Nullable, less clutter, actual compile-safety 1. if(sth.a() == null 2. && sth.b() != null 3. && sth.b().bb() != null){ 4. 5. aMethod(sth.b(), sth.c()); 6. } NoSuchElementException: Forgot to check .c().isPresent() [NullAway] dereferenced expression something.c() is @Nullable
  25. Slides by richargh.de and Optional for optional parameters: quite noisy

    and cluttered 45 With Optional 1. myMethod(a, Optional.of(b), c, Optional.of(d)) With @Nullable, less clutter, compile-safety 1. myMethod(a, b, c, d) NullPointerException: d is null, so this should’ve been Optional.ofNullable(d)
  26. Slides by richargh.de and Modelling Absence in Java with Nullaway

    and sometimes Optional<T> 48 Explicit Model absence Scala, Kotlin, F#, Java 1. public class Addresses { 2. private final Map<PersonId, Address> all = new HashMap<>(); 3. 4. public Optional<Address> getById(PersonId personId) { 5. return Optional.ofNullable(all.get(personId)); 6. } 7. /* … */ 1. record Person( 2. PersonId id, 3. @Nullable 123){ 4. 5. Address withoutNotebook(){ /* … */ } 6. } Compile-time safety Optional<T> where Fluent Api useful
  27. Slides by richargh.de and #4 Make non-null your default •

    NullAway assumes this • JSR-305 has @ParametersAreNonnullByDefault for classes and packages • JSpecify has @NullMarked 50 1
  28. Slides by richargh.de and 51 #4 Model Optional Fields with

    @Nullable 2 1. public class <coolClass> { 2. private @Nullable Address address; 3. } 4. 5. public record <coolRecord> ( 6. @Nullable Address address){ 7. }
  29. Slides by richargh.de and 52 #6 Never pass null, consider

    explicit methods instead 1. var p1 = notebookPriceFor( 2. employeeId, notebookType); 3. 4. // what does passing null mean? 5. // what is this supposed to return? 6. var p2 = notebookPriceFor( 7. null, notebookType); Clean Code Chapter 7 3 1. var p1 = notebookPriceFor( 2. employeeId, notebookType); 3. 4. // create an explicit method that indicates a query without an employee is just an estimate 5. var p2 = estimateNotebookPrice( 6. notebookType);
  30. Slides by richargh.de and #4 Mark nullable method returns 53

    4a 1. @Nullable myMethod(A a, B b) { 2. /*…*/ 3. }
  31. Slides by richargh.de and 6. // Choice A: caller says

    null is not allowed 7. var account = accountsfindAccountById(id).orElseThrow(); #4 Occasionally consider Optional for method returns to give choices to caller 54 4b 1. // Optional is quite useful for repositories 2. Optional<Address> getAddressById(employeeId id) { 3. return Optional.ofNullable(addresses.get(id)); 4. } 8. // Choice B: caller says default is possible 9. var account = accounts.findAccountById(id).orElse(Account.none()); 10.// Choice C: callers uses fluent chain 11.var budget = accounts.findAccountById(id) 12. .map(Account::budgetId) 13. .flatMap(budgets::getById)
  32. Slides by richargh.de and 56 #5 Return empty collections or

    arrays, not nulls 1. // not like this 2. @Nullable List<Address> recentAddresses(){ 3. return employee.isTrackingAllowed() 4. ? recentAddresses 5. : null; 6. } 1. // like this 2. List<Address> recentAddresses(){ 3. return employee.isTrackingAllowed() 4. ? recentAddresses 5. : Collections.emptyList(); 6. } Effective Java 3rd Edition Item 54 Makes Api more difficult to use for all callers. 5
  33. Slides by richargh.de and 57 #5 Use JDK 16 records

    for domain types 6 1. public class Address { 2. private final String city; 3. private final String street; 4. 5. Address( 6. String city, String street){ 7. /*…*/ 8. } 9. 10. @Override boolean equals /*…*/ 11. 12. @Override int hashCode /*…*/ 13. 14. @Override String toString /*…*/ 15.} 1. public record Address( 2. String city, 3. @Nullable String street 4. ) {}
  34. Slides by richargh.de and Records provide value-equality and immutability 1.

    public record Address( 2. String city, 3. @Nullable String street 4. ); 58 5. var address1 = new Address( 6. “Brühl”, "Berggeiststraße 31-41"); 7. var address2 = new Address( 8. “Brühl”, "Berggeiststraße 31-41"); 9. 10. address1.equals(address2); // true
  35. Slides by richargh.de and 59 #5 Don’t trust types from

    the edge of your system 7 1. public record RenterDto( 2. String id, 3. String name) 4. var json = """{ ”name": ”Alex" }"""; 5. var result = objectMapper.readValue(json, RenterDto.class); 6. // result = RenterDto[id=null, name=Alex] 7. result.id() == null; // true… Mapping-Frameworks like Jackson see null-checks as validation. And they don’t do validation. We can write our own ConstraintValidator<DefaultIsNonNullable> however
  36. Slides by richargh.de and The Future…? 61 Java Checker Frameworkhttps://checkerframework.org/

    Nullaway Paper https://www.uber.com/blog/research/nullaway-practical-type-based-null-safety-for-java/ JSpecify https://jspecify.dev/ ‘08 ‘19 ‘23 ‘25 Checker Framework › Compile-time checks › NullChecker is alternative to Nullaway Nullaway › Compile-time @Nullable check Primitive Optional<>? › Optional becomes Primitive › Can then only be present or empty, never null JSpecify Ref. Impl. › Unifying the JSR305 annotation-jungle › @Nullable, @NullMarked JDK Null-Safety? › Maybe here › Might happen if C# Null-Addition works out well
  37. Slides by @arghrich Thanks 62 IT-Tailor Richard Gross (he/him) Archaeologist

    Auditor richargh.de/ speakerdeck.com/richargh @arghrich Works for maibornwolff.de/ People. Code. Commitment. DE TN ES Code at https://github.com/Richargh/fixing-the-billion-dollar-mistake
  38. Slides by richargh.de and 2022 CWE Top 20 Most Dangerous

    Software Weaknesses 1. Out-of-bounds Write 2. ‘Cross-site Scripting’ 3. ‘SQL Injection’ 4. Improper Input Validation 5. Out-of-bounds Read 6. ‘OS Command Injection’ 7. Use After Free 8. ‘Path Traversal’ 9. Cross-Site Request Forgery 10. Unrestricted File Upload with ‘Evil’ Type 11. NULL Pointer Dereference 12. Deserialization of Untrusted Data 13. Integer Overflow or Wraparound 14. Improper Authentication 15. Use of Hard-Coded Credentials 16. Missing Authorizations 17. ‘Command Injection’ 18. Missing Auth. for Critical Function 19. Improper Restriction … of Memory Bounds 20. Incorrect Default Permissions 64 https://cwe.mitre.org/top25/archive/2022/2022_cwe_top25.html & Comment by Jeff Atwood Rough categories Manual Memory Input Validation Auth
  39. Slides by richargh.de and 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 … 65 https://cwe.mitre.org/top25/archive/2021/2021_cwe_top25.html & Comment by Jeff Atwood Rough categories Manual Memory Input Validation Auth
  40. Slides by richargh.de and 66 € Dealing with “nothing” Effective

    “nothing” Fixing the Mistake in C# One more thing …
  41. Slides by richargh.de and 67 I‘ve enabled nullable reference types

    but now I‘ve got null-checks everywhere!!11!eleven
  42. Slides by richargh.de and 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. } 69
  43. Slides by richargh.de and 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.} 70 Where did my happy path go? Only one null- check to keep example short
  44. Slides by richargh.de and A shallow dive into Railway-Oriented Programming*

    72 *By Scott Wlaschin https://fsharpforfunandprofit.com/rop/
  45. Slides by richargh.de and Happy Path without error handling 73

    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. }
  46. Slides by richargh.de and ROP Path with error handling 74

    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
  47. Slides by richargh.de and Focus on Happy Path, Result does

    the rest 76 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){ /*…*/ }
  48. Slides by richargh.de and How to get on the rail

    77 J L Ok(sth) Fail(“why”) OfResult(sth?)
  49. Slides by richargh.de and Against Railway-Oriented Programming* 78 *Also Scott

    Wlaschin https://fsharpforfunandprofit.com/posts/against-railway-oriented-programming/ https://eiriktsarpalis.wordpress.com/2017/02/19/youre-better-off-using-exceptions/
  50. Slides by richargh.de and Don‘t wrap all exceptions with Results

    79 *Also Scott Wlaschin https://fsharpforfunandprofit.com/posts/against-railway-oriented-programming/ https://eiriktsarpalis.wordpress.com/2017/02/19/youre-better-off-using-exceptions/
  51. Slides by richargh.de and Use ROP only for Domain Errors

    80 *Also Scott Wlaschin https://fsharpforfunandprofit.com/posts/against-railway-oriented-programming/ https://eiriktsarpalis.wordpress.com/2017/02/19/youre-better-off-using-exceptions/
  52. Slides by richargh.de and How to get started? 81 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 Java Result class Watch a deep- dive talk J
  53. Slides by richargh.de and 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] 83
  54. Slides by richargh.de and UseCase: Change Employee Address 84 EmployeeId

    From Request Path Find Employee Address from Path Change Address Store changed Employee Greet Employee with new Email
  55. Slides by richargh.de and 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.} 85 What about DbExceptions? Does not compile. All nullable.
  56. Slides by richargh.de and 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”); 86 Where did my happy path go?
  57. Slides by richargh.de and ROP Path with error handling 87

    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
  58. Slides by richargh.de and UseCase: Rent NotebookType 88 Employee doesn’t

    have a notebook Employee has enough budget? Notebook with desired type is available? Rent notebook for employee
  59. Slides by richargh.de and 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.} 89 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
  60. Slides by richargh.de and 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.} 90 Where did all my business rules go?