Skip to main content

Day 17 - Beware of global variables

TODO: these examples are too simple / repetitive. Revise.

Outcomes:

Pitfalls of Global Variables

1. Introduction (10 minutes)

A. Global Variables and Mutable State

  • Definition:
    • Global variables are defined at the top level and can be accessed or modified by multiple functions.
  • Problems for Testing:
    • They create hidden dependencies between functions.
    • Tests may pass or fail based on the order in which functions are called.
    • They reduce "referential transparency" (functions, given same input, may no longer produce same output), making it hard to test functions in isolation, hard to understand function behavior.

B. Learning Goals

  • Understand how global mutable state can interfere with testing.
  • See concrete Pyret examples showing these issues.
  • Discuss best practices for isolating function behavior.

2. Demonstration: Global Variables and Testing Difficulties (25 minutes)

A. Example Scenario: A Global Counter

  • Setup:

    • We define a global variable global-counter that is updated by different functions.
  • Pyret Code Example:

    # Global variable declaration
    var global-counter = 0

    # Function that increments the counter by a given value
    fun increment-counter(x :: Number) -> Number block:
    global-counter := global-counter + x
    global-counter
    end

    # Function that resets the counter to 0
    fun reset-counter() -> Nothing:
    global-counter := 0
    end

B. Testing Without Isolation

  • Test Case Example:

    # Test Block 1: Reset state before testing
    check:
    reset-counter()
    increment-counter(5) is 5
    increment-counter(3) is 8
    end

    # Test Block 2: No reset between tests
    check:
    # No call to reset-counter here!
    increment-counter(2) is ? # What should the result be?
    end
  • Discussion:

    • Ask: “If Test Block 1 runs first, what will the result in Block 2 be? What if Block 2 runs in isolation?”
    • Key Point:
      • Since global-counter is shared, Block 2’s outcome depends on whether the counter was reset. Tests become order‑dependent and non‑repeatable.

C. Real-World Consequences

  • Side Effects and Hidden Dependencies:
    • Functions that rely on global variables cause side effects that “leak” between tests.
    • Example: A logging function that appends to a global log makes it hard to test without first clearing the log.
    • Harder to test is also harder to debug, harder to understand!
  • Interactive Exercise:
    • Ask students: “Predict the value of global-counter after the following sequence:
      reset-counter()
      increment-counter(4) # Expected: 4
      increment-counter(6) # Expected: 10
      # Then later call increment-counter(2) without a reset.
      What is the final value?”
    • Discuss the potential for intermittent test failures when state is not controlled.

3. Discussion and Best Practices (15 minutes)

A. Pure Functions Are Easier to Test

  • Referential Transparency:

    • A pure function’s output depends only on its inputs, making it predictable and easy to test.
  • Example Contrast:

    # Impure function with global state
    fun increment-counter(x :: Number) -> Number block:
    global-counter := global-counter + x
    global-counter
    end

    # Pure function version
    fun pure-increment(total :: Number, x :: Number) -> Number:
    total + x
    end
  • Testing pure-increment is straightforward because it has no side effects.

B. Strategies to Avoid Global State

  • Pass Variables as Parameters:
    • Instead of using a global variable, pass the current state as a parameter.
  • Localize Mutable State:
    • Use local accumulators within functions (e.g., within loops) so that state does not leak.
  • Isolate Side Effects:
    • If side effects are necessary, confine them to a small portion of your code and reset state between tests.

C. Group Discussion

  • Interactive Question:
    • “How would you rewrite our counter example so that each test can run in isolation without depending on a global variable?”
    • Encourage students to share ideas (e.g., using pure functions or passing the counter as a parameter).

4. Wrap-Up (10 minutes)

  • Global mutable state (global variables) can interfere with testing by introducing hidden dependencies.
  • Tests may behave unpredictably if global state isn’t reset between function calls.
  • Pure functions—those that depend only on their inputs—are much easier to test and reason about.
  • Emphasize that designing code to minimize or isolate side effects leads to more maintainable and testable software.