Alpha. Vary is under active development and not ready for production use. Syntax, APIs, performance, and behaviour may change between releases.

LLM coding checklist

A compact checklist distilled from Varyonic programming and Designing for mutation. Give this page (or its contents) to an LLM coding assistant as part of its system prompt or project instructions.

Return structured data

DoDon't
Return data types with named fieldsReturn Bool or None for success/failure
Return report objects with counts and listsPrint details to stdout
Return Result[T, E] for fallible opsCall exit() from library code
# Good
data CheckReport {
    success: Bool
    issues: List[Str]
    files_checked: List[Str]
}

def check_site(pages: List[Str]) -> CheckReport { ... }

# Bad
def check_site(pages: List[Str]) -> Bool { ... }

Use domain types

DoDon't
Define enum for closed choicesUse magic strings ("prod")
Define data for domain conceptsUse Dict[Str, Any]
Use Str? for optional valuesUse "" as a sentinel for "missing"
# Good
enum DeployTarget { Local, Preview, Production }

# Bad
def deploy(mode: Str) { if mode == "prod" { ... } }

Write pure functions for core logic

DoDon't
Mark decision logic pure defMix I/O and logic in one function
Put validation and planning in pure functionsRead config inside core logic
Return a plan; execute it separatelyPerform effects inside decision logic
# Good
pure def plan_build(sources: List[Str]) -> BuildPlan { ... }
def run_build(plan: BuildPlan) -> BuildReport { ... }

# Bad
def build(sources: List[Str]) {
    for s in sources { fs.write_text(fs.write_path(s + ".out").unwrap(), compile(s)).unwrap() }
}

Separate decisions from effects

DoDon't
Inputs -> planning -> execution -> reportOne function that reads, decides, acts, prints
Keep effectful functions thinPut print() or exit() in planning logic
Limit exit() to the CLI entry pointCall exit() from library functions

Test with observe, not just success checks

DoDon't
observe report.issues.len() == 0observe result == true
observe report.files_checked.len() == 5observe report is not None
Assert every important fieldAssert only that no error occurred
Test relationships between outputsTest only one output in isolation
# Good
test "plan has correct fields" {
    let plan = plan_deploy("prod", "/var/www/site")
    observe plan.strategy == DeployStrategy.Rsync
    observe plan.source_dir == "build/"
    observe plan.destination == "/var/www/site"
}

# Bad
test "plan works" {
    let plan = plan_deploy("prod", "/var/www/site")
    observe plan is not None
}

Test negative paths

DoDon't
Test empty lists, bad input, boundariesOnly test the happy path
Test "almost valid" inputAssume bad input never arrives
Test every Result.Err branchLeave error branches unexercised

For every important function, test at least: normal success, obvious invalid input, an edge case, a boundary condition, and "almost valid" input.

Use enums and match for finite decisions

DoDon't
match kind { case PageKind.Doc { ... } }if kind == "doc" { ... }
Cover every enum variantLeave a catch-all else hiding cases

Use contracts for invariants

DoDon't
Add in { } preconditionsSilently accept invalid arguments
Add out (result) { } postconditionsHope the caller checks the result
Use old(expr) for before/after stateRely on manual inspection
def parse_doc(md: Str) -> DocPage {
    in { len(md) > 0 }
    out (result) { result.slug != "" }
    ...
}

Use Result for errors, not exceptions

DoDon't
def load(path: Str) -> Result[Doc, Str]def load(path: Str) -> Doc that raises
Propagate with ?else return Err(...)Catch and swallow exceptions
Return Err("descriptive message")Return None for "went wrong"

Extract small helpers

DoDon't
Extract: normalize_link(), classify_change()Inline 20 lines inside a loop body
Name helpers by one responsibilityCreate a utils.vary grab-bag
Keep helpers independently testableAbstract one-time operations

Use defer for resource cleanup

DoDon't
defer { fs.remove_dir(fs.path(dir)).unwrap() } after acquiringClean up manually at function end
One defer per resource, right after creationForget cleanup on early-return paths

Module organization

DoDon't
Split types, logic, and effects into filesPut everything in one file
Name by responsibility: check_plan.varyName generically: helpers.vary
Keep main.vary as a thin CLI shellPut business logic in main.vary

Anti-patterns to avoid

These patterns cause mutation survivors and weak test coverage:

PatternFix
Magic strings instead of enumsDefine an enum
Functions returning None silentlyReturn structured data or Result
Side effects mixed with decisionsSeparate pure planning from execution
Logging instead of structured reportsReturn a report data type
Tests asserting string fragmentsAssert on typed fields
Bare try/except with no re-raiseUse Result or re-raise
Large functions mixing plan and executeSplit into plan + execute
Dict instead of domain modelsDefine data types
Tests that only check "no error"Assert exact output values

Quick reference

Program structure:
  CLI -> Load context -> Pure planning -> Execution layer -> Structured report -> CLI prints + exit

Test structure:
  test "descriptive name" {
      let result = function_under_test(inputs)
      observe result.field1 == expected1
      observe result.field2 == expected2
      observe result.count == result.items.len()   # relational check
  }

File layout:
  types.vary           # data and enum definitions
  logic_plan.vary      # pure planning functions
  logic_execute.vary   # effectful execution
  main.vary            # thin CLI entry point