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

Fixing the Billion Dollar Mistake JavaLand

Fixing the Billion Dollar Mistake JavaLand

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

    View Slide

  2. Slides by richargh.de and
    5
    € Dealing
    with
    “nothing”
    Effective
    “nothing”
    Fixing the
    Mistake in
    Java

    View Slide

  3. Slides by richargh.de and
    What is the most
    dangerous error
    programmers make?
    6

    View Slide

  4. 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

    View Slide

  5. Slides by richargh.de and
    Handling null is
    important but not the
    most reported mistake
    8

    View Slide

  6. Slides by richargh.de and
    How often does null
    cause business
    problems?
    9

    View Slide

  7. 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. …

    View Slide

  8. Slides by richargh.de and
    NullPointer
    The most popular exception
    11

    View Slide

  9. 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

    View Slide

  10. Slides by richargh.de and
    14
    € Dealing
    with
    “nothing”
    Effective
    “nothing”
    Fixing the
    Mistake in
    Java

    View Slide

  11. Slides by richargh.de and
    Designing a Map
    1. public class Addresses {
    2. private final Map 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?

    View Slide

  12. Slides by richargh.de and
    Modelling “Nothing”
    16
    1. public class Addresses {
    2. private final Map 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 ???;

    View Slide

  13. 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

    View Slide

  14. Slides by richargh.de and
    Wouldn‘t it be nice if
    Java had explicit
    absence?
    18

    View Slide

  15. 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
    Maybe T
    Nullable?
    *Edsger Dijkstra „A Case Against the Goto Statement“ aka Goto Statement Considered Harmful.

    View Slide

  16. 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

    View Slide

  17. Slides by richargh.de and
    Luckily, older
    languages evolve as
    well
    21

    View Slide

  18. 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
    instead of return
    null;
    Python 3.5
    › Type hints
    › Optional[T]
    JSR 305
    › @Nullable
    › FindBugs initiative
    › Dormant since ‘12
    Nullaway
    › Compile-time
    @Nullable check

    View Slide

  19. Slides by richargh.de and
    23
    € Dealing
    with
    “nothing”
    Effective
    “nothing”
    Fixing the
    Mistake in
    Java

    View Slide

  20. 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. }

    View Slide

  21. 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

    View Slide

  22. 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. }

    View Slide

  23. Slides by richargh.de and
    Stack traces are useless
    for NullPointerExceptions
    27

    View Slide

  24. Slides by richargh.de and
    Add compile-time null checks with
    Nullaway
    1. // add processor to pom.xml (or .gradle):
    2.
    3.
    4. maven-compiler-plugin>
    5.
    6.
    7.
    8. -Xplugin:ErrorProne
    9. -Xep:NullAway:ERROR
    10. -XepOpt:NullAway
    11. :AnnotatedPackages=de.richargh>>
    12.
    13.
    14.
    15. com.google.errorprone>>
    16.
    17. com.uber.nullaway>>
    28
    Nullaway has instructions for Maven, Gradle, … https://github.com/uber/NullAway
    1. // add annotation to *.java file:
    2. public class {
    3. // can now signal null
    4. private @Nullable String text1;
    5. { /* … */}
    6. private @Nullable data;
    7. { /* … */}
    8. // this is not nullable
    9. private String text2;
    10.}

    View Slide

  25. 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

    View Slide

  26. 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. }

    View Slide

  27. 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

    View Slide

  28. Slides by richargh.de and
    The compiler guides you
    through the code base
    32

    View Slide

  29. 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.}

    View Slide

  30. Slides by richargh.de and
    Nullaway helps us find&fix
    the root cause of bugs
    34

    View Slide

  31. Slides by richargh.de and
    Migration is worth it. You‘ll
    catch so many bugs
    37

    View Slide

  32. 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

    View Slide

  33. Slides by richargh.de and
    39
    € Dealing
    with
    “nothing”
    Effective
    “nothing”
    Fixing the
    Mistake in
    Java
    What
    about
    Optional?

    View Slide

  34. Slides by richargh.de and
    Optional
    Awesome but often misunderstood
    40

    View Slide

  35. Slides by richargh.de and
    It’s actually OptionalReturn
    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

    View Slide

  36. 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 OptionalReturn1 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

    View Slide

  37. Slides by richargh.de and
    Optional does not eliminate
    NullPointerExceptions
    43
    1. // this is valid without Nullaway
    2. Optional 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 employer) {}

    View Slide

  38. 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

    View Slide

  39. 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)

    View Slide

  40. Slides by richargh.de and
    Modelling Absence in Java with Nullaway
    and sometimes Optional
    48
    Explicit
    Model absence
    Scala, Kotlin, F#, Java
    1. public class Addresses {
    2. private final Map all = new HashMap<>();
    3.
    4. public Optional 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 where
    Fluent Api useful

    View Slide

  41. Slides by richargh.de and
    49
    € Dealing
    with
    “nothing”
    Effective
    “nothing”
    Fixing the
    Mistake in
    Java

    View Slide

  42. 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

    View Slide

  43. Slides by richargh.de and
    51
    #4 Model Optional Fields with @Nullable
    2
    1. public class {
    2. private @Nullable Address address;
    3. }
    4.
    5. public record (
    6. @Nullable Address address){
    7. }

    View Slide

  44. 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);

    View Slide

  45. Slides by richargh.de and
    #4 Mark nullable method returns
    53
    4a
    1. @Nullable myMethod(A a, B b) {
    2. /*…*/
    3. }

    View Slide

  46. 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 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)

    View Slide

  47. Slides by richargh.de and
    56
    #5 Return empty collections or arrays, not
    nulls
    1. // not like this
    2. @Nullable List recentAddresses(){
    3. return employee.isTrackingAllowed()
    4. ? recentAddresses
    5. : null;
    6. }
    1. // like this
    2. List 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

    View Slide

  48. 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. ) {}

    View Slide

  49. 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

    View Slide

  50. 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 however

    View Slide

  51. 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

    View Slide

  52. 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

    View Slide

  53. Slides by richargh.de and
    Backup

    View Slide

  54. 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

    View Slide

  55. 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

    View Slide

  56. Slides by richargh.de and
    66
    € Dealing
    with
    “nothing”
    Effective
    “nothing”
    Fixing the
    Mistake in
    C#
    One more thing …

    View Slide

  57. Slides by richargh.de and
    67
    I‘ve enabled nullable reference types but
    now I‘ve got null-checks everywhere!!11!eleven

    View Slide

  58. Slides by richargh.de and
    Great, you‘ve fixed the root
    cause of many bugs at
    compile time
    68

    View Slide

  59. 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

    View Slide

  60. 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

    View Slide

  61. Slides by richargh.de and
    Can we clarify the business
    rules again?
    71

    View Slide

  62. Slides by richargh.de and
    A shallow dive into
    Railway-Oriented
    Programming*
    72
    *By Scott Wlaschin https://fsharpforfunandprofit.com/rop/

    View Slide

  63. 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. }

    View Slide

  64. 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

    View Slide

  65. Slides by richargh.de and
    Focus on Happy Path, Result does the rest
    76
    J L
    thenTry
    then
    thenTryFix
    thenFix
    1. Result Then(
    2. Func onOk){ /*…*/ }
    3. Result ThenTry(
    4. Func> onOk){ /*…*/ }

    View Slide

  66. Slides by richargh.de and
    How to get on the rail
    77
    J L
    Ok(sth) Fail(“why”)
    OfResult(sth?)

    View Slide

  67. 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/

    View Slide

  68. 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/

    View Slide

  69. 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/

    View Slide

  70. 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

    View Slide

  71. Slides by richargh.de and
    Now we‘re done J
    82

    View Slide

  72. 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

    View Slide

  73. 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

    View Slide

  74. 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.

    View Slide

  75. 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?

    View Slide

  76. 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

    View Slide

  77. 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

    View Slide

  78. 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

    View Slide

  79. 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?

    View Slide