cc-control-flow-quality

Use when code has deep nesting (3+ levels), complex conditionals, loop design questions, high cyclomatic complexity (McCabe >10), or callback hell. Symptoms: arrow-shaped code, repeated conditions, confusing loop exits, lengthy if-else chains

211 stars

Best use case

cc-control-flow-quality is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Use when code has deep nesting (3+ levels), complex conditionals, loop design questions, high cyclomatic complexity (McCabe >10), or callback hell. Symptoms: arrow-shaped code, repeated conditions, confusing loop exits, lengthy if-else chains

Teams using cc-control-flow-quality should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/cc-control-flow-quality/SKILL.md --create-dirs "https://raw.githubusercontent.com/ryanthedev/code-foundations/main/skills/cc-control-flow-quality/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/cc-control-flow-quality/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How cc-control-flow-quality Compares

Feature / Agentcc-control-flow-qualityStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Use when code has deep nesting (3+ levels), complex conditionals, loop design questions, high cyclomatic complexity (McCabe >10), or callback hell. Symptoms: arrow-shaped code, repeated conditions, confusing loop exits, lengthy if-else chains

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

Related Guides

SKILL.md Source

# Skill: cc-control-flow-quality

## STOP - Never Skip (even under time pressure)

- Check nesting depth before adding levels (max 3)
- Verify loop exit conditions are reachable
- Name loop indexes meaningfully in nested loops
- McCabe complexity >10 = redesign required

---

## Quick Reference

| Threshold | Value | Source |
|-----------|-------|--------|
| Max nesting depth | 3 levels | Chomsky, Weinberg via Yourdon 1986a |
| McCabe complexity | >10 decision points = redesign | McCabe 1976 |
| Else clause consideration | 50-80% of ifs need else | Elshoff 1976 |
| Loop-with-exit comprehension | 25% better than top/bottom test | Soloway 1983 |
| Mental entity limit | 5-9 items | Miller 1956 |

**Key Principles:**
- Use `true`/`false` not `0`/`1` for booleans
- Use `for` when iteration count is known, `while` when unknown, `foreach` for collections
- Test at beginning (may not execute) vs end (executes at least once)
- Put nominal case in `if`, error case in `else` (exception: guard clauses invert this intentionally—see below)
- Fully parenthesize complex boolean expressions

**NEVER Skip (even under time pressure):**
- Check nesting depth before adding levels
- Verify loop exit conditions are reachable
- Name loop indexes meaningfully in nested loops

## Core Patterns

### Deep Nesting → Guard Clauses

**Reconciling with "nominal in if":** Guard clauses are an *exception pattern* where error/invalid cases exit early at the top of a function. This contradicts the general "nominal in if" rule *intentionally*—the goal is to get preconditions out of the way so the nominal path flows without nesting. Use guard clauses at function entry; use "nominal in if" for conditionals within the function body.

```cpp
// BEFORE: Arrow-shaped code (4 levels) [ANTI-PATTERN]
if (file.validName()) {
    if (file.open()) {
        if (encryptionKey.valid()) {
            if (file.decrypt(encryptionKey)) {
                // lots of code (nominal case buried)
            }
        }
    }
}

// AFTER: Guard clauses flatten structure
if (!file.validName()) return;
if (!file.open()) return;
if (!encryptionKey.valid()) return;
if (!file.decrypt(encryptionKey)) return;

// lots of code (nominal case at top level)
```

### Complex Boolean → Intermediate Variables
```cpp
// BEFORE: Monstrous test [ANTI-PATTERN]
if (inputStatus == SUCCESS && printerReady && !printerBusy &&
    paperAvailable && paperLevel > MIN_PAPER &&
    documentValid && !documentEmpty && documentSize < MAX_SIZE) {
    print();
}

// AFTER: Named intermediate booleans
bool printerCanPrint = printerReady && !printerBusy &&
                       paperAvailable && paperLevel > MIN_PAPER;
bool documentCanBePrinted = documentValid && !documentEmpty &&
                            documentSize < MAX_SIZE;

if (inputStatus == SUCCESS && printerCanPrint && documentCanBePrinted) {
    print();
}
```

