"Bất kỳ ai cũng có thể viết code máy hiểu. Lập trình viên giỏi viết code người hiểu." - Martin Fowler
1. Coding Standards cho .NET Projects
Naming Conventions
| Element | Convention | Ví dụ |
|---|---|---|
| Namespace | PascalCase, theo folder structure | MyApp.Application.Services |
| Class / Interface | PascalCase, Interface prefix I | OrderService, IOrderRepository |
| Method | PascalCase, verb-first | GetOrderById(), ValidatePayment() |
| Property | PascalCase | CustomerName, IsActive |
| Parameter / Local | camelCase | orderId, customerName |
| Private field | _camelCase (underscore prefix) | _orderRepository, _logger |
| Constant | PascalCase | MaxRetryCount, DefaultTimeout |
| Async method | Suffix Async | GetOrderByIdAsync() |
| Boolean | Prefix Is/Has/Can/Should | IsValid, HasPermission |
Anti-Patterns to Avoid
csharp// BAD: Hungarian notation
string strName;
int intAge;
List<Order> lstOrders;
// GOOD: Descriptive names
string customerName;
int age;
List<Order> activeOrders;
// BAD: Magic numbers
if (retryCount > 3) { ... }
Thread.Sleep(5000);
// GOOD: Named constants
private const int MaxRetryAttempts = 3;
private static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(5);
if (retryCount > MaxRetryAttempts) { ... }
await Task.Delay(RetryDelay);
// BAD: God method
public async Task ProcessOrder(Order order)
{
// 200 lines doing validation, payment, inventory, email...
}
// GOOD: Single Responsibility
public async Task ProcessOrder(Order order)
{
await _validator.ValidateAsync(order);
await _paymentService.ChargeAsync(order);
await _inventoryService.ReserveAsync(order.Items);
await _notificationService.SendConfirmationAsync(order);
}
Method Length & Complexity Guidelines
| Metric | Target | Tool |
|---|---|---|
| Method length | < 30 lines (guideline, not hard rule) | SonarQube |
| Cyclomatic complexity | < 10 per method | Roslyn Analyzers |
| Class length | < 300 lines | SonarQube |
| Parameters per method | < 4 (dùng object nếu nhiều hơn) | StyleCop |
| Nesting depth | < 3 levels | Roslyn Analyzers |
2. EditorConfig & .NET Analyzers Setup
.editorconfig cho .NET
ini# Top-level EditorConfig
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.cs]
# Namespace
dotnet_style_namespace_match_folder = true
# Using directives
csharp_using_directive_placement = outside_namespace
dotnet_sort_system_directives_first = true
# var preferences
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
# Expression-bodied members
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
csharp_style_expression_bodied_properties = true:suggestion
# Null checking
dotnet_style_null_propagation = true:warning
csharp_style_throw_expression = true:suggestion
# Naming rules
dotnet_naming_rule.private_fields_should_be_camel_case.severity = warning
dotnet_naming_rule.private_fields_should_be_camel_case.symbols = private_fields
dotnet_naming_rule.private_fields_should_be_camel_case.style = camel_case_underscore
dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
dotnet_naming_style.camel_case_underscore.required_prefix = _
dotnet_naming_style.camel_case_underscore.capitalization = camel_case
# Severity levels
dotnet_analyzer_diagnostic.severity = warning
dotnet_diagnostic.CA1062.severity = warning # Validate arguments
dotnet_diagnostic.CA2007.severity = warning # ConfigureAwait
dotnet_diagnostic.CS8600.severity = error # Null literal
Directory.Build.props - Central Analyzer Config
xml<Project>
<PropertyGroup>
<AnalysisLevel>latest-recommended</AnalysisLevel>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.*" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.*" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.*" />
<PackageReference Include="Roslynator.Analyzers" Version="4.*" />
</ItemGroup>
</Project>
Tip:
TreatWarningsAsErrors = truebuộc team fix analyzer warnings ngay, không để tích tụ.
3. Documentation Standards
Inline Comments - When and How
csharp// BAD: Comment nói lại code (useless)
// Increment counter by 1
counter++;
// BAD: Comment thay vì rename
// Check if the user can access the resource
if (u.r == 1 && u.a == true) { ... }
// GOOD: Explain "why", not "what"
// Retry 3 times because payment gateway has intermittent 503 errors
// during peak hours (see incident INC-2024-089)
await Policy.Handle<HttpRequestException>()
.RetryAsync(3)
.ExecuteAsync(() => _paymentClient.ChargeAsync(amount));
// GOOD: Document non-obvious business rule
// Tax-exempt for orders shipped to specific regions per regulation VN-TAX-2024
if (IsRegionTaxExempt(order.ShippingAddress.RegionCode))
order.TaxAmount = 0;
XML Documentation cho Public APIs
csharp/// <summary>
/// Creates a new order and initiates the payment process.
/// </summary>
/// <param name="request">Order creation request containing items and shipping info.</param>
/// <param name="cancellationToken">Cancellation token for async operation.</param>
/// <returns>The created order with payment status.</returns>
/// <exception cref="InsufficientStockException">
/// Thrown when one or more items are out of stock.
/// </exception>
/// <exception cref="PaymentDeclinedException">
/// Thrown when the payment gateway declines the transaction.
/// </exception>
public async Task<OrderResponse> CreateOrderAsync(
CreateOrderRequest request,
CancellationToken cancellationToken = default)
README Template cho mỗi Service/Project
markdown# [Service Name]
## Overview
[1-2 sentences mô tả purpose]
## Tech Stack
- .NET 8, ASP.NET Core Minimal API
- SQL Server, Entity Framework Core
- MassTransit + RabbitMQ
## Getting Started
1. Prerequisites: .NET 8 SDK, Docker
2. `docker compose up -d` (dependencies)
3. `dotnet run --project src/MyService.Api`
## Architecture
[Link to architecture diagram]
## API Documentation
- Swagger UI: https://localhost:5001/swagger
- [Link to Postman collection]
## Configuration
| Variable | Description | Default |
|----------|-------------|---------|
| `ConnectionStrings__Default` | SQL Server connection | ... |
## Testing
- `dotnet test` - run all tests
- `dotnet test --filter Category=Integration` - integration only
4. Git Conventions
Commit Messages - Conventional Commits
<type>(<scope>): <description>
[optional body]
[optional footer]
| Type | Khi nào dùng | Ví dụ |
|---|---|---|
feat | Feature mới | feat(orders): add bulk order creation endpoint |
fix | Bug fix | fix(auth): resolve token refresh race condition |
refactor | Code refactoring | refactor(payments): extract PaymentValidator class |
perf | Performance improvement | perf(reports): add caching for dashboard queries |
test | Add/update tests | test(orders): add edge case tests for discount calc |
docs | Documentation | docs(api): update Swagger descriptions for v2 |
chore | Build, CI, dependencies | chore(deps): update EF Core to 8.0.4 |
ci | CI/CD changes | ci: add SonarQube analysis to PR pipeline |
Branching Strategy - Trunk-Based (recommended)
main ─────────────────────────────────────────────→
│ │ │
├─ feat/order-bulk-create ─────┤ (short-lived, < 2 days)
│ │
├─ fix/token-refresh ──────────┤ (merged same day)
│ │
└─ release/v2.3.0 ────────────────→ (only if needed)
Rules:
- Feature branches sống < 2 ngày - merge sớm, merge thường
- Dùng feature flags thay vì long-lived branches
- Squash merge cho clean history
- Protected main branch - require PR + 2 approvals + CI pass
5. Definition of Done Checklist
Team-Level DoD
markdown## Definition of Done - [Team Name]
### Code Quality
- [ ] Code follows team coding standards (EditorConfig + analyzers pass)
- [ ] No new SonarQube issues (quality gate passed)
- [ ] Self-review completed before requesting PR
- [ ] PR reviewed and approved by >= 2 team members
### Testing
- [ ] Unit tests written for new logic (coverage >= 80%)
- [ ] Integration tests for API endpoints
- [ ] Edge cases and error scenarios covered
- [ ] All existing tests pass (no regressions)
### Documentation
- [ ] Public API has XML documentation
- [ ] README updated if new setup steps needed
- [ ] Swagger/OpenAPI spec updated for API changes
- [ ] Architecture Decision Record (ADR) written for significant decisions
### Deployment
- [ ] Database migration tested on staging
- [ ] Feature flag configured (if applicable)
- [ ] Environment variables documented
- [ ] CI/CD pipeline passes (build + test + analysis)
### Acceptance
- [ ] Acceptance criteria verified
- [ ] Demonstrated to PO in Sprint Review
- [ ] No known defects remaining
6. Quality Gates in CI/CD Pipeline
Pipeline Stages & Gates
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ BUILD │───→│ TEST │───→│ ANALYZE │───→│ DEPLOY │
│ │ │ │ │ │ │ │
│ dotnet │ │ Unit │ │ SonarQube│ │ Staging │
│ build │ │ Integr. │ │ CodeQL │ │ Canary │
│ │ │ Coverage │ │ License │ │ Prod │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │
Gate 1 Gate 2 Gate 3 Gate 4
Build OK Tests pass Quality gate Smoke test
No errors Coverage>80% No critical Health check
No high vuln Error rate<1%
Azure DevOps Pipeline Example
yamlstages:
- stage: Build
jobs:
- job: BuildAndTest
steps:
- task: DotNetCoreCLI@2
inputs:
command: build
arguments: --configuration Release /warnaserror
- task: DotNetCoreCLI@2
inputs:
command: test
arguments: >
--configuration Release
--collect:"XPlat Code Coverage"
--logger trx
# Gate: Coverage check
- script: |
coverage=$(cat coverage.xml | grep line-rate | head -1)
if (( $(echo "$coverage < 0.80" | bc -l) )); then
echo "##vso[task.logissue type=error]Coverage below 80%"
exit 1
fi
- stage: Analyze
jobs:
- job: SonarQube
steps:
- task: SonarQubePrepare@5
- task: DotNetCoreCLI@2
inputs: { command: build }
- task: SonarQubeAnalyze@5
- task: SonarQubePublish@5
# Gate: Quality gate status
- task: sonar-buildbreaker@8
Nguyên tắc: Quality gates phải fail the build - không chỉ warning. Nếu chỉ warning, team sẽ ignore.
Session tiếp theo: Session 04 - Architecture Governance sẽ đi sâu vào cách quản trị kiến trúc phần mềm.