SWDev Impl Patterns

SWDev Impl Patterns: Clarity and Structure

composed method, intention-revealing name, explaining variable, guard clause

1.0 Clarity and Structure

The four patterns in this chapter operate at the level of individual methods and expressions. They share a single goal: a reader encountering a method for the first time should understand its intent immediately, without tracing execution or consulting comments. Beck's central observation is that code is read far more often than it is written. Methods that communicate clearly reduce the cost of every future change: the reader quickly understands what exists before deciding what to add or modify.

1.1 Composed Method

Problem: a method grows to dozens of lines, mixing high-level coordination with low-level detail. Understanding it requires reading the whole thing. Solution: decompose the method into a small number of helper calls, each at the same level of abstraction. The top-level method reads like a prose description of the algorithm; the helpers contain the detail. Beck: divide a program into methods that perform one identifiable task. Keep all of the operations in a method at the same level of abstraction.
// Before: one method at many levels of abstraction
fn process_order(order: &Order) {
    // validate
    if order.items.is_empty() { return Err("empty"); }
    if order.customer.credit_limit < order.total() { return Err("credit"); }
    // reserve inventory
    for item in &order.items {
        inventory.decrement(item.sku, item.qty);
    }
    // write to database
    db.insert("orders", order.to_row());
    for item in &order.items {
        db.insert("order_items", item.to_row());
    }
    // notify
    email.send(order.customer.email, confirmation_text(order));
}

// After: each helper is one level of abstraction
fn process_order(order: &Order) -> Result {
    validate_order(order)?;
    reserve_inventory(order)?;
    persist_order(order)?;
    notify_customer(order)?;
    Ok(())
}
The composed version answers "what does this method do?" in four words per line. A reader interested in how inventory is reserved reads only reserve_inventory.
Composed Method - Notes
Notes
Rule of thumb If you can describe the method in a single sentence without "and" or "then", it is probably composed correctly. If you need "first... then... then...", it needs decomposition.
Size Beck recommends five to ten lines per method. The goal is not brevity for its own sake but ensuring the method is comprehensible at a single level of detail.
Fowler equivalent Extract Method - the primary refactoring that produces composed methods. Name the extracted method for what it does, not how.

1.2 Intention-Revealing Name

Problem: names describe how something is implemented rather than what it means. A reader must trace the implementation to understand the purpose. Solution: name methods, variables, and types for their purpose and role, not their mechanism. The name should make the implementation detail irrelevant to a reader of the calling code. Beck: name methods after what they do, not how they do it.
// Mechanistic names: reader must infer purpose
fn iterate_and_compare(list: &[Item]) -> Vec<Item>
fn check_flag(x: bool) -> bool
let temp2 = result * 0.0875;

// Intention-revealing names: purpose is explicit
fn find_duplicates(list: &[Item]) -> Vec<Item>
fn is_eligible_for_discount(member: &Member) -> bool
let sales_tax = subtotal * TAX_RATE;
Naming - Notes by Language
LanguageConventions that support intention-revealing names
Rust Predicate methods use is_ or has_ prefix. Consuming transformations use verb form (into_string()). Iterators use iter() / iter_mut() / into_iter() to express borrowing intent.
C++ Boolean getters conventionally use is_ or has_. Const member functions signal query intent. Prefer reserve() over set_capacity() when the name matches domain vocabulary.
C# Properties named as nouns; methods named as verb phrases. Boolean properties use Is, Has, Can prefix. LINQ method names (Where, Select, GroupBy) are a model of intention-revealing naming.
Python PEP 8 names methods as verb_noun; predicates as is_condition. Avoid abbreviation. Single-letter variables are acceptable only in short mathematical expressions where the variable is conventional (e.g., i in a loop, x in a coordinate).

1.3 Explaining Variable

Problem: a complex boolean condition, arithmetic expression, or nested function call appears inline. Its meaning is not recoverable without stepping through each term. Solution: assign the expression to a local variable whose name describes what it represents. The variable documents the expression for every reader without requiring a comment. Fowler calls this Introduce Explaining Variable.
// Opaque inline condition
if order.customer.tier == "gold" && order.total() > 500.0
   && !order.has_pending_returns() && calendar.is_business_day(today) {
    apply_priority_discount(order);
}

// Explained with named variables
let is_premium_customer     = order.customer.tier == "gold";
let qualifies_by_amount     = order.total() > 500.0;
let has_clean_account       = !order.has_pending_returns();
let discount_period_active  = calendar.is_business_day(today);

if is_premium_customer && qualifies_by_amount
   && has_clean_account && discount_period_active {
    apply_priority_discount(order);
}
The condition now reads as a policy statement, not a computation. A future maintainer can change the discount eligibility rules by reading and modifying clearly labeled sub-conditions.
Explaining Variable - Notes
Notes
When to use Any expression that requires the reader to evaluate it mentally to understand its meaning. Arithmetic involving named constants, multi-term booleans, and index arithmetic are common candidates.
vs. comment A named variable is better than a comment above the expression: the name travels with the value wherever it is used; a comment does not. Comments also drift out of sync with the code; names are always accurate.
vs. extract method Prefer explaining variable when the expression is local to the method. Extract to a method when the same expression appears in multiple places or when it needs its own tests.

1.4 Guard Clause

Problem: a method handles error cases and precondition failures inside nested if-else blocks. The main logic is buried at a high indentation level; the reader must track multiple nesting levels to reach it. Solution: test preconditions and exceptional inputs at the top of the method and return (or throw) early. The remaining method body contains only the normal case at a single indentation level. Beck calls these guard clauses; Fowler calls the refactoring Replace Nested Conditional with Guard Clauses.
// Before: main logic buried in nesting
fn process_payment(payment: &Payment) -> Result {
    if payment.amount > 0.0 {
        if payment.account.is_active() {
            if !payment.is_duplicate() {
                // ... main logic here, deeply indented
                charge(payment.account, payment.amount);
                Ok(())
            } else {
                Err("duplicate payment")
            }
        } else {
            Err("account inactive")
        }
    } else {
        Err("invalid amount")
    }
}

// After: guard clauses eliminate nesting
fn process_payment(payment: &Payment) -> Result {
    if payment.amount <= 0.0       { return Err("invalid amount"); }
    if !payment.account.is_active() { return Err("account inactive"); }
    if payment.is_duplicate()       { return Err("duplicate payment"); }

    charge(payment.account, payment.amount);
    Ok(())
}
The guard clause version has one level of indentation for the main logic. Each guard states a single rule. Adding another precondition is one line at the top, not another level of nesting.
Guard Clause - Notes
Notes
Early return Guard clauses require early returns. Some style guides prohibit multiple return points; Beck argues this is a misapplication - early returns for guards are always clearer than deep nesting.
Rust / C# The ? operator in Rust and pattern matching naturally produce guard-like code. C# throw expressions (C# 7+) allow guards inline in null-coalescing chains.
Limit Guard clauses are for genuinely exceptional or invalid input. If every caller passes all guards, the guards should be assertions or debug checks, not runtime guards.

1.5 References

Clarity and Structure References
ResourceDescription
Refactoring Guru - Composing Methods Extract Method, Inline Method, Introduce Explaining Variable, and related refactorings that produce composed, clear methods.
Refactoring Guru - Guard Clauses Replace Nested Conditional with Guard Clauses - step-by-step transformation.
Fowler - Function Length Fowler's position on short functions and the Composed Method principle.