### Meaningless Loop Indexes → Descriptive Names
```java
// BEFORE: Impossible to verify correct array access [ANTI-PATTERN]
for (int i = 0; i < numPayCodes; i++) {
    for (int j = 0; j < 12; j++) {
        for (int k = 0; k < numDivisions; k++) {
            sum = sum + transaction[j][i][k];  // Is this right? Who knows!
        }
    }
}

// AFTER: Self-documenting and verifiable
for (int payCodeIdx = 0; payCodeIdx < numPayCodes; payCodeIdx++) {
    for (int month = 0; month < 12; month++) {
        for (int divisionIdx = 0; divisionIdx < numDivisions; divisionIdx++) {
            sum = sum + transaction[month][payCodeIdx][divisionIdx];
        }
    }
}
```

### Single-Use Test → Named Boolean Function
```cpp
// BEFORE: Complex inline test obscures main flow
if (((('a' <= inputChar) && (inputChar <= 'z')) ||
     (('A' <= inputChar) && (inputChar <= 'Z'))) &&
    (inputChar != 'X') && (inputChar != 'x')) {
    processLetter(inputChar);
}

// AFTER: Named function (even for single use!)
// "Putting a test into a well-named function improves readability,
//  and that's a sufficient reason to do it." [KEY POINT p.433]
bool isProcessableLetter(char c) {
    bool isLetter = (('a' <= c) && (c <= 'z')) ||
                    (('A' <= c) && (c <= 'Z'));
    bool isExcluded = (c == 'X') || (c == 'x');
    return isLetter && !isExcluded;
}

if (isProcessableLetter(inputChar)) {
    processLetter(inputChar);
}
```

### Number-Line Ordering for Comparisons
```cpp
// Range checks: order smallest → variable → largest
if (MIN_VALUE <= count && count <= MAX_VALUE)  // count is between

// Out-of-range: variable on outside
if (count < MIN_VALUE || MAX_VALUE < count)    // count is outside

// This visual mapping aids comprehension [KEY POINT p.440]
```

### Type-Appropriate Zero Comparisons
```cpp
// Boolean: compare implicitly
while (!done)                    // not: while (done == false)

// Numeric: compare explicitly
while (balance != 0)             // not: while (balance)

// Character (C): compare to null terminator explicitly
while (*charPtr != '\0')         // not: while (*charPtr)

// Pointer: compare to NULL explicitly
while (bufferPtr != NULL)        // not: while (bufferPtr)
```

### Loop-With-Exit Avoiding Duplication
```cpp
// BEFORE: Duplicated code that will break under maintenance [ANTI-PATTERN]
GetNextRating(&ratingIncrement);
rating = rating + ratingIncrement;
while ((score < targetScore) && (ratingIncrement != 0)) {
    GetNextScore(&scoreIncrement);
    score = score + scoreIncrement;
    GetNextRating(&ratingIncrement);      // Duplicated!
    rating = rating + ratingIncrement;    // Duplicated!
}

// AFTER: Loop-with-exit eliminates duplication
while (true) {
    GetNextRating(&ratingIncrement);
    rating = rating + ratingIncrement;
    if (!((score < targetScore) && (ratingIncrement != 0))) {
        break;
    }
    GetNextScore(&scoreIncrement);
    score = score + scoreIncrement;
}
// Students scored 25% higher on comprehension with this pattern [Soloway 1983]
```

## Decision Guidance

### Loop Selection
```
1. Is iteration count known ahead of time?
   YES → Use `for` or `foreach`
   NO  → Use `while`

2. Must body execute at least once?
   YES → Use loop with test at END (`do-while`) or MIDDLE
   NO  → Use loop with test at BEGINNING (`while`)

3. Simple iteration through container?
   YES → Use `foreach` (eliminates housekeeping arithmetic errors)
   NO  → Use appropriate `for` or `while`

4. Need to exit from middle of loop?
   YES → Consider loop-with-exit (`while(true)` + `break`)
   NO  → Standard loop structure
```

### Nesting Reduction Techniques (by invasiveness, not strict priority)

These techniques are ordered from least to most invasive changes—**not** a strict "try #1 before #2" sequence. Choose based on what fits the problem:

