Skip to content
Merged
9 changes: 9 additions & 0 deletions src/docs/Coding-Standards/Functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ Functions are the unit of intent. A good function does one thing, says what it d
- Keep it small enough to hold in your head. Whether it fits on a screen is a better test than a line count.
- One level of abstraction per function β€” don't mix high-level orchestration with low-level detail in the same body.

## Reuse before you build

Before writing new logic, use what already exists β€” and build only what does not. This is DRY and one responsibility applied across the whole codebase, not just within one function.

- Prefer a built-in. If the language or runtime already does the job, use it instead of a hand-rolled version.
- Reuse an existing function instead of re-implementing it. If it is the weak link β€” too slow or imprecise on a hot path β€” fix it there so every caller benefits, rather than working around it.
- Take a dependency on a trusted module for a larger capability that already exists elsewhere; declare it explicitly instead of copying it in.
- Build it only when nothing fits β€” no built-in, no existing function, no trusted dependency. Size the build to the need: small logic lives inline where it is used; a larger, cohesive capability becomes its own module.

## Signatures are contracts

- Type the parameters and return value where the language allows. The signature documents intent and lets tooling catch misuse before it runs.
Expand Down
3 changes: 3 additions & 0 deletions src/docs/Coding-Standards/PowerShell/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ Beyond the basics, these language-specific habits keep PowerShell correct and fa
- **Single-quote strings unless you need expansion.** Use `'literal'` by default; reserve `"...$var..."` for interpolation or escape sequences, and here-strings (`@'...'@`, `@"..."@`) for multi-line text β€” literal-versus-interpolated intent then stays obvious.
- **Splat calls that carry many parameters.** Build a `@{}` of parameters and splat it (`Get-Thing @params`) instead of a long line of `-Param value` pairs or backtick continuations; it reads better and diffs cleanly.
- **Put `$null` on the left of a comparison** β€” `$null -eq $x`, never `$x -eq $null`. Against a collection the right-hand form *filters* rather than tests. Use `-contains` / `-in` for membership, never `-eq`.
- **Reuse before you build.** Work down the [reuse order](../Functions.md#reuse-before-you-build) β€” a built-in cmdlet or operator, then an existing function (public or private), then a trusted module (`#Requires -Modules` / `RequiredModules`), then your own code (small logic inline, a larger capability as its own module).
- **PowerShell already *is* .NET; work at that level rather than wrapping it.** Casts, type accelerators (`[datetime]`, `[int]`), the `-split` / `-replace` / `-match` operators, and member methods (`.Trim()`, `.Where()`) all resolve to the base class library β€” using .NET means reaching for BCL types and methods for the computation, not restating everything as `[Namespace.Type]::Method(...)`. Where idiomatic PowerShell already resolves to the same .NET call, leave it; reach for explicit .NET only where it is measurably faster or more precise, and keep cmdlets and the pipeline where you need them for glue or readability.
- **Do the work in .NET when you implement it.** When you write the logic yourself β€” or fix an internal function that is too slow or imprecise on a hot path β€” call the .NET base class library directly instead of a cmdlet pipeline: `[System.IO.File]::ReadAllText($path)` over `Get-Content -Raw`, `[System.IO.Path]::Combine(...)` for paths, `[System.Text.StringBuilder]` for repeated concatenation, `[int]::TryParse(...)` for parsing. .NET methods are faster and their contracts are precise; keep cmdlets where their clarity is worth more than the speed. The next two rules are specific cases.
- **Suppress unwanted output with `$null = ...`** (or `[void]` for method calls), not `| Out-Null` β€” the pipeline form is markedly slower on hot paths.
- **Build collections with a typed list, not `+=` in a loop.** `$a += $x` reallocates the whole array every iteration; use `[System.Collections.Generic.List[T]]` with `.Add()`, and prefer a cmdlet's `-Filter` over piping to `Where-Object` on large sets.
- **Keep secrets out of source, and never `Invoke-Expression` untrusted input.** Accept credentials as a `[PSCredential]` parameter with the `[Credential()]` attribute rather than calling `Get-Credential` inside a reusable function, so a caller can pass one they already hold, and take other sensitive values as `[securestring]`. Guard state-changing commands with `ShouldProcess` (see [Functions](Functions.md)); the wider rules live in the [Security](../Security.md) baseline.
Expand Down