@mark-gozner/aigile-method 0.4.5
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/LICENSE.md +26 -0
- package/README.md +300 -0
- package/core/agent-teams/team-all.yaml +24 -0
- package/core/agent-teams/team-company.yaml +17 -0
- package/core/agent-teams/team-enterprise.yaml +17 -0
- package/core/agent-teams/team-fullstack.yaml +16 -0
- package/core/agent-teams/team-ide-minimal.yaml +10 -0
- package/core/agents/aigile-master.md +476 -0
- package/core/agents/aigile-orchestrator.agent.md +200 -0
- package/core/agents/analyst.md +45 -0
- package/core/agents/architect.md +43 -0
- package/core/agents/code-tour.agent.md +208 -0
- package/core/agents/dev.agent.md +145 -0
- package/core/agents/dev.md +130 -0
- package/core/agents/expert-react-frontend-engineer.agent.md +741 -0
- package/core/agents/pm.md +33 -0
- package/core/agents/po.md +35 -0
- package/core/agents/qa.md +38 -0
- package/core/agents/sm.md +31 -0
- package/core/agents/ui-expert.md +39 -0
- package/core/agents/ux-expert.md +31 -0
- package/core/checklists/architect-checklist.md +246 -0
- package/core/checklists/change-checklist.md +182 -0
- package/core/checklists/pm-checklist.md +286 -0
- package/core/checklists/po-master-checklist.md +291 -0
- package/core/checklists/story-dod-checklist.md +94 -0
- package/core/checklists/story-draft-checklist.md +153 -0
- package/core/core-config.yaml +22 -0
- package/core/data/aigile-kb.md +112 -0
- package/core/data/brainstorming-techniques.md +73 -0
- package/core/data/elicitation-methods.md +42 -0
- package/core/data/technical-preferences.md +52 -0
- package/core/data/test-levels-framework.md +43 -0
- package/core/data/test-priorities-matrix.md +26 -0
- package/core/instructions/csharp.instructions.md +656 -0
- package/core/instructions/dotnet/csharp.instructions.md +656 -0
- package/core/instructions/java/java.instructions.md +67 -0
- package/core/instructions/java/spring-boot.instructions.md +122 -0
- package/core/instructions/java.instructions.md +67 -0
- package/core/instructions/spring-boot.instructions.md +122 -0
- package/core/prompts/README.md +11 -0
- package/core/prompts/architecture/architecture-blueprint-generator.prompt.md +322 -0
- package/core/prompts/architecture/architecture-validation.prompt.md +71 -0
- package/core/prompts/architecture/file-tree-generator.prompt.md +405 -0
- package/core/prompts/architecture/technical-project-analyze.prompt.md +43 -0
- package/core/prompts/architecture-blueprint-generator.prompt.md +322 -0
- package/core/prompts/architecture-validation.prompt.md +71 -0
- package/core/prompts/code-review.prompt.md +107 -0
- package/core/prompts/confluence-in-md.prompt.md +167 -0
- package/core/prompts/copilot-instructions-blueprint-generator.prompt.md +294 -0
- package/core/prompts/create-implementation-plan.prompt.md +157 -0
- package/core/prompts/create-oo-component-documentation.prompt.md +193 -0
- package/core/prompts/file-tree-generator.prompt.md +405 -0
- package/core/prompts/generate-unit-tests.prompt.md +291 -0
- package/core/prompts/java/java-doc.prompt.md +24 -0
- package/core/prompts/java/java-junit.prompt.md +64 -0
- package/core/prompts/java/junit-5.prompt.md +64 -0
- package/core/prompts/java-doc.prompt.md +24 -0
- package/core/prompts/java-junit.prompt.md +64 -0
- package/core/prompts/junit-5.prompt.md +64 -0
- package/core/prompts/release-notes/README.md +11 -0
- package/core/prompts/release-notes/release-notes.prompt.md +723 -0
- package/core/prompts/release-notes.prompt.md +723 -0
- package/core/prompts/technical-project-analyze.prompt.md +43 -0
- package/core/tasks/advanced-elicitation.md +119 -0
- package/core/tasks/check-story-implemented.md +44 -0
- package/core/tasks/code-arch-review-with-github.md +40 -0
- package/core/tasks/create-architecture-doc.md +55 -0
- package/core/tasks/create-jira-epic-from-confluence.md +70 -0
- package/core/tasks/create-jira-story-from-confluence.md +39 -0
- package/core/tasks/create-jira-story-from-text.md +39 -0
- package/core/tasks/create-next-story.md +35 -0
- package/core/tasks/create-prd-doc.md +54 -0
- package/core/tasks/create-stories-from-epic.md +66 -0
- package/core/tasks/create-tasks-for-story.md +60 -0
- package/core/tasks/document-project.md +69 -0
- package/core/tasks/execute-checklist.md +37 -0
- package/core/tasks/explain-story-from-jira.md +44 -0
- package/core/tasks/facilitate-brainstorming-session.md +69 -0
- package/core/tasks/figma-audit-design-system.md +20 -0
- package/core/tasks/front-end-spec-from-design.md +33 -0
- package/core/tasks/gate.md +64 -0
- package/core/tasks/groom-jira-story.md +52 -0
- package/core/tasks/help.md +27 -0
- package/core/tasks/implement-freeform-work-item.md +30 -0
- package/core/tasks/implement-story-from-jira.md +63 -0
- package/core/tasks/implement-unit-tests.md +45 -0
- package/core/tasks/market-research-from-context7.md +37 -0
- package/core/tasks/review-story.md +30 -0
- package/core/tasks/sonarqube-hotspot-review.md +39 -0
- package/core/tasks/standup-digest.md +21 -0
- package/core/tasks/sync-jira-backlog.md +32 -0
- package/core/tasks/test-design.md +68 -0
- package/core/tasks/validate-next-story.md +37 -0
- package/core/tasks/verify-jira-story-e2e.md +45 -0
- package/core/templates/architecture-tmpl.yaml +651 -0
- package/core/templates/brainstorming-output-tmpl.yaml +156 -0
- package/core/templates/brownfield-architecture-tmpl.yaml +477 -0
- package/core/templates/brownfield-prd-tmpl.yaml +281 -0
- package/core/templates/front-end-architecture-tmpl.yaml +219 -0
- package/core/templates/front-end-spec-tmpl.yaml +350 -0
- package/core/templates/fullstack-architecture-tmpl.yaml +824 -0
- package/core/templates/market-research-tmpl.yaml +253 -0
- package/core/templates/prd-tmpl.yaml +203 -0
- package/core/templates/project-brief-tmpl.yaml +222 -0
- package/core/templates/qa-gate-tmpl.yaml +103 -0
- package/core/templates/story-tmpl.yaml +138 -0
- package/core/workflows/brownfield-fullstack.yaml +298 -0
- package/core/workflows/brownfield-service.yaml +188 -0
- package/core/workflows/brownfield-ui.yaml +198 -0
- package/core/workflows/greenfield-fullstack.yaml +241 -0
- package/core/workflows/greenfield-service.yaml +207 -0
- package/core/workflows/greenfield-ui.yaml +236 -0
- package/dist/agents/aigile-master.txt +500 -0
- package/dist/agents/aigile-orchestrator.agent.txt +224 -0
- package/dist/agents/analyst.txt +69 -0
- package/dist/agents/architect.txt +67 -0
- package/dist/agents/code-tour.agent.txt +232 -0
- package/dist/agents/dev.agent.txt +169 -0
- package/dist/agents/dev.txt +154 -0
- package/dist/agents/expert-react-frontend-engineer.agent.txt +765 -0
- package/dist/agents/pm.txt +57 -0
- package/dist/agents/po.txt +59 -0
- package/dist/agents/qa.txt +62 -0
- package/dist/agents/sm.txt +55 -0
- package/dist/agents/ui-expert.txt +63 -0
- package/dist/agents/ux-expert.txt +55 -0
- package/dist/dev-agent-bundle.txt +154 -0
- package/dist/teams/team-company.txt +10789 -0
- package/docs/mcp-servers.md +102 -0
- package/docs/orchestrator-guide.md +526 -0
- package/mcp/servers.json +108 -0
- package/mcp/servers.yaml +124 -0
- package/package.json +72 -0
- package/tools/cli.js +1864 -0
- package/tools/installer/README.md +24 -0
- package/tools/installer/lib/ide-setup.js +295 -0
- package/tools/installer/lib/installer.js +131 -0
- package/tools/md-assets/web-agent-startup-instructions.md +21 -0
- package/tools/postinstall.js +72 -0
- package/tools/shared/bannerArt.js +68 -0
- package/tools/validate-bundles.js +54 -0
- package/tools/verify-publish-registry.js +34 -0
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "This file provides guidelines for writing clean, maintainable, and idiomatic C# code with a focus on functional patterns and proper abstraction."
|
|
3
|
+
applyTo: '**/*.cs'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Role Definition:
|
|
7
|
+
|
|
8
|
+
- C# Language Expert
|
|
9
|
+
- Software Architect
|
|
10
|
+
- Code Quality Specialist
|
|
11
|
+
|
|
12
|
+
## General:
|
|
13
|
+
|
|
14
|
+
**Description:**
|
|
15
|
+
C# code should be written to maximize readability, maintainability, and correctness while minimizing complexity and coupling. Prefer functional patterns and immutable data
|
|
16
|
+
where appropriate, and keep abstractions simple and focused.
|
|
17
|
+
|
|
18
|
+
**Requirements:**
|
|
19
|
+
|
|
20
|
+
- Write clear, self-documenting code
|
|
21
|
+
- Keep abstractions simple and focused
|
|
22
|
+
- Minimize dependencies and coupling
|
|
23
|
+
- Use modern C# features appropriately
|
|
24
|
+
|
|
25
|
+
## Code Organization:
|
|
26
|
+
|
|
27
|
+
- Use meaningful names:
|
|
28
|
+
```csharp
|
|
29
|
+
// Good: Clear intent
|
|
30
|
+
public async Task<Result<Order>> ProcessOrderAsync(OrderRequest request, CancellationToken cancellationToken)
|
|
31
|
+
|
|
32
|
+
// Avoid: Unclear abbreviations
|
|
33
|
+
public async Task<Result<T>> ProcAsync<T>(ReqDto r, CancellationToken ct)
|
|
34
|
+
```
|
|
35
|
+
- Separate state from behavior:
|
|
36
|
+
```csharp
|
|
37
|
+
// Good: Behavior separate from state
|
|
38
|
+
public sealed record Order(OrderId Id, List<OrderLine> Lines);
|
|
39
|
+
|
|
40
|
+
public static class OrderOperations
|
|
41
|
+
{
|
|
42
|
+
public static decimal CalculateTotal(Order order) =>
|
|
43
|
+
order.Lines.Sum(line => line.Price * line.Quantity);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
- Prefer pure methods:
|
|
47
|
+
```csharp
|
|
48
|
+
// Good: Pure function
|
|
49
|
+
public static decimal CalculateTotalPrice(
|
|
50
|
+
IEnumerable<OrderLine> lines,
|
|
51
|
+
decimal taxRate) =>
|
|
52
|
+
lines.Sum(line => line.Price * line.Quantity) * (1 + taxRate);
|
|
53
|
+
|
|
54
|
+
// Avoid: Method with side effects
|
|
55
|
+
public void CalculateAndUpdateTotalPrice()
|
|
56
|
+
{
|
|
57
|
+
this.Total = this.Lines.Sum(l => l.Price * l.Quantity);
|
|
58
|
+
this.UpdateDatabase();
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
- Use extension methods appropriately:
|
|
62
|
+
```csharp
|
|
63
|
+
// Good: Extension method for domain-specific operations
|
|
64
|
+
public static class OrderExtensions
|
|
65
|
+
{
|
|
66
|
+
public static bool CanBeFulfilled(this Order order, Inventory inventory) =>
|
|
67
|
+
order.Lines.All(line => inventory.HasStock(line.ProductId, line.Quantity));
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
- Design for testability:
|
|
71
|
+
```csharp
|
|
72
|
+
// Good: Easy to test pure functions
|
|
73
|
+
public static class PriceCalculator
|
|
74
|
+
{
|
|
75
|
+
public static decimal CalculateDiscount(
|
|
76
|
+
decimal price,
|
|
77
|
+
int quantity,
|
|
78
|
+
CustomerTier tier) =>
|
|
79
|
+
// Pure calculation
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Avoid: Hard to test due to hidden dependencies
|
|
83
|
+
public decimal CalculateDiscount()
|
|
84
|
+
{
|
|
85
|
+
var user = _userService.GetCurrentUser(); // Hidden dependency
|
|
86
|
+
var settings = _configService.GetSettings(); // Hidden dependency
|
|
87
|
+
// Calculation
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Dependency Management:
|
|
92
|
+
|
|
93
|
+
- Minimize constructor injection:
|
|
94
|
+
```csharp
|
|
95
|
+
// Good: Minimal dependencies
|
|
96
|
+
public sealed class OrderProcessor(IOrderRepository repository)
|
|
97
|
+
{
|
|
98
|
+
// Implementation
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Avoid: Too many dependencies
|
|
102
|
+
// Too many dependencies indicates possible design issues
|
|
103
|
+
public class OrderProcessor(
|
|
104
|
+
IOrderRepository repository,
|
|
105
|
+
ILogger logger,
|
|
106
|
+
IEmailService emailService,
|
|
107
|
+
IMetrics metrics,
|
|
108
|
+
IValidator validator)
|
|
109
|
+
{
|
|
110
|
+
// Implementation
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
- Prefer composition with interfaces:
|
|
114
|
+
```csharp
|
|
115
|
+
// Good: Composition with interfaces
|
|
116
|
+
public sealed class EnhancedLogger(ILogger baseLogger, IMetrics metrics) : ILogger
|
|
117
|
+
{
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Type Definitions:
|
|
122
|
+
|
|
123
|
+
- Prefer records for data types:
|
|
124
|
+
```csharp
|
|
125
|
+
// Good: Immutable data type with value semantics
|
|
126
|
+
public sealed record CustomerDto(string Name, Email Email);
|
|
127
|
+
|
|
128
|
+
// Avoid: Class with mutable properties
|
|
129
|
+
public class Customer
|
|
130
|
+
{
|
|
131
|
+
public string Name { get; set; }
|
|
132
|
+
public string Email { get; set; }
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
- Make classes sealed by default:
|
|
136
|
+
```csharp
|
|
137
|
+
// Good: Make classes sealed by default
|
|
138
|
+
public sealed class OrderProcessor
|
|
139
|
+
{
|
|
140
|
+
// Implementation
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Only unsealed when inheritance is specifically designed for
|
|
144
|
+
public abstract class Repository<T>
|
|
145
|
+
{
|
|
146
|
+
// Base implementation
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Variable Declarations:
|
|
151
|
+
|
|
152
|
+
- Use var where possible:
|
|
153
|
+
```csharp
|
|
154
|
+
// Good: Using var for type inference
|
|
155
|
+
var fruit = "Apple";
|
|
156
|
+
var number = 42;
|
|
157
|
+
var order = new Order(fruit, number);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Control Flow:
|
|
161
|
+
|
|
162
|
+
- Prefer range indexers over LINQ:
|
|
163
|
+
```csharp
|
|
164
|
+
// Good: Using range indexers with clear comments
|
|
165
|
+
var lastItem = items[^1]; // ^1 means "1 from the end"
|
|
166
|
+
var firstThree = items[..3]; // ..3 means "take first 3 items"
|
|
167
|
+
var slice = items[2..5]; // take items from index 2 to 4 (5 exclusive)
|
|
168
|
+
|
|
169
|
+
// Avoid: Using LINQ when range indexers are clearer
|
|
170
|
+
var lastItem = items.LastOrDefault();
|
|
171
|
+
var firstThree = items.Take(3).ToList();
|
|
172
|
+
var slice = items.Skip(2).Take(3).ToList();
|
|
173
|
+
```
|
|
174
|
+
- Prefer collection initializers:
|
|
175
|
+
```csharp
|
|
176
|
+
// Good: Using collection initializers
|
|
177
|
+
string[] fruits = ["Apple", "Banana", "Cherry"];
|
|
178
|
+
|
|
179
|
+
// Avoid: Using explicit initialization when type is clear
|
|
180
|
+
var fruits = new List<int>() {
|
|
181
|
+
"Apple",
|
|
182
|
+
"Banana",
|
|
183
|
+
"Cherry"
|
|
184
|
+
};
|
|
185
|
+
```
|
|
186
|
+
- Use pattern matching effectively:
|
|
187
|
+
```csharp
|
|
188
|
+
// Good: Clear pattern matching
|
|
189
|
+
public decimal CalculateDiscount(Customer customer) =>
|
|
190
|
+
customer switch
|
|
191
|
+
{
|
|
192
|
+
{ Tier: CustomerTier.Premium } => 0.2m,
|
|
193
|
+
{ OrderCount: > 10 } => 0.1m,
|
|
194
|
+
_ => 0m
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Avoid: Nested if statements
|
|
198
|
+
public decimal CalculateDiscount(Customer customer)
|
|
199
|
+
{
|
|
200
|
+
if (customer.Tier == CustomerTier.Premium)
|
|
201
|
+
return 0.2m;
|
|
202
|
+
if (customer.OrderCount > 10)
|
|
203
|
+
return 0.1m;
|
|
204
|
+
return 0m;
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Nullability:
|
|
209
|
+
|
|
210
|
+
- Mark nullable fields explicitly:
|
|
211
|
+
```csharp
|
|
212
|
+
// Good: Explicit nullability
|
|
213
|
+
public class OrderProcessor
|
|
214
|
+
{
|
|
215
|
+
private readonly ILogger<OrderProcessor>? _logger;
|
|
216
|
+
private string? _lastError;
|
|
217
|
+
|
|
218
|
+
public OrderProcessor(ILogger<OrderProcessor>? logger = null)
|
|
219
|
+
{
|
|
220
|
+
_logger = logger;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Avoid: Implicit nullability
|
|
225
|
+
public class OrderProcessor
|
|
226
|
+
{
|
|
227
|
+
private readonly ILogger<OrderProcessor> _logger; // Warning: Could be null
|
|
228
|
+
private string _lastError; // Warning: Could be null
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
- Use null checks only when necessary for reference types and public methods:
|
|
232
|
+
```csharp
|
|
233
|
+
// Good: Proper null checking
|
|
234
|
+
public void ProcessOrder(Order order)
|
|
235
|
+
{
|
|
236
|
+
ArgumentNullException.ThrowIfNull(order); // Appropriate for reference types
|
|
237
|
+
|
|
238
|
+
_logger?.LogInformation("Processing order {Id}", order.Id);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Good: Using pattern matching for null checks
|
|
242
|
+
public decimal CalculateTotal(Order? order) =>
|
|
243
|
+
order switch
|
|
244
|
+
{
|
|
245
|
+
null => throw new ArgumentNullException(nameof(order)),
|
|
246
|
+
{ Lines: null } => throw new ArgumentException("Order lines cannot be null", nameof(order)),
|
|
247
|
+
_ => order.Lines.Sum(l => l.Total)
|
|
248
|
+
};
|
|
249
|
+
// BAD: Avoid null checks for value types
|
|
250
|
+
public void ProcessOrder(int orderId)
|
|
251
|
+
{
|
|
252
|
+
ArgumentNullException.ThrowIfNull(order); // DON'T USE Null checks are unnecessary for value types
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Avoid: null checks for non-public methods
|
|
256
|
+
private void ProcessOrder(Order order)
|
|
257
|
+
{
|
|
258
|
+
ArgumentNullException.ThrowIfNull(order); // DON'T USE, ProcessOrder is private
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
- Use null-forgiving operator when appropriate:
|
|
262
|
+
```csharp
|
|
263
|
+
public class OrderValidator
|
|
264
|
+
{
|
|
265
|
+
private readonly IValidator<Order> _validator;
|
|
266
|
+
|
|
267
|
+
public OrderValidator(IValidator<Order> validator)
|
|
268
|
+
{
|
|
269
|
+
_validator = validator ?? throw new ArgumentNullException(nameof(validator));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
public ValidationResult Validate(Order order)
|
|
273
|
+
{
|
|
274
|
+
// We know _validator can't be null due to constructor check
|
|
275
|
+
return _validator!.Validate(order);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
- Use nullability attributes:
|
|
280
|
+
```csharp
|
|
281
|
+
public class StringUtilities
|
|
282
|
+
{
|
|
283
|
+
// Output is non-null if input is non-null
|
|
284
|
+
[return: NotNullIfNotNull(nameof(input))]
|
|
285
|
+
public static string? ToUpperCase(string? input) =>
|
|
286
|
+
input?.ToUpperInvariant();
|
|
287
|
+
|
|
288
|
+
// Method never returns null
|
|
289
|
+
[return: NotNull]
|
|
290
|
+
public static string EnsureNotNull(string? input) =>
|
|
291
|
+
input ?? string.Empty;
|
|
292
|
+
|
|
293
|
+
// Parameter must not be null when method returns true
|
|
294
|
+
public static bool TryParse(string? input, [NotNullWhen(true)] out string? result)
|
|
295
|
+
{
|
|
296
|
+
result = null;
|
|
297
|
+
if (string.IsNullOrEmpty(input))
|
|
298
|
+
return false;
|
|
299
|
+
|
|
300
|
+
result = input;
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
- Use init-only properties with non-null validation:
|
|
306
|
+
```csharp
|
|
307
|
+
// Good: Non-null validation in constructor
|
|
308
|
+
public sealed record Order
|
|
309
|
+
{
|
|
310
|
+
public required OrderId Id { get; init; }
|
|
311
|
+
public required ImmutableList<OrderLine> Lines { get; init; }
|
|
312
|
+
|
|
313
|
+
public Order()
|
|
314
|
+
{
|
|
315
|
+
Id = null!; // Will be set by required property
|
|
316
|
+
Lines = null!; // Will be set by required property
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private Order(OrderId id, ImmutableList<OrderLine> lines)
|
|
320
|
+
{
|
|
321
|
+
Id = id;
|
|
322
|
+
Lines = lines;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
public static Order Create(OrderId id, IEnumerable<OrderLine> lines) =>
|
|
326
|
+
new(id, lines.ToImmutableList());
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
- Document nullability in interfaces:
|
|
330
|
+
```csharp
|
|
331
|
+
public interface IOrderRepository
|
|
332
|
+
{
|
|
333
|
+
// Explicitly shows that null is a valid return value
|
|
334
|
+
Task<Order?> FindByIdAsync(OrderId id, CancellationToken ct = default);
|
|
335
|
+
|
|
336
|
+
// Method will never return null
|
|
337
|
+
[return: NotNull]
|
|
338
|
+
Task<IReadOnlyList<Order>> GetAllAsync(CancellationToken ct = default);
|
|
339
|
+
|
|
340
|
+
// Parameter cannot be null
|
|
341
|
+
Task SaveAsync([NotNull] Order order, CancellationToken ct = default);
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Safe Operations:
|
|
346
|
+
|
|
347
|
+
- Use Try methods for safer operations:
|
|
348
|
+
```csharp
|
|
349
|
+
// Good: Using TryGetValue for dictionary access
|
|
350
|
+
if (dictionary.TryGetValue(key, out var value))
|
|
351
|
+
{
|
|
352
|
+
// Use value safely here
|
|
353
|
+
}
|
|
354
|
+
else
|
|
355
|
+
{
|
|
356
|
+
// Handle missing key case
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
```csharp
|
|
360
|
+
// Avoid: Direct indexing which can throw
|
|
361
|
+
var value = dictionary[key]; // Throws if key doesn't exist
|
|
362
|
+
|
|
363
|
+
// Good: Using Uri.TryCreate for URL parsing
|
|
364
|
+
if (Uri.TryCreate(urlString, UriKind.Absolute, out var uri))
|
|
365
|
+
{
|
|
366
|
+
// Use uri safely here
|
|
367
|
+
}
|
|
368
|
+
else
|
|
369
|
+
{
|
|
370
|
+
// Handle invalid URL case
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
```csharp
|
|
374
|
+
// Avoid: Direct Uri creation which can throw
|
|
375
|
+
var uri = new Uri(urlString); // Throws on invalid URL
|
|
376
|
+
|
|
377
|
+
// Good: Using int.TryParse for number parsing
|
|
378
|
+
if (int.TryParse(input, out var number))
|
|
379
|
+
{
|
|
380
|
+
// Use number safely here
|
|
381
|
+
}
|
|
382
|
+
else
|
|
383
|
+
{
|
|
384
|
+
// Handle invalid number case
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
```csharp
|
|
388
|
+
// Good: Combining Try methods with null coalescing
|
|
389
|
+
var value = dictionary.TryGetValue(key, out var result)
|
|
390
|
+
? result
|
|
391
|
+
: defaultValue;
|
|
392
|
+
|
|
393
|
+
// Good: Using Try methods in LINQ with pattern matching
|
|
394
|
+
var validNumbers = strings
|
|
395
|
+
.Select(s => (Success: int.TryParse(s, out var num), Value: num))
|
|
396
|
+
.Where(x => x.Success)
|
|
397
|
+
.Select(x => x.Value);
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
- Prefer Try methods over exception handling:
|
|
401
|
+
```csharp
|
|
402
|
+
// Good: Using Try method
|
|
403
|
+
if (decimal.TryParse(priceString, out var price))
|
|
404
|
+
{
|
|
405
|
+
// Process price
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Avoid: Exception handling for expected cases
|
|
409
|
+
try
|
|
410
|
+
{
|
|
411
|
+
var price = decimal.Parse(priceString);
|
|
412
|
+
// Process price
|
|
413
|
+
}
|
|
414
|
+
catch (FormatException)
|
|
415
|
+
{
|
|
416
|
+
// Handle invalid format
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## Asynchronous Programming:
|
|
421
|
+
|
|
422
|
+
- Use Task.FromResult for pre-computed values:
|
|
423
|
+
```csharp
|
|
424
|
+
// Good: Return pre-computed value
|
|
425
|
+
public Task<int> GetDefaultQuantityAsync() =>
|
|
426
|
+
Task.FromResult(1);
|
|
427
|
+
|
|
428
|
+
// Better: Use ValueTask for zero allocations
|
|
429
|
+
public ValueTask<int> GetDefaultQuantityAsync() =>
|
|
430
|
+
new ValueTask<int>(1);
|
|
431
|
+
|
|
432
|
+
// Avoid: Unnecessary thread pool usage
|
|
433
|
+
public Task<int> GetDefaultQuantityAsync() =>
|
|
434
|
+
Task.Run(() => 1);
|
|
435
|
+
```
|
|
436
|
+
- Always flow CancellationToken:
|
|
437
|
+
```csharp
|
|
438
|
+
// Good: Propagate cancellation
|
|
439
|
+
public async Task<Order> ProcessOrderAsync(
|
|
440
|
+
OrderRequest request,
|
|
441
|
+
CancellationToken cancellationToken)
|
|
442
|
+
{
|
|
443
|
+
var order = await _repository.GetAsync(
|
|
444
|
+
request.OrderId,
|
|
445
|
+
cancellationToken);
|
|
446
|
+
|
|
447
|
+
await _processor.ProcessAsync(
|
|
448
|
+
order,
|
|
449
|
+
cancellationToken);
|
|
450
|
+
|
|
451
|
+
return order;
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
- Prefer await:
|
|
455
|
+
```csharp
|
|
456
|
+
// Good: Using await
|
|
457
|
+
public async Task<Order> ProcessOrderAsync(OrderId id)
|
|
458
|
+
{
|
|
459
|
+
var order = await _repository.GetAsync(id);
|
|
460
|
+
await _validator.ValidateAsync(order);
|
|
461
|
+
return order;
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
- Never use Task.Result or Task.Wait:
|
|
465
|
+
```csharp
|
|
466
|
+
// Good: Async all the way
|
|
467
|
+
public async Task<Order> GetOrderAsync(OrderId id)
|
|
468
|
+
{
|
|
469
|
+
return await _repository.GetAsync(id);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Avoid: Blocking on async code
|
|
473
|
+
public Order GetOrder(OrderId id)
|
|
474
|
+
{
|
|
475
|
+
return _repository.GetAsync(id).Result; // Can deadlock
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
- Use TaskCompletionSource correctly:
|
|
479
|
+
```csharp
|
|
480
|
+
// Good: Using RunContinuationsAsynchronously
|
|
481
|
+
private readonly TaskCompletionSource<Order> _tcs =
|
|
482
|
+
new(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
483
|
+
|
|
484
|
+
// Avoid: Default TaskCompletionSource can cause deadlocks
|
|
485
|
+
private readonly TaskCompletionSource<Order> _tcs = new();
|
|
486
|
+
```
|
|
487
|
+
- Always dispose CancellationTokenSources:
|
|
488
|
+
```csharp
|
|
489
|
+
// Good: Proper disposal of CancellationTokenSource
|
|
490
|
+
public async Task<Order> GetOrderWithTimeout(OrderId id)
|
|
491
|
+
{
|
|
492
|
+
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
|
|
493
|
+
return await _repository.GetAsync(id, cts.Token);
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
- Prefer async/await over direct Task return:
|
|
497
|
+
```csharp
|
|
498
|
+
// Good: Using async/await
|
|
499
|
+
public async Task<Order> ProcessOrderAsync(OrderRequest request)
|
|
500
|
+
{
|
|
501
|
+
await _validator.ValidateAsync(request);
|
|
502
|
+
var order = await _factory.CreateAsync(request);
|
|
503
|
+
return order;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Avoid: Manual task composition
|
|
507
|
+
public Task<Order> ProcessOrderAsync(OrderRequest request)
|
|
508
|
+
{
|
|
509
|
+
return _validator.ValidateAsync(request)
|
|
510
|
+
.ContinueWith(t => _factory.CreateAsync(request))
|
|
511
|
+
.Unwrap();
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
## Symbol References:
|
|
516
|
+
|
|
517
|
+
- Always use nameof operator:
|
|
518
|
+
```csharp
|
|
519
|
+
// Good: Using nameof in attributes
|
|
520
|
+
public class OrderProcessor
|
|
521
|
+
{
|
|
522
|
+
[Required(ErrorMessage = "The {0} field is required")]
|
|
523
|
+
[Display(Name = nameof(OrderId))]
|
|
524
|
+
public string OrderId { get; init; }
|
|
525
|
+
|
|
526
|
+
[MemberNotNull(nameof(_repository))]
|
|
527
|
+
private void InitializeRepository()
|
|
528
|
+
{
|
|
529
|
+
_repository = new OrderRepository();
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
[NotifyPropertyChangedFor(nameof(FullName))]
|
|
533
|
+
public string FirstName
|
|
534
|
+
{
|
|
535
|
+
get => _firstName;
|
|
536
|
+
set => SetProperty(ref _firstName, value);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
- Use nameof with exceptions:
|
|
541
|
+
```csharp
|
|
542
|
+
public class OrderService
|
|
543
|
+
{
|
|
544
|
+
public async Task<Order> GetOrderAsync(OrderId id, CancellationToken ct)
|
|
545
|
+
{
|
|
546
|
+
var order = await _repository.FindAsync(id, ct);
|
|
547
|
+
|
|
548
|
+
if (order is null)
|
|
549
|
+
throw new OrderNotFoundException(
|
|
550
|
+
$"Order with {nameof(id)} '{id}' not found");
|
|
551
|
+
|
|
552
|
+
if (!order.Lines.Any())
|
|
553
|
+
throw new InvalidOperationException(
|
|
554
|
+
$"{nameof(order.Lines)} cannot be empty");
|
|
555
|
+
|
|
556
|
+
return order;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
public void ValidateOrder(Order order)
|
|
560
|
+
{
|
|
561
|
+
if (order.Lines.Count == 0)
|
|
562
|
+
throw new ArgumentException(
|
|
563
|
+
"Order must have at least one line",
|
|
564
|
+
nameof(order));
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
```
|
|
568
|
+
- Use nameof in logging:
|
|
569
|
+
```csharp
|
|
570
|
+
public class OrderProcessor
|
|
571
|
+
{
|
|
572
|
+
private readonly ILogger<OrderProcessor> _logger;
|
|
573
|
+
|
|
574
|
+
public async Task ProcessAsync(Order order)
|
|
575
|
+
{
|
|
576
|
+
_logger.LogInformation(
|
|
577
|
+
"Starting {Method} for order {OrderId}",
|
|
578
|
+
nameof(ProcessAsync),
|
|
579
|
+
order.Id);
|
|
580
|
+
|
|
581
|
+
try
|
|
582
|
+
{
|
|
583
|
+
await ProcessInternalAsync(order);
|
|
584
|
+
}
|
|
585
|
+
catch (Exception ex)
|
|
586
|
+
{
|
|
587
|
+
_logger.LogError(
|
|
588
|
+
ex,
|
|
589
|
+
"Error in {Method} for {Property} {Value}",
|
|
590
|
+
nameof(ProcessAsync),
|
|
591
|
+
nameof(order.Id),
|
|
592
|
+
order.Id);
|
|
593
|
+
throw;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### Usings and Namespaces:
|
|
600
|
+
|
|
601
|
+
- Use implicit usings:
|
|
602
|
+
```csharp
|
|
603
|
+
// Good: Implicit
|
|
604
|
+
namespace MyNamespace
|
|
605
|
+
{
|
|
606
|
+
public class MyClass
|
|
607
|
+
{
|
|
608
|
+
// Implementation
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
// Avoid:
|
|
612
|
+
using System; // DON'T USE
|
|
613
|
+
using System.Collections.Generic; // DON'T USE
|
|
614
|
+
using System.IO; // DON'T USE
|
|
615
|
+
using System.Linq; // DON'T USE
|
|
616
|
+
using System.Net.Http; // DON'T USE
|
|
617
|
+
using System.Threading; // DON'T USE
|
|
618
|
+
using System.Threading.Tasks;// DON'T USE
|
|
619
|
+
using System.Net.Http.Json; // DON'T USE
|
|
620
|
+
using Microsoft.AspNetCore.Builder; // DON'T USE
|
|
621
|
+
using Microsoft.AspNetCore.Hosting; // DON'T USE
|
|
622
|
+
using Microsoft.AspNetCore.Http; // DON'T USE
|
|
623
|
+
using Microsoft.AspNetCore.Routing; // DON'T USE
|
|
624
|
+
using Microsoft.Extensions.Configuration; // DON'T USE
|
|
625
|
+
using Microsoft.Extensions.DependencyInjection; // DON'T USE
|
|
626
|
+
using Microsoft.Extensions.Hosting; // DON'T USE
|
|
627
|
+
using Microsoft.Extensions.Logging; // DON'T USE
|
|
628
|
+
using Good: Explicit usings; // DON'T USE
|
|
629
|
+
|
|
630
|
+
namespace MyNamespace
|
|
631
|
+
{
|
|
632
|
+
public class MyClass
|
|
633
|
+
{
|
|
634
|
+
// Implementation
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
```
|
|
638
|
+
- Use file-scoped namespaces:
|
|
639
|
+
```csharp
|
|
640
|
+
// Good: File-scoped namespace
|
|
641
|
+
namespace MyNamespace;
|
|
642
|
+
|
|
643
|
+
public class MyClass
|
|
644
|
+
{
|
|
645
|
+
// Implementation
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Avoid: Block-scoped namespace
|
|
649
|
+
namespace MyNamespace
|
|
650
|
+
{
|
|
651
|
+
public class MyClass
|
|
652
|
+
{
|
|
653
|
+
// Implementation
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
```
|