1. **Guard clauses** - Exit early on error conditions (least invasive; see Core Patterns)
2. **Extract to routine** - Move nested code into well-named function
3. **Combine conditions** - Merge nested ifs into single compound test
4. **Convert to case/switch** - When testing same variable repeatedly
5. **Use polymorphism** - Replace type-checking conditionals with dispatch
6. **Table-driven methods** - When logic maps data to outcomes (most architectural)

**Note:** #5 and #6 are context-dependent, not ranked. Tables can be simpler than polymorphism (see "The fact that a design uses inheritance and polymorphism doesn't make it a good design" [p.423]).

### When to Use Table-Driven Methods
Consider tables instead of logic when:
- Writing 4th+ branch in if-else chain for same classification
- Creating subclass just to change a data value
- Format/rules change frequently without code changes desired
- Same lookup calculation duplicated in multiple places

**"The fact that a design uses inheritance and polymorphism doesn't make it a good design."** [p.423]

### Table Access Method Selection
```
1. Can data key directly into table? (e.g., month 1-12, char code 0-255)
   YES → Direct Access
   NO  → Continue

2. Is keyspace large/sparse but entries few? (e.g., 100 items with 4-digit part numbers)
   YES → Indexed Access (index table + main table)
   NO  → Continue

3. Are entries valid for ranges, not distinct points? (e.g., grade thresholds)
   YES → Stair-Step Access (loop through range boundaries)
   NO  → Reconsider if table-driven is appropriate
```

### Table-Driven Example: Message Processing
```cpp
// BEFORE: 20 routines, one per message type [ANTI-PATTERN]
void processMessage(Message& msg) {
    if (msg.type == CYCLIC_REDUNDANCY_CHECK) {
        processCRC(msg);
    } else if (msg.type == BUOY_TEMPERATURE) {
        processBuoyTemp(msg);
    } else if (msg.type == SONOBUOY_PING) {
        processSonobuoyPing(msg);
    }
    // ... 17 more branches
}

// AFTER: One generic routine + table definition [p.418-420]
// Table describes message format; routine interprets any message
struct FieldDef { FieldType type; string name; };
struct MessageDef { string name; vector<FieldDef> fields; };

map<MessageType, MessageDef> messageTable = {
    {BUOY_TEMPERATURE, {"Buoy Temperature", {
        {FLOAT, "Average Temperature"},
        {FLOAT, "Temperature Range"},
        {INT, "Number of Samples"},
        {STRING, "Location"},
        {TIME, "Time of Measurement"}
    }}}
    // New message types = new table entries, zero code changes
};

void processMessage(Message& msg) {
    auto& def = messageTable[msg.type];
    for (auto& field : def.fields) {
        readAndPrint(field, msg);  // One routine handles all types
    }
}
```

### McCabe Complexity Counting [p.458]
```
How to count decision points in a routine:

1. Start with 1 for the straight path through the routine
2. Add 1 for each: if, while, repeat, for, and, or
3. Add 1 for each case in a case/switch statement

Interpretation:
  0-5   → Routine is probably fine
  6-10  → Start thinking about simplification
  10-20 → Strong case needed to keep as-is; review for extraction
  20+   → Mandatory refactor (no exceptions)
```

**Exception criteria for 10-20 range:**
A routine may legitimately exceed 10 ONLY when ALL of:
1. It's a flat dispatch (switch/case with no nesting within cases)
2. Each case is ≤3 lines (ideally just a function call)
3. Cases are exhaustive and unlikely to grow unboundedly
4. Cognitive complexity is low despite high McCabe count

**NOT a valid exception:** "It's just a big if-else chain" or "The complexity is inherent."

## Emergency Minimum (Crisis Mode)

When production is down and you must fix immediately, you still MUST:

1. **Check nesting depth** - Don't add a 4th level to fix a 3-level problem
2. **Verify loop exits** - Ensure your fix doesn't create infinite loops
3. **Name any new variables meaningfully** - `tempFix` guarantees confusion later
4. **Document WHY** - A one-line comment explaining the crisis fix

**After crisis resolved:** Return within 24 hours to apply full control flow review. Crisis fixes that "work" often hide deeper issues.

## Modern Patterns (Beyond Code Complete)

These patterns weren't covered in Code Complete (2004) but follow the same principles:

### Async/Await (Promise Chains)
```javascript
// BEFORE: Callback hell (deep nesting equivalent)
getData(url, (data) => {
    process(data, (result) => {
        save(result, (saved) => {
            notify(saved, () => { /* ... */ });
        });
    });
});

// AFTER: Flattened with async/await
const data = await getData(url);
const result = await process(data);
const saved = await save(result);
await notify(saved);
```
Same principle as guard clauses: flatten the structure.

### Pattern Matching (Rust, C#, etc.)
```rust
// Pattern matching can exceed McCabe threshold but remain clear
// because it's flat dispatch with exhaustiveness checking
match status {
    Status::Pending => handle_pending(),
    Status::Active => handle_active(),
    Status::Suspended => handle_suspended(),
    Status::Closed => handle_closed(),
}
```
This is the "valid exception" for McCabe—flat dispatch with ≤3 lines per case.

### Functional Pipelines
```javascript
// Functional pipelines replace explicit loops
const result = items
    .filter(item => item.active)
    .map(item => transform(item))
    .reduce((acc, item) => acc + item.value, 0);
```
Prefer over explicit loops when: operations are independent, no early exit needed, and pipeline reads clearly.


---

## Chain

| After | Next |
|-------|------|
| Control flow verified | code-clarity-and-docs (CHECKER) |

Related Skills

cc-quality-practices

211
from ryanthedev/code-foundations

Use when planning QA, choosing review methods, designing tests, or debugging fails. Triggers on: defects found late, tests pass but production bugs, coverage disputes, review ineffective, spending excessive time debugging.

whiteboarding-planning

211
from ryanthedev/code-foundations

Standard/Full planning pipeline for whiteboarding. Steps: discover, classify, explore, detail, save, check, confirm, handoff. Use when dispatched from whiteboarding command for Medium/Complex tasks. Triggers on 'planning pipeline', 'standard track', 'full track'.

welc-legacy-code

211
from ryanthedev/code-foundations

Use when facing untested legacy code, test harness problems, dependency issues, or time pressure. Triggers on: legacy code, no tests, can't test, afraid to change, need to modify untested code.

performance-optimization

211
from ryanthedev/code-foundations

Use when code is too slow, has performance issues, timeouts, OOM errors, high CPU/memory, or doesn't scale. Triggers on: profiler hot spots, latency complaints, needs optimization, critical path analysis.

code-clarity-and-docs

211
from ryanthedev/code-foundations

Use when reviewing code clarity, writing comments, checking documentation accuracy, or auditing AI-facing docs. Triggers on: naming, comments, documentation, README, CLAUDE.md.

clarify

211
from ryanthedev/code-foundations

Decompose user intent through structured brainstorming. Detects underspecification, ambiguity, and false premises through hypothesis-driven questioning. Use when a request is unclear, could have multiple valid interpretations, or critical details are missing.

cc-routine-and-class-design

211
from ryanthedev/code-foundations

Use when designing routines or classes, reviewing class interfaces, choosing between inheritance and containment, or evaluating routine cohesion. Also trigger when inheritance is used without LSP verification, or when design issues are present despite passing tests

cc-refactoring-guidance

211
from ryanthedev/code-foundations

Use when modifying existing code, improving structure without changing behavior, or deciding between refactor, rewrite, or fix-first.

cc-pseudocode-programming

211
from ryanthedev/code-foundations

Use when designing routines, stuck on where to start coding, caught in compile-debug loops, or code works but you don't understand why. Triggers on: starting a new coding task

cc-defensive-programming

211
from ryanthedev/code-foundations

Use when auditing defensive code, designing barricades, choosing assertion vs error handling, or deciding correctness vs robustness strategy. Triggers on: empty catch blocks, missing input validation, assertions with side effects, wrong exception abstraction level, garbage in garbage out mentality, deadline pressure to skip validation, trusted source rationalization.

ca-architecture-boundaries

211
from ryanthedev/code-foundations

Use when designing system architecture, drawing boundaries between business logic and infrastructure, or when changes touch many unrelated files. Triggers on: architecture design, dependency direction, separating business rules from database/UI/frameworks.

aposd-verifying-correctness

211
from ryanthedev/code-foundations

Use after implementing code. Triggers on: is it done, ready to commit, verify correctness, did I miss anything, pre-commit check.