← ClaudeAtlas

exception-and-result-strategylisted

Decide when C# code should throw, when to return a Result type, what exceptions to define, and what must never be swallowed. Use when reviewing error handling, try/catch blocks, or designing failure contracts for services and APIs.
Sarmkadan/dotnet-senior-skills · ★ 0 · AI & Automation · score 72
Install: claude install-skill Sarmkadan/dotnet-senior-skills
# Exception and Result Strategy ## The dividing line - **Throw** for the exceptional: broken invariants, unreachable states, infrastructure failure, programmer error. The caller cannot meaningfully continue. - **Return a result** for expected domain outcomes the caller must branch on: validation failure, "insufficient funds", "already exists", not-found in a lookup that legitimately misses. The test: if the immediate caller would catch the exception and convert it to control flow, it should have been a return value. Exceptions-as-control-flow costs a stack capture per throw (~microseconds each - ruinous in loops) and hides branches from the reader. ```csharp // WRONG: expected outcome modeled as exception try { await _orders.PlaceAsync(cmd); return Ok(); } catch (InsufficientStockException e) { return Conflict(e.Message); } // RIGHT: expected outcome in the signature var result = await _orders.PlaceAsync(cmd); return result.Match<IActionResult>( order => Ok(order), error => error.Code == "OutOfStock" ? Conflict(error) : BadRequest(error)); ``` Pick one Result implementation for the codebase (ErrorOr, FluentResults, or a 30-line in-house `Result<T>`) and use it only at the application-service boundary. Result types in every private helper produce `.Bind(...)` soup C# has no syntax for; deep internals may throw and let the service method translate. ## Rules for throwing - Guard clauses throw immediately: `ArgumentNullException.ThrowIfNull(x)`, `ArgumentOutOfRange