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

    View Slide

  2. Slides by @arghrich
    5
    € Dealing
    with
    “nothing”
    Effective
    “nothing”
    Fixing the
    Mistake in
    C#

    View Slide

  3. Slides by @arghrich
    What is the most
    dangerous error
    programmers make?
    6 Slides by @arghrich

    View Slide

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

    View Slide

  5. Slides by @arghrich
    Handling null is
    important but not the
    most dangerous
    mistake
    8 Slides by @arghrich

    View Slide

  6. Slides by @arghrich
    Still …
    9 Slides by @arghrich

    View Slide

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

    View Slide

  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

    View Slide

  9. Slides by @arghrich
    13
    € Dealing
    with
    “nothing”
    Effective
    “nothing”
    Fixing the
    Mistake in
    C#

    View Slide

  10. Slides by @arghrich
    Designing a Dictionary
    1. public class Addresses
    2. {
    3. private readonly Dictionary _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?

    View Slide

  11. Slides by @arghrich
    Modelling “Nothing”
    15
    1. public class Addresses
    2. {
    3. private readonly Dictionary _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 ???;

    View Slide

  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

    View Slide

  13. Slides by @arghrich
    Wouldn‘t it be nice if
    C# had explicit
    absence?
    17 Slides by @arghrich

    View Slide

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

    View Slide

  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

    View Slide

  16. Slides by @arghrich
    Luckily, languages
    evolve
    22 Slides by @arghrich

    View Slide

  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
    instead of nullable
    Python 3.5
    › Type hints
    › Optional[T]

    View Slide

  18. Slides by @arghrich
    24
    € Dealing
    with
    “nothing”
    Effective
    “nothing”
    Fixing the
    Mistake in
    C#

    View Slide

  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;
    }

    View Slide

  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

    View Slide

  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;}
    /*…*/
    }

    View Slide

  22. Slides by @arghrich
    Stack traces are useless
    for
    NullReferenceExceptions
    28 Slides by @arghrich

    View Slide

  23. Slides by @arghrich
    Enable nullable reference types
    1. // per *.csproj file:
    2.
    3.
    4.
    5. enable
    6.
    7.
    8. 8.0
    9.
    10.
    11. nullable
    12.
    13. start without WarningsAsErrors -->
    14.
    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
    4. {
    5. public class
    6. {
    7. // can now signal null
    8. static string? Name()
    9. { /* … */}
    10. static ? Data()
    11. { /* … */}
    12.// disable wherever you want
    13.#nullable disable
    OR

    View Slide

  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

    View Slide

  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

    View Slide

  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;
    }

    View Slide

  27. Slides by @arghrich
    The compiler guides you
    through the code base
    33 Slides by @arghrich

    View Slide

  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);
    }

    View Slide

  29. Slides by @arghrich
    Nullable Reference Types
    help us find&fix the root
    cause of bugs
    35 Slides by @arghrich

    View Slide

  30. Slides by @arghrich
    37
    € Dealing
    with
    “nothing”
    Effective
    “nothing”
    Fixing the
    Mistake in
    C#

    View Slide

  31. Slides by @arghrich
    Recap: Modelling “Nothing”
    38
    1. public class Addresses
    2. {
    3. private readonly Dictionary _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 ???;

    View Slide

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

    View Slide

  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) {/*…*/}

    View Slide

  34. Slides by @arghrich
    Nullable Reference Types
    more important than
    introduction of Generics
    41 Slides by @arghrich

    View Slide

  35. Slides by @arghrich
    Migration is worth it. You‘ll
    catch so many bugs
    42 Slides by @arghrich

    View Slide

  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

    View Slide

  37. Slides by @arghrich
    Use nullable reference
    types effectively
    44 Slides by @arghrich

    View Slide

  38. Slides by @arghrich
    45
    #4 Always specify when returning null
    1. Address? FindAddress(PersonId pId)
    2. {
    3. return _addresses.GetValueOrDefault(pId);
    4. }
    1

    View Slide

  39. Slides by @arghrich
    46
    #5 Return empty collections or arrays, not
    nulls
    1. // not like this
    2. IList? RecentAddresses()
    3. {
    4. return _employee.TrackingAllowed
    5. ? _recentAddresses
    6. : null;
    7. }
    1. // like this
    2. IList RecentAddresses()
    3. {
    4. return _employee.TrackingAllowed
    5. ? _recentAddresses
    6. : new List();
    7. }
    Effective Java 3rd Edition Item 54
    Makes Api more
    difficult to use for all
    callers.
    2

    View Slide

  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;

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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(json);
    3. // result = EmployeeDto { Id=, Name=, Email= }
    4. result.Id is null // true which should not be possible

    View Slide

  45. Slides by @arghrich
    54
    € Dealing
    with
    “nothing”
    Effective
    “nothing”
    Fixing the
    Mistake in
    C#
    One more thing …

    View Slide

  46. Slides by @arghrich
    55 Slides by @arghrich
    I‘ve enabled nullable reference types but
    now I‘ve got null-checks everywhere!!11!eleven

    View Slide

  47. Slides by @arghrich
    Great, you‘ve fixed the root
    cause of many bugs at
    compile time
    56 Slides by @arghrich

    View Slide

  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

    View Slide

  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

    View Slide

  50. Slides by @arghrich
    Can we clarify the business
    rules again?
    59 Slides by @arghrich

    View Slide

  51. Slides by @arghrich
    A shallow dive into
    Railway-Oriented
    Programming*
    60 Slides by @arghrich
    *By Scott Wlaschin https://fsharpforfunandprofit.com/rop/

    View Slide

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

    View Slide

  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

    View Slide

  54. Slides by @arghrich
    Focus on Happy Path, Result does the rest
    64
    J L
    thenTry
    then
    thenTryFix
    thenFix
    1. Result Then(
    2. Func onOk){ /*…*/ }
    3. Result ThenTry(
    4. Func> onOk){ /*…*/ }

    View Slide

  55. Slides by @arghrich
    How to get on the rail
    65
    J L
    Ok(sth) Fail(“why”)
    OfResult(sth?)

    View Slide

  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/

    View Slide

  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/

    View Slide

  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/

    View Slide

  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

    View Slide

  60. Slides by @arghrich
    Now we‘re done J
    70 Slides by @arghrich

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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
    Enable
    nullable
    (4) Use records for
    domain types
    Use ROP only for
    Domain Errors

    View Slide

  65. Slides by @arghrich
    Backup

    View Slide

  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

    View Slide

  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

    View Slide

  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.

    View Slide

  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?

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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?

    View Slide

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

    View Slide