@deftai/directive-content 0.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/deft/SKILL.md +6 -0
- package/.agents/skills/deft-directive-article-review/SKILL.md +11 -0
- package/.agents/skills/deft-directive-build/SKILL.md +10 -0
- package/.agents/skills/deft-directive-cost/SKILL.md +12 -0
- package/.agents/skills/deft-directive-debug/SKILL.md +13 -0
- package/.agents/skills/deft-directive-gh-arch/SKILL.md +11 -0
- package/.agents/skills/deft-directive-gh-slice/SKILL.md +10 -0
- package/.agents/skills/deft-directive-glossary/SKILL.md +10 -0
- package/.agents/skills/deft-directive-interview/SKILL.md +9 -0
- package/.agents/skills/deft-directive-pre-pr/SKILL.md +9 -0
- package/.agents/skills/deft-directive-refinement/SKILL.md +10 -0
- package/.agents/skills/deft-directive-release/SKILL.md +12 -0
- package/.agents/skills/deft-directive-review-cycle/SKILL.md +10 -0
- package/.agents/skills/deft-directive-setup/SKILL.md +10 -0
- package/.agents/skills/deft-directive-swarm/SKILL.md +10 -0
- package/.agents/skills/deft-directive-sync/SKILL.md +9 -0
- package/.agents/skills/deft-directive-triage/SKILL.md +9 -0
- package/.agents/skills/deft-directive-write-skill/SKILL.md +9 -0
- package/LICENSE.md +22 -0
- package/QUICK-START.md +167 -0
- package/UPGRADING.md +517 -0
- package/coding/build-output.md +28 -0
- package/coding/coding.md +235 -0
- package/coding/debugging.md +110 -0
- package/coding/holzmann.md +96 -0
- package/coding/hygiene.md +127 -0
- package/coding/security.md +158 -0
- package/coding/testing.md +162 -0
- package/coding/toolchain.md +44 -0
- package/commands.md +300 -0
- package/context/context.md +57 -0
- package/context/deterministic-split.md +67 -0
- package/context/examples.md +26 -0
- package/context/fractal-summaries.md +69 -0
- package/context/long-horizon.md +46 -0
- package/context/spec-deltas.md +177 -0
- package/context/tool-design.md +34 -0
- package/context/working-memory.md +62 -0
- package/contracts/boundary-maps.md +65 -0
- package/contracts/deterministic-questions.md +59 -0
- package/contracts/hierarchy.md +77 -0
- package/conventions/content-manifest.json +399 -0
- package/conventions/machine-generated-banner.md +130 -0
- package/conventions/references.md +120 -0
- package/conventions/rule-ownership.json +382 -0
- package/conventions/task-caching.md +43 -0
- package/conventions/vbrief-filenames.md +70 -0
- package/deployments/README.md +37 -0
- package/deployments/agentuity/README.md +138 -0
- package/deployments/agentuity/via-cli.md +380 -0
- package/deployments/agentuity/via-cloud.md +425 -0
- package/deployments/agentuity/via-github-actions.md +664 -0
- package/deployments/agentuity/via-gravity-network.md +606 -0
- package/deployments/agentuity/via-vpc.md +607 -0
- package/deployments/aws/README.md +38 -0
- package/deployments/aws/via-app-runner.md +612 -0
- package/deployments/aws/via-ecs-fargate.md +561 -0
- package/deployments/aws/via-elastic-beanstalk.md +628 -0
- package/deployments/aws/via-lambda.md +649 -0
- package/deployments/azure/README.md +37 -0
- package/deployments/azure/via-aks.md +390 -0
- package/deployments/azure/via-app-service.md +564 -0
- package/deployments/azure/via-container-apps.md +599 -0
- package/deployments/azure/via-functions.md +552 -0
- package/deployments/cloud-gov/README.md +63 -0
- package/deployments/cloud-gov/agents/compliance-docs.md +154 -0
- package/deployments/cloud-gov/agents.md +39 -0
- package/deployments/cloud-gov/cicd.md +64 -0
- package/deployments/cloud-gov/deployment.md +150 -0
- package/deployments/cloud-gov/logging.md +43 -0
- package/deployments/cloud-gov/manifest.md +121 -0
- package/deployments/cloud-gov/overview.md +58 -0
- package/deployments/cloud-gov/security.md +46 -0
- package/deployments/cloud-gov/services.md +72 -0
- package/deployments/cloud-gov/upstream/README.md +18 -0
- package/deployments/cloudflare/README.md +33 -0
- package/deployments/cloudflare/via-dashboard.md +83 -0
- package/deployments/cloudflare/via-git.md +90 -0
- package/deployments/cloudflare/via-github-actions.md +185 -0
- package/deployments/cloudflare/via-terraform.md +157 -0
- package/deployments/cloudflare/via-wrangler.md +165 -0
- package/deployments/fly-io/README.md +37 -0
- package/deployments/fly-io/via-dockerfile.md +648 -0
- package/deployments/fly-io/via-flyctl.md +653 -0
- package/deployments/fly-io/via-github-actions.md +695 -0
- package/deployments/fly-io/via-multi-region.md +598 -0
- package/deployments/google/README.md +34 -0
- package/deployments/google/via-app-engine.md +42 -0
- package/deployments/google/via-cloud-functions.md +23 -0
- package/deployments/google/via-cloud-run.md +330 -0
- package/deployments/google/via-gke.md +23 -0
- package/deployments/netlify/README.md +99 -0
- package/deployments/netlify/via-cli.md +17 -0
- package/deployments/netlify/via-functions.md +19 -0
- package/deployments/netlify/via-git.md +25 -0
- package/deployments/vercel/README.md +90 -0
- package/deployments/vercel/via-api.md +16 -0
- package/deployments/vercel/via-cli.md +17 -0
- package/deployments/vercel/via-git.md +24 -0
- package/docs/BROWNFIELD.md +179 -0
- package/docs/getting-started.md +137 -0
- package/docs/good-agents-md.md +137 -0
- package/events/README.md +89 -0
- package/events/event-record.schema.json +26 -0
- package/events/registry.json +166 -0
- package/events/registry.schema.json +71 -0
- package/glossary.md +145 -0
- package/incidents/README.md +81 -0
- package/incidents/_template.md +38 -0
- package/interfaces/cli.md +104 -0
- package/interfaces/rest.md +212 -0
- package/interfaces/tui.md +242 -0
- package/interfaces/web.md +123 -0
- package/languages/6502-DASM.md +132 -0
- package/languages/c.md +235 -0
- package/languages/commands.md +23 -0
- package/languages/cpp.md +132 -0
- package/languages/csharp.md +259 -0
- package/languages/dart.md +183 -0
- package/languages/delphi.md +218 -0
- package/languages/elixir.md +208 -0
- package/languages/go.md +78 -0
- package/languages/java.md +278 -0
- package/languages/javascript.md +163 -0
- package/languages/julia.md +175 -0
- package/languages/kotlin.md +193 -0
- package/languages/markdown.md +168 -0
- package/languages/mermaid.md +146 -0
- package/languages/officejs.md +392 -0
- package/languages/python.md +209 -0
- package/languages/r.md +163 -0
- package/languages/rust.md +216 -0
- package/languages/sql.md +216 -0
- package/languages/swift.md +153 -0
- package/languages/typescript.md +132 -0
- package/languages/vba.md +279 -0
- package/languages/vhdl.md +180 -0
- package/languages/visual-basic.md +151 -0
- package/languages/zig.md +196 -0
- package/meta/SOUL.md +27 -0
- package/meta/code-field.md +44 -0
- package/meta/morals.md +40 -0
- package/meta/philosophy.md +39 -0
- package/meta/project.md +49 -0
- package/meta/ralph.md +223 -0
- package/meta/security.md +80 -0
- package/meta/versioning.md +326 -0
- package/package.json +22 -0
- package/packs/lessons/lessons-pack-0.1.json +553 -0
- package/packs/patterns/patterns-pack-0.1.json +57 -0
- package/packs/rules/rules-pack-0.1.json +4767 -0
- package/packs/skills/skills-pack-0.1.json +262 -0
- package/packs/strategies/strategies-pack-0.1.json +167 -0
- package/packs/swarm-spec/swarm-spec-pack-0.1.json +17 -0
- package/patterns/executor-layer-credentials.md +227 -0
- package/patterns/llm-app.md +156 -0
- package/patterns/multi-agent.md +278 -0
- package/patterns/prompt-assembly-layer-ordering.md +154 -0
- package/patterns/role-as-overlay.md +179 -0
- package/platforms/2600.md +137 -0
- package/platforms/unity.md +329 -0
- package/references/composer-skill-porting.md +152 -0
- package/references/cost-models.md +163 -0
- package/references/ip-risk.md +246 -0
- package/references/plain-english-ux.md +275 -0
- package/resilience/context-pruning.md +67 -0
- package/resilience/continue-here.md +62 -0
- package/scm/changelog.md +276 -0
- package/scm/git.md +139 -0
- package/scm/github.md +265 -0
- package/secrets/.gitkeep +0 -0
- package/skills/deft-build/SKILL.md +20 -0
- package/skills/deft-directive-article-review/SKILL.md +156 -0
- package/skills/deft-directive-build/SKILL.md +302 -0
- package/skills/deft-directive-cost/SKILL.md +201 -0
- package/skills/deft-directive-debug/SKILL.md +140 -0
- package/skills/deft-directive-decompose/SKILL.md +96 -0
- package/skills/deft-directive-gh-arch/SKILL.md +160 -0
- package/skills/deft-directive-gh-slice/SKILL.md +199 -0
- package/skills/deft-directive-glossary/SKILL.md +118 -0
- package/skills/deft-directive-interview/SKILL.md +528 -0
- package/skills/deft-directive-pre-pr/SKILL.md +131 -0
- package/skills/deft-directive-probe/SKILL.md +127 -0
- package/skills/deft-directive-refinement/SKILL.md +403 -0
- package/skills/deft-directive-release/SKILL.md +266 -0
- package/skills/deft-directive-review-cycle/SKILL.md +401 -0
- package/skills/deft-directive-setup/SKILL.md +717 -0
- package/skills/deft-directive-swarm/SKILL.md +989 -0
- package/skills/deft-directive-sync/SKILL.md +288 -0
- package/skills/deft-directive-triage/SKILL.md +137 -0
- package/skills/deft-directive-write-skill/SKILL.md +169 -0
- package/skills/deft-interview/SKILL.md +16 -0
- package/skills/deft-pre-pr/SKILL.md +16 -0
- package/skills/deft-review-cycle/SKILL.md +16 -0
- package/skills/deft-roadmap-refresh/SKILL.md +16 -0
- package/skills/deft-setup/SKILL.md +20 -0
- package/skills/deft-swarm/SKILL.md +16 -0
- package/skills/deft-sync/SKILL.md +16 -0
- package/strategies/README.md +83 -0
- package/strategies/artifact-guards.md +85 -0
- package/strategies/bdd.md +115 -0
- package/strategies/brownfield.md +7 -0
- package/strategies/discuss.md +129 -0
- package/strategies/emit-hints.md +69 -0
- package/strategies/enterprise.md +193 -0
- package/strategies/interview.md +551 -0
- package/strategies/map.md +179 -0
- package/strategies/probe.md +151 -0
- package/strategies/rapid.md +155 -0
- package/strategies/research.md +155 -0
- package/strategies/roadmap.md +9 -0
- package/strategies/speckit.md +437 -0
- package/strategies/v0-20-contract.md +134 -0
- package/strategies/yolo.md +169 -0
- package/swarm/swarm.md +300 -0
- package/templates/COST-ESTIMATE.md +114 -0
- package/templates/PULL_REQUEST_TEMPLATE.md +35 -0
- package/templates/agent-prompt-preamble.md +409 -0
- package/templates/agents-entry.md +211 -0
- package/templates/agents-entry.placeholders.md +75 -0
- package/templates/embed.go +20 -0
- package/templates/embed_test.go +36 -0
- package/templates/make-spec-example.md +9 -0
- package/templates/make-spec.md +246 -0
- package/templates/project.md.template +52 -0
- package/templates/specification.md +1 -0
- package/templates/swarm-greptile-poller-prompt.md +556 -0
- package/templates/user.md.template +31 -0
- package/tools/RWLDL.md +80 -0
- package/tools/greptile.md +141 -0
- package/tools/installer.md +23 -0
- package/tools/taskfile-migration.md +32 -0
- package/tools/taskfile.md +185 -0
- package/tools/telemetry.md +285 -0
- package/vbrief/schemas/cache-meta.schema.json +137 -0
- package/vbrief/schemas/candidates.schema.json +130 -0
- package/vbrief/schemas/codebase-map.schema.json +213 -0
- package/vbrief/schemas/lessons-pack.schema.json +134 -0
- package/vbrief/schemas/patterns-pack.schema.json +84 -0
- package/vbrief/schemas/rules-pack.schema.json +105 -0
- package/vbrief/schemas/skills-pack.schema.json +94 -0
- package/vbrief/schemas/slices.schema.json +87 -0
- package/vbrief/schemas/strategies-pack.schema.json +89 -0
- package/vbrief/schemas/swarm-spec-pack.schema.json +84 -0
- package/vbrief/schemas/vbrief-core.schema.json +1022 -0
- package/vbrief/vbrief.md +684 -0
- package/verification/integration.md +76 -0
- package/verification/plan-checking.md +85 -0
- package/verification/uat.md +60 -0
- package/verification/verification.md +117 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# C# Standards
|
|
2
|
+
|
|
3
|
+
Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
|
|
4
|
+
|
|
5
|
+
**⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
|
|
6
|
+
|
|
7
|
+
**Stack**: C# 12+/.NET 8+ (LTS), SDK-style projects; Web: ASP.NET Core/Minimal APIs; Testing: xUnit + NSubstitute + FluentAssertions; Analysis: Roslyn Analyzers, StyleCop.Analyzers
|
|
8
|
+
|
|
9
|
+
## Standards
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
- ! XML documentation comments (`///`) on all public types, methods, properties, and constants
|
|
13
|
+
- ! Document `<param>`, `<returns>`, `<exception>`, `<summary>` for public/protected members
|
|
14
|
+
- ~ Use `<see cref=""/>` and `<inheritdoc/>` to reduce duplication
|
|
15
|
+
- ~ Use `<remarks>` for complex behaviors or threading considerations
|
|
16
|
+
|
|
17
|
+
### Testing
|
|
18
|
+
See [testing.md](../coding/testing.md).
|
|
19
|
+
|
|
20
|
+
- ! Use xUnit (`[Fact]`, `[Theory]`, `[InlineData]`)
|
|
21
|
+
- ! Use NSubstitute or Moq for mocking; FluentAssertions for readable assertions
|
|
22
|
+
- ≉ Use MSTest or NUnit for new code (maintain existing suites only)
|
|
23
|
+
- Files: `*Tests.cs` in a parallel `*.Tests` project
|
|
24
|
+
|
|
25
|
+
### Coverage
|
|
26
|
+
- ! ≥85% coverage
|
|
27
|
+
- ! Count src/\*
|
|
28
|
+
- ! Exclude entry points, generated code, migrations, program bootstrapping
|
|
29
|
+
|
|
30
|
+
### Style
|
|
31
|
+
- ! Use `.editorconfig` for code style (project root, checked in)
|
|
32
|
+
- ! Enable Roslyn analyzers and StyleCop.Analyzers as project dependencies
|
|
33
|
+
- ! Follow [Microsoft C# Coding Conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions)
|
|
34
|
+
- ! SDK-style `.csproj` with `<Nullable>enable</Nullable>` and `<ImplicitUsings>enable</ImplicitUsings>`
|
|
35
|
+
- ! `<TreatWarningsAsErrors>true</TreatWarningsAsErrors>` in CI builds
|
|
36
|
+
|
|
37
|
+
### Naming Conventions
|
|
38
|
+
- ! `PascalCase` for types, methods, properties, events, namespaces, public fields
|
|
39
|
+
- ! `camelCase` for parameters, local variables
|
|
40
|
+
- ! `_camelCase` for private fields: `private readonly ILogger _logger;`
|
|
41
|
+
- ! `IPascalCase` for interfaces: `IRepository`, `ILogger`
|
|
42
|
+
- ! `UPPER_SNAKE_CASE` only for interop constants; otherwise `PascalCase` for const
|
|
43
|
+
- ! Async methods suffixed with `Async`: `GetUserAsync()`, `SaveAsync()`
|
|
44
|
+
- ⊗ Hungarian notation or type prefixes (`strName`, `intCount`)
|
|
45
|
+
|
|
46
|
+
### Modern Language Features (C# 12+)
|
|
47
|
+
- ! Use records for immutable data carriers: `record UserDto(long Id, string Name);`
|
|
48
|
+
- ! Use `required` properties for mandatory init-only data
|
|
49
|
+
- ! Use primary constructors for simple DI and parameter capture
|
|
50
|
+
- ! Use `init` accessors for immutable-after-construction properties
|
|
51
|
+
- ! Use pattern matching (`is`, `switch` expressions, list patterns)
|
|
52
|
+
- ! Use raw string literals (`"""`) for multi-line strings
|
|
53
|
+
- ~ Use `file`-scoped types for implementation-only helpers
|
|
54
|
+
- ~ Use collection expressions (`[1, 2, 3]`) where supported (C# 12+)
|
|
55
|
+
- ≉ Use verbose constructors + field assignments when primary constructors suffice
|
|
56
|
+
|
|
57
|
+
### Nullable Reference Types
|
|
58
|
+
- ! Enable `<Nullable>enable</Nullable>` in all projects
|
|
59
|
+
- ! Annotate API boundaries with `?` for nullable; treat non-nullable as contract
|
|
60
|
+
- ! Handle `null` at system boundaries (deserialization, DB, external APIs)
|
|
61
|
+
- ⊗ Use `null!` (null-forgiving operator) except as last resort with documented reason
|
|
62
|
+
- ⊗ Return `null` from methods typed as non-nullable
|
|
63
|
+
|
|
64
|
+
### Resource Management
|
|
65
|
+
- ! Use `using` declarations or `using` blocks for all `IDisposable` resources
|
|
66
|
+
- ! Implement `IAsyncDisposable` for types holding async resources
|
|
67
|
+
- ! Use `await using` for async disposal
|
|
68
|
+
- ⊗ Call `.Dispose()` manually in a `finally` block (use `using` instead)
|
|
69
|
+
- ⊗ Implement finalizers unless wrapping unmanaged resources directly
|
|
70
|
+
|
|
71
|
+
### Error Handling
|
|
72
|
+
- ! Throw specific exception types: `ArgumentNullException`, `InvalidOperationException`, etc.
|
|
73
|
+
- ! Use `ArgumentNullException.ThrowIfNull()` (C# 10+) for guard clauses
|
|
74
|
+
- ! Include context in exception messages (what failed, relevant IDs)
|
|
75
|
+
- ⊗ Catch `Exception` or `SystemException` broadly — catch the most specific type
|
|
76
|
+
- ⊗ Swallow exceptions (empty catch blocks)
|
|
77
|
+
- ⊗ Use exceptions for flow control
|
|
78
|
+
- ~ Use Result/OneOf patterns for expected failures in domain logic
|
|
79
|
+
- ~ Log at the handling boundary, not at every catch-and-rethrow
|
|
80
|
+
|
|
81
|
+
### Async/Await
|
|
82
|
+
- ! Use `async`/`await` for all I/O-bound operations
|
|
83
|
+
- ! Return `Task`/`ValueTask` — never `async void` (except event handlers)
|
|
84
|
+
- ! Use `CancellationToken` in all async public APIs
|
|
85
|
+
- ! Pass `CancellationToken` through the entire call chain
|
|
86
|
+
- ⊗ Use `.Result` or `.Wait()` on tasks (deadlock risk)
|
|
87
|
+
- ⊗ Use `Task.Run()` to wrap sync-over-async (hides problems)
|
|
88
|
+
- ~ Use `ValueTask` for hot-path methods that often complete synchronously
|
|
89
|
+
- ~ Use `ConfigureAwait(false)` in library code (not in app/UI code)
|
|
90
|
+
|
|
91
|
+
### Dependency Injection
|
|
92
|
+
- ! Use constructor injection; ⊗ service locator pattern (`IServiceProvider.GetService`)
|
|
93
|
+
- ! Register services with appropriate lifetimes: `Singleton`, `Scoped`, `Transient`
|
|
94
|
+
- ! Inject interfaces, not implementations
|
|
95
|
+
- ⊗ Inject `IServiceProvider` into business logic classes
|
|
96
|
+
- ~ Use `IOptions<T>` / `IOptionsSnapshot<T>` for configuration binding
|
|
97
|
+
- ≉ Use `static` helper classes for behavior that should be injected
|
|
98
|
+
|
|
99
|
+
### Collections & LINQ
|
|
100
|
+
- ! Prefer immutable collections for shared/public data: `IReadOnlyList<T>`, `IReadOnlyDictionary<K,V>`
|
|
101
|
+
- ! Use LINQ for declarative transforms; ⊗ for side-effectful iterations
|
|
102
|
+
- ~ Materialize queries early to avoid multiple enumeration (`.ToList()`, `.ToArray()`)
|
|
103
|
+
- ~ Use `Span<T>` / `ReadOnlySpan<T>` for performance-critical slicing
|
|
104
|
+
- ⊗ Return `IEnumerable<T>` backed by a deferred query from public APIs without documentation
|
|
105
|
+
|
|
106
|
+
### Database (EF Core)
|
|
107
|
+
- ! Use parameterized queries (EF Core does this automatically)
|
|
108
|
+
- ! Use `AsNoTracking()` for read-only queries
|
|
109
|
+
- ! Use explicit transactions for multi-step writes
|
|
110
|
+
- ~ Use repository pattern or query objects; ≉ scatter `DbContext` calls across controllers
|
|
111
|
+
- ~ Use migrations for schema evolution
|
|
112
|
+
- ⊗ Use raw SQL with string interpolation (SQL injection risk — use `FromSqlInterpolated`)
|
|
113
|
+
|
|
114
|
+
### Telemetry
|
|
115
|
+
- See [telemetry.md](../tools/telemetry.md)
|
|
116
|
+
- ~ Structured logging (Serilog or Microsoft.Extensions.Logging) for production
|
|
117
|
+
- ~ Sentry.io for error tracking
|
|
118
|
+
- ~ Prometheus/Grafana or Application Insights for metrics
|
|
119
|
+
- ? OpenTelemetry .NET for distributed tracing
|
|
120
|
+
|
|
121
|
+
### Safety
|
|
122
|
+
- ⊗ Hardcode secrets, keys, or credentials in source
|
|
123
|
+
- ! Use User Secrets (dev), environment variables, or Azure Key Vault (prod)
|
|
124
|
+
- ! Validate all external input (model binding validation, FluentValidation)
|
|
125
|
+
- ~ Scan dependencies with `dotnet list package --vulnerable` or Snyk/OWASP
|
|
126
|
+
|
|
127
|
+
## Commands
|
|
128
|
+
|
|
129
|
+
See [commands.md](./commands.md).
|
|
130
|
+
|
|
131
|
+
## Patterns
|
|
132
|
+
|
|
133
|
+
### Testing (xUnit + FluentAssertions + NSubstitute)
|
|
134
|
+
```csharp
|
|
135
|
+
public class CalculatorTests {
|
|
136
|
+
private readonly Calculator _sut = new();
|
|
137
|
+
|
|
138
|
+
[Fact] public void Add_ReturnsSum() => _sut.Add(2, 3).Should().Be(5);
|
|
139
|
+
|
|
140
|
+
[Theory] [InlineData(0,1,1)] [InlineData(-1,1,0)]
|
|
141
|
+
public void Add_Parameterized(int a, int b, int expected) =>
|
|
142
|
+
_sut.Add(a, b).Should().Be(expected);
|
|
143
|
+
|
|
144
|
+
[Fact] public void Divide_ByZero_Throws() =>
|
|
145
|
+
((Action)(() => _sut.Divide(10, 0))).Should().Throw<DivideByZeroException>();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
public class UserServiceTests {
|
|
149
|
+
private readonly IUserRepository _repo = Substitute.For<IUserRepository>();
|
|
150
|
+
private readonly UserService _sut;
|
|
151
|
+
public UserServiceTests() => _sut = new UserService(_repo);
|
|
152
|
+
|
|
153
|
+
[Fact] public async Task GetByIdAsync_ExistingUser_ReturnsUser() {
|
|
154
|
+
var expected = new User(1, "Alice");
|
|
155
|
+
_repo.GetByIdAsync(1).Returns(expected);
|
|
156
|
+
(await _sut.GetByIdAsync(1)).Should().Be(expected);
|
|
157
|
+
await _repo.Received(1).GetByIdAsync(1);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Records, Primary Constructors & Pattern Matching
|
|
163
|
+
```csharp
|
|
164
|
+
public record UserDto(long Id, string Name, string Email);
|
|
165
|
+
|
|
166
|
+
public class CreateOrderRequest {
|
|
167
|
+
public required string ProductId { get; init; }
|
|
168
|
+
public required int Quantity { get; init; }
|
|
169
|
+
public string? Notes { get; init; }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
public abstract record Result<T>;
|
|
173
|
+
public record Success<T>(T Value) : Result<T>;
|
|
174
|
+
public record Failure<T>(string Error, Exception? Cause = null) : Result<T>;
|
|
175
|
+
|
|
176
|
+
string Describe<T>(Result<T> r) => r switch {
|
|
177
|
+
Success<T> s => $"OK: {s.Value}", Failure<T> f => $"Error: {f.Error}", _ => "Unknown"
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Primary constructor DI (C# 12)
|
|
181
|
+
public class UserService(IUserRepository repo, ILogger<UserService> logger) {
|
|
182
|
+
public async Task<User?> GetByIdAsync(long id, CancellationToken ct = default) =>
|
|
183
|
+
await repo.GetByIdAsync(id, ct);
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Async/Await & Resources
|
|
188
|
+
```csharp
|
|
189
|
+
// CancellationToken through entire chain
|
|
190
|
+
public async Task<IReadOnlyList<Order>> GetOrdersAsync(long userId, CancellationToken ct = default) {
|
|
191
|
+
await using var conn = await _dataSource.OpenConnectionAsync(ct);
|
|
192
|
+
return await conn.QueryAsync<Order>("SELECT * FROM orders WHERE user_id = @Id",
|
|
193
|
+
new { Id = userId }).ToListAsync(ct);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// using declarations for IDisposable/IAsyncDisposable
|
|
197
|
+
await using var stream = File.OpenRead(path);
|
|
198
|
+
using var reader = new StreamReader(stream);
|
|
199
|
+
var content = await reader.ReadToEndAsync();
|
|
200
|
+
|
|
201
|
+
// Minimal API
|
|
202
|
+
var app = WebApplication.CreateBuilder(args).Build();
|
|
203
|
+
app.MapGet("/users/{id}", async (long id, IUserService svc, CancellationToken ct) =>
|
|
204
|
+
await svc.GetByIdAsync(id, ct) is { } user ? Results.Ok(user) : Results.NotFound());
|
|
205
|
+
app.Run();
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Build Configuration
|
|
209
|
+
|
|
210
|
+
**`.csproj` key settings**:
|
|
211
|
+
- ! `<TargetFramework>net8.0</TargetFramework>`
|
|
212
|
+
- ! `<Nullable>enable</Nullable>`, `<ImplicitUsings>enable</ImplicitUsings>`
|
|
213
|
+
- ! `<TreatWarningsAsErrors>true</TreatWarningsAsErrors>`
|
|
214
|
+
- ! `<AnalysisLevel>latest-recommended</AnalysisLevel>`
|
|
215
|
+
- ! `StyleCop.Analyzers` as `PrivateAssets="all"` PackageReference
|
|
216
|
+
|
|
217
|
+
**Test project deps**: `Microsoft.NET.Test.Sdk`, `xunit`, `xunit.runner.visualstudio`, `NSubstitute`, `FluentAssertions`, `coverlet.collector`
|
|
218
|
+
|
|
219
|
+
**`.editorconfig` key rules**: `indent_size = 4`, `file_scoped` namespaces, `simple_using_statement`, `require_accessibility_modifiers = always`, `_camelCase` private fields
|
|
220
|
+
|
|
221
|
+
## Anti-Patterns
|
|
222
|
+
|
|
223
|
+
Items marked ⊗ in Standards above are not repeated here.
|
|
224
|
+
|
|
225
|
+
- ≉ **Verbose constructors**: Use primary constructors (C# 12) or records
|
|
226
|
+
- ≉ **Manual null guards**: Use `ArgumentNullException.ThrowIfNull()`
|
|
227
|
+
- ≉ **`IEnumerable<T>` as public return**: Materialize or use `IReadOnlyList<T>`
|
|
228
|
+
- ≉ **God classes**: Keep <500 lines; extract services/handlers
|
|
229
|
+
- ≉ **`static` utility classes**: Inject via DI for testability
|
|
230
|
+
|
|
231
|
+
## Hygiene
|
|
232
|
+
|
|
233
|
+
**Types:**
|
|
234
|
+
- ⊗ `object` as parameter or return type where a concrete or generic type is knowable
|
|
235
|
+
- ⊗ `dynamic` outside genuine late-binding scenarios (COM interop, DLR, legacy)
|
|
236
|
+
- ⊗ `null!` (null-forgiving operator) to suppress nullable warnings without a documented invariant
|
|
237
|
+
- ⊗ `#pragma warning disable` without an inline comment explaining why it is safe
|
|
238
|
+
|
|
239
|
+
**Error handling:**
|
|
240
|
+
- ⊗ Empty `catch {}` or `catch (Exception) { }` — handle or re-throw
|
|
241
|
+
- ⊗ `catch (Exception e) { }` that swallows without logging — use structured logging and propagate
|
|
242
|
+
- ⊗ Returning `null` or a default value from a public method to mask an exception
|
|
243
|
+
|
|
244
|
+
**Dead code:**
|
|
245
|
+
- ~ Roslyn IDE analyzers report unused private members; treat `IDE0051` as a warning-as-error in CI
|
|
246
|
+
- ~ Run `dotnet-unused` for projects with large public APIs to detect unreferenced symbols
|
|
247
|
+
- ⊗ `#pragma warning disable IDE0051` to hide dead code from analysis instead of deleting it
|
|
248
|
+
|
|
249
|
+
## Compliance Checklist
|
|
250
|
+
|
|
251
|
+
- ! XML doc comments on all public APIs
|
|
252
|
+
- ! See [testing.md](../coding/testing.md) for testing requirements
|
|
253
|
+
- ! Use xUnit + FluentAssertions + NSubstitute
|
|
254
|
+
- ! Nullable reference types enabled; `TreatWarningsAsErrors` in CI
|
|
255
|
+
- ! Records for data carriers; primary constructors for simple DI
|
|
256
|
+
- ! `async`/`await` with `CancellationToken` for all I/O
|
|
257
|
+
- ! `using` declarations for all `IDisposable`/`IAsyncDisposable`
|
|
258
|
+
- ⊗ `async void`, `.Result`/`.Wait()`, service locator, null-forgiving `null!`
|
|
259
|
+
- ! Run `task check` before commit
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Dart Standards
|
|
2
|
+
|
|
3
|
+
Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
|
|
4
|
+
|
|
5
|
+
**⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md)
|
|
6
|
+
|
|
7
|
+
**Stack**: Dart 3.x+; Framework: Flutter (mobile/web) or standalone; Testing: `package:test` / `flutter_test`; Lint: `analysis_options.yaml` + custom_lint; Format: `dart format`; Docs: dartdoc
|
|
8
|
+
|
|
9
|
+
## Standards
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
- ! dartdoc comments (`///`) on all public classes, functions, properties, and libraries
|
|
13
|
+
- ! Document parameters, return values, and exceptions for public API
|
|
14
|
+
- ! Library-level doc comments (`/// {@category ...}`) for package organization
|
|
15
|
+
- ~ Use `[ClassName]` and `[methodName]` for cross-references in doc comments
|
|
16
|
+
- ~ Include code examples in `/// ```dart` blocks
|
|
17
|
+
|
|
18
|
+
### Testing
|
|
19
|
+
See [testing.md](../coding/testing.md).
|
|
20
|
+
|
|
21
|
+
- ! Use `package:test` (Dart) or `flutter_test` (Flutter) for unit tests
|
|
22
|
+
- ! Place tests in `test/` directory mirroring `lib/` structure
|
|
23
|
+
- ! Test widget trees with `testWidgets()` and `WidgetTester` (Flutter)
|
|
24
|
+
- ~ Use `mockito` or `mocktail` for mocking dependencies
|
|
25
|
+
- ~ Use golden tests for UI regression testing (Flutter)
|
|
26
|
+
|
|
27
|
+
### Coverage
|
|
28
|
+
- ! ≥80% coverage (measured via `dart test --coverage` or `flutter test --coverage`)
|
|
29
|
+
- ! Count lib/**
|
|
30
|
+
- ! Exclude generated files (`*.g.dart`, `*.freezed.dart`), entry points
|
|
31
|
+
|
|
32
|
+
### Style
|
|
33
|
+
- ! Follow [Effective Dart](https://dart.dev/effective-dart) guidelines
|
|
34
|
+
- ! Use `analysis_options.yaml` with recommended lints enabled
|
|
35
|
+
- ! Use `dart format` (or `flutter format`) for all code
|
|
36
|
+
- ! 2-space indentation
|
|
37
|
+
- ! Line length ≤80 characters
|
|
38
|
+
- ! Enable `strict-casts`, `strict-inference`, `strict-raw-types` in analysis options
|
|
39
|
+
|
|
40
|
+
### Naming Conventions
|
|
41
|
+
- ! `lowerCamelCase` for variables, functions, parameters, named parameters
|
|
42
|
+
- ! `UpperCamelCase` for classes, enums, typedefs, extensions, mixins
|
|
43
|
+
- ! `lowerCamelCase` for library/package names and file names
|
|
44
|
+
- ! `SCREAMING_SNAKE_CASE` for compile-time constants only when following Flutter convention
|
|
45
|
+
- ! Prefix private members with `_`: `_internalState`
|
|
46
|
+
- ! Prefix boolean variables/getters with `is`, `has`, `can`: `isLoading`, `hasError`
|
|
47
|
+
- ⊗ Hungarian notation or type prefixes
|
|
48
|
+
|
|
49
|
+
### Null Safety
|
|
50
|
+
- ! Use sound null safety (Dart 3.x enforces this)
|
|
51
|
+
- ! Use `?` for nullable types only when null is a valid state
|
|
52
|
+
- ! Use `late` only when initialization is guaranteed before access
|
|
53
|
+
- ! Prefer `??` (if-null) and `?.` (null-aware) over null checks
|
|
54
|
+
- ⊗ Force-unwrap (`!`) without prior null check or assertion
|
|
55
|
+
- ≉ `late` as a workaround for poor initialization design
|
|
56
|
+
|
|
57
|
+
### Immutability
|
|
58
|
+
- ! Use `final` for variables that are assigned once
|
|
59
|
+
- ! Use `const` for compile-time constants and constructors
|
|
60
|
+
- ! Prefer immutable data classes (use `freezed` or manual `copyWith`)
|
|
61
|
+
- ≉ Mutable state where `final` + rebuild pattern suffices (esp. Flutter)
|
|
62
|
+
|
|
63
|
+
### Classes & Types
|
|
64
|
+
- ! Use `sealed class` (Dart 3) for exhaustive type hierarchies
|
|
65
|
+
- ! Use `enum` with members for fixed sets of values
|
|
66
|
+
- ! Use extension types for zero-cost wrappers
|
|
67
|
+
- ! Use mixins for shared behavior across unrelated classes
|
|
68
|
+
- ~ Use `typedef` for complex function signatures
|
|
69
|
+
- ⊗ Deep inheritance hierarchies (>3 levels) — prefer composition
|
|
70
|
+
|
|
71
|
+
### Async & Concurrency
|
|
72
|
+
- ! Use `async`/`await` for asynchronous operations
|
|
73
|
+
- ! Always handle errors from Futures (`try`/`catch` or `.catchError`)
|
|
74
|
+
- ! Use `Stream` for reactive data flows; `StreamController` for custom streams
|
|
75
|
+
- ! Close `StreamController` and cancel `StreamSubscription` to avoid leaks
|
|
76
|
+
- ⊗ Fire-and-forget Futures without error handling
|
|
77
|
+
- ~ Use `Isolate` for CPU-intensive work (keeps UI responsive in Flutter)
|
|
78
|
+
- ~ Use `compute()` helper for simple isolate tasks in Flutter
|
|
79
|
+
|
|
80
|
+
### State Management (Flutter)
|
|
81
|
+
- ~ Choose one state management approach per project and document it
|
|
82
|
+
- ~ Options: Riverpod, Bloc/Cubit, Provider, signals
|
|
83
|
+
- ! Separate business logic from UI (no business logic in `build()`)
|
|
84
|
+
- ! Dispose controllers, streams, and subscriptions in `dispose()`
|
|
85
|
+
- ⊗ Calling `setState` from outside the widget or after disposal
|
|
86
|
+
|
|
87
|
+
### Dependencies
|
|
88
|
+
- ! Pin dependency versions in `pubspec.yaml` (use `^` for semver ranges)
|
|
89
|
+
- ! Use `pubspec.lock` committed for applications (not for packages)
|
|
90
|
+
- ! Minimize dependencies; prefer `dart:*` core libraries
|
|
91
|
+
- ~ Use `dependency_overrides` only for testing, never in production
|
|
92
|
+
- ~ Run `dart pub outdated` regularly
|
|
93
|
+
|
|
94
|
+
### Code Generation
|
|
95
|
+
- ! Use `build_runner` for code generation (`json_serializable`, `freezed`, etc.)
|
|
96
|
+
- ! Commit generated files or document regeneration in task commands
|
|
97
|
+
- ! Exclude generated files from lint and coverage
|
|
98
|
+
|
|
99
|
+
### Security
|
|
100
|
+
- ⊗ Hardcode secrets or credentials in source (esp. Flutter — compiled but extractable)
|
|
101
|
+
- ! Validate all external inputs (user data, API responses, deep links)
|
|
102
|
+
- ~ Use `flutter_secure_storage` for sensitive data on device
|
|
103
|
+
- ~ Use certificate pinning for critical API connections
|
|
104
|
+
|
|
105
|
+
### Telemetry
|
|
106
|
+
- ~ Use `package:logging` or structured logger for backend Dart
|
|
107
|
+
- ~ Use Firebase Crashlytics / Sentry for Flutter crash reporting
|
|
108
|
+
- ? OpenTelemetry for backend Dart services
|
|
109
|
+
|
|
110
|
+
## Commands
|
|
111
|
+
|
|
112
|
+
See [commands.md](./commands.md).
|
|
113
|
+
|
|
114
|
+
## Patterns
|
|
115
|
+
|
|
116
|
+
### Sealed Class + Pattern Matching (Dart 3)
|
|
117
|
+
```dart
|
|
118
|
+
sealed class Result<T> { const Result(); }
|
|
119
|
+
class Success<T> extends Result<T> { final T data; const Success(this.data); }
|
|
120
|
+
class Failure<T> extends Result<T> { final String message; const Failure(this.message); }
|
|
121
|
+
|
|
122
|
+
String display(Result<User> r) => switch (r) {
|
|
123
|
+
Success(data: final u) => 'Welcome, ${u.name}',
|
|
124
|
+
Failure(message: final m) => 'Error: $m',
|
|
125
|
+
};
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Testing (Flutter Widget)
|
|
129
|
+
```dart
|
|
130
|
+
testWidgets('Counter increments', (tester) async {
|
|
131
|
+
await tester.pumpWidget(const MyApp());
|
|
132
|
+
expect(find.text('0'), findsOneWidget);
|
|
133
|
+
await tester.tap(find.byIcon(Icons.add));
|
|
134
|
+
await tester.pump();
|
|
135
|
+
expect(find.text('1'), findsOneWidget);
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Repository Pattern
|
|
140
|
+
```dart
|
|
141
|
+
abstract interface class UserRepository {
|
|
142
|
+
Future<User?> findById(int id);
|
|
143
|
+
Future<List<User>> findAll({int limit = 20, int offset = 0});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
class ApiUserRepository implements UserRepository {
|
|
147
|
+
final HttpClient _client;
|
|
148
|
+
ApiUserRepository(this._client);
|
|
149
|
+
|
|
150
|
+
@override
|
|
151
|
+
Future<User?> findById(int id) async {
|
|
152
|
+
final response = await _client.get(Uri.parse('/users/$id'));
|
|
153
|
+
if (response.statusCode == 404) return null;
|
|
154
|
+
return User.fromJson(jsonDecode(response.body));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Extension Types (Dart 3)
|
|
160
|
+
```dart
|
|
161
|
+
extension type UserId(int value) { factory UserId.parse(String s) => UserId(int.parse(s)); }
|
|
162
|
+
extension type Email(String value) { bool get isValid => value.contains('@'); }
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Anti-Patterns
|
|
166
|
+
|
|
167
|
+
Items marked ⊗ in Standards above are not repeated here.
|
|
168
|
+
|
|
169
|
+
- ≉ **`late` as lazy init workaround**: Design proper initialization
|
|
170
|
+
- ≉ **Deep inheritance (>3 levels)**: Prefer composition/mixins
|
|
171
|
+
- ≉ **`dynamic` type**: Use specific types; `Object?` if truly unknown
|
|
172
|
+
- ≉ **`print()` for logging**: Use `package:logging`
|
|
173
|
+
|
|
174
|
+
## Compliance Checklist
|
|
175
|
+
|
|
176
|
+
- ! dartdoc on all public API
|
|
177
|
+
- ! See [testing.md](../coding/testing.md) for testing requirements
|
|
178
|
+
- ! Unit + widget tests; ≥80% coverage
|
|
179
|
+
- ! `dart format` + `dart analyze` with strict options enforced
|
|
180
|
+
- ! Effective Dart conventions; sound null safety; `final`/`const` by default
|
|
181
|
+
- ! Sealed classes for exhaustive hierarchies; pattern matching
|
|
182
|
+
- ⊗ `!` without check, fire-and-forget Futures, `dynamic`, business logic in `build()`
|
|
183
|
+
- ! Run `task check` before commit
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Delphi / Object Pascal Standards
|
|
2
|
+
|
|
3
|
+
Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
|
|
4
|
+
|
|
5
|
+
**⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
|
|
6
|
+
|
|
7
|
+
**Stack**: Delphi 12+/Object Pascal, RAD Studio; GUI: VCL (Windows), FMX (cross-platform); DB: FireDAC; Testing: DUnitX
|
|
8
|
+
|
|
9
|
+
## Standards
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
- ! XML documentation comments (`///`) for all public types, methods, and properties
|
|
13
|
+
- ! Unit header comment: purpose, author, date, dependencies
|
|
14
|
+
- ~ Document preconditions, postconditions, and exceptions on public methods
|
|
15
|
+
- ~ Use `{$REGION}` / `{$ENDREGION}` to organize long units
|
|
16
|
+
|
|
17
|
+
### Testing
|
|
18
|
+
See [testing.md](../coding/testing.md).
|
|
19
|
+
|
|
20
|
+
- ! Use DUnitX for all new test projects
|
|
21
|
+
- ≉ Use legacy DUnit for new code (maintain existing DUnit suites only)
|
|
22
|
+
- Files: `Test*.pas` or `*Tests.pas`
|
|
23
|
+
|
|
24
|
+
### Coverage
|
|
25
|
+
- ! ≥85% coverage
|
|
26
|
+
- ! Count src/\*
|
|
27
|
+
- ! Exclude entry points, generated code, DFM/FMX form logic
|
|
28
|
+
|
|
29
|
+
### Style
|
|
30
|
+
- ! Follow [Embarcadero Object Pascal Style Guide](https://docwiki.embarcadero.com/RADStudio/en/Delphi's_Object_Pascal_Style_Guide)
|
|
31
|
+
- ! Use PascalCase (InfixCaps) for all identifiers; ⊗ underscores (except header translations)
|
|
32
|
+
- ! Reserved words and keywords in lowercase (`begin`, `end`, `string`, `nil`)
|
|
33
|
+
- ! Two-space indentation (no tabs)
|
|
34
|
+
- ⊗ Hungarian notation (except header translations)
|
|
35
|
+
- ~ Use [Pascal Analyzer](https://www.peganza.com/) or [FixInsight](https://www.yourkit.com/) for static analysis
|
|
36
|
+
|
|
37
|
+
### Naming Conventions
|
|
38
|
+
- ! Types prefixed with `T`: `TCustomer`, `TOrderStatus`
|
|
39
|
+
- ! Interfaces prefixed with `I`: `IRepository`, `ILogger`
|
|
40
|
+
- ! Exception types prefixed with `E`: `EInvalidArgument`, `ENotFound`
|
|
41
|
+
- ! Private fields prefixed with `F`: `FName`, `FCount`
|
|
42
|
+
- ! Enumeration members prefixed with 2–3 char mnemonic: `TColor = (clRed, clGreen, clBlue)`
|
|
43
|
+
- ! Component variables prefixed by type abbreviation: `btnSubmit`, `edtName`, `lblTitle`
|
|
44
|
+
- ~ Constants in PascalCase (not ALL_CAPS): `const MaxRetries = 3;`
|
|
45
|
+
- ~ Descriptive names; prefer `ObjectPointer` over `ObjPtr`
|
|
46
|
+
|
|
47
|
+
### Types
|
|
48
|
+
- ! Use strong typing: enumerations, records, distinct types over raw integers/strings
|
|
49
|
+
- ! Use generics (`TList<T>`, `TDictionary<K,V>`, `TObjectList<T>`) over untyped containers
|
|
50
|
+
- ⊗ Use `Variant` / `OleVariant` except when required for COM interop
|
|
51
|
+
- ⊗ Use untyped containers (`TList`, `TStringList` for non-string data)
|
|
52
|
+
- ~ Use `TArray<T>` over dynamic array declarations
|
|
53
|
+
- ~ Use `Double` for floating-point; ≉ use `Real` (backward compat only)
|
|
54
|
+
- ~ Use `NativeInt`/`NativeUInt` for pointer-sized integers
|
|
55
|
+
|
|
56
|
+
### Memory Management
|
|
57
|
+
- ! Every object you create, you must free (clear ownership model)
|
|
58
|
+
- ! Use `FreeAndNil` over bare `Free` for field cleanup in destructors
|
|
59
|
+
- ! Override `Destroy` (not `Free`); always mark with `override`
|
|
60
|
+
- ! Use `try..finally` to guarantee cleanup of locally created objects
|
|
61
|
+
- ⊗ Call `Destroy` directly; always use `Free` or `FreeAndNil`
|
|
62
|
+
- ~ Use TComponent ownership (`AOwner` parameter) for form/component lifecycles
|
|
63
|
+
- ~ Use interfaces (reference counting) for shared/cross-boundary objects
|
|
64
|
+
- ~ Use managed records (`class operator Initialize/Finalize`) for RAII patterns (Delphi 10.4+)
|
|
65
|
+
- ≉ Use `GetMem`/`FreeMem` unless writing low-level or performance-critical code
|
|
66
|
+
|
|
67
|
+
### Interfaces
|
|
68
|
+
- ! Use interfaces for abstraction, testability, and dependency injection
|
|
69
|
+
- ! Declare interfaces in a shared unit separate from implementations
|
|
70
|
+
- ~ Use interface delegation (`implements` keyword) to compose behavior
|
|
71
|
+
- ~ Prefer interface references over class references for cross-module boundaries
|
|
72
|
+
- ⊗ Mix interface references and object references to the same instance (prevents double-free / ref-count issues)
|
|
73
|
+
|
|
74
|
+
### Error Handling
|
|
75
|
+
- ! Use exceptions for exceptional/unexpected errors
|
|
76
|
+
- ! Derive custom exceptions from `Exception` or `EArgumentException`, etc.
|
|
77
|
+
- ! Use `try..except` with specific exception types; ⊗ bare `except` without re-raise
|
|
78
|
+
- ! Use `try..finally` for resource cleanup (separate from `try..except`)
|
|
79
|
+
- ⊗ Silently swallow exceptions (empty `except` blocks)
|
|
80
|
+
- ~ Use `EAbort` / `Abort` for non-error flow control (e.g., cancel operations)
|
|
81
|
+
- ~ Log exceptions with context before re-raising
|
|
82
|
+
|
|
83
|
+
### Concurrency
|
|
84
|
+
- ! Use `TTask` / `TParallel` (Parallel Programming Library) for new async work
|
|
85
|
+
- ! Never access VCL/FMX controls from background threads; use `TThread.Synchronize` or `TThread.Queue`
|
|
86
|
+
- ~ Use `TThreadPool` for managing concurrent workloads
|
|
87
|
+
- ~ Use `TMonitor` or `TCriticalSection` for shared state protection
|
|
88
|
+
- ⊗ Use `Suspend`/`Resume` on threads (deprecated, unsafe)
|
|
89
|
+
- ≉ Directly subclass `TThread` when PPL tasks suffice
|
|
90
|
+
|
|
91
|
+
### Database (FireDAC)
|
|
92
|
+
- ! Use parameterized queries; ⊗ string concatenation for SQL
|
|
93
|
+
- ! Use `TFDConnection` with connection definitions (not hard-coded connection strings)
|
|
94
|
+
- ~ Use `TFDQuery` over `TFDTable` for production code
|
|
95
|
+
- ~ Wrap multi-step operations in explicit transactions
|
|
96
|
+
- ⊗ Use BDE (Borland Database Engine) — fully deprecated
|
|
97
|
+
|
|
98
|
+
### Telemetry
|
|
99
|
+
- See [telemetry.md](../tools/telemetry.md)
|
|
100
|
+
- ~ Structured logging (CodeSite, SmartInspect, or custom structured logger) for production
|
|
101
|
+
- ~ Sentry.io (via REST) or equivalent for error tracking
|
|
102
|
+
- ? OpenTelemetry (via REST integration) for distributed tracing
|
|
103
|
+
|
|
104
|
+
### Safety
|
|
105
|
+
- ⊗ Hardcode API keys or secrets in source or shipped binaries
|
|
106
|
+
- ! Validate all external input (user input, file data, network responses)
|
|
107
|
+
- ~ Use `{$WARN}` directives to treat warnings as errors in CI
|
|
108
|
+
- ~ Enable range checking (`{$R+}`) and overflow checking (`{$Q+}`) in debug builds
|
|
109
|
+
|
|
110
|
+
## Commands
|
|
111
|
+
|
|
112
|
+
See [commands.md](./commands.md).
|
|
113
|
+
|
|
114
|
+
## Patterns
|
|
115
|
+
|
|
116
|
+
### Testing (DUnitX)
|
|
117
|
+
```pascal
|
|
118
|
+
unit TestCalculator;
|
|
119
|
+
interface
|
|
120
|
+
uses DUnitX.TestFramework, Calculator;
|
|
121
|
+
type
|
|
122
|
+
[TestFixture]
|
|
123
|
+
TTestCalculator = class
|
|
124
|
+
private
|
|
125
|
+
FSut: TCalculator;
|
|
126
|
+
public
|
|
127
|
+
[Setup] procedure Setup;
|
|
128
|
+
[TearDown] procedure TearDown;
|
|
129
|
+
[Test] procedure Add_TwoPositiveNumbers_ReturnsSum;
|
|
130
|
+
[Test] [TestCase('Zero','0,1,1')] [TestCase('Neg','-1,1,0')]
|
|
131
|
+
procedure Add_Parameterized(const A, B, Expected: Integer);
|
|
132
|
+
end;
|
|
133
|
+
implementation
|
|
134
|
+
procedure TTestCalculator.Setup; begin FSut := TCalculator.Create end;
|
|
135
|
+
procedure TTestCalculator.TearDown; begin FreeAndNil(FSut) end;
|
|
136
|
+
procedure TTestCalculator.Add_TwoPositiveNumbers_ReturnsSum;
|
|
137
|
+
begin Assert.AreEqual(5, FSut.Add(2, 3)) end;
|
|
138
|
+
procedure TTestCalculator.Add_Parameterized(const A, B, Expected: Integer);
|
|
139
|
+
begin Assert.AreEqual(Expected, FSut.Add(A, B)) end;
|
|
140
|
+
initialization
|
|
141
|
+
TDUnitX.RegisterTestFixture(TTestCalculator);
|
|
142
|
+
end.
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Interface-Based Design
|
|
146
|
+
```pascal
|
|
147
|
+
unit Services.Interfaces;
|
|
148
|
+
interface
|
|
149
|
+
type
|
|
150
|
+
ILogger = interface
|
|
151
|
+
['{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}']
|
|
152
|
+
procedure Info(const Msg: string);
|
|
153
|
+
procedure Error(const Msg: string; E: Exception = nil);
|
|
154
|
+
end;
|
|
155
|
+
IRepository<T: class> = interface
|
|
156
|
+
['{B2C3D4E5-F6A7-8901-BCDE-F12345678901}']
|
|
157
|
+
function GetById(const Id: Integer): T;
|
|
158
|
+
function GetAll: TArray<T>;
|
|
159
|
+
procedure Save(const Entity: T);
|
|
160
|
+
procedure Delete(const Id: Integer);
|
|
161
|
+
end;
|
|
162
|
+
implementation
|
|
163
|
+
end.
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Resource Cleanup & Generics
|
|
167
|
+
```pascal
|
|
168
|
+
procedure ProcessFile(const FileName: string);
|
|
169
|
+
var Stream: TFileStream; Reader: TStreamReader;
|
|
170
|
+
begin
|
|
171
|
+
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
|
|
172
|
+
try
|
|
173
|
+
Reader := TStreamReader.Create(Stream);
|
|
174
|
+
try while not Reader.EndOfStream do ProcessLine(Reader.ReadLine);
|
|
175
|
+
finally FreeAndNil(Reader) end;
|
|
176
|
+
finally FreeAndNil(Stream) end;
|
|
177
|
+
end;
|
|
178
|
+
|
|
179
|
+
var Customers: TObjectList<TCustomer>;
|
|
180
|
+
begin
|
|
181
|
+
Customers := TObjectList<TCustomer>.Create(True); // OwnsObjects
|
|
182
|
+
try
|
|
183
|
+
for var C in Customers do DoWork(C);
|
|
184
|
+
finally Customers.Free end;
|
|
185
|
+
end;
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Async with PPL
|
|
189
|
+
```pascal
|
|
190
|
+
procedure TMainForm.btnProcessClick(Sender: TObject);
|
|
191
|
+
begin
|
|
192
|
+
btnProcess.Enabled := False;
|
|
193
|
+
TTask.Run(procedure var R: string; begin
|
|
194
|
+
R := PerformHeavyWork;
|
|
195
|
+
TThread.Queue(nil, procedure begin lblResult.Caption := R; btnProcess.Enabled := True end);
|
|
196
|
+
end);
|
|
197
|
+
end;
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Anti-Patterns
|
|
201
|
+
|
|
202
|
+
Items marked ⊗ in Standards above are not repeated here.
|
|
203
|
+
|
|
204
|
+
- ≉ **`Application.ProcessMessages` in loops**: Use PPL/threads
|
|
205
|
+
- ≉ **Published fields without properties**: Use properties
|
|
206
|
+
- ≉ **Circular unit references**: Extract shared types to common unit
|
|
207
|
+
|
|
208
|
+
## Compliance Checklist
|
|
209
|
+
|
|
210
|
+
- ! Follow Embarcadero Object Pascal Style Guide
|
|
211
|
+
- ! PascalCase identifiers; `T`/`I`/`E`/`F` prefixes
|
|
212
|
+
- ! See [testing.md](../coding/testing.md) for testing requirements
|
|
213
|
+
- ! Use DUnitX for unit tests
|
|
214
|
+
- ! Use `try..finally` for resource cleanup; `FreeAndNil` in destructors
|
|
215
|
+
- ! Use generics and interfaces for type safety and abstraction
|
|
216
|
+
- ⊗ Use `Variant`, `with`, global state, or BDE
|
|
217
|
+
- ⊗ Access UI from background threads
|
|
218
|
+
- ! Run `task check` before commit
|