include::../intro.adoc[] == Why is this an issue? include::../why.adoc[] === What is the potential impact? include::../impact.adoc[] == How to fix it include::../how-with-null-safe.adoc[] === Code examples **Extraction of a complex condition in a new function.** ==== Noncompliant code example The code is using a complex condition and has a cognitive cost of 3. [source,csharp,diff-id=1,diff-type=noncompliant] ---- decimal CalculateFinalPrice(User user, Cart cart) { decimal total = CalculateTotal(cart); if (user.HasMembership() // +1 (if) && user.OrdersCount > 10 // +1 (more than one condition) && user.AccountActive && !user.HasDiscount || user.OrdersCount == 1) // +1 (change of operator in condition) { total = ApplyDiscount(user, total); } return total; } ---- ==== Compliant solution Even if the cognitive complexity of the whole program did not change, it is easier for a reader to understand the code of the `calculateFinalPrice` function, which now only has a cognitive cost of 1. [source,csharp,diff-id=1,diff-type=compliant] ---- decimal CalculateFinalPrice(User user, Cart cart) { decimal total = CalculateTotal(cart); if (IsEligibleForDiscount(user)) // +1 (if) { total = applyDiscount(user, total); } return total; } bool IsEligibleForDiscount(User user) { return user.HasMembership() && user.OrdersCount > 10 // +1 (more than one condition) && user.AccountActive && !user.HasDiscount || user.OrdersCount == 1; // +1 (change of operator in condition) } ---- **Break down large functions.** ==== Noncompliant code example For example, consider a function that calculates the total price of a shopping cart, including sales tax and shipping. + __Note:__ The code is simplified here, to illustrate the purpose. Please imagine there is more happening in the `foreach` loops. [source,csharp,diff-id=3,diff-type=noncompliant] ---- decimal CalculateTotal(Cart cart) { decimal total = 0; foreach (Item item in cart.Items) // +1 (foreach) { total += item.Price; } // calculateSalesTax foreach (Item item in cart.Items) // +1 (foreach) { total += 0.2m * item.Price; } //calculateShipping total += 5m * cart.Items.Count; return total; } ---- This function could be refactored into smaller functions: The complexity is spread over multiple functions and the complex `CalculateTotal` has now a complexity score of zero. ==== Compliant solution [source,csharp,diff-id=3,diff-type=compliant] ---- decimal CalculateTotal(Cart cart) { decimal total = 0; total = CalculateSubtotal(cart, total); total += CalculateSalesTax(cart, total); total += CalculateShipping(cart, total); return total; } decimal CalculateSubtotal(Cart cart, decimal total) { foreach (Item item in cart.Items) // +1 (foreach) { total += item.Price; } return total; } decimal CalculateSalesTax(Cart cart, decimal total) { foreach (Item item in cart.Items) // +1 (foreach) { total += 0.2m * item.Price; } return total; } decimal CalculateShipping(Cart cart, decimal total) { total += 5m * cart.Items.Count; return total; } ---- **Avoid deep nesting by returning early.** ==== Noncompliant code example The below code has a cognitive complexity of 6. [source,csharp,diff-id=4,diff-type=noncompliant] ---- decimal CalculateDiscount(decimal price, User user) { if (IsEligibleForDiscount(user)) // +1 ( if ) { if (user.HasMembership()) // +2 ( nested if ) { return price * 0.9m; } else if (user.OrdersCount == 1) // +1 ( else ) { return price * 0.95m; } else // +1 ( else ) { return price; } } else // +1 ( else ) { return price; } } ---- ==== Compliant solution Checking for the edge case first flattens the `if` statements and reduces the cognitive complexity to 3. [source,csharp,diff-id=4,diff-type=compliant] ---- decimal CalculateDiscount(decimal price, User user) { if (!IsEligibleForDiscount(user)) // +1 ( if ) { return price; } if (user.HasMembership()) // +1 ( if ) { return price * 0.9m; } if (user.OrdersCount == 1) // +1 ( else ) { return price * 0.95m; } return price; } ---- **Use the null-conditional operator to access data.** In the below code, the cognitive complexity is increased due to the multiple checks required to access the manufacturer's name. This can be simplified using the optional chaining operator. ==== Noncompliant code example [source,csharp,diff-id=2,diff-type=noncompliant] ---- string GetManufacturerName(Product product) { string manufacturerName = null; if (product != null && product.Details != null && product.Details.Manufacturer != null) // +1 (if) +1 (multiple condition) { manufacturerName = product.Details.Manufacturer.Name; } if (manufacturerName != null) // +1 (if) { return manufacturerName; } return "Unknown"; } ---- ==== Compliant solution The optional chaining operator will return `null` if any reference in the chain is `null`, avoiding multiple checks. The `??` operator allows to provide the default value to use. [source,csharp,diff-id=2,diff-type=compliant] ---- string GetManufacturerName(Product product) { return product?.Details?.Manufacturer?.Name ?? "Unknown"; } ---- === Pitfalls As this code is complex, ensure that you have unit tests that cover the code before refactoring. include::../resources.adoc[] include::../rspecator-dotnet.adoc[]