Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Wide Struct Audit

Status: Current Last modified: 2026-10-20 (commit f7e32e22)

A repository-wide audit rule for struct shape. Applies to the crates in TalkBank/chatter (model, parser, transform, CLI, CLAN, LSP, cache, and related tooling). This page is scoped to that repository.

A struct with many fields is not automatically wrong. The smell is:

  • many unrelated concerns packed into one value
  • several related booleans that act like implicit policy enums
  • repeated field-name prefixes that point to missing sub-structs
  • parallel vectors or stringly runtime fields
  • runtime code reaching into many unrelated fields of the same value

The repo therefore treats 10 or more named fields as an audit threshold, not as an automatic ban.

Categories

Wide structs fall into four categories.

1. Boundary shim, may stay wide

CLI, JSON, or clap boundary types. Acceptable if they are converted into typed policies or sub-structs before entering core runtime code.

Examples: ValidateDirectoryOptions, clap-facing CLI arg structs, JSON boundary records.

2. Transport or schema record, may stay wide

DB rows, HTTP response shapes, JSON schema mirrors. Acceptable as long as they don’t become the internal runtime shape.

Examples: WordJsonSchema, DbMetadata, CoverageReport.

3. Real aggregate, may stay wide

Domain values whose fields all answer one coherent question and whose callers consume the whole rather than spelunking through unrelated subsets.

Examples: metric/report records like SpeakerEval, SpeakerKideval, SpeakerComplexity, and SpeakerFluency (report records, not runtime coordination).

4. Refactor target, must be split

Mix of policy and state, multiple responsibilities, or callers needing to know the whole subsystem to use a subset of fields.

Design Rules

  1. Treat 10 or more named fields as an audit trigger.
  2. Treat 3 or more related boolean fields as a smell even below that threshold.
  3. Boundary and transport records may stay wide when they mirror a real external shape.
  4. Runtime coordination structs prefer named sub-structs over flat bags.
  5. Replace parallel vectors with per-item records where possible.
  6. If a wide struct stays wide, record the reason in the surrounding design docs, audit notes, or code review rather than letting it remain unexplained.

Refactor Examples

ValidateDirectoryOptions (chatter)

Format, cache, roundtrip, parser, audit and TUI settings are grouped by concern rather than held as a flat bag:

  • ValidationRules
  • ValidationExecution
  • ValidationPresentation (one enum: Streamed(Lines | Json | Audit) or Tui, resolved once from --format, --quiet, --audit and --tui-mode; one value stands in for a format, a quiet flag, an interface flag and an audit path, so no precedence between them is ever evaluated)
  • the resolved --suppress codes
  • renderer-facing RunNotice values

(There is no traversal-mode field: every invocation is an explicit file list, so no traversal choice exists to carry.)

Shape this audit wants for policy-rich CLI boundaries: one small top-level struct with explicit sub-objects and enums rather than a dozen flat fields.

ParseHealth (talkbank-model)

ParseHealth stores taint as a compact tier bitset keyed by ParseHealthTier, not parallel booleans. The enclosing ParseHealthState distinguishes Unknown, parser-backed Clean, explicit Constructed, and Tainted(ParseHealth). A zero bitset is not interchangeable with unknown provenance or proof of model validity.

flowchart LR
    tier["ParseHealthTier"] --> set["Tier health set"]
    set --> checks["Alignment safety checks"]

Review candidates

TUI state bags

Inspect state ownership by concern (selection, progress, render flags and status) before proposing a change:

  • crates/chatter/src/ui/validation_tui/state.rs TuiState

Backend (talkbank-lsp)

crates/talkbank-lsp/src/backend/state.rs is a service-root aggregate. Review whether document/cache, validation and language-service responsibilities have coherent owners. Its field count alone does not demonstrate a defect.

These are review prompts, not a current defect inventory or authorization for a broad refactor. Inspect current source and callers before changing a type.

Metric structs

SpeakerEval/SpeakerKideval are examples of report records. If output renderers keep needing subsets (lexical metrics, morphosyntax metrics, error counts, derived scores), those records should eventually nest along those lines.

Audit Guardrail

There is currently no repo-local automated wide-struct lint in TalkBank/chatter. Treat this page as a manual review checklist and refactor trigger: when a type grows past the threshold, decide explicitly whether it is an acceptable boundary/schema aggregate or a real split target.


This page last changed: 2026-10-20 (commit f7e32e22). The whole book last changed: 2026-10-27 (commit 5bfee2f9).