Skip to content

Commit 4f4f21a

Browse files
CopilotKSemenenko
andcommitted
Add comprehensive best practices, code style, and Copilot instructions
Co-authored-by: KSemenenko <4385716+KSemenenko@users.noreply.github.com>
1 parent 71a0936 commit 4f4f21a

File tree

1 file changed

+180
-0
lines changed

1 file changed

+180
-0
lines changed

.github/copilot-instructions.md

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,184 @@ if (result.IsSuccess)
4747
var chainResult = ValidateEmail("test@example.com")
4848
.Then(email => ProcessEmail(email))
4949
.Then(processed => SaveToDatabase(processed));
50+
```
51+
52+
## Code Style & Conventions
53+
54+
**Framework Configuration:**
55+
- Target: .NET 9.0
56+
- Nullable reference types enabled
57+
- TreatWarningsAsErrors = true
58+
- EnableNETAnalyzers = true
59+
60+
**Formatting (from .editorconfig):**
61+
- 4 spaces for C# indentation
62+
- CRLF line endings
63+
- Opening braces on new lines (`csharp_new_line_before_open_brace = all`)
64+
- Spaces around binary operators
65+
- No space after cast: `(int)value`
66+
67+
**C# Style Preferences:**
68+
- Use `var` only when type is apparent: `var user = GetUser();`
69+
- Prefer explicit types for built-ins: `int count = 0;` not `var count = 0;`
70+
- Expression-bodied properties preferred: `public string Name => _name;`
71+
- Pattern matching over is/as: `if (obj is User user)`
72+
- Null conditional operators: `user?.Name` over null checks
73+
74+
**Naming Conventions:**
75+
- PascalCase for public members, types, constants
76+
- No prefixes for interfaces, fields, or private members
77+
- Method names should be descriptive: `ValidateEmailAddress()` not `Validate()`
78+
79+
## Testing Patterns
80+
81+
**Test Framework:**
82+
- xUnit for test framework
83+
- FluentAssertions for readable assertions
84+
- Coverlet for code coverage
85+
86+
**Test Structure:**
87+
```csharp
88+
[Fact]
89+
public void Method_Scenario_ExpectedResult()
90+
{
91+
// Arrange
92+
var input = CreateTestData();
93+
94+
// Act
95+
var result = systemUnderTest.Method(input);
96+
97+
// Assert
98+
result.IsSuccess.Should().BeTrue();
99+
result.Value.Should().NotBeNull();
100+
}
101+
```
102+
103+
## Best Practices
104+
105+
**DO ✅ - Result Pattern Usage:**
106+
```csharp
107+
// Use Result for operations that can fail
108+
public Result<User> GetUser(int id)
109+
{
110+
var user = _repository.FindById(id);
111+
return user != null
112+
? Result<User>.Succeed(user)
113+
: Result<User>.FailNotFound($"User {id} not found");
114+
}
115+
116+
// Chain operations using railway-oriented programming
117+
public Result<Order> ProcessOrder(OrderDto dto)
118+
{
119+
return ValidateOrder(dto)
120+
.Then(CreateOrder)
121+
.Then(CalculateTotals)
122+
.Then(SaveOrder);
123+
}
124+
125+
// Provide specific error information
126+
public Result ValidateEmail(string email)
127+
{
128+
if (string.IsNullOrEmpty(email))
129+
return Result.FailValidation(("email", "Email is required"));
130+
131+
if (!email.Contains("@"))
132+
return Result.FailValidation(("email", "Invalid email format"));
133+
134+
return Result.Succeed();
135+
}
136+
137+
// Use CollectionResult for paginated data
138+
public CollectionResult<Product> GetProducts(int page, int pageSize)
139+
{
140+
var products = _repository.GetPaged(page, pageSize);
141+
var total = _repository.Count();
142+
return CollectionResult<Product>.Succeed(products, page, pageSize, total);
143+
}
144+
```
145+
146+
**DON'T ❌ - Anti-patterns:**
147+
```csharp
148+
// DON'T: Throw exceptions from Result-returning methods
149+
public Result<User> GetUser(int id)
150+
{
151+
if (id <= 0)
152+
return Result<User>.FailValidation(("id", "ID must be positive")); //
153+
// NOT: throw new ArgumentException("Invalid ID"); // ❌
154+
}
155+
156+
// DON'T: Ignore Result values
157+
var result = UpdateUser(user);
158+
if (result.IsFailed)
159+
return result; // ✅ Handle the failure
160+
161+
// DON'T: Mix Result and exceptions
162+
// DON'T: Create generic error messages - be specific
163+
return Result.Fail("User creation failed", "Email already exists"); //
164+
```
165+
166+
**Performance Guidelines:**
167+
1. `Result` and `Result<T>` are structs - avoid boxing
168+
2. Use railway-oriented programming to avoid intermediate variables
169+
3. Cache common Problem instances for frequent errors
170+
4. Use `ConfigureAwait(false)` in library code
171+
172+
## Framework Integration
173+
174+
**ASP.NET Core:**
175+
- Controllers can return Result types directly
176+
- Automatic HTTP status code mapping from Problem Details
177+
- Built-in filters for Result handling
178+
179+
**Orleans:**
180+
- Use `UseOrleansCommunication()` for automatic serialization
181+
- Result types work across grain boundaries
182+
- Problem Details preserved in distributed calls
183+
184+
**Command Pattern:**
185+
- Built-in command infrastructure with idempotency
186+
- Commands implement `ICommand` or `ICommand<T>`
187+
- Automatic validation and result wrapping
188+
189+
## Common Patterns
190+
191+
**Validation:**
192+
```csharp
193+
private Result ValidateDto(CreateUserDto dto)
194+
{
195+
var errors = new List<(string field, string message)>();
196+
197+
if (string.IsNullOrWhiteSpace(dto.Email))
198+
errors.Add(("email", "Email is required"));
199+
200+
return errors.Any()
201+
? Result.FailValidation(errors.ToArray())
202+
: Result.Succeed();
203+
}
204+
```
205+
206+
**Error Recovery:**
207+
```csharp
208+
public async Task<Result<User>> GetUserWithFallback(int id)
209+
{
210+
return await GetUser(id)
211+
.CompensateAsync(async error =>
212+
{
213+
var archived = await GetArchivedUser(id);
214+
return archived ?? Result<User>.FailNotFound($"User {id} not found");
215+
});
216+
}
217+
```
218+
219+
**Aggregating Results:**
220+
```csharp
221+
public Result<Order> CreateOrder(List<OrderItem> items)
222+
{
223+
var validationResults = items.Select(ValidateItem);
224+
var combinedResult = Result.Combine(validationResults);
225+
226+
return combinedResult.IsSuccess
227+
? ProcessOrder(items)
228+
: Result<Order>.Fail(combinedResult.Problem);
229+
}
50230
```

0 commit comments

Comments
 (0)