docs: formalize C# formatting, brace discipline, FluentResults vs ternary, grouping, and clean namespace rules in GEMINI.md

This commit is contained in:
2026-10-22 05:01:10 +02:00
parent 5a6c951641
commit 87f597c091
+100 -30
View File
@@ -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:
#### 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
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:
if (decodeResult.IsFailed)
return Result.Fail<Contract>(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()!);
```
* **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:
return Result.OkIf(changedTotal > 0, UpdateError.Create("No changes detected, update cancelled."));
// Avoid (Ternary branching):
// return project is null ? Result.Fail<Project>(...) : Result.Ok(project.Map());
```
#### 4. Expression-Bodied Members & Switch Expressions
* Methods, mappers, getters, and endpoint registrations strongly prefer expression bodies (`=>`):
```csharp
public sealed class ProjectService(
IDbContextFactory<ApplicationDbContext> contextFactory,
HashService hashService,
ITenantContextService tenantContext) : IProjectService
public static Activity Map(this Entities.Activity entity) => new()
{
Id = entity.Id,
Title = entity.Title,
// ...
};
```
* Methods leverage expression bodies (`=>`) to delegate directly to `await Result.Try(...)` or endpoint mappings.
* Nullable reference types (`<Nullable>enable</Nullable>`) are strictly enforced.
#### 4. Pattern-Matching Endpoint Handlers
* Minimal API endpoints map `Result<T>` directly to HTTP status codes via C# switch expressions, completely eliminating endpoint `if-else` branching:
* Minimal API endpoints map `Result<T>` 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<Result> UpdateProjectStatusAsync(UpdateProjectStatusRequest request, Guid userId, CancellationToken cancellationToken = default) =>
public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> contextFactory, HashService hashService,
ITenantContextService tenantContext) : IProjectService
// Avoid:
// public async Task<Result> 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<Result> MethodAsync(...) =>
await Result.Try((Func<Task<Result>>)(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<T>`, `Result.Try`, `CausedBy`).
#### 10. High-Concurrency DbContext Management
* Services inject `IDbContextFactory<ApplicationDbContext>` 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_"`).