6.1.1 Decomposition and abstraction in code

From Problem to Code

Decomposition means breaking a complex problem into smaller, manageable sub-problems. Abstraction means focusing on the essential features of each sub-problem while hiding unnecessary detail. Together, they allow a programmer to tackle large problems by writing focused, reusable subprograms — rather than one unmanageable block of code.

In code, decomposition is implemented through functions and procedures: each sub-problem becomes its own subprogram with a clear purpose, defined inputs and (for functions) a returned result.

Worked Example: Calculating a Bill

Problem: a café system needs to calculate a customer's total bill, apply a discount if they are a member, and print a receipt. Decomposed into three sub-problems: calculate subtotal, apply discount, print receipt.

def calculate_subtotal(items):
    """Returns the sum of a list of item prices."""
    total = 0
    for price in items:
        total = total + price
    return total

def apply_discount(subtotal, is_member):
    """Returns the discounted total if the customer is a member."""
    DISCOUNT_RATE = 0.10   # 10% member discount
    if is_member:
        return subtotal * (1 - DISCOUNT_RATE)
    else:
        return subtotal

def print_receipt(subtotal, final_total, is_member):
    """Prints a formatted receipt."""
    print("--- Receipt ---")
    print("Subtotal: £" + str(round(subtotal, 2)))
    if is_member:
        print("Member discount applied (10%)")
    print("Total: £" + str(round(final_total, 2)))

# Main program
items = [3.50, 1.20, 4.75]
subtotal = calculate_subtotal(items)
total = apply_discount(subtotal, True)
print_receipt(subtotal, total, True)

Each function does one job. apply_discount() does not need to know how the subtotal was calculated — it just uses the value passed to it. This is abstraction in action.

using System;
using System.Collections.Generic;

class CafeSystem {
    static double CalculateSubtotal(List<double> items) {
        double total = 0;
        foreach (double price in items) {
            total += price;
        }
        return total;
    }

    static double ApplyDiscount(double subtotal, bool isMember) {
        const double DISCOUNT_RATE = 0.10;
        if (isMember)
            return subtotal * (1 - DISCOUNT_RATE);
        else
            return subtotal;
    }

    static void PrintReceipt(double subtotal, double finalTotal, bool isMember) {
        Console.WriteLine("--- Receipt ---");
        Console.WriteLine("Subtotal: £" + Math.Round(subtotal, 2));
        if (isMember)
            Console.WriteLine("Member discount applied (10%)");
        Console.WriteLine("Total: £" + Math.Round(finalTotal, 2));
    }

    static void Main() {
        var items = new List<double> { 3.50, 1.20, 4.75 };
        double subtotal = CalculateSubtotal(items);
        double total = ApplyDiscount(subtotal, true);
        PrintReceipt(subtotal, total, true);
    }
}
Module CafeSystem
    Function CalculateSubtotal(items As List(Of Double)) As Double
        Dim total As Double = 0
        For Each price As Double In items
            total = total + price
        Next
        Return total
    End Function

    Function ApplyDiscount(subtotal As Double, isMember As Boolean) As Double
        Const DISCOUNT_RATE As Double = 0.10
        If isMember Then
            Return subtotal * (1 - DISCOUNT_RATE)
        Else
            Return subtotal
        End If
    End Function

    Sub PrintReceipt(subtotal As Double, finalTotal As Double, isMember As Boolean)
        Console.WriteLine("--- Receipt ---")
        Console.WriteLine("Subtotal: £" & Math.Round(subtotal, 2))
        If isMember Then
            Console.WriteLine("Member discount applied (10%)")
        End If
        Console.WriteLine("Total: £" & Math.Round(finalTotal, 2))
    End Sub

    Sub Main()
        Dim items As New List(Of Double) From {3.50, 1.20, 4.75}
        Dim subtotal As Double = CalculateSubtotal(items)
        Dim total As Double = ApplyDiscount(subtotal, True)
        PrintReceipt(subtotal, total, True)
    End Sub
End Module

Benefits of Decomposition in Code

  • Easier to write: each function is a small, focused problem — simpler to reason about than one large block
  • Easier to test: each function can be tested independently with known inputs and expected outputs
  • Reusable: apply_discount() could be used in other parts of the program (e.g. a takeaway system) without rewriting the logic
  • Easier to maintain: if the discount rate changes, only one function needs editing — not every place discount logic appears
  • Readable: the main program reads like a description of the task: calculate, apply discount, print

 Key Takeaways

  • Decomposition breaks a problem into sub-problems; each becomes a function or procedure in code.
  • Abstraction hides detail — a function's caller only needs to know what it does, not how.
  • Functions should do one job and have a clear, descriptive name.
  • Decomposed code is easier to write, test, reuse and maintain than monolithic code.