From 27db155b850c9322feb9e691f832b7679fa295bf Mon Sep 17 00:00:00 2001 From: Julian Lechner Date: Thu, 24 Sep 2026 21:46:50 +0200 Subject: [PATCH] refactor(rules): split C# guidance by topic --- shared/rules/csharp.md | 117 ------------------ shared/rules/csharp/index.md | 14 +++ .../references/architecture-and-apis.md | 15 +++ .../references/async-and-concurrency.md | 13 ++ .../rules/csharp/references/coding-style.md | 13 ++ shared/rules/csharp/references/performance.md | 14 +++ .../csharp/references/safety-and-errors.md | 14 +++ shared/rules/csharp/references/security.md | 14 +++ .../references/testing-and-dependencies.md | 10 ++ .../references/types-and-collections.md | 11 ++ 10 files changed, 118 insertions(+), 117 deletions(-) delete mode 100644 shared/rules/csharp.md create mode 100644 shared/rules/csharp/index.md create mode 100644 shared/rules/csharp/references/architecture-and-apis.md create mode 100644 shared/rules/csharp/references/async-and-concurrency.md create mode 100644 shared/rules/csharp/references/coding-style.md create mode 100644 shared/rules/csharp/references/performance.md create mode 100644 shared/rules/csharp/references/safety-and-errors.md create mode 100644 shared/rules/csharp/references/security.md create mode 100644 shared/rules/csharp/references/testing-and-dependencies.md create mode 100644 shared/rules/csharp/references/types-and-collections.md diff --git a/shared/rules/csharp.md b/shared/rules/csharp.md deleted file mode 100644 index 434a118..0000000 --- a/shared/rules/csharp.md +++ /dev/null @@ -1,117 +0,0 @@ -# C# Code Guidelines - -* Write clear, readable, maintainable code first. -* Optimize only based on measurements and profiling. -* Prefer simple solutions over clever abstractions. -* Follow current C# and .NET conventions. -* Prefer immutable data where practical. -* Keep methods small and focused. -* Keep classes focused on a single responsibility. -* Avoid unnecessary inheritance. -* Prefer composition over inheritance. -* Use `sealed` when inheritance is not intended. -* Avoid unnecessary abstractions and wrapper layers. -* Avoid premature generalization. -* Prefer explicit code over hidden magic. -* Avoid unnecessary reflection. -* Avoid unnecessary runtime type checks. -* Avoid unnecessary allocations in hot paths. -* Avoid unnecessary collection copies. -* Avoid unnecessary string allocations. -* Avoid unnecessary LINQ in performance-critical paths. -* Use LINQ where it improves readability and performance is irrelevant. -* Choose collections based on access patterns. -* Preallocate collection capacity when the expected size is known. -* Avoid large mutable structs. -* Use `Span` and related APIs only when they provide a measurable benefit. -* Prefer `Task` over `ValueTask` unless `ValueTask` is justified by profiling. -* Use async only for genuinely asynchronous operations. -* Keep async code async throughout the call chain. -* Avoid `.Result`, `.Wait()` and synchronous blocking of async operations. -* Propagate `CancellationToken` through asynchronous APIs. -* Do not create unnecessary background tasks. -* Limit concurrency when processing large workloads. -* Avoid uncontrolled `Task.WhenAll` over large collections. -* Avoid exceptions for normal control flow. -* Fail fast on invalid arguments and invalid state. -* Avoid swallowing exceptions. -* Preserve meaningful exception context. -* Use dependency injection where dependencies require substitution or lifecycle management. -* Avoid service locator patterns. -* Avoid excessive constructor dependencies. -* Keep application, domain and infrastructure concerns separated. -* Avoid unnecessary cross-layer coupling. -* Prefer explicit dependencies. -* Minimize global and static mutable state. -* Design APIs with clear contracts. -* Prefer strongly typed models over loosely typed dictionaries or dynamic objects. -* Validate input at system boundaries. -* Avoid duplicate validation deep inside trusted internal code. -* Treat external input as untrusted. -* Do not expose internal implementation details through public APIs. -* Avoid leaking sensitive information through logs or exceptions. -* Use structured logging. -* Avoid excessive logging in hot paths. -* Avoid logging sensitive data. -* Prefer database-side filtering, projection and aggregation. -* Do not load data that is not needed. -* Avoid N+1 database queries. -* Use no-tracking queries for read-only EF Core operations where appropriate. -* Keep database round trips minimal. -* Avoid unnecessary serialization and deserialization. -* Reuse expensive resources such as HTTP clients and database infrastructure correctly. -* Do not optimize micro-level CPU operations before fixing architectural, database or I/O bottlenecks. -* Prefer algorithms and data structures with appropriate complexity. -* Measure allocations, GC pressure, CPU usage and latency when investigating performance. -* Write code that is easy to test. -* Avoid tests coupled to implementation details. -* Prefer deterministic behavior. -* Avoid hidden side effects. -* Keep naming consistent and descriptive. -* Avoid abbreviations unless they are established domain terminology. -* Remove dead code instead of keeping it as commented-out code. -* Add comments or API documentation when they clarify non-obvious decisions, public contracts, or project conventions require them; otherwise prefer self-explanatory code. -* Follow existing project conventions unless there is a strong reason to change them. -* Do not introduce new libraries when the BCL or existing dependencies already solve the problem adequately. -* Prefer fewer dependencies. -* Keep dependency versions current and supported. -* Treat warnings seriously. -* Do not suppress analyzers without a concrete reason. -* Keep performance improvements readable unless the hot path clearly justifies additional complexity. -* Prefer correctness, maintainability and predictable behavior over theoretical micro-optimizations. - -## C# Style & Safety - -* Prefer modern, readable C# syntax and current .NET conventions. -* Prefer simple, explicit code over outdated or unnecessarily verbose patterns. -* Treat collection-type guidance as a default; follow established project conventions and public API contracts when they require a different choice. -* Prefer concrete collection types for internal parameters, properties, and local APIs when they express the intended contract without reducing needed flexibility. -* Do not introduce collection interfaces solely for abstraction. Use `IReadOnlyList`, `IEnumerable`, `IDictionary`, `IList`, or `ICollection` only when a public API or boundary intentionally exposes that capability. -* Prefer `Dictionary`, `List`, arrays, or other concrete types when the implementation type is known and intentional. -* Prefer strongly typed models over `object`, `dynamic`, loosely typed dictionaries or string-based contracts. -* Validate external input at system boundaries. -* Fail fast on invalid arguments and impossible states. -* Avoid unsafe code unless explicitly required. -* Avoid reflection when a strongly typed solution is practical. -* Avoid hidden side effects and implicit global state. -* Prefer immutable or readonly data where practical. -* Avoid exposing mutable internal collections directly. -* Prefer safe framework APIs over custom low-level implementations. -* Never weaken validation, authorization, certificate checks, encryption or other security controls for convenience. -* Do not log secrets, tokens, credentials or sensitive data. -* Treat all external data as untrusted. -* Prefer correctness and security over minor convenience or micro-optimizations. - -## Performance Priority - -Prioritize performance work in this order: - -* Architecture -* Database access -* Network and I/O -* Algorithms and data structures -* Concurrency -* Allocations and GC pressure -* CPU micro-optimizations - -Never optimize based purely on assumptions. diff --git a/shared/rules/csharp/index.md b/shared/rules/csharp/index.md new file mode 100644 index 0000000..843dc59 --- /dev/null +++ b/shared/rules/csharp/index.md @@ -0,0 +1,14 @@ +# C# Rules + +For C# or .NET work, open only the topic rules relevant to the task. Follow repository conventions and load applicable shared security rules as well. + +| Topic | Codex rule | Claude skill | +| --- | --- | --- | +| Language style and code organization | `references/coding-style.md` | `rules-csharp-coding-style` | +| Types, collections, and public contracts | `references/types-and-collections.md` | `rules-csharp-types-and-collections` | +| Architecture and API boundaries | `references/architecture-and-apis.md` | `rules-csharp-architecture-and-apis` | +| Async work and concurrency | `references/async-and-concurrency.md` | `rules-csharp-async-and-concurrency` | +| Performance | `references/performance.md` | `rules-csharp-performance` | +| Security | `references/security.md` | `rules-csharp-security` | +| Validation, errors, and logging | `references/safety-and-errors.md` | `rules-csharp-safety-and-errors` | +| Tests, dependencies, and analyzers | `references/testing-and-dependencies.md` | `rules-csharp-testing-and-dependencies` | diff --git a/shared/rules/csharp/references/architecture-and-apis.md b/shared/rules/csharp/references/architecture-and-apis.md new file mode 100644 index 0000000..bd8d6f2 --- /dev/null +++ b/shared/rules/csharp/references/architecture-and-apis.md @@ -0,0 +1,15 @@ +# C# Architecture and APIs + +Apply these rules when designing public APIs, service boundaries, or application architecture. + +* Give APIs explicit, strongly typed contracts. Validate external input at the boundary and keep trusted internal paths free of redundant checks. +* Separate request models from persistence and domain models. Bind only fields the caller may set, and project only fields the caller may read. +* Authenticate and authorize before accessing protected resources. Enforce resource ownership and tenant scope in the data access path, not only in the UI or route layer. +* Bound page size, payload size, query complexity, and operation cost. Use explicit pagination for potentially large collections. +* Keep application, domain, and infrastructure concerns separate. Avoid cross-layer coupling and service-locator patterns. +* Use dependency injection when substitution or lifecycle management is needed. Keep dependencies explicit and avoid excessive constructor dependencies. +* Prefer composition over inheritance; use inheritance only when the contract requires it. Seal types when extension is not intended. +* Keep classes and methods focused. Avoid wrappers, abstractions, reflection, and global mutable state without a concrete need. +* Preserve established project and public API conventions unless a concrete compatibility or correctness issue requires a change. +* Keep authorization decisions close to the protected operation and fail closed when identity, ownership, or policy evaluation is unavailable. +* Avoid returning persistence entities, internal exception details, or fields that are not part of the public contract. diff --git a/shared/rules/csharp/references/async-and-concurrency.md b/shared/rules/csharp/references/async-and-concurrency.md new file mode 100644 index 0000000..38acd96 --- /dev/null +++ b/shared/rules/csharp/references/async-and-concurrency.md @@ -0,0 +1,13 @@ +# C# Async and Concurrency + +Apply these rules when adding or changing asynchronous APIs, background work, or concurrent processing. + +* Use asynchronous APIs for genuinely asynchronous I/O; do not wrap synchronous work in unnecessary tasks. +* Keep asynchronous operations async through the call chain. Avoid `.Result`, `.Wait()`, and other synchronous waits that can block threads or deadlock. +* Accept and propagate `CancellationToken` through work that can take meaningful time or be cancelled by its caller. +* Bound concurrency for large or unbounded workloads. Avoid creating a task per item or using uncontrolled `Task.WhenAll`; use an appropriate queue or concurrency limiter when work must be throttled. +* Use `ValueTask` only when profiling supports it and its usage contract is clear; otherwise prefer `Task`. +* Keep shared state safe under concurrent access and preserve cancellation and exception behavior when coordinating tasks. Prefer immutable messages or synchronization primitives with clear ownership over unsynchronized shared mutation. +* Avoid holding locks across `await`, blocking thread-pool threads, and starting background work whose lifetime is detached from its owner. +* For producer/consumer work, apply backpressure and bound buffered items so a fast producer cannot exhaust memory. +* Retry only transient failures, with bounded attempts and delay. Preserve idempotency and cancellation, and avoid multiplying retries across nested layers. diff --git a/shared/rules/csharp/references/coding-style.md b/shared/rules/csharp/references/coding-style.md new file mode 100644 index 0000000..88343fc --- /dev/null +++ b/shared/rules/csharp/references/coding-style.md @@ -0,0 +1,13 @@ +# C# Coding Style + +Apply these rules when editing C# syntax, class structure, and everyday code organization. + +* Follow current C# and .NET conventions and the style already used in the project. +* Prefer direct, readable code over clever control flow, hidden magic, or outdated verbosity. +* Keep classes, methods, and responsibilities focused. Use descriptive names and established domain terms; avoid unexplained abbreviations. +* Prefer composition over inheritance. Use inheritance only for a real substitutable contract, and seal types when extension is not intended. +* Avoid unnecessary wrappers, abstractions, reflection, runtime type checks, and global mutable state. +* Prefer immutable or readonly state when it simplifies ownership and reduces accidental mutation. +* Keep side effects visible. Make behavior deterministic and easy to test where practical. +* Add comments or API documentation only for non-obvious decisions, public contracts, or documented project requirements. +* Remove dead code instead of leaving commented-out implementations. diff --git a/shared/rules/csharp/references/performance.md b/shared/rules/csharp/references/performance.md new file mode 100644 index 0000000..b084278 --- /dev/null +++ b/shared/rules/csharp/references/performance.md @@ -0,0 +1,14 @@ +# C# Performance + +Apply these rules when optimizing code or reviewing runtime and resource costs. + +* Establish a representative baseline before changing code. Measure the affected workload, input size, concurrency, and environment; distinguish average latency from tail latency and throughput. +* Find the dominant cost before optimizing: architecture, database access, network and I/O, algorithms, concurrency, allocations and GC, then CPU-level work. +* Choose collections and algorithms for actual access patterns. Check asymptotic cost and data growth; avoid repeated scans, unnecessary sorting, copies, and unbounded result sets. +* Keep data close to where it is processed. Filter and project in the database, avoid N+1 queries, bound page sizes, and avoid materializing rows or columns the caller does not use. +* Stream large inputs and outputs when buffering the whole value would waste memory. Bound queues, batches, and parallel work to match downstream capacity. +* Reduce allocations in measured hot paths: avoid repeated string construction, iterator/delegate capture, boxing, and collection resizing when profiling identifies them as material costs. +* Consider pooling only for expensive reusable objects with clear ownership and reset rules. Pooling can retain memory or leak state between users; measure it and cap retained capacity. +* Use `Span`, `ValueTask`, preallocation, or specialized low-level APIs only when their benefit is demonstrated and they preserve readable, safe ownership. +* Cache only when repeated work is measurable and the key, lifetime, size bound, invalidation, and sensitive-data handling are explicit. +* After an optimization, run the same workload and compare correctness, allocations, GC, CPU, latency, and throughput against the baseline. Keep the simplest change that meets the target. diff --git a/shared/rules/csharp/references/safety-and-errors.md b/shared/rules/csharp/references/safety-and-errors.md new file mode 100644 index 0000000..8e68c4c --- /dev/null +++ b/shared/rules/csharp/references/safety-and-errors.md @@ -0,0 +1,14 @@ +# C# Safety and Error Handling + +Apply these rules when handling external input, failures, logs, or sensitive information. + +* Treat external data as untrusted and validate it at system boundaries. Do not rely on static types to validate runtime input. +* Validate size, range, format, and allowed values before expensive parsing or allocation. Keep validation consistent with downstream business and security constraints. +* Fail fast for invalid arguments or impossible state. Do not use exceptions for routine control flow or swallow unexpected exceptions. +* Preserve useful exception context without exposing secrets, personal data, or internal details in logs, APIs, or user-facing errors. +* Use structured logging and avoid excessive logging, especially in hot paths. Never log credentials, tokens, or sensitive payloads. +* Keep authorization, certificate validation, encryption, and other security controls intact. Follow the focused security rules for the affected boundary. +* Keep side effects explicit and deterministic where practical; propagate cancellation and dispose owned resources correctly. +* Use narrow exception handling around the operation that can fail. Do not catch broadly merely to retry, continue with partial state, or convert failures into success. +* Avoid returning different error detail to unauthenticated callers when it would disclose whether protected accounts, records, or resources exist. +* Treat resource limits as part of safe error handling: cap request bodies, decompressed data, collection sizes, parser depth, and other attacker-controlled work. diff --git a/shared/rules/csharp/references/security.md b/shared/rules/csharp/references/security.md new file mode 100644 index 0000000..70ec8b4 --- /dev/null +++ b/shared/rules/csharp/references/security.md @@ -0,0 +1,14 @@ +# C# Security + +Apply these rules when implementing or reviewing security-sensitive .NET code. Load the matching shared security rule for the affected API, identity, data, file, network, cryptographic, or dependency boundary as well. + +* Validate untrusted input at the boundary and enforce authorization for the specific resource and operation. Static types and client-side checks do not establish trust. +* Use parameterized database APIs. If a query identifier or sort expression cannot be parameterized, select it from a fixed allowlist; never concatenate untrusted input into SQL. +* For file access, canonicalize the resolved path and verify it remains inside the authorized root. Account for reparse points and symlinks when the attacker can affect directory contents. +* For process execution, avoid a shell. Pass executable and arguments separately using APIs supported by the target framework, validate each argument, and restrict executable paths. +* Deserialize into explicit DTOs or allowlisted types. Avoid unsafe polymorphic type activation and unbounded object graphs for untrusted data. +* Bound request and parser work before allocating or processing large values. Apply timeouts to regular expressions that evaluate untrusted input and prefer patterns with predictable complexity. +* Keep secrets in an approved secret store or protected configuration source. Never embed them in assemblies, client bundles, source, diagnostics, or exception messages. +* Use platform TLS and established cryptographic APIs. Do not create custom cryptography or disable certificate validation; use cryptographically secure randomness for security tokens. +* Use framework output encoding and safe APIs for HTML, XML, and other interpreters. Do not build executable markup or queries from untrusted strings. +* Review security-sensitive changes with tests for rejected, malformed, oversized, unauthenticated, and cross-tenant inputs, as applicable. diff --git a/shared/rules/csharp/references/testing-and-dependencies.md b/shared/rules/csharp/references/testing-and-dependencies.md new file mode 100644 index 0000000..772bbde --- /dev/null +++ b/shared/rules/csharp/references/testing-and-dependencies.md @@ -0,0 +1,10 @@ +# C# Testing and Dependencies + +Apply these rules when changing tests, package references, analyzers, or build diagnostics. + +* Write deterministic behavior-based tests. Avoid coupling assertions to private call order, implementation details, current time, or uncontrolled external services. +* Make code testable through clear contracts and dependencies; do not add production hooks or abstractions solely for tests without a concrete need. +* Prefer the .NET BCL and existing supported packages over new libraries when they solve the problem adequately. +* Keep dependencies minimal, maintained, compatible, and updated through the repository's normal package-management process. +* Treat compiler, analyzer, and nullable warnings seriously. Suppress a diagnostic only for a specific, documented reason and narrow the suppression to the affected code. +* Run focused tests and analyzers for the changed path; broaden verification when shared behavior or compatibility could be affected. diff --git a/shared/rules/csharp/references/types-and-collections.md b/shared/rules/csharp/references/types-and-collections.md new file mode 100644 index 0000000..8fbc15d --- /dev/null +++ b/shared/rules/csharp/references/types-and-collections.md @@ -0,0 +1,11 @@ +# C# Types and Collections + +Apply these rules when designing type contracts, choosing collection shapes, or exposing data through APIs. + +* Prefer strongly typed models over `object`, `dynamic`, loosely typed dictionaries, or string-based contracts. +* Prefer concrete collection types for internal parameters, properties, and local APIs when the contract does not need abstraction. +* Expose collection interfaces such as `IReadOnlyList`, `IEnumerable`, `IDictionary`, or `ICollection` only when their capability is part of an intentional boundary or public contract. +* Choose collection types based on lookup, ordering, mutation, and iteration needs. Pre-size collections only when expected capacity is known and material. +* Avoid large mutable structs and direct exposure of mutable internal collections. +* Prefer immutable data where practical, while preserving established API and serialization contracts. +* Use `Span` and related low-level types only when the performance rule's measurement justifies their lifetime and ownership constraints.