Best Practices in Architecture and .NET Development

“Best practice” is one of the most abused terms in software engineering. It usually means “what looks defensible in a slide deck” rather than “what actually works in production”. The articles in this collection treat the label with skepticism — every practice is a trade-off, and the only honest question is which trade-off applies to the situation in front of you.

The framing across these articles is consistent: a practice is worth adopting when the failure mode it prevents matters more than the overhead it adds. SOLID principles are a starting point, not a creed. Clean Code passages get quoted in code reviews far more often than the code in the repository reflects them. The articles examine where the gap between stated practice and actual behavior comes from and what changes that gap in real teams.

Specific topics span the .NET stack and the systems built on top of it. Defensive programming with throw helpers, multi-targeting build hygiene, central package management, structured logging that does not lie about what happened, mutation testing that exposes the blind spots line coverage hides, and TimeProvider adoption two years after it shipped — each is treated as a concrete intervention with measurable cost and benefit rather than a virtue to be signaled.

A separate cluster addresses the practices that hold up under pressure: incident response procedures rehearsed before the incident, audit logging that survives the audit, access control that fails closed, and feedback loops that catch regressions before customers do. The recurring observation is that practices fail not because they are wrong but because they are applied as rituals rather than as decisions with named consequences.

The collection also documents the practices the author has changed his mind about. Calling a pattern a best practice today and a mistake five years later is normal — the discipline is naming why the context shifted, not pretending the original advice was always correct.

Incremental Source Generators Done Right: Ship It Without Breaking Consumers

Packaging Generators

Parts 1 through 3 of this series made the generator correct, incremental, and proven by tests. None of it reaches a single consumer if the NuGet package is laid out wrong — and the failure modes are silent: a DLL in lib/ instead of analyzers/dotnet/cs simply never loads, a missing dependency throws only inside the compiler, and a marker attribute delivered carelessly breaks InternalsVisibleTo in ways users cannot diagnose. This final part covers the packaging contract: the netstandard2.0 rule, the package layout, dependency bundling, attribute delivery, debugging the shipped bits, and the analyzer hygiene rules that keep you honest.
Incremental Source Generators Done Right: Prove the Cache Hits

Prove the Cache Hits

You followed the equality rules, you shaped the pipeline correctly, and your generator still might be re-running on every keystroke — because nobody ever asked the driver. Roslyn records exactly why each pipeline step re-executed, and you can assert against those reasons in a plain xUnit test. This is the test that turns “my generator is incremental” from a claim into a regression-protected fact, plus the catalog of anti-patterns it catches in the wild.
Incremental Source Generators Done Right: Pipeline Patterns That Scale

Pipeline Patterns

Part 1 of this series established the equality contract: every value flowing through an incremental generator pipeline must be comparable by value, or the cache misses and the generator re-runs on every keystroke. This part is about the pipeline itself — ForAttributeWithMetadataName as the entry point, where to filter, where to transform, how to combine providers without accidentally subscribing to the entire Compilation, and the Collect trap that silently breaks everything you fixed in part 1. It ends with a complete worked generator you can steal.
Incremental Source Generators Done Right: The Equality Contract

The Equality Contract

IIncrementalGenerator exists for one reason: to run only when its inputs actually change. Most generators fail at exactly that, because somewhere in the pipeline a value is compared by reference and the cache silently misses. This is part 1 of a 4-part series on getting incrementalism right, and it starts where every broken generator starts: the equality contract, the ImmutableArray trap, and the EquatableArray that fixes it.
Structured Logging Patterns That Actually Survive Production

Structured Logging That Survives

Every pattern here addresses a failure mode I have either shipped or inherited. Source generators on hot paths, scope opt-in per provider, end-to-end correlation ID propagation, log levels as an ops contract, sink selection as an architecture decision, and OpenTelemetry Logs for greenfield services: six concrete changes that make structured logging trustworthy in production.