From 87f597c091aefe9e869910589a3ea16e4bfd6298 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Thu, 22 Oct 2026 05:01:10 +0200 Subject: [PATCH] docs: formalize C# formatting, brace discipline, FluentResults vs ternary, grouping, and clean namespace rules in GEMINI.md --- GEMINI.md | 144 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 107 insertions(+), 37 deletions(-) diff --git a/GEMINI.md b/GEMINI.md index 62fcaf4..cd26b55 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -983,15 +983,20 @@ The PFM backend strikes a deliberate, pragmatic balance between **Clean Architec --- -### 16.3 C# 13 & .NET 10 Coding Style: Functional Results & Syntax Conventions +### 16.3 C# 13 & .NET 10 Coding Style: Functional Results, Formatting & Syntax Rules > [!IMPORTANT] -> **Mandatory Assistant Rule (Never Regress or Undo This Approach)**: -> Antigravity / Gemini must strictly adhere to the established functional, low-branching C# style. When adding, modifying, or refactoring backend code: +> **Authoritative Coding Style Rules (Never Regress or Undo This Approach)**: +> Antigravity / Gemini must strictly adhere to the established C# functional idioms, layout, and syntax conventions across all projects. When authoring, refactoring, or extending backend code: > - **DO NOT introduce imperative `try-catch` blocks** for control flow or routine domain logic. > - **DO NOT introduce nested `if-else` return branches**. -> - **DO NOT throw exceptions** for predictable domain validation or mutation failures. -> - **Always use FluentResults** (`Result.Try`, `Result.OkIf`, `Result.FailIf`) and pattern-matching switch expressions on endpoints. +> - **DO NOT use braces `{}` on single-line `if` statements** — braces are reserved exclusively for blocks with more than one line of code. +> - **Prefer FluentResults over ternary operators** (`Result.FailIf(...).ToResult(...)` over `condition ? Result.Fail(...) : Result.Ok(...)`). +> - **Prefer expression-bodied members (`=>`) and switch expressions** over statement bodies and traditional switch statements. +> - **Never split method parameters one-per-line** in method or constructor definitions. +> - **Group related variable declarations and function calls** without empty blank lines between them. +> - **Never leave unused `using` namespaces** in `.cs` files. +> - **Pragmatically avoid magic strings** using constants, enums, or domain errors without obsessive over-engineering. #### 1. Extensive Avoidance of `try-catch` Blocks * Imperative `try-catch` blocks are strictly avoided across all application services, endpoints, and domain logic. @@ -1005,37 +1010,51 @@ The PFM backend strikes a deliberate, pragmatic balance between **Clean Architec ``` * Unexpected runtime exceptions are automatically caught by `Result.Try` and converted into a `Result.Fail` with exception metadata, allowing `LoggingInterceptor` to record errors without leaking raw exceptions or crashing threads. -#### 2. Minimal Code Branching & Elimination of Nested `if-else` -* Traditional branching constructs (`if-else-if-else`) on returns are eliminated in favor of clean guard clauses and FluentResults combinators: - * **Concise Guard Clauses**: Early validation failure returns: - ```csharp - if (!await context.Users.AnyAsync(u => u.Id == userId, cancellationToken)) - return Result.Fail(NotFoundError.New); - ``` - * **`Result.OkIf` & `Result.FailIf`**: Replace conditional return statements: - ```csharp - // Mutation success check: - return Result.OkIf(changedTotal > 0, UpdateError.Create("No changes detected, update cancelled.")); - - // Existence check with mapper transformation: - return Result.FailIf(project is null, NotFoundError.Create("Project not found")) - .ToResult(project?.Map()!); - ``` - * **Chained Functional Evaluations**: Guard against invalid decodes or missing foreign keys with immediate failure returns instead of deeply indented blocks. - -#### 3. Primary Constructors & Expression-Bodied Members -* Services and endpoints use primary constructors to eliminate constructor assignment boilerplate: +#### 2. Minimal Branching, Guard Clauses & If-Statement Brace Discipline +* **No Braces for Single-Line `if`**: Braces `{}` are strictly omitted when the `if` body consists of a single statement/line: ```csharp - public sealed class ProjectService( - IDbContextFactory contextFactory, - HashService hashService, - ITenantContextService tenantContext) : IProjectService - ``` -* Methods leverage expression bodies (`=>`) to delegate directly to `await Result.Try(...)` or endpoint mappings. -* Nullable reference types (`enable`) are strictly enforced. + if (!await context.Users.AnyAsync(u => u.Id == userId, cancellationToken)) + return Result.Fail(NotFoundError.New); -#### 4. Pattern-Matching Endpoint Handlers -* Minimal API endpoints map `Result` directly to HTTP status codes via C# switch expressions, completely eliminating endpoint `if-else` branching: + if (decodeResult.IsFailed) + return Result.Fail(IdDecodeError.Create().CausedBy(decodeResult.Errors)); + ``` +* **Braces Only for Multi-Line Blocks**: Braces `{}` are used **only** when the `if` block encompasses more than one line of code: + ```csharp + if (string.IsNullOrWhiteSpace(cached)) + { + var result = await service.GetActivitiesAsync(projectIdHash, cancellationToken); + if (result.IsFailed) return result; + + await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken); + return result.Value; + } + ``` + +#### 3. FluentResults Monadic Chaining over Ternary Operators +* Whenever processing or transforming a response, **always choose FluentResults methods over ternary operators (`? :`)**: + ```csharp + // Preferred (Fluent monadic style): + return Result.FailIf(project is null, NotFoundError.Create("Project not found")) + .ToResult(project?.Map()!); + + return Result.OkIf(changedTotal > 0, UpdateError.Create("No changes detected, update cancelled.")); + + // Avoid (Ternary branching): + // return project is null ? Result.Fail(...) : Result.Ok(project.Map()); + ``` + +#### 4. Expression-Bodied Members & Switch Expressions +* Methods, mappers, getters, and endpoint registrations strongly prefer expression bodies (`=>`): + ```csharp + public static Activity Map(this Entities.Activity entity) => new() + { + Id = entity.Id, + Title = entity.Title, + // ... + }; + ``` +* Minimal API endpoints map `Result` directly to HTTP status codes via C# pattern-matching switch expressions: ```csharp await service.UpdateProjectStatusAsync(request, context.GetUserId(), cancellationToken) switch { @@ -1048,16 +1067,67 @@ The PFM backend strikes a deliberate, pragmatic balance between **Clean Architec } ``` -#### 5. High-Concurrency DbContext Management +#### 5. Compact Method & Constructor Definitions +* Method signatures are kept compact and readable on one line or cleanly wrapped; **parameters are never split into one line per variable**: + ```csharp + // Preferred: + public async Task UpdateProjectStatusAsync(UpdateProjectStatusRequest request, Guid userId, CancellationToken cancellationToken = default) => + + public sealed class ProjectService(IDbContextFactory contextFactory, HashService hashService, + ITenantContextService tenantContext) : IProjectService + + // Avoid: + // public async Task UpdateProjectStatusAsync( + // UpdateProjectStatusRequest request, + // Guid userId, + // CancellationToken cancellationToken = default) + ``` + +#### 6. Bracket Placement (Allman Style) +* Opening braces `{` are placed on a new line for type declarations, multi-line blocks, lambda bodies in `Result.Try`, and object initializers: + ```csharp + public sealed class ProjectService(...) : IProjectService + { + public async Task MethodAsync(...) => + await Result.Try((Func>)(async () => + { + // ... + })); + } + ``` + +#### 7. Dense Grouping of Related Declarations & Calls +* Related variable declarations are grouped together without blank lines between them. +* Related sequential invocations are kept adjacent without extraneous empty lines: + ```csharp + var name = request.Name.Humanize(LetterCasing.Title).Trim(); + var description = request.Description.Humanize(LetterCasing.Sentence).Trim(); + + var cacheKey = HashService.ToMd5Hash($"project-{id}").Value; + var cached = await cache.GetStringAsync(cacheKey, cancellationToken); + ``` + +#### 8. Clean Namespaces & Pragmatic String Handling +* **Zero Unused Namespaces**: Every `.cs` file maintains clean, minimal `using` directives without stale or unused imports. File-scoped namespaces (`namespace PostFundManagement...;`) are standard. +* **Pragmatic Magic String Avoidance**: Avoid magic strings for business-critical values (use constants in `Constants.cs`, `EndpointTags`, domain enums, or typed domain errors), but remain pragmatic (e.g. human-readable error messages and route patterns need not be over-abstracted into single-use constants). + +#### 9. Functional Programming Orientation +* While C# is multi-paradigm, the codebase leans intentionally into functional programming concepts: + * Immutability via positional records (`public sealed record UpdateProjectStatusRequest(...)`). + * Pure extension mapping functions (`entity.Map()`). + * Declarative LINQ queries and pipeline data transformations. + * Monadic error handling (`Result`, `Result.Try`, `CausedBy`). + +#### 10. High-Concurrency DbContext Management * Services inject `IDbContextFactory` rather than scoped `ApplicationDbContext`. * Creates short-lived contexts using `await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);`. * Avoids thread concurrency issues in parallel mediator requests and background Quartz jobs. -#### 6. Query & Mutation Optimizations +#### 11. Query & Mutation Optimizations * Queries use `.AsNoTracking()` to eliminate EF Core tracking overhead. * Targeted updates leverage `.ExecuteUpdateAsync(...)` for in-database bulk updates without loading full entities into memory. -#### 7. Tiered Hybrid Caching +#### 12. Tiered Hybrid Caching * Implements `Microsoft.Extensions.Caching.Hybrid` alongside StackExchange Redis. * Local L1 memory cache: 30-minute expiration. * Distributed L2 Redis cache: 2-minute expiration (`InstanceName = "PFM-API_"`).