Refactoring 029 - Replace NULL With Collection

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5175

    #1

    Refactoring 029 - Replace NULL With Collection

    Transform optional attributes into empty collections for cleaner, safer, and polymorphic code, banishing the billion-dollar mistake


    TL;DR: Replace nullable optional attributes with empty collections to eliminate null checks and leverage polymorphism.


    Problems Addressed πŸ˜”



    Related Code Smells πŸ’¨











    Code Smell 192 - Optional Attributes

    Maxi Contieri ・ Jan 17 '23

    #programming
    #opensource
    #firstpost
    #indie



















    Code Smell 149 - Optional Chaining

    Maxi Contieri ・ Jul 16 '22

    #javascript
    #webdev
    #beginners
    #programming



















    Code Smell 19 β€” Optional Arguments

    Maxi Contieri ・ Nov 7 '20

    #codenewbie
    #oop
    #tutorial



















    Code Smell 12 - Null

    Maxi Contieri ・ Oct 31 '20

    #codenewbie
    #tutorial
    #oop



















    Code Smell 45β€Š-β€ŠNot Polymorphic

    Maxi Contieri ・ Dec 7 '20

    #oop
    #codesmell
    #webdev
    #cleancode









    Steps πŸ‘£

    1. Identify nullable optional attributes that could be collections
    2. Replace single nullable objects with empty collections
    3. Remove all null checks related to these optional attributes
    4. Update methods to work with collections instead of single objects


    Sample Code πŸ’»

    Before 🚨





    public class ShoppingCart {
    private ListItem> items = new ArrayList();
    private Coupon coupon = null;

    public void addItem(Item item) {
    this.items.add(item);
    }

    public void redeemCoupon(Coupon coupon) {
    this.coupon = coupon;
    }

    public double total() {
    double total = 0;

    for (Item item : this.items) {
    total += item.getPrice();
    }

    // This a polluted IF and null check
    if (this.coupon != null) {
    total -= this.coupon.getDiscount();
    }

    return total;
    }

    public boolean hasUnsavedChanges() {
    // Explicit null check
    return !this.items.isEmpty() || this.coupon != null;
    }

    public boolean hasCoupon() {
    return this.coupon != null;
    }
    }











    public class ShoppingCart {
    private final ListItem> items = new ArrayList();

    // This version uses Optionals
    // Not all programming languages support this feature
    private OptionalCoupon> coupon = Optional.empty();

    public void addItem(Item item) {
    items.add(item);
    }

    public void redeemCoupon(Coupon coupon) {
    // You need to understand how optionals work
    this.coupon = Optional.ofNullable(coupon);
    }

    public boolean hasUnsavedChanges() {
    return !items.isEmpty() || !coupon.isPresent();
    }

    public boolean hasCoupon() {
    return coupon.isPresent();
    }
    }





    After πŸ‘‰






    public class ShoppingCart {
    private ListItem> items = new ArrayList();

    // 1. Identify nullable optional attributes
    // that could be collections
    // 2. Replace single nullable objects with empty collections
    private ListCoupon> coupons = new ArrayList();

    public void addItem(Item item) {
    this.items.add(item);
    }

    // Step 4: Work with collection
    // instead of single nullable object
    public void redeemCoupon(Coupon coupon) {
    this.coupons.add(coupon);
    }

    // Step 4: Simplified logic without null checks
    public double total() {
    double total = 0;

    for (Item item : this.items) {
    total += item.getPrice();
    }

    // 3. Remove all null checks
    // related to these optional attributes
    for (Coupon coupon : this.coupons) {
    total -= coupon.getDiscount();
    }

    return total;
    }

    // Consistent behavior with empty collections
    public boolean hasUnsavedChanges() {
    // 4. Update methods to work with collections
    // instead of single objects
    return !this.items.isEmpty() || !this.coupons.isEmpty();
    }

    // 3. Remove all null checks
    // related to these optional attributes
    // Collection-based check instead of null check
    public boolean hasCoupon() {
    return !this.coupons.isEmpty();
    }
    }





    Type πŸ“

    [X] Semi-Automatic

    Safety πŸ›‘οΈ

    This refactoring is generally safe when you control all access points to the collection attributes.


    You need to ensure that no external code expects null values and deal with inside APIs.


    The refactoring maintains the same external behavior while simplifying internal logic.


    You should verify that all constructors and factory methods initialize collections properly.

    Why is the Code Better? ✨

    The refactored code eliminates null pointer exceptions and reduces conditional complexity.


    Empty collections and non-empty collections behave polymorphically, allowing you to treat them uniformly.


    The code becomes more predictable since collections always exist (at least empty) and respond to the same operations.


    Method implementations become shorter and more focused on business logic rather than null handling.


    The approach aligns with the principle of making illegal states unrepresentable in your domain model, leading to more robust and maintainable code.


    Empty collections and non-empty collections are polymorphic.

    How Does it Improve the Bijection? πŸ—ΊοΈ

    In the real world, containers exist even when empty.


    By representing optional collections as empty collections rather than null, you create a more accurate model of reality.


    Null does not exist in real world and it always breaks the bijection.


    This maintains the one-to-one correspondence between real-world concepts and your computational model, creating a good MAPPER.


    When you return a collection instead of nulls, you also reduce the coupling.

    Limitations ⚠️

    This refactoring may not be suitable when null has semantic meaning different from "empty". Some legacy APIs might expect null values, requiring adaptation layers.


    You need to ensure all code paths initialize collections consistently to avoid mixed null and empty states.

    Refactor with AI πŸ€–


    Suggested Prompt: 1. Identify nullable optional attributes that could be collections 2. Replace single nullable objects with empty collections 3. Remove all null checks related to these optional attributes 4. Update methods to work with collections instead of single objects 5. Test that empty and non-empty collections behave consistently



    Tags 🏷️

    • Null

    Level πŸ”‹

    [X] Intermediate

    Related Refactorings πŸ”„











    Refactoring 015 - Remove NULL

    Maxi Contieri ・ Jul 28 '24

    #webdev
    #beginners
    #java
    #programming




















    Refactoring 014 - Remove IF

    Maxi Contieri ・ Jul 5 '24

    #webdev
    #beginners
    #programming
    #java









    See also πŸ“š











    Null: The Billion dollar mistake

    Maxi Contieri ・ Nov 18 '20

    #codenewbie
    #tutorial
    #programming
    #webdev



















    How to Get Rid of Annoying IFsΒ Forever

    Maxi Contieri ・ Nov 9 '20

    #oop
    #programming
    #codenewbie
    #tutorial









    Credits πŸ™

    Image by Eak K. on Pixabay





    This article is part of the Refactoring Series.












    How to Improve your Code With easy Refactorings

    Maxi Contieri ・ Oct 24 '22

    #webdev
    #beginners
    #programming
    #tutorial











    More...
Working...