32
This commit is contained in:
parent
d8d9214243
commit
d9d4c7d844
|
|
@ -18,6 +18,38 @@
|
|||
"create_design_system_rules",
|
||||
"whoami"
|
||||
]
|
||||
},
|
||||
"admin-sqlserver": {
|
||||
"command": "node",
|
||||
"args": [
|
||||
"C:/mcp/mssql-mcp-server/index.js"
|
||||
],
|
||||
"env": {
|
||||
"MSSQL_SERVER": "192.168.195.15",
|
||||
"MSSQL_PORT": "1433",
|
||||
"MSSQL_USER": "sa",
|
||||
"MSSQL_PASSWORD": "Dbt@com@123",
|
||||
"MSSQL_DATABASE": "MiAssessment_Admin"
|
||||
},
|
||||
"autoApprove": [
|
||||
"execute_sql"
|
||||
]
|
||||
},
|
||||
"api-sqlserver": {
|
||||
"command": "node",
|
||||
"args": [
|
||||
"C:/mcp/mssql-mcp-server/index.js"
|
||||
],
|
||||
"env": {
|
||||
"MSSQL_SERVER": "192.168.195.15",
|
||||
"MSSQL_PORT": "1433",
|
||||
"MSSQL_USER": "sa",
|
||||
"MSSQL_PASSWORD": "Dbt@com@123",
|
||||
"MSSQL_DATABASE": "MiAssessment_Business"
|
||||
},
|
||||
"autoApprove": [
|
||||
"execute_sql"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
476
.kiro/steering/development-standards.md
Normal file
476
.kiro/steering/development-standards.md
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
# 学业邑规划 - 开发规范
|
||||
|
||||
本文档定义了 MiAssessment 项目的开发规范和编码标准,所有开发工作必须遵循这些规范。
|
||||
|
||||
## 一、技术栈
|
||||
|
||||
| 层级 | 技术 |
|
||||
|---|---|
|
||||
| 后端框架 | .NET 10 Web API (C#) |
|
||||
| 数据库 | SQL Server 2022 |
|
||||
| ORM | Entity Framework Core |
|
||||
| 缓存 | Redis |
|
||||
| 接口风格 | RPC 风格(仅 GET / POST 请求) |
|
||||
| 前端 | UniApp + Vue 3 + TypeScript |
|
||||
|
||||
## 二、项目结构
|
||||
|
||||
```
|
||||
server/MiAssessment/src/
|
||||
├── MiAssessment.Api/ # 小程序 API 接口
|
||||
├── MiAssessment.Admin/ # 后台管理系统基础模块
|
||||
├── MiAssessment.Admin.Business/ # 后台业务模块(主要开发区域)
|
||||
├── MiAssessment.Core/ # 核心业务逻辑
|
||||
├── MiAssessment.Infrastructure/ # 基础设施
|
||||
└── MiAssessment.Model/ # 数据模型
|
||||
```
|
||||
|
||||
### Admin.Business 项目结构
|
||||
|
||||
```
|
||||
MiAssessment.Admin.Business/
|
||||
├── Controllers/ # 控制器(API 入口)
|
||||
├── Services/ # 业务服务
|
||||
│ └── Interfaces/ # 服务接口
|
||||
├── Models/ # 请求/响应模型(DTO)
|
||||
│ ├── Common/ # 通用模型
|
||||
│ └── {Module}/ # 各模块模型
|
||||
├── Entities/ # 数据库实体类
|
||||
├── Data/ # 数据库上下文
|
||||
└── Attributes/ # 自定义特性
|
||||
```
|
||||
|
||||
## 三、命名规范
|
||||
|
||||
### 3.1 数据库命名
|
||||
|
||||
| 类型 | 规范 | 示例 |
|
||||
|---|---|---|
|
||||
| 表名 | snake_case(小写下划线) | `users`, `assessment_records` |
|
||||
| 字段名 | PascalCase | `UserId`, `CreateTime` |
|
||||
| 主键 | `Id` (bigint 自增) | `Id` |
|
||||
| 外键 | `{表名}Id` | `UserId`, `OrderId` |
|
||||
| 时间字段 | `CreateTime`, `UpdateTime` | - |
|
||||
| 状态字段 | `Status` | - |
|
||||
| 软删除 | `IsDeleted` (bit) | - |
|
||||
|
||||
### 3.2 C# 代码命名
|
||||
|
||||
| 类型 | 规范 | 示例 |
|
||||
|---|---|---|
|
||||
| 命名空间 | PascalCase | `MiAssessment.Admin.Business.Services` |
|
||||
| 类名 | PascalCase | `UserService`, `OrderController` |
|
||||
| 接口 | I + PascalCase | `IUserService`, `IOrderService` |
|
||||
| 方法 | PascalCase + Async 后缀 | `GetUserListAsync`, `CreateOrderAsync` |
|
||||
| 属性 | PascalCase | `UserId`, `CreateTime` |
|
||||
| 私有字段 | _camelCase | `_userService`, `_dbContext` |
|
||||
| 参数 | camelCase | `userId`, `request` |
|
||||
| 常量 | PascalCase | `MaxPageSize`, `DefaultStatus` |
|
||||
|
||||
### 3.3 文件命名
|
||||
|
||||
| 类型 | 规范 | 示例 |
|
||||
|---|---|---|
|
||||
| 实体类 | 单数形式 | `User.cs`, `Order.cs` |
|
||||
| 服务接口 | I + 服务名 | `IUserService.cs` |
|
||||
| 服务实现 | 服务名 | `UserService.cs` |
|
||||
| 控制器 | 模块名 + Controller | `UserController.cs` |
|
||||
| DTO 模型 | 功能 + Request/Dto | `CreateUserRequest.cs`, `UserDto.cs` |
|
||||
|
||||
## 四、代码规范
|
||||
|
||||
### 4.1 实体类规范
|
||||
|
||||
```csharp
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace MiAssessment.Admin.Business.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// 用户表
|
||||
/// </summary>
|
||||
[Table("users")]
|
||||
public class User
|
||||
{
|
||||
/// <summary>
|
||||
/// 主键ID
|
||||
/// </summary>
|
||||
[Key]
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户UID
|
||||
/// </summary>
|
||||
[Required]
|
||||
[MaxLength(6)]
|
||||
public string Uid { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新时间
|
||||
/// </summary>
|
||||
public DateTime UpdateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 软删除标记
|
||||
/// </summary>
|
||||
public bool IsDeleted { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**要点**:
|
||||
- 使用 `[Table("表名")]` 指定数据库表名
|
||||
- 使用 `[Key]` 标记主键
|
||||
- 使用 `[Required]` 标记必填字段
|
||||
- 使用 `[MaxLength(n)]` 限制字符串长度
|
||||
- 所有属性必须有 XML 注释
|
||||
- 字符串属性使用 `= null!` 初始化
|
||||
|
||||
### 4.2 服务接口规范
|
||||
|
||||
```csharp
|
||||
namespace MiAssessment.Admin.Business.Services.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// 用户服务接口
|
||||
/// </summary>
|
||||
public interface IUserService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取用户列表
|
||||
/// </summary>
|
||||
Task<PagedResult<UserDto>> GetUserListAsync(UserQueryRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户详情
|
||||
/// </summary>
|
||||
Task<UserDetailDto?> GetUserDetailAsync(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 更新用户状态
|
||||
/// </summary>
|
||||
Task<bool> UpdateUserStatusAsync(long id, int status);
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 服务实现规范
|
||||
|
||||
```csharp
|
||||
namespace MiAssessment.Admin.Business.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 用户服务实现
|
||||
/// </summary>
|
||||
public class UserService : IUserService
|
||||
{
|
||||
private readonly AdminBusinessDbContext _dbContext;
|
||||
private readonly ILogger<UserService> _logger;
|
||||
|
||||
public UserService(
|
||||
AdminBusinessDbContext dbContext,
|
||||
ILogger<UserService> logger)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<PagedResult<UserDto>> GetUserListAsync(UserQueryRequest request)
|
||||
{
|
||||
var query = _dbContext.Users
|
||||
.Where(u => !u.IsDeleted);
|
||||
|
||||
// 条件筛选
|
||||
if (!string.IsNullOrEmpty(request.Phone))
|
||||
{
|
||||
query = query.Where(u => u.Phone.Contains(request.Phone));
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
var total = await query.CountAsync();
|
||||
|
||||
// 分页查询
|
||||
var items = await query
|
||||
.OrderByDescending(u => u.CreateTime)
|
||||
.Skip(request.Skip)
|
||||
.Take(request.PageSize)
|
||||
.Select(u => new UserDto
|
||||
{
|
||||
Id = u.Id,
|
||||
Uid = u.Uid,
|
||||
// ...
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
return new PagedResult<UserDto>(items, total, request.Page, request.PageSize);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 控制器规范
|
||||
|
||||
```csharp
|
||||
namespace MiAssessment.Admin.Business.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 用户管理控制器
|
||||
/// </summary>
|
||||
[Route("api/admin/user")]
|
||||
public class UserController : BusinessControllerBase
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public UserController(IUserService userService)
|
||||
{
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户列表
|
||||
/// </summary>
|
||||
[HttpGet("getList")]
|
||||
public async Task<IActionResult> GetList([FromQuery] UserQueryRequest request)
|
||||
{
|
||||
var result = await _userService.GetUserListAsync(request);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新用户状态
|
||||
/// </summary>
|
||||
[HttpPost("updateStatus")]
|
||||
public async Task<IActionResult> UpdateStatus([FromBody] UpdateStatusRequest request)
|
||||
{
|
||||
var success = await _userService.UpdateUserStatusAsync(request.Id, request.Status);
|
||||
return success ? Ok() : Error(ErrorCodes.UserNotFound, "用户不存在");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 五、API 接口规范
|
||||
|
||||
### 5.1 接口风格
|
||||
|
||||
- **仅使用 GET 和 POST 请求**
|
||||
- GET:查询操作,参数通过 Query String 传递
|
||||
- POST:新增、修改、删除操作,参数通过 Request Body (JSON) 传递
|
||||
|
||||
### 5.2 路由规范
|
||||
|
||||
```
|
||||
/api/admin/{module}/{action}
|
||||
```
|
||||
|
||||
| 操作 | 方法 | 路由示例 |
|
||||
|---|---|---|
|
||||
| 获取列表 | GET | `/api/admin/user/getList` |
|
||||
| 获取详情 | GET | `/api/admin/user/getDetail?id=1` |
|
||||
| 创建 | POST | `/api/admin/user/create` |
|
||||
| 更新 | POST | `/api/admin/user/update` |
|
||||
| 删除 | POST | `/api/admin/user/delete` |
|
||||
| 更新状态 | POST | `/api/admin/user/updateStatus` |
|
||||
| 更新排序 | POST | `/api/admin/user/updateSort` |
|
||||
|
||||
### 5.3 响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
- `code`: 0 表示成功,非 0 表示错误
|
||||
- `message`: 提示信息
|
||||
- `data`: 业务数据
|
||||
|
||||
### 5.4 分页响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"items": [],
|
||||
"total": 100,
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"totalPages": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 六、错误码规范
|
||||
|
||||
| 范围 | 说明 |
|
||||
|---|---|
|
||||
| 0 | 成功 |
|
||||
| 1000-1999 | 通用错误 |
|
||||
| 2000-2999 | 业务错误 |
|
||||
| 3000-3099 | 配置模块错误 |
|
||||
| 3100-3199 | 内容模块错误 |
|
||||
| 3200-3299 | 测评模块错误 |
|
||||
| 3300-3399 | 用户模块错误 |
|
||||
| 3400-3499 | 订单模块错误 |
|
||||
| 3500-3599 | 规划师模块错误 |
|
||||
| 3600-3699 | 分销模块错误 |
|
||||
| 5000-5999 | 系统错误 |
|
||||
|
||||
## 七、注释规范
|
||||
|
||||
### 7.1 XML 注释
|
||||
|
||||
所有公开的类、方法、属性必须有 XML 注释:
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// 获取用户列表
|
||||
/// </summary>
|
||||
/// <param name="request">查询参数</param>
|
||||
/// <returns>分页用户列表</returns>
|
||||
public async Task<PagedResult<UserDto>> GetUserListAsync(UserQueryRequest request)
|
||||
```
|
||||
|
||||
### 7.2 代码注释
|
||||
|
||||
- 使用中文注释
|
||||
- 复杂逻辑需要添加说明注释
|
||||
- 避免无意义的注释
|
||||
|
||||
## 八、数据库操作规范
|
||||
|
||||
### 8.1 查询规范
|
||||
|
||||
```csharp
|
||||
// 始终过滤软删除记录
|
||||
var query = _dbContext.Users.Where(u => !u.IsDeleted);
|
||||
|
||||
// 使用 AsNoTracking 提高只读查询性能
|
||||
var users = await _dbContext.Users
|
||||
.AsNoTracking()
|
||||
.Where(u => !u.IsDeleted)
|
||||
.ToListAsync();
|
||||
```
|
||||
|
||||
### 8.2 软删除
|
||||
|
||||
```csharp
|
||||
// 删除操作使用软删除
|
||||
public async Task<bool> DeleteAsync(long id)
|
||||
{
|
||||
var entity = await _dbContext.Users.FindAsync(id);
|
||||
if (entity == null) return false;
|
||||
|
||||
entity.IsDeleted = true;
|
||||
entity.UpdateTime = DateTime.Now;
|
||||
await _dbContext.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 更新时间
|
||||
|
||||
```csharp
|
||||
// 更新操作自动设置 UpdateTime
|
||||
entity.UpdateTime = DateTime.Now;
|
||||
await _dbContext.SaveChangesAsync();
|
||||
```
|
||||
|
||||
## 九、状态值定义
|
||||
|
||||
### 9.1 通用状态
|
||||
|
||||
| 值 | 说明 |
|
||||
|---|---|
|
||||
| 0 | 禁用/下线 |
|
||||
| 1 | 启用/正常 |
|
||||
|
||||
### 9.2 订单状态
|
||||
|
||||
| 值 | 说明 |
|
||||
|---|---|
|
||||
| 1 | 待支付 |
|
||||
| 2 | 已支付 |
|
||||
| 3 | 已完成 |
|
||||
| 4 | 退款中 |
|
||||
| 5 | 已退款 |
|
||||
| 6 | 已取消 |
|
||||
|
||||
### 9.3 用户等级
|
||||
|
||||
| 值 | 说明 |
|
||||
|---|---|
|
||||
| 1 | 普通用户 |
|
||||
| 2 | 合伙人 |
|
||||
| 3 | 渠道合伙人 |
|
||||
|
||||
## 十、测试规范
|
||||
|
||||
### 10.1 测试框架
|
||||
|
||||
- 单元测试:xUnit + Moq
|
||||
- 属性测试:FsCheck
|
||||
|
||||
### 10.2 测试命名
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public async Task GetUserListAsync_WithValidRequest_ReturnsPagedResult()
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
// Assert
|
||||
}
|
||||
```
|
||||
|
||||
### 10.3 属性测试注释
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// Property: 分页查询返回的记录数不超过 PageSize
|
||||
/// **Validates: Requirements 9.1**
|
||||
/// </summary>
|
||||
[Property]
|
||||
public Property PaginationReturnsCorrectCount()
|
||||
{
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## 十一、Git 提交规范
|
||||
|
||||
### 11.1 提交信息格式
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
<body>
|
||||
```
|
||||
|
||||
### 11.2 Type 类型
|
||||
|
||||
| Type | 说明 |
|
||||
|---|---|
|
||||
| feat | 新功能 |
|
||||
| fix | 修复 bug |
|
||||
| docs | 文档更新 |
|
||||
| style | 代码格式 |
|
||||
| refactor | 重构 |
|
||||
| test | 测试 |
|
||||
| chore | 构建/工具 |
|
||||
|
||||
### 11.3 示例
|
||||
|
||||
```
|
||||
feat(user): 添加用户列表分页查询功能
|
||||
|
||||
- 实现 GetUserListAsync 方法
|
||||
- 支持按手机号、昵称筛选
|
||||
- 添加分页参数验证
|
||||
```
|
||||
740
docs/开发规范/1-编程规约/1.1-命名风格.md
Normal file
740
docs/开发规范/1-编程规约/1.1-命名风格.md
Normal file
|
|
@ -0,0 +1,740 @@
|
|||
# 1.1 命名风格
|
||||
|
||||
---
|
||||
|
||||
## 1. 通用命名原则
|
||||
- 使用有意义且可读的名称
|
||||
```csharp
|
||||
// ❌ 不建议:缩写和无意义的名称
|
||||
var d = DateTime.Now;
|
||||
var usr = new User();
|
||||
int n = 100;
|
||||
|
||||
// ✅ 建议:清晰表达意图
|
||||
var currentDate = DateTime.Now;
|
||||
var newUser = new User();
|
||||
int maxRetryCount = 100;
|
||||
```
|
||||
|
||||
- 不建议使用拼音和中文
|
||||
```csharp
|
||||
// ❌ 不建议
|
||||
var yongHu = new User();
|
||||
string xingMing = "张三";
|
||||
var keHuMingCheng = "张三";
|
||||
var 订单数量 = 10;
|
||||
|
||||
// ✅ 建议
|
||||
var user = new User();
|
||||
string fullName = "张三";
|
||||
```
|
||||
|
||||
- 不建议过度缩写
|
||||
```csharp
|
||||
// ❌ 不建议
|
||||
var cSvc = new CustomerService();
|
||||
var ordMgr = new OrderManager();
|
||||
|
||||
// ✅ 建议
|
||||
var customerService = new CustomerService();
|
||||
var orderManager = new OrderManager();
|
||||
|
||||
// ✅ 可接受含义广为人知的缩写
|
||||
var customerId = 1; // Id
|
||||
var htmlContent = ""; // Html
|
||||
var xmlDocument = ""; // Xml
|
||||
var urlAddress = ""; // Url
|
||||
var httpClient = new HttpClient(); // Http
|
||||
```
|
||||
|
||||
- 不建议使用下划线开头(除私有字段外)
|
||||
```csharp
|
||||
// ❌ 不建议
|
||||
public string _customerName;
|
||||
public void _ProcessOrder() { }
|
||||
|
||||
// ✅ 建议
|
||||
public string CustomerName { get; set; }
|
||||
private string _customerName; // 私有字段可以使用
|
||||
public void ProcessOrder() { }
|
||||
```
|
||||
## 2. 命名约定
|
||||
### 2.1 类
|
||||
- 使用 PascalCase(帕斯卡命名法)
|
||||
```csharp
|
||||
// ✅ 建议的类命名
|
||||
public class Customer { }
|
||||
public class OrderService { }
|
||||
public class ProductRepository { }
|
||||
public class PaymentProcessor { }
|
||||
```
|
||||
|
||||
- 使用名词或名词短语
|
||||
```csharp
|
||||
// ✅ 建议:名词形式
|
||||
public class Invoice { }
|
||||
public class ShoppingCart { }
|
||||
public class UserProfile { }
|
||||
public class EmailValidator { }
|
||||
public class UserAuthentication { }
|
||||
|
||||
// ❌ 不建议:动词形式
|
||||
public class ProcessOrder { } // 应该是 OrderProcessor
|
||||
public class ValidateUser { } // 应该是 UserValidator
|
||||
public class Calculate { } // 适用于方法命名
|
||||
```
|
||||
|
||||
- 建议的命名方式
|
||||
```csharp
|
||||
// 服务类
|
||||
// ✅ 以 Service 结尾
|
||||
public class CustomerService { }
|
||||
public class EmailService { }
|
||||
// ✅ 以职责命名
|
||||
public class OrderProcessor { }
|
||||
public class EmailSender { }
|
||||
public class PaymentGateway { }
|
||||
public class CacheManager { }
|
||||
|
||||
// 仓储类
|
||||
// ✅ 以 Repository 结尾
|
||||
public class OrderRepository { }
|
||||
public class ProductRepository { }
|
||||
|
||||
// ✅ Provider
|
||||
public class AuthenticationProvider { }
|
||||
public class DataProvider { }
|
||||
|
||||
// ✅ Helper/Utility
|
||||
public class StringHelper { }
|
||||
public class DateTimeUtility { }
|
||||
|
||||
// 异常类
|
||||
// ✅ 以 Exception 结尾
|
||||
public class OrderNotFoundException { }
|
||||
public class InvalidOperationException { }
|
||||
|
||||
// ✅ Attribute
|
||||
public class ValidateAttribute { }
|
||||
public class AuthorizeAttribute { }
|
||||
|
||||
// ✅ DTO/ViewModel
|
||||
public class CustomerDto { }
|
||||
public class OrderViewModel { }
|
||||
public class CreateProductRequest { }
|
||||
public class ProductResponse { }
|
||||
public class DoSomethingInput { }
|
||||
public class DoSomethingOutput { }
|
||||
|
||||
// ✅ Entity/Model
|
||||
public class Order { }
|
||||
public class Product { }
|
||||
public class Customer { }
|
||||
|
||||
// 验证器
|
||||
// ✅ 以 Validator 结尾
|
||||
public class OrderValidator { }
|
||||
public class EmailValidator { }
|
||||
public class CreateOrderRequestValidator { }
|
||||
```
|
||||
- 不建议的命名方式
|
||||
```csharp
|
||||
// ❌ 不建议:使用下划线或特殊字符
|
||||
public class Order_Service { }
|
||||
public class Product$Manager { }
|
||||
|
||||
// ❌ 不建议:使用前缀
|
||||
public class COrder { }
|
||||
public class TCustomer { }
|
||||
|
||||
// ❌ 不建议:过于通用
|
||||
public class Data { }
|
||||
public class Info { }
|
||||
public class Manager { } // 太宽泛,应该是具体的 xxxManager
|
||||
```
|
||||
### 2.2 接口
|
||||
- 使用 `I` 前缀 + PascalCase
|
||||
```csharp
|
||||
// ✅ 正确的接口命名
|
||||
public interface ICustomerService { }
|
||||
public interface IOrderRepository { }
|
||||
public interface IPaymentProcessor { }
|
||||
public interface ILogger { }
|
||||
```
|
||||
|
||||
- 使用名词、名词短语或形容词
|
||||
```csharp
|
||||
// ✅ 建议:名词 (一般表示为业务接口)
|
||||
public interface IRepository<T> { }
|
||||
public interface IValidator { }
|
||||
public interface IOrderService { }
|
||||
public interface ILogger { }
|
||||
public interface ICache { }
|
||||
|
||||
// ✅ 建议:形容词(一般表示为能力接口)
|
||||
public interface IComparable { }
|
||||
public interface IDisposable { }
|
||||
public interface IEnumerable { }
|
||||
public interface IQueryable { }
|
||||
|
||||
// ✅ 建议:名词短语
|
||||
public interface IDataAccess { }
|
||||
public interface IUserAuthentication { }
|
||||
```
|
||||
|
||||
- 避免使用 Interface 后缀
|
||||
```csharp
|
||||
// ❌ 不建议
|
||||
public interface IOrderServiceInterface { }
|
||||
|
||||
// ✅ 建议
|
||||
public interface IOrderService { }
|
||||
```
|
||||
### 2.3 方法
|
||||
- 使用 PascalCase
|
||||
```csharp
|
||||
public class CustomerService
|
||||
{
|
||||
// ✅ 建议
|
||||
public void CreateCustomer() { }
|
||||
public Customer GetCustomerById(int id) { }
|
||||
public void UpdateCustomerProfile() { }
|
||||
public void DeleteCustomer() { }
|
||||
}
|
||||
```
|
||||
|
||||
- 使用动词或动词短语
|
||||
```csharp
|
||||
// ✅ 建议:明确、清晰的动作
|
||||
public void CreateOrder() { }
|
||||
public void UpdateCustomer() { }
|
||||
public void DeleteProduct() { }
|
||||
public Order GetOrderById(int id) { }
|
||||
public List<Order> GetOrdersByCustomer(int customerId) { }
|
||||
public bool ValidateEmail(string email) { }
|
||||
public decimal CalculateDiscount(Order order) { }
|
||||
|
||||
// ❌ 不建议:名词形式
|
||||
public void Order() { } // 应该是 CreateOrder 或 ProcessOrder
|
||||
public void Customer() { }
|
||||
```
|
||||
|
||||
- 常用动词前缀
|
||||
```csharp
|
||||
// Get - 获取数据(通常有返回值)
|
||||
public Customer GetCustomerById(int id) { }
|
||||
public List<Order> GetOrders() { }
|
||||
|
||||
// Find - 查找数据(可能返回 null)
|
||||
public Customer? FindCustomerByEmail(string email) { }
|
||||
|
||||
// Create/Add - 创建新对象
|
||||
public void CreateOrder(Order order) { }
|
||||
public void AddProduct(Product product) { }
|
||||
|
||||
// Update/Modify - 更新现有对象
|
||||
public void UpdateCustomer(Customer customer) { }
|
||||
public void ModifyOrderStatus(int orderId, OrderStatus status) { }
|
||||
|
||||
// Delete/Remove - 删除对象
|
||||
public void DeleteOrder(int orderId) { }
|
||||
public void RemoveProduct(int productId) { }
|
||||
|
||||
// Save - 保存(创建或更新)
|
||||
public void SaveCustomer(Customer customer) { }
|
||||
|
||||
// Validate - 验证
|
||||
public bool ValidateOrder(Order order) { }
|
||||
public ValidationResult ValidateInput(string input) { }
|
||||
|
||||
// Calculate/Compute - 计算
|
||||
public decimal CalculateTotal(List<OrderItem> items) { }
|
||||
public int ComputeAge(DateTime birthDate) { }
|
||||
|
||||
// Process/Handle - 处理
|
||||
public void ProcessOrder(Order order) { }
|
||||
public void ProcessPayment(Payment payment) { }
|
||||
|
||||
// 布尔判断
|
||||
// ✅ 建议: 使用 Is、Has、Can、Should 等前缀更能提高可读性
|
||||
public bool IsValid() { }
|
||||
public bool IsActive() { }
|
||||
public bool HasPermission() { }
|
||||
public bool CanProcess() { }
|
||||
public bool ShouldRetry() { }
|
||||
public bool Exists(int id) { }
|
||||
public bool Contains(string value) { }
|
||||
// ❌ 不建议:不清晰的命名
|
||||
public bool Valid() { }
|
||||
public bool Active() { }
|
||||
public bool Permission() { }
|
||||
|
||||
// Convert/Transform - 转换
|
||||
public OrderDto ConvertToDto(Order order) { }
|
||||
public string TransformToJson(object obj) { }
|
||||
|
||||
// Send/Receive - 发送/接收
|
||||
public void SendNotification(string message) { }
|
||||
public Message ReceiveMessage() { }
|
||||
``
|
||||
|
||||
- 常用查询方法命名方式
|
||||
```csharp
|
||||
// ✅ 单个对象
|
||||
public Customer GetCustomerById(int id) { }
|
||||
public Order FindOrderByNumber(string orderNumber) { }
|
||||
|
||||
// ✅ 集合
|
||||
public List<Customer> GetAllCustomers() { }
|
||||
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { }
|
||||
public List<Product> FindProductsByCategory(string category) { }
|
||||
|
||||
// ✅ 条件查询
|
||||
public List<Order> GetActiveOrders() { }
|
||||
public List<Customer> GetVipCustomers() { }
|
||||
|
||||
// ✅ 分页查询
|
||||
public PagedResult<Product> GetProducts(int pageIndex, int pageSize) { }
|
||||
public List<Order> GetOrdersByPage(int pageNumber, int pageSize) { }
|
||||
```
|
||||
|
||||
- 私有方法也应该使用 PascalCase
|
||||
```csharp
|
||||
public class OrderProcessor
|
||||
{
|
||||
// ✅ 私有方法也使用 PascalCase
|
||||
private void ValidateOrderItems(Order order) { }
|
||||
private decimal CalculateItemTotal(OrderItem item) { }
|
||||
private bool CheckInventory(int productId) { }
|
||||
}
|
||||
```
|
||||
### 2.4 属性与字段
|
||||
- 属性使用 PascalCase
|
||||
```csharp
|
||||
public class Customer
|
||||
{
|
||||
// ✅ 建议的属性命名
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
public DateTime CreatedDate { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public decimal TotalAmount { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
- 私有字段使用 _camelCase
|
||||
```csharp
|
||||
public class OrderService
|
||||
{
|
||||
// ✅ 建议的私有字段命名
|
||||
private readonly IOrderRepository _orderRepository;
|
||||
private readonly ILogger<OrderService> _logger;
|
||||
private readonly IEmailService _emailService;
|
||||
private int _retryCount;
|
||||
private bool _isProcessing;
|
||||
}
|
||||
```
|
||||
|
||||
- 只读字段
|
||||
```csharp
|
||||
public class Configuration
|
||||
{
|
||||
// ✅ 公共只读字段使用 PascalCase
|
||||
public readonly string DefaultCulture = "zh-CN"; //(公共只读字段建议改为属性方式使用)
|
||||
|
||||
// ✅ 私有只读字段使用 _camelCase
|
||||
private readonly string _connectionString;
|
||||
private readonly int _maxRetryCount;
|
||||
}
|
||||
```
|
||||
|
||||
- 静态字段
|
||||
```csharp
|
||||
public class AppConstants
|
||||
{
|
||||
// ✅ 公共静态字段使用 PascalCase
|
||||
public static readonly int MaxPageSize = 100;
|
||||
public static readonly string DefaultDateFormat = "yyyy-MM-dd";
|
||||
|
||||
// ✅ 私有静态字段使用 _camelCase
|
||||
private static readonly object _lockObject = new object();
|
||||
}
|
||||
```
|
||||
|
||||
- 布尔属性
|
||||
```csharp
|
||||
public class Order
|
||||
{
|
||||
// ✅ 建议使用 Is、Has、Can 等前缀提高可读性
|
||||
public bool IsCompleted { get; set; }
|
||||
public bool IsPaid { get; set; }
|
||||
public bool HasDiscount { get; set; }
|
||||
public bool CanBeCancelled { get; set; }
|
||||
|
||||
// ❌ 不建议
|
||||
public bool Completed { get; set; }
|
||||
public bool Paid { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
- 集合属性
|
||||
```csharp
|
||||
public class Order
|
||||
{
|
||||
// ✅ 建议:使用复数形式
|
||||
public List<OrderItem> Items { get; set; }
|
||||
public ICollection<Payment> Payments { get; set; }
|
||||
public IEnumerable<Comment> Comments { get; set; }
|
||||
|
||||
// ❌ 不建议:使用单数
|
||||
public List<OrderItem> Item { get; set; }
|
||||
}
|
||||
```
|
||||
### 2.5 变量与参数
|
||||
- 局部变量使用 camelCase
|
||||
```csharp
|
||||
public void ProcessOrder(int orderId)
|
||||
{
|
||||
// ✅ 建议的局部变量命名
|
||||
var order = GetOrder(orderId);
|
||||
var totalAmount = CalculateTotal(order);
|
||||
var discountRate = GetDiscountRate(order);
|
||||
var finalPrice = totalAmount * (1 - discountRate);
|
||||
var isValid = ValidateOrder(order);
|
||||
}
|
||||
```
|
||||
|
||||
- 方法参数使用 camelCase
|
||||
```csharp
|
||||
// ✅ 建议的参数命名
|
||||
public Order CreateOrder(int customerId, List<OrderItem> items, decimal discountAmount, string shippingAddress)
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
|
||||
// ✅ 布尔参数命名
|
||||
public List<Order> GetOrders(int customerId, bool includeCompleted, bool includeCancelled)
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
```
|
||||
|
||||
- 循环变量命名
|
||||
```csharp
|
||||
// ✅ 简单循环可以使用单字母
|
||||
for (int i = 0; i < orders.Count; i++)
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
|
||||
// ✅ 有意义的循环变量
|
||||
foreach (var order in orders)
|
||||
{
|
||||
ProcessOrder(order);
|
||||
}
|
||||
|
||||
foreach (var item in order.Items)
|
||||
{
|
||||
ValidateItem(item);
|
||||
}
|
||||
|
||||
// ❌ 不建议:不清晰的变量名
|
||||
foreach (var o in orders) // o 不如 order 清晰
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
```
|
||||
|
||||
- LINQ 查询变量命名
|
||||
```csharp
|
||||
// ✅ 建议
|
||||
var activeCustomers = customers
|
||||
.Where(c => c.IsActive)
|
||||
.ToList();
|
||||
|
||||
var customerOrders = orders
|
||||
.Where(o => o.CustomerId == customerId)
|
||||
.OrderByDescending(o => o.CreatedDate)
|
||||
.ToList();
|
||||
|
||||
// ✅ 建议:复杂查询使用有意义的变量
|
||||
var highValueOrders = orders
|
||||
.Where(order => order.TotalAmount > 1000)
|
||||
.Select(order => new
|
||||
{
|
||||
OrderId = order.Id,
|
||||
CustomerName = order.Customer.Name,
|
||||
TotalAmount = order.TotalAmount
|
||||
})
|
||||
.ToList();
|
||||
```
|
||||
|
||||
- 临时变量命名
|
||||
```csharp
|
||||
public decimal CalculateOrderTotal(Order order)
|
||||
{
|
||||
// ✅ 建议:清晰的临时变量
|
||||
var subtotal = order.Items.Sum(item => item.Price * item.Quantity);
|
||||
var taxAmount = subtotal * 0.1m;
|
||||
var shippingFee = CalculateShippingFee(order);
|
||||
var discountAmount = CalculateDiscount(order, subtotal);
|
||||
var total = subtotal + taxAmount + shippingFee - discountAmount;
|
||||
|
||||
return total;
|
||||
}
|
||||
```
|
||||
|
||||
- 避免使用无意义的变量名
|
||||
```csharp
|
||||
// ❌ 不建议
|
||||
var temp = GetCustomer();
|
||||
var data = ProcessData();
|
||||
var obj = CreateObject();
|
||||
var result = DoSomething();
|
||||
|
||||
// ✅ 建议
|
||||
var customer = GetCustomer();
|
||||
var processedOrders = ProcessOrders();
|
||||
var paymentRecord = CreatePaymentRecord();
|
||||
var validationResult = ValidateInput();
|
||||
```
|
||||
### 2.6 常量与枚举
|
||||
- 常量命名
|
||||
```csharp
|
||||
// ✅ 方式一:PascalCase(推荐)
|
||||
public class OrderConstants
|
||||
{
|
||||
public const int MaxItemsPerOrder = 100;
|
||||
public const decimal MinOrderAmount = 10.0m;
|
||||
public const string DefaultCurrency = "CNY";
|
||||
}
|
||||
|
||||
// ✅ 方式二:UPPER_CASE(C# 中较少使用)
|
||||
public class AppSettings
|
||||
{
|
||||
public const int MAX_RETRY_COUNT = 3;
|
||||
public const string DEFAULT_CULTURE = "zh-CN";
|
||||
}
|
||||
|
||||
// ✅ 私有常量
|
||||
private const int MaxRetryAttempts = 3;
|
||||
private const string ErrorMessageTemplate = "处理订单 {0} 时发生错误";
|
||||
```
|
||||
|
||||
- 枚举命名
|
||||
```csharp
|
||||
// ✅ 枚举类型使用 PascalCase,单数形式
|
||||
public enum OrderStatus
|
||||
{
|
||||
Pending, // 枚举值使用 PascalCase
|
||||
Processing,
|
||||
Completed,
|
||||
Cancelled,
|
||||
Refunded
|
||||
}
|
||||
|
||||
// ✅ 标志枚举使用复数
|
||||
[Flags]
|
||||
public enum UserPermissions
|
||||
{
|
||||
None = 0,
|
||||
Read = 1,
|
||||
Write = 2,
|
||||
Delete = 4,
|
||||
Admin = 8
|
||||
}
|
||||
|
||||
// ❌ 不建议:使用复数(非标志枚举)
|
||||
public enum OrderStatuses { }
|
||||
|
||||
// ❌ 不建议:枚举值使用前缀
|
||||
public enum OrderStatus
|
||||
{
|
||||
OrderStatusPending, // 不需要前缀
|
||||
OrderStatusCompleted
|
||||
}
|
||||
```
|
||||
|
||||
- 常量类组织
|
||||
```csharp
|
||||
// ✅ 按功能组织常量
|
||||
public static class ValidationConstants
|
||||
{
|
||||
public const int MinPasswordLength = 8;
|
||||
public const int MaxPasswordLength = 50;
|
||||
public const int MinUsernameLength = 3;
|
||||
public const int MaxUsernameLength = 20;
|
||||
}
|
||||
|
||||
public static class CacheKeys
|
||||
{
|
||||
public const string CustomerPrefix = "customer_";
|
||||
public const string OrderPrefix = "order_";
|
||||
public const string ProductPrefix = "product_";
|
||||
}
|
||||
|
||||
public static class ErrorCodes
|
||||
{
|
||||
public const string OrderNotFound = "ORDER_NOT_FOUND";
|
||||
public const string InsufficientStock = "INSUFFICIENT_STOCK";
|
||||
public const string PaymentFailed = "PAYMENT_FAILED";
|
||||
}
|
||||
```
|
||||
### 2.7 布尔类型
|
||||
- 使用肯定的命名方式
|
||||
```csharp
|
||||
// ✅ 建议:肯定形式
|
||||
public bool IsActive { get; set; }
|
||||
public bool IsEnabled { get; set; }
|
||||
public bool HasPermission { get; set; }
|
||||
public bool CanEdit { get; set; }
|
||||
|
||||
// ❌ 不建议:否定形式(增加理解难度)
|
||||
public bool IsNotActive { get; set; }
|
||||
public bool IsDisabled { get; set; }
|
||||
```
|
||||
|
||||
- 常用布尔命名前缀
|
||||
```csharp
|
||||
public class Order
|
||||
{
|
||||
// Is - 状态判断
|
||||
public bool IsCompleted { get; set; }
|
||||
public bool IsValid { get; set; }
|
||||
public bool IsPaid { get; set; }
|
||||
|
||||
// Has - 拥有判断
|
||||
public bool HasDiscount { get; set; }
|
||||
public bool HasShipped { get; set; }
|
||||
|
||||
// Can - 能力判断
|
||||
public bool CanBeCancelled { get; set; }
|
||||
public bool CanBeRefunded { get; set; }
|
||||
|
||||
// Should - 应该判断
|
||||
public bool ShouldSendEmail { get; set; }
|
||||
public bool ShouldApplyDiscount { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
- 布尔方法命名
|
||||
```csharp
|
||||
public class OrderValidator
|
||||
{
|
||||
// ✅ 建议
|
||||
public bool IsValidOrder(Order order) { }
|
||||
public bool HasSufficientStock(int productId, int quantity) { }
|
||||
public bool CanProcessPayment(Payment payment) { }
|
||||
public bool Exists(int orderId) { }
|
||||
|
||||
// ✅ 验证方法也可以使用 Validate 开头,返回 bool
|
||||
public bool ValidateOrderItems(Order order) { }
|
||||
}
|
||||
```
|
||||
### 2.8 集合类型
|
||||
- 使用复数形式
|
||||
```csharp
|
||||
public class Customer
|
||||
{
|
||||
// ✅ 建议:使用复数
|
||||
public List<Order> Orders { get; set; }
|
||||
public ICollection<Address> Addresses { get; set; }
|
||||
public IEnumerable<Payment> Payments { get; set; }
|
||||
|
||||
// ❌ 不建议
|
||||
public List<Order> OrderList { get; set; } // 不需要 List 后缀
|
||||
public List<Order> Order { get; set; } // 应使用复数
|
||||
}
|
||||
```
|
||||
|
||||
- 集合局部变量命名
|
||||
```csharp
|
||||
public void ProcessOrders()
|
||||
{
|
||||
// ✅ 建议
|
||||
var orders = GetOrders();
|
||||
var activeOrders = orders.Where(o => o.IsActive).ToList();
|
||||
var orderIds = orders.Select(o => o.Id).ToList();
|
||||
var customerNames = customers.Select(c => c.Name).ToList();
|
||||
|
||||
// ❌ 不建议
|
||||
var orderDictionary = orders.ToDictionary(o => o.Id);
|
||||
var customersByEmail = customers.ToDictionary(c => c.Email);
|
||||
}
|
||||
```
|
||||
|
||||
- 特殊集合命名
|
||||
```csharp
|
||||
// ✅ 字典
|
||||
private readonly Dictionary<int, Order> _orderCache;
|
||||
private readonly ConcurrentDictionary<string, Customer> _customerLookup;
|
||||
|
||||
// ✅ 队列
|
||||
private readonly Queue<OrderMessage> _orderQueue;
|
||||
private readonly ConcurrentQueue<Task> _taskQueue;
|
||||
|
||||
// ✅ 堆栈
|
||||
private readonly Stack<Command> _commandHistory;
|
||||
```
|
||||
### 2.9 异步方法
|
||||
- 使用 `Async` 后缀
|
||||
```csharp
|
||||
// ✅ 异步方法使用 Async 后缀
|
||||
public async Task<Order> GetOrderAsync(int orderId)
|
||||
{
|
||||
return await _repository.GetByIdAsync(orderId);
|
||||
}
|
||||
|
||||
public async Task<List<Order>> GetOrdersAsync(int customerId)
|
||||
{
|
||||
return await _repository.GetOrdersByCustomerAsync(customerId);
|
||||
}
|
||||
|
||||
public async Task CreateOrderAsync(CreateOrderRequest request)
|
||||
{
|
||||
await _repository.AddAsync(MapToOrder(request));
|
||||
}
|
||||
|
||||
public async Task UpdateOrderAsync(Order order)
|
||||
{
|
||||
await _repository.UpdateAsync(order);
|
||||
}
|
||||
|
||||
public async Task DeleteOrderAsync(int orderId)
|
||||
{
|
||||
await _repository.DeleteAsync(orderId);
|
||||
}
|
||||
```
|
||||
|
||||
- 异步方法返回类型
|
||||
```csharp
|
||||
// ✅ 返回 Task
|
||||
public async Task SendEmailAsync(string to, string subject)
|
||||
{
|
||||
await _emailService.SendAsync(to, subject);
|
||||
}
|
||||
|
||||
// ✅ 返回 Task<T>
|
||||
public async Task<bool> ValidateOrderAsync(Order order)
|
||||
{
|
||||
return await _validator.ValidateAsync(order);
|
||||
}
|
||||
|
||||
// ✅ 返回 ValueTask(性能敏感场景)
|
||||
public async ValueTask<int> GetCachedCountAsync()
|
||||
{
|
||||
if (_cache.TryGetValue("count", out int count))
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
return await _repository.GetCountAsync();
|
||||
}
|
||||
|
||||
// ❌ 错误:避免使用 async void(除事件处理程序外)
|
||||
public async void ProcessOrderAsync(int orderId) // 错误!
|
||||
{
|
||||
await _service.ProcessAsync(orderId);
|
||||
}
|
||||
```
|
||||
824
docs/开发规范/1-编程规约/1.2-代码风格.md
Normal file
824
docs/开发规范/1-编程规约/1.2-代码风格.md
Normal file
|
|
@ -0,0 +1,824 @@
|
|||
# 1.2 代码风格
|
||||
|
||||
---
|
||||
|
||||
## 1. 文件组织结构
|
||||
- 类成员组织顺序
|
||||
```csharp
|
||||
public class OrderService : IOrderService
|
||||
{
|
||||
// 1. 常量
|
||||
private const int MaxRetryCount = 3;
|
||||
public const string DefaultCurrency = "CNY";
|
||||
|
||||
// 2. 静态字段
|
||||
private static readonly object _lockObject = new object();
|
||||
|
||||
// 3. 私有字段(只读字段在前)
|
||||
private readonly IOrderRepository _orderRepository;
|
||||
private readonly ILogger<OrderService> _logger;
|
||||
private readonly IMapper _mapper;
|
||||
private int _processCount;
|
||||
|
||||
// 4. 构造函数
|
||||
public OrderService(
|
||||
IOrderRepository orderRepository,
|
||||
ILogger<OrderService> logger,
|
||||
IMapper mapper)
|
||||
{
|
||||
_orderRepository = orderRepository;
|
||||
_logger = logger;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
// 5. 属性
|
||||
public int ProcessedCount => _processCount;
|
||||
|
||||
// 6. 公共方法(按功能分组)
|
||||
public async Task<Order> GetOrderAsync(int orderId)
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
|
||||
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
|
||||
// 7. 受保护方法
|
||||
protected virtual bool ValidateOrder(Order order)
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
|
||||
// 8. 私有方法
|
||||
private decimal CalculateDiscount(Order order)
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
|
||||
private void LogOrderCreation(Order order)
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
|
||||
// 9. 嵌套类型(如果有)
|
||||
private class OrderValidationContext
|
||||
{
|
||||
public Order Order { get; set; }
|
||||
public List<string> Errors { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 一个文件一个主要类原则
|
||||
```csharp
|
||||
// ✅ 建议:一个文件一个主要类
|
||||
// filepath: Models/Order.cs
|
||||
public class Order
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
|
||||
// ❌ 不建议:一个文件多个不相关的类
|
||||
// filepath: Models.cs
|
||||
public class Order { }
|
||||
public class Customer { }
|
||||
public class Product { }
|
||||
|
||||
// ✅ 例外:紧密相关的小类可以放在一起
|
||||
// filepath: Models/OrderEnums.cs
|
||||
public enum OrderStatus
|
||||
{
|
||||
Pending,
|
||||
Completed
|
||||
}
|
||||
|
||||
public enum PaymentStatus
|
||||
{
|
||||
Unpaid,
|
||||
Paid
|
||||
}
|
||||
```
|
||||
## 2. 缩进与空格
|
||||
- 使用 4 个空格缩进
|
||||
```csharp
|
||||
public class OrderService
|
||||
{
|
||||
public void ProcessOrder(Order order)
|
||||
{
|
||||
if (order != null)
|
||||
{
|
||||
var total = CalculateTotal(order);
|
||||
order.TotalAmount = total;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 运算符周围使用空格
|
||||
```csharp
|
||||
// ✅ 建议
|
||||
var total = price * quantity;
|
||||
var discount = total > 1000 ? 0.1m : 0;
|
||||
var result = (a + b) * c;
|
||||
var isValid = count > 0 && amount < maxAmount;
|
||||
|
||||
// ❌ 不建议
|
||||
var total=price*quantity;
|
||||
var result=(a+b)*c;
|
||||
```
|
||||
|
||||
- 逗号后使用空格
|
||||
```csharp
|
||||
// ✅ 建议
|
||||
public void CreateOrder(int customerId, decimal amount, string address)
|
||||
{
|
||||
var items = new List<int> { 1, 2, 3, 4 };
|
||||
ProcessOrder(customerId, amount, address);
|
||||
}
|
||||
|
||||
// ❌ 不建议
|
||||
public void CreateOrder(int customerId,decimal amount,string address)
|
||||
{
|
||||
var items = new List<int> { 1,2,3,4 };
|
||||
}
|
||||
```
|
||||
|
||||
- 方法参数对齐
|
||||
```csharp
|
||||
// ✅ 短参数列表:单行
|
||||
public Order CreateOrder(int customerId, decimal amount)
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
|
||||
// ✅ 长参数列表:每个参数一行
|
||||
public Order CreateOrder(
|
||||
int customerId,
|
||||
string customerName,
|
||||
List<OrderItem> items,
|
||||
string shippingAddress,
|
||||
PaymentMethod paymentMethod,
|
||||
decimal discountAmount)
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
|
||||
// ✅ LINQ 链式调用对齐
|
||||
var result = orders
|
||||
.Where(o => o.IsActive)
|
||||
.OrderByDescending(o => o.CreatedDate)
|
||||
.Select(o => new OrderDto
|
||||
{
|
||||
OrderId = o.Id,
|
||||
CustomerName = o.Customer.Name,
|
||||
TotalAmount = o.TotalAmount
|
||||
})
|
||||
.ToList();
|
||||
```
|
||||
## 3. 大括号
|
||||
- 大括号独占一行(Allman 风格)
|
||||
```csharp
|
||||
// ✅ 建议:大括号独占一行
|
||||
public void ProcessOrder(Order order)
|
||||
{
|
||||
if (order != null)
|
||||
{
|
||||
ValidateOrder(order);
|
||||
SaveOrder(order);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentNullException(nameof(order));
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ 不建议:K&R 风格(不推荐在 C# 中使用)
|
||||
public void ProcessOrder(Order order) {
|
||||
if (order != null) {
|
||||
ValidateOrder(order);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 单行语句也使用大括号
|
||||
```csharp
|
||||
// ✅ 建议:即使单行也使用大括号
|
||||
if (order.IsValid)
|
||||
{
|
||||
ProcessOrder(order);
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ProcessItem(i);
|
||||
}
|
||||
|
||||
// ❌ 不建议:省略大括号(易出错、可读性差)
|
||||
if (order.IsValid)
|
||||
ProcessOrder(order);
|
||||
LogOrder(order); // 这行不在 if 块内!
|
||||
```
|
||||
## 4. 空行
|
||||
- 方法之间使用空行
|
||||
```csharp
|
||||
public class OrderService
|
||||
{
|
||||
public Order GetOrder(int id)
|
||||
{
|
||||
return _repository.GetById(id);
|
||||
}
|
||||
// 空行分隔方法
|
||||
public void CreateOrder(Order order)
|
||||
{
|
||||
_repository.Add(order);
|
||||
}
|
||||
// 空行分隔方法
|
||||
public void UpdateOrder(Order order)
|
||||
{
|
||||
_repository.Update(order);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 逻辑块之间使用空行
|
||||
```csharp
|
||||
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
|
||||
{
|
||||
// 验证输入
|
||||
if (request == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
ValidateRequest(request);
|
||||
// 空行分隔逻辑块
|
||||
// 创建订单
|
||||
var order = new Order
|
||||
{
|
||||
CustomerId = request.CustomerId,
|
||||
CreatedDate = DateTime.Now
|
||||
};
|
||||
// 空行分隔逻辑块
|
||||
// 保存并返回
|
||||
await _repository.AddAsync(order);
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
return order;
|
||||
}
|
||||
```
|
||||
|
||||
- 不要使用多个连续空行
|
||||
```csharp
|
||||
// ✅ 建议:使用单个空行
|
||||
public void Method1()
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
|
||||
public void Method2()
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
|
||||
// ❌ 不建议:多个连续空行
|
||||
|
||||
|
||||
|
||||
|
||||
public void Method3()
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
```
|
||||
## 5. 语句换行
|
||||
- 长条件表达式换行
|
||||
```csharp
|
||||
// ✅ 建议:在逻辑运算符前换行
|
||||
if (customer.IsActive
|
||||
&& customer.TotalOrders > 10
|
||||
&& customer.TotalAmount > 10000
|
||||
&& !customer.IsBlacklisted)
|
||||
{
|
||||
ApplyVipDiscount(customer);
|
||||
}
|
||||
|
||||
// ✅ 复杂条件时可提取为变量
|
||||
var isEligibleForDiscount = customer.IsActive
|
||||
&& customer.TotalOrders > 10
|
||||
&& customer.TotalAmount > 10000
|
||||
&& !customer.IsBlacklisted;
|
||||
|
||||
if (isEligibleForDiscount)
|
||||
{
|
||||
ApplyVipDiscount(customer);
|
||||
}
|
||||
```
|
||||
|
||||
- LINQ 查询换行
|
||||
```csharp
|
||||
// ✅ 建议:每个操作符一行
|
||||
var result = orders
|
||||
.Where(o => o.IsActive)
|
||||
.Where(o => o.TotalAmount > 1000)
|
||||
.OrderByDescending(o => o.CreatedDate)
|
||||
.Select(o => new OrderDto
|
||||
{
|
||||
Id = o.Id,
|
||||
CustomerName = o.Customer.Name,
|
||||
TotalAmount = o.TotalAmount
|
||||
})
|
||||
.ToList();
|
||||
```
|
||||
## 6. using 指令组织
|
||||
- 移除未使用的 `using`
|
||||
```csharp
|
||||
// ✅ 建议:只保留使用的命名空间
|
||||
using System;
|
||||
using System.Linq;
|
||||
using CompanyName.ProjectName.Core.Entities;
|
||||
|
||||
// ❌ 不建议:包含未使用的命名空间
|
||||
using System;
|
||||
using System.Collections.Generic; // 未使用
|
||||
using System.Text; // 未使用
|
||||
using System.Linq;
|
||||
```
|
||||
|
||||
- 使用 `global using` (`C#` 10 +)
|
||||
```csharp
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Linq;
|
||||
global using System.Threading.Tasks;
|
||||
global using Microsoft.Extensions.Logging;
|
||||
```
|
||||
- `using static` 使用
|
||||
```csharp
|
||||
// ✅ 建议:简化静态成员调用
|
||||
using static System.Math;
|
||||
|
||||
public class Calculator
|
||||
{
|
||||
public double CalculateArea(double radius)
|
||||
{
|
||||
return PI * Pow(radius, 2); // 无需 Math.PI 和 Math.Pow
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ 不建议:过度使用会降低可读性
|
||||
using static System.Console; // 不推荐,WriteLine 来源不明确
|
||||
```
|
||||
## 7. 变量与数据类型
|
||||
### 7.1 变量声明原则
|
||||
- 就近声明原则
|
||||
```csharp
|
||||
// ✅ 建议:在使用前声明
|
||||
public void ProcessOrder(int orderId)
|
||||
{
|
||||
var order = GetOrder(orderId);
|
||||
if (order == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 在需要时才声明
|
||||
var discount = CalculateDiscount(order);
|
||||
order.DiscountAmount = discount;
|
||||
|
||||
// 在循环中声明
|
||||
foreach (var item in order.Items)
|
||||
{
|
||||
var itemTotal = item.Price * item.Quantity;
|
||||
ValidateItem(item, itemTotal);
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ 不建议:过早声明
|
||||
public void ProcessOrder(int orderId)
|
||||
{
|
||||
var order = GetOrder(orderId);
|
||||
var discount = 0m; // 过早声明
|
||||
var itemTotal = 0m; // 过早声明
|
||||
|
||||
if (order == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
discount = CalculateDiscount(order);
|
||||
// ...existing code...
|
||||
}
|
||||
```
|
||||
|
||||
- 最小作用域原则
|
||||
```csharp
|
||||
// ✅ 建议:限制变量作用域
|
||||
public decimal CalculateTotal(Order order)
|
||||
{
|
||||
decimal total = 0;
|
||||
{
|
||||
var taxRate = GetTaxRate();
|
||||
var tax = order.Subtotal * taxRate;
|
||||
total = order.Subtotal + tax;
|
||||
}
|
||||
|
||||
// taxRate 和 tax 在此处不可访问
|
||||
return total;
|
||||
}
|
||||
|
||||
// ✅ 在 if 语句中声明
|
||||
if (TryGetOrder(orderId, out var order))
|
||||
{
|
||||
ProcessOrder(order);
|
||||
}
|
||||
// order 在此处不可访问
|
||||
```
|
||||
|
||||
- 初始化时声明
|
||||
```csharp
|
||||
// ✅ 建议:声明时初始化
|
||||
var customerName = "张三";
|
||||
var orderCount = 0;
|
||||
var items = new List<OrderItem>();
|
||||
var isValid = ValidateInput();
|
||||
|
||||
// ❌ 不建议:分离声明和初始化
|
||||
string customerName;
|
||||
int orderCount;
|
||||
List<OrderItem> items;
|
||||
|
||||
customerName = "张三";
|
||||
orderCount = 0;
|
||||
items = new List<OrderItem>();
|
||||
```
|
||||
### 7.2 `null` 处理
|
||||
- 使用可空类型
|
||||
```csharp
|
||||
// ✅ 建议:使用可空类型表示可能为 null 的值
|
||||
public class Order
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public DateTime? CompletedDate { get; set; } // 可能未完成
|
||||
public decimal? DiscountAmount { get; set; } // 可能无折扣
|
||||
public string? Note { get; set; } // 可能无备注 (C# 8.0+)
|
||||
}
|
||||
```
|
||||
|
||||
- `null` 检查
|
||||
```csharp
|
||||
// ✅ 建议:参数 null 检查
|
||||
public void ProcessOrder(Order order)
|
||||
{
|
||||
if (order == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(order));
|
||||
}
|
||||
|
||||
// 或使用 C# 11+ 参数 null 检查
|
||||
public void ProcessOrder(Order order!!)
|
||||
{
|
||||
// order 不为 null
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 返回值 null 检查
|
||||
var order = GetOrder(orderId);
|
||||
if (order == null)
|
||||
{
|
||||
_logger.LogWarning("订单 {OrderId} 不存在", orderId);
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ 使用模式匹配
|
||||
if (GetOrder(orderId) is Order order)
|
||||
{
|
||||
ProcessOrder(order);
|
||||
}
|
||||
```
|
||||
|
||||
- 使用 `?.` 和 `??` 操作符
|
||||
```csharp
|
||||
// ✅ null 条件运算符 ?.
|
||||
var customerName = order?.Customer?.Name;
|
||||
var itemCount = order?.Items?.Count ?? 0;
|
||||
|
||||
// ✅ null 合并运算符 ??
|
||||
var discountAmount = order.DiscountAmount ?? 0m;
|
||||
var shippingAddress = order.ShippingAddress ?? order.Customer.DefaultAddress;
|
||||
|
||||
// ✅ null 合并赋值运算符 ??=
|
||||
order.Note ??= "无备注";
|
||||
_cache ??= new Dictionary<string, object>();
|
||||
|
||||
// ✅ 链式使用
|
||||
var cityName = order?.Customer?.Address?.City ?? "未知";
|
||||
```
|
||||
|
||||
- 避免返回 `null`
|
||||
```csharp
|
||||
// ❌ 不建议:返回 null
|
||||
public List<Order> GetOrders(int customerId)
|
||||
{
|
||||
var orders = _repository.GetByCustomerId(customerId);
|
||||
return orders; // 可能返回 null
|
||||
}
|
||||
|
||||
// ✅ 建议:返回空集合
|
||||
public List<Order> GetOrders(int customerId)
|
||||
{
|
||||
var orders = _repository.GetByCustomerId(customerId);
|
||||
return orders ?? new List<Order>();
|
||||
}
|
||||
|
||||
// ✅ 建议:使用 LINQ
|
||||
public IEnumerable<Order> GetOrders(int customerId)
|
||||
{
|
||||
return _repository.GetByCustomerId(customerId)
|
||||
?? Enumerable.Empty<Order>();
|
||||
}
|
||||
```
|
||||
|
||||
- 使用 `nullable reference types` (`C#` 8.0+)
|
||||
```csharp
|
||||
#nullable enable
|
||||
|
||||
public class OrderService
|
||||
{
|
||||
// 不可为 null
|
||||
private readonly IOrderRepository _repository;
|
||||
|
||||
// 可为 null
|
||||
private ILogger? _logger;
|
||||
|
||||
public OrderService(IOrderRepository repository)
|
||||
{
|
||||
_repository = repository; // 必须赋值
|
||||
}
|
||||
|
||||
public Order? GetOrder(int id) // 明确返回可能为 null
|
||||
{
|
||||
return _repository.GetById(id);
|
||||
}
|
||||
|
||||
public void ProcessOrder(Order order) // 参数不可为 null
|
||||
{
|
||||
// order 保证不为 null
|
||||
var total = order.TotalAmount;
|
||||
}
|
||||
}
|
||||
```
|
||||
### 7.3 字符串操作
|
||||
- 字符转拼接
|
||||
```csharp
|
||||
// ❌ 不建议:循环中使用 + 拼接
|
||||
string result = "";
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
result += i.ToString(); // 性能差,产生大量临时对象
|
||||
}
|
||||
|
||||
// ✅ 建议:使用 StringBuilder
|
||||
var sb = new StringBuilder();
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
sb.Append(i);
|
||||
}
|
||||
string result = sb.ToString();
|
||||
|
||||
// ✅ 少量拼接可以使用字符串插值
|
||||
string name = "张三";
|
||||
int age = 25;
|
||||
string message = $"姓名:{name},年龄:{age}"; // 推荐
|
||||
|
||||
// ✅ 使用 string.Join
|
||||
var items = new[] { "苹果", "香蕉", "橙子" };
|
||||
string result = string.Join(", ", items); // "苹果, 香蕉, 橙子"
|
||||
```
|
||||
|
||||
- 字符串比较
|
||||
```csharp
|
||||
// ✅ 建议:使用 StringComparison
|
||||
if (string.Equals(str1, str2, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// 忽略大小写比较
|
||||
}
|
||||
|
||||
// ✅ 检查 null 或空
|
||||
if (string.IsNullOrEmpty(customerName))
|
||||
{
|
||||
throw new ArgumentException("客户名称不能为空");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(note))
|
||||
{
|
||||
note = "无备注";
|
||||
}
|
||||
|
||||
// ❌ 不建议:使用 == 比较可能为 null 的字符串
|
||||
if (str1 == str2) // 如果都为 null 会返回 true
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
```
|
||||
|
||||
- 字符串格式化
|
||||
```csharp
|
||||
// ✅ 推荐使用字符串插值
|
||||
decimal price = 99.99m;
|
||||
string message = $"价格:{price:C}"; // "价格:¥99.99"
|
||||
|
||||
// ✅ 格式化数字
|
||||
int number = 1234567;
|
||||
string formatted = $"{number:N0}"; // "1,234,567"
|
||||
string hex = $"{number:X}"; // "12D687"
|
||||
|
||||
// ✅ 格式化日期
|
||||
DateTime now = DateTime.Now;
|
||||
string dateStr = $"{now:yyyy-MM-dd HH:mm:ss}"; // "2025-11-26 14:30:00"
|
||||
string shortDate = $"{now:d}"; // "2025/11/26"
|
||||
|
||||
// ✅ 对齐和填充
|
||||
string name = "张三";
|
||||
string aligned = $"{name, 10}"; // 右对齐,总宽度10
|
||||
string leftAligned = $"{name, -10}"; // 左对齐
|
||||
```
|
||||
### 7.4 集合
|
||||
- 集合初始化
|
||||
```csharp
|
||||
// ✅ 集合初始化器
|
||||
var numbers = new List<int> { 1, 2, 3, 4, 5 };
|
||||
|
||||
var dict = new Dictionary<string, int>
|
||||
{
|
||||
["apple"] = 1,
|
||||
["banana"] = 2,
|
||||
["orange"] = 3
|
||||
};
|
||||
|
||||
// ✅ 指定初始容量(已知大小时)
|
||||
var largeList = new List<int>(1000); // 避免多次扩容
|
||||
|
||||
// ✅ 数组初始化
|
||||
int[] scores = { 85, 90, 78, 92 };
|
||||
int[] grades = new int[5]; // 固定大小
|
||||
```
|
||||
|
||||
- 集合选择
|
||||
```csharp
|
||||
// ✅ List<T> - 需要按索引访问和频繁添加
|
||||
var customers = new List<Customer>();
|
||||
|
||||
// ✅ HashSet<T> - 需要唯一性检查和快速查找
|
||||
var uniqueIds = new HashSet<int>();
|
||||
uniqueIds.Add(1);
|
||||
uniqueIds.Add(1); // 不会重复添加
|
||||
|
||||
// ✅ Dictionary<TKey, TValue> - 键值对存储和快速查找
|
||||
var customerCache = new Dictionary<int, Customer>();
|
||||
|
||||
// ✅ Queue<T> - 先进先出
|
||||
var taskQueue = new Queue<Task>();
|
||||
|
||||
// ✅ Stack<T> - 后进先出
|
||||
var history = new Stack<string>();
|
||||
|
||||
// ✅ ImmutableList<T> - 不可变集合(线程安全)
|
||||
var immutableList = ImmutableList.Create(1, 2, 3);
|
||||
```
|
||||
### 7.5 常量与魔法数字处理
|
||||
- 常量定义
|
||||
```csharp
|
||||
// ✅ 定义常量类
|
||||
public static class OrderConstants
|
||||
{
|
||||
public const int MaxItemsPerOrder = 100;
|
||||
public const decimal MinOrderAmount = 50.0m;
|
||||
public const int OrderTimeoutMinutes = 30;
|
||||
|
||||
// 字符串常量
|
||||
public const string DefaultCurrency = "CNY";
|
||||
public const string DateFormat = "yyyy-MM-dd";
|
||||
}
|
||||
|
||||
// ✅ 在类中使用私有常量
|
||||
public class OrderService
|
||||
{
|
||||
private const int MaxRetryCount = 3;
|
||||
private const string LogPrefix = "[OrderService]";
|
||||
|
||||
public void ProcessOrder(Order order)
|
||||
{
|
||||
if (order.Items.Count > OrderConstants.MaxItemsPerOrder)
|
||||
{
|
||||
throw new BusinessException($"订单商品数量不能超过{OrderConstants.MaxItemsPerOrder}");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 避免魔法数字
|
||||
```csharp
|
||||
// ❌ 不建议:魔法数字
|
||||
public decimal CalculateDiscount(decimal amount)
|
||||
{
|
||||
if (amount > 1000)
|
||||
{
|
||||
return amount * 0.1m; // 0.1 是什么?
|
||||
}
|
||||
|
||||
if (amount > 500)
|
||||
{
|
||||
return amount * 0.05m; // 0.05 是什么?
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ✅ 建议:使用命名常量
|
||||
public class DiscountCalculator
|
||||
{
|
||||
private const decimal HighAmountThreshold = 1000m;
|
||||
private const decimal MediumAmountThreshold = 500m;
|
||||
private const decimal HighDiscountRate = 0.1m;
|
||||
private const decimal MediumDiscountRate = 0.05m;
|
||||
|
||||
public decimal CalculateDiscount(decimal amount)
|
||||
{
|
||||
if (amount > HighAmountThreshold)
|
||||
{
|
||||
return amount * HighDiscountRate;
|
||||
}
|
||||
|
||||
if (amount > MediumAmountThreshold)
|
||||
{
|
||||
return amount * MediumDiscountRate;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
### 7.6 枚举类型
|
||||
-
|
||||
```csharp
|
||||
public class OrderService
|
||||
{
|
||||
// ✅ 使用枚举而非字符串或数字
|
||||
public void UpdateOrderStatus(int orderId, OrderStatus status)
|
||||
{
|
||||
// 类型安全,编译时检查
|
||||
switch (status)
|
||||
{
|
||||
case OrderStatus.Pending:
|
||||
// 处理待处理状态
|
||||
break;
|
||||
case OrderStatus.Confirmed:
|
||||
// 处理已确认状态
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"不支持的订单状态:{status}");
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 枚举转换字符串
|
||||
public string GetStatusName(OrderStatus status)
|
||||
{
|
||||
return status.ToString(); // "Pending"
|
||||
}
|
||||
|
||||
// ✅ 字符串转枚举(安全)
|
||||
public OrderStatus ParseStatus(string statusText)
|
||||
{
|
||||
if (Enum.TryParse<OrderStatus>(statusText, out var status))
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"无效的订单状态:{statusText}");
|
||||
}
|
||||
|
||||
// ✅ 获取所有枚举值
|
||||
public IEnumerable<OrderStatus> GetAllStatuses()
|
||||
{
|
||||
return Enum.GetValues<OrderStatus>();
|
||||
}
|
||||
|
||||
// ✅ 标志枚举操作
|
||||
public void ManagePermissions()
|
||||
{
|
||||
var permissions = FilePermissions.None;
|
||||
|
||||
// 添加权限
|
||||
permissions |= FilePermissions.Read;
|
||||
permissions |= FilePermissions.Write;
|
||||
|
||||
// 检查权限
|
||||
bool canRead = permissions.HasFlag(FilePermissions.Read);
|
||||
|
||||
// 移除权限
|
||||
permissions &= ~FilePermissions.Write;
|
||||
}
|
||||
}
|
||||
```
|
||||
476
docs/开发规范/1-编程规约/1.3-注释规约.md
Normal file
476
docs/开发规范/1-编程规约/1.3-注释规约.md
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
# 1.3 注释规约
|
||||
|
||||
---
|
||||
|
||||
## 1. XML 文档注释
|
||||
- 类注释
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// 订单服务,提供订单管理相关业务逻辑
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 此服务负责:
|
||||
/// - 订单创建、查询、更新、删除
|
||||
/// - 订单状态流转
|
||||
/// - 订单金额计算
|
||||
/// </remarks>
|
||||
public class OrderService : IOrderService
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
```
|
||||
|
||||
- 接口注释
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// 订单仓储接口,定义订单数据访问操作
|
||||
/// </summary>
|
||||
public interface IOrderRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据订单ID获取订单详情
|
||||
/// </summary>
|
||||
/// <param name="orderId">订单ID</param>
|
||||
/// <returns>订单实体,如果不存在返回 null</returns>
|
||||
Task<Order> GetByIdAsync(int orderId);
|
||||
}
|
||||
```
|
||||
|
||||
- 方法注释
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// 创建新订单
|
||||
/// </summary>
|
||||
/// <param name="request">订单创建请求对象</param>
|
||||
/// <returns>创建成功的订单实体</returns>
|
||||
/// <exception cref="ArgumentNullException">当 request 为 null 时抛出</exception>
|
||||
/// <exception cref="ValidationException">当订单数据验证失败时抛出</exception>
|
||||
/// <exception cref="InsufficientStockException">当库存不足时抛出</exception>
|
||||
/// <remarks>
|
||||
/// 此方法执行以下步骤:
|
||||
/// 1. 验证请求数据
|
||||
/// 2. 检查库存
|
||||
/// 3. 计算订单金额
|
||||
/// 4. 创建订单记录
|
||||
/// 5. 发送确认邮件
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var request = new CreateOrderRequest
|
||||
/// {
|
||||
/// CustomerId = 123,
|
||||
/// Items = new List<OrderItemDto>
|
||||
/// {
|
||||
/// new() { ProductId = 1, Quantity = 2 }
|
||||
/// }
|
||||
/// };
|
||||
/// var order = await orderService.CreateOrderAsync(request);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
```
|
||||
|
||||
- 属性注释
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// 获取或设置订单ID
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置客户ID
|
||||
/// </summary>
|
||||
public int CustomerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置订单总金额(含税)
|
||||
/// </summary>
|
||||
/// <value>订单总金额,单位:元</value>
|
||||
public decimal TotalAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置订单是否已完成
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> 表示订单已完成;否则为 <c>false</c>
|
||||
/// </value>
|
||||
public bool IsCompleted { get; set; }
|
||||
```
|
||||
|
||||
- 枚举注释
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// 订单状态枚举
|
||||
/// </summary>
|
||||
public enum OrderStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// 待处理
|
||||
/// </summary>
|
||||
Pending = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 处理中
|
||||
/// </summary>
|
||||
Processing = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 已完成
|
||||
/// </summary>
|
||||
Completed = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 已取消
|
||||
/// </summary>
|
||||
Cancelled = 3
|
||||
}
|
||||
```
|
||||
|
||||
- 泛型参数注释
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// 通用仓储接口
|
||||
/// </summary>
|
||||
/// <typeparam name="T">实体类型,必须是引用类型</typeparam>
|
||||
public interface IRepository<T> where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据ID获取实体
|
||||
/// </summary>
|
||||
/// <param name="id">实体ID</param>
|
||||
/// <returns>实体对象,如果不存在返回 null</returns>
|
||||
Task<T> GetByIdAsync(int id);
|
||||
}
|
||||
```
|
||||
## 2. 行内注释使用场景
|
||||
- 解释复杂业务逻辑
|
||||
```csharp
|
||||
public decimal CalculateDiscount(Order order)
|
||||
{
|
||||
// 根据业务规则BR-2023-001:
|
||||
// VIP客户订单金额超过1000元享受9折优惠
|
||||
// 新客户首单享受95折优惠
|
||||
// 优惠不可叠加,取最优惠方案
|
||||
|
||||
if (order.Customer.IsVip && order.TotalAmount > 1000)
|
||||
{
|
||||
return order.TotalAmount * 0.1m;
|
||||
}
|
||||
|
||||
if (order.Customer.IsNewCustomer && order.IsFirstOrder)
|
||||
{
|
||||
return order.TotalAmount * 0.05m;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
- 解释不明显的代码意图
|
||||
```csharp
|
||||
public void ProcessOrder(Order order)
|
||||
{
|
||||
// 将订单状态设置为处理中,防止并发处理同一订单
|
||||
order.Status = OrderStatus.Processing;
|
||||
order.LastModifiedDate = DateTime.UtcNow;
|
||||
|
||||
// 使用UTC时间避免时区问题
|
||||
var processStartTime = DateTime.UtcNow;
|
||||
|
||||
// 临时解决方案:忽略已删除的订单项
|
||||
// TODO: 后续需要从数据库层面过滤
|
||||
var activeItems = order.Items.Where(i => !i.IsDeleted).ToList();
|
||||
}
|
||||
```
|
||||
|
||||
- 标记临时代码或待优化代码
|
||||
```csharp
|
||||
public async Task<List<Order>> GetOrdersAsync(int customerId)
|
||||
{
|
||||
// HACK: 临时解决方案,直接加载所有订单
|
||||
// 性能问题:当订单量大时会导致内存占用过高
|
||||
// TODO: 实现分页加载
|
||||
var orders = await _context.Orders
|
||||
.Where(o => o.CustomerId == customerId)
|
||||
.ToListAsync();
|
||||
|
||||
return orders;
|
||||
}
|
||||
```
|
||||
|
||||
- 解释算法或公式
|
||||
```csharp
|
||||
public decimal CalculateShippingFee(Order order)
|
||||
{
|
||||
// 运费计算公式:
|
||||
// 基础运费 = 10元
|
||||
// 重量费用 = 总重量 * 2元/kg
|
||||
// 距离费用 = 距离 * 0.5元/km
|
||||
// 如果订单金额超过200元,免运费
|
||||
|
||||
if (order.TotalAmount >= 200)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var baseFee = 10m;
|
||||
var weightFee = order.TotalWeight * 2m;
|
||||
var distanceFee = order.DeliveryDistance * 0.5m;
|
||||
|
||||
return baseFee + weightFee + distanceFee;
|
||||
}
|
||||
```
|
||||
## 3. `TODO`、`FIXME`、`HACK`、`NOTE`/`IMPORTANT` 标记使用
|
||||
- `TODO` - 待实现功能
|
||||
```csharp
|
||||
public class OrderService
|
||||
{
|
||||
public async Task CreateOrderAsync(Order order)
|
||||
{
|
||||
await _repository.AddAsync(order);
|
||||
|
||||
// TODO: 实现订单创建后发送确认邮件
|
||||
// TODO: 实现库存扣减逻辑
|
||||
// TODO: 集成支付网关
|
||||
}
|
||||
|
||||
// TODO: [张三] 2024-01-15 实现退款功能
|
||||
public Task RefundOrderAsync(int orderId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `FIXME` - 已知问题需要修复
|
||||
```csharp
|
||||
public async Task<List<Order>> GetOrdersAsync(int pageIndex, int pageSize)
|
||||
{
|
||||
// FIXME: 当pageSize过大时会导致性能问题
|
||||
// FIXME: 未处理pageIndex为负数的情况
|
||||
|
||||
return await _context.Orders
|
||||
.Skip(pageIndex * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public decimal CalculateTotal(Order order)
|
||||
{
|
||||
// FIXME: 并发情况下可能导致重复计算
|
||||
// 需要添加锁机制或使用数据库事务
|
||||
|
||||
return order.Items.Sum(i => i.Price * i.Quantity);
|
||||
}
|
||||
```
|
||||
|
||||
- `HACK` - 临时解决方案
|
||||
```csharp
|
||||
public async Task ProcessOrderAsync(int orderId)
|
||||
{
|
||||
// HACK: 临时方案,硬编码重试3次
|
||||
// 应该从配置文件读取重试次数
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessAsync(orderId);
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (i == 2)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `NOTE`/`IMPORTANT` - 重要说明
|
||||
```csharp
|
||||
public class PaymentService
|
||||
{
|
||||
// NOTE: 此方法会修改订单状态,调用前请确保已获取锁
|
||||
// IMPORTANT: 必须在事务中调用此方法
|
||||
public async Task ProcessPaymentAsync(Payment payment)
|
||||
{
|
||||
// ...existing code...
|
||||
}
|
||||
}
|
||||
```
|
||||
## 4. 复杂业务逻辑注释要求
|
||||
- 业务规则注释
|
||||
```csharp
|
||||
public bool CanApplyDiscount(Order order, Customer customer)
|
||||
{
|
||||
// 业务规则 BR-2024-001: 折扣适用条件
|
||||
// 1. 客户必须是激活状态
|
||||
// 2. 订单金额必须大于等于100元
|
||||
// 3. 客户当月订单数量不超过10个
|
||||
// 4. 产品不在促销黑名单中
|
||||
|
||||
if (!customer.IsActive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (order.TotalAmount < 100)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var monthlyOrderCount = GetMonthlyOrderCount(customer.Id);
|
||||
if (monthlyOrderCount > 10)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var hasBlacklistedProducts = order.Items.Any(i => IsProductBlacklisted(i.ProductId));
|
||||
|
||||
return !hasBlacklistedProducts;
|
||||
}
|
||||
```
|
||||
|
||||
- 状态机转换注释
|
||||
```csharp
|
||||
public void UpdateOrderStatus(Order order, OrderStatus newStatus)
|
||||
{
|
||||
// 订单状态转换规则:
|
||||
// Pending -> Processing (允许)
|
||||
// Pending -> Cancelled (允许)
|
||||
// Processing -> Completed (允许)
|
||||
// Processing -> Cancelled (不允许,必须先退款)
|
||||
// Completed -> Refunded (允许,需要走退款流程)
|
||||
// Cancelled -> * (不允许任何转换)
|
||||
|
||||
var allowedTransitions = new Dictionary<OrderStatus, List<OrderStatus>>
|
||||
{
|
||||
{ OrderStatus.Pending, new List<OrderStatus> { OrderStatus.Processing, OrderStatus.Cancelled } },
|
||||
{ OrderStatus.Processing, new List<OrderStatus> { OrderStatus.Completed } },
|
||||
{ OrderStatus.Completed, new List<OrderStatus> { OrderStatus.Refunded } }
|
||||
};
|
||||
|
||||
if (!allowedTransitions.ContainsKey(order.Status) || !allowedTransitions[order.Status].Contains(newStatus))
|
||||
{
|
||||
throw new InvalidOperationException($"不允许从 {order.Status} 转换到 {newStatus}");
|
||||
}
|
||||
|
||||
order.Status = newStatus;
|
||||
}
|
||||
```
|
||||
|
||||
- 复杂计算注释
|
||||
```csharp
|
||||
public decimal CalculateCommission(Order order, Salesperson salesperson)
|
||||
{
|
||||
// 佣金计算规则(2024年Q1版本):
|
||||
//
|
||||
// 基础佣金率:
|
||||
// - 初级销售:订单金额的 3%
|
||||
// - 中级销售:订单金额的 5%
|
||||
// - 高级销售:订单金额的 8%
|
||||
//
|
||||
// 阶梯奖励(累加):
|
||||
// - 月度销售额超过 10万,额外 +1%
|
||||
// - 月度销售额超过 50万,额外 +2%
|
||||
// - 月度销售额超过100万,额外 +3%
|
||||
//
|
||||
// 特殊产品加成:
|
||||
// - 高端产品类别,佣金率 *1.5
|
||||
|
||||
var baseRate = salesperson.Level switch
|
||||
{
|
||||
SalesLevel.Junior => 0.03m,
|
||||
SalesLevel.Intermediate => 0.05m,
|
||||
SalesLevel.Senior => 0.08m,
|
||||
_ => 0.03m
|
||||
};
|
||||
|
||||
var monthlySales = GetMonthlySales(salesperson.Id);
|
||||
var bonusRate = monthlySales switch
|
||||
{
|
||||
>= 1000000 => 0.03m,
|
||||
>= 500000 => 0.02m,
|
||||
>= 100000 => 0.01m,
|
||||
_ => 0m
|
||||
};
|
||||
|
||||
var finalRate = baseRate + bonusRate;
|
||||
|
||||
var hasHighEndProducts = order.Items.Any(i => i.Product.Category == ProductCategory.HighEnd);
|
||||
|
||||
if (hasHighEndProducts)
|
||||
{
|
||||
finalRate *= 1.5m;
|
||||
}
|
||||
|
||||
return order.TotalAmount * finalRate;
|
||||
}
|
||||
```
|
||||
## 5. 注释的维护与更新
|
||||
- 注释应与代码同步更新
|
||||
```csharp
|
||||
// ❌ 错误:注释已过时
|
||||
// 返回所有订单(实际代码已改为返回激活订单)
|
||||
public List<Order> GetOrders()
|
||||
{
|
||||
return _context.Orders
|
||||
.Where(o => o.IsActive) // 代码已修改,但注释未更新
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// ✅ 正确:更新注释
|
||||
/// <summary>
|
||||
/// 获取所有激活状态的订单
|
||||
/// </summary>
|
||||
public List<Order> GetActiveOrders()
|
||||
{
|
||||
return _context.Orders
|
||||
.Where(o => o.IsActive)
|
||||
.ToList();
|
||||
}
|
||||
```
|
||||
|
||||
- 删除无用注释
|
||||
```csharp
|
||||
// ❌ 错误:保留已注释的旧代码
|
||||
public void ProcessOrder(Order order)
|
||||
{
|
||||
// var oldDiscount = order.TotalAmount * 0.05m;
|
||||
// order.DiscountAmount = oldDiscount;
|
||||
|
||||
// 新的折扣计算逻辑
|
||||
var newDiscount = CalculateDiscount(order);
|
||||
order.DiscountAmount = newDiscount;
|
||||
}
|
||||
|
||||
// ✅ 正确:删除已注释代码,使用版本控制系统管理历史
|
||||
public void ProcessOrder(Order order)
|
||||
{
|
||||
var discount = CalculateDiscount(order);
|
||||
order.DiscountAmount = discount;
|
||||
}
|
||||
```
|
||||
|
||||
- 避免显而易见的注释
|
||||
```csharp
|
||||
// ❌ 错误:显而易见的注释
|
||||
// 设置客户名称
|
||||
customer.Name = "张三";
|
||||
|
||||
// 增加计数器
|
||||
counter ++;
|
||||
|
||||
// ✅ 正确:只注释需要解释的内容
|
||||
// 使用UTC时间避免时区转换问题
|
||||
order.CreatedDate = DateTime.UtcNow;
|
||||
|
||||
// 预留30天的订单保留期
|
||||
var retentionDays = 30;
|
||||
```
|
||||
184
docs/开发规范/1-编程规约/1.4-多线程与异步规范.md
Normal file
184
docs/开发规范/1-编程规约/1.4-多线程与异步规范.md
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
# 1.4 多线程与异步规范
|
||||
|
||||
本规范旨在指导 .NET 中的多线程与异步编程,确保代码的高效性、稳定性和可维护性,避免常见的死锁、线程饥饿和竞态条件问题。
|
||||
|
||||
## 1. 异步编程 (Async/Await)
|
||||
|
||||
### 1.1 避免 `async void`
|
||||
|
||||
* **规则**:严禁使用 `async void`,唯一的例外是事件处理程序 (Event Handlers)。
|
||||
* **理由**:
|
||||
* `async void` 方法无法被等待 (await)。
|
||||
* `async void` 方法中抛出的异常无法被调用方捕获,会导致进程崩溃(除非在 SynchronizationContext 中捕获)。
|
||||
* 难以测试和组合。
|
||||
* **反例** :
|
||||
```csharp
|
||||
// ❌ [不推荐] 使用 async void,异常无法被外部捕获
|
||||
private async void ExecuteAsync(object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _jobPerformer.Perform(...);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// 必须在内部吞掉所有异常,否则进程崩溃
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
}
|
||||
```
|
||||
* **正例**:
|
||||
```csharp
|
||||
// ✅ [推荐] 返回 Task,允许调用方等待和处理异常
|
||||
private async Task ExecuteAsync(object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _jobPerformer.Perform(...);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Error occurred");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 异步全链路 (Async All the Way)
|
||||
|
||||
* **规则**:一旦开始使用异步,应在整个调用链路中保持异步。
|
||||
* **理由**:避免同步/异步混合导致的死锁(Sync-over-Async)和线程池饥饿。
|
||||
* **反例**:
|
||||
```csharp
|
||||
// ❌ [禁止] 在异步方法中阻塞等待
|
||||
public void DoSomething()
|
||||
{
|
||||
DoSomethingAsync().Result; // 或者 .Wait()
|
||||
}
|
||||
```
|
||||
* **正例**:
|
||||
```csharp
|
||||
// ✅ [推荐] 使用 await
|
||||
public async Task DoSomething()
|
||||
{
|
||||
await DoSomethingAsync();
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 库代码使用 `ConfigureAwait(false)`
|
||||
|
||||
* **规则**:在通用类库 (非 UI 层、非 ASP.NET Core Controller 层) 代码中,应使用 `.ConfigureAwait(false)`。
|
||||
* **理由**:避免在不需要特定 `SynchronizationContext` (如 UI 线程) 的情况下强制切回原上下文,提高性能并减少死锁风险。
|
||||
* **注意**:在 ASP.NET Core 应用层代码中通常不需要,因为 ASP.NET Core 没有 SynchronizationContext。但在编写底层 SDK (如 `Aegis.Caching`) 时必须遵守。
|
||||
* **正例**:
|
||||
```csharp
|
||||
// ✅ [推荐] 类库代码
|
||||
public async Task<string> GetDataAsync()
|
||||
{
|
||||
var result = await _httpClient.GetStringAsync(url).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### 1.4 传入 CancellationToken
|
||||
|
||||
* **规则**:异步方法应尽可能支持 `CancellationToken`,以便在操作不再需要时取消。
|
||||
* **正例**:
|
||||
```csharp
|
||||
// ✅ [推荐] 支持取消
|
||||
public async Task ProcessAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(1000, cancellationToken);
|
||||
// ... 业务逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 线程与任务 (Threads & Tasks)
|
||||
|
||||
### 2.1 优先使用 Task 而非 Thread
|
||||
|
||||
* **规则**:使用 `Task.Run` 或 `Task.Factory.StartNew` (带有 `TaskCreationOptions.LongRunning`),尽量避免直接 `new Thread()`。
|
||||
* **理由**:`Task` 基于线程池,资源利用率更高,且 API 更易于组合和异常处理。
|
||||
* **例外**:需要设置线程优先级、名称或必须是前台线程的特殊场景。
|
||||
|
||||
### 2.2 避免长时间阻塞线程池线程
|
||||
|
||||
* **规则**:如果任务是 CPU 密集型且运行时间很长,应指定 `TaskCreationOptions.LongRunning`,或者使用独立的线程,避免耗尽线程池导致吞吐量下降。
|
||||
|
||||
## 3. 线程安全与锁 (Locking)
|
||||
|
||||
### 3.1 锁的选择
|
||||
|
||||
* **规则**:
|
||||
* **同步代码**:首选 `lock` (即 `Monitor`)。
|
||||
* **异步代码**:必须使用 `SemaphoreSlim`,严禁在 `lock` 块中使用 `await`。
|
||||
* **高性能/轻量级**:`Interlocked` 用于简单的计数器或原子交换。
|
||||
* **自旋锁**:慎用 `SpinLock` 或 `SpinWait`,除非非常短的临界区且你非常清楚自己在做什么。
|
||||
*建议:对于大多数业务场景,普通的 `lock` 或 `SemaphoreSlim` 足够且更安全。除非经过 Benchmark 证明必须使用自旋锁。*
|
||||
|
||||
* **正例 (异步锁)**:
|
||||
```csharp
|
||||
private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
|
||||
|
||||
public async Task SafeOperationAsync()
|
||||
{
|
||||
await _lock.WaitAsync();
|
||||
try
|
||||
{
|
||||
// ✅ 可以在锁内 await
|
||||
await DoSomethingAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.Release();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 线程安全的集合
|
||||
|
||||
* **规则**:在多线程环境下读写集合,应使用 `System.Collections.Concurrent` 命名空间下的集合 (如 `ConcurrentDictionary`, `ConcurrentQueue`),而不是手动加锁的 `List` 或 `Dictionary`。
|
||||
* **正例**:
|
||||
```csharp
|
||||
private readonly ConcurrentDictionary<string, User> _cache = new();
|
||||
|
||||
public void AddUser(User user)
|
||||
{
|
||||
// ✅ 线程安全
|
||||
_cache.TryAdd(user.Id, user);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 静态成员的线程安全
|
||||
|
||||
* **规则**:静态成员 (Static Members) 在多线程环境下是共享的,必须保证其线程安全。
|
||||
* **建议**:
|
||||
* 尽量设计为不可变 (Immutable)。
|
||||
* 如果必须可变,使用 `lock` 或 `Concurrent` 类型保护。
|
||||
|
||||
## 4. 并行处理 (Parallel)
|
||||
|
||||
### 4.1 控制并发度
|
||||
|
||||
* **规则**:使用 `Parallel.ForEach` 或 `Task.WhenAll` 时,必须控制并发数量 (`MaxDegreeOfParallelism`),防止瞬间并发过高压垮下游服务 (数据库、Redis、外部 API)。
|
||||
* **正例**:
|
||||
```csharp
|
||||
var options = new ParallelOptions { MaxDegreeOfParallelism = 5 };
|
||||
await Parallel.ForEachAsync(items, options, async (item, token) =>
|
||||
{
|
||||
await ProcessItemAsync(item, token);
|
||||
});
|
||||
```
|
||||
|
||||
### 4.2 避免 Parallel 里的共享状态写入
|
||||
|
||||
* **规则**:避免在并行循环中写入共享变量,这通常是非线程安全的。应使用线程局部变量或并发集合。
|
||||
|
||||
## 5. 最佳实践总结
|
||||
|
||||
1. **不要阻塞异步代码**:避免 `.Result`, `.Wait()`。
|
||||
2. **异常处理**:`Task` 中的异常会被包装在 `AggregateException` 中 (如果使用 `.Result` 或 `.Wait()`),但在 `await` 时会解包抛出第一个异常。建议始终使用 `await`。
|
||||
3. **避免线程饥饿**:不要在线程池线程中执行长时间的同步 IO 操作。
|
||||
4. **不可变性**:尽可能设计不可变对象,天然线程安全。
|
||||
799
docs/开发规范/1-编程规约/1.5-OOP规范.md
Normal file
799
docs/开发规范/1-编程规约/1.5-OOP规范.md
Normal file
|
|
@ -0,0 +1,799 @@
|
|||
# 1.5 OOP规范
|
||||
|
||||
---
|
||||
|
||||
## 1. 类的职责划分
|
||||
### 1.1 单一职责原则
|
||||
```csharp
|
||||
// ❌ 不建议:一个类承担多个职责
|
||||
public class User
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
// 数据访问职责
|
||||
public void SaveToDatabase() { }
|
||||
// 邮件发送职责
|
||||
public void SendWelcomeEmail() { }
|
||||
// 日志记录职责
|
||||
public void LogUserAction() { }
|
||||
// 数据验证职责
|
||||
public bool ValidateEmail() { }
|
||||
}
|
||||
// ✅ 建议:职责分离
|
||||
public class User
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
}
|
||||
public class UserRepository
|
||||
{
|
||||
public async Task SaveAsync(User user)
|
||||
{
|
||||
// 数据访问逻辑
|
||||
}
|
||||
}
|
||||
public class UserEmailService
|
||||
{
|
||||
public async Task SendWelcomeEmailAsync(User user)
|
||||
{
|
||||
// 邮件发送逻辑
|
||||
}
|
||||
}
|
||||
public class UserValidator
|
||||
{
|
||||
public bool IsValidEmail(string email)
|
||||
{
|
||||
// 验证逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
### 1.2 类的大小控制
|
||||
```csharp
|
||||
// - 方法数量:建议不超过 20 个
|
||||
// - 代码行数:建议不超过 300 行
|
||||
// - 依赖数量:建议不超过 5 个
|
||||
public class OrderService
|
||||
{
|
||||
// 依赖注入(建议最多5个依赖)
|
||||
private readonly IOrderRepository _orderRepository;
|
||||
private readonly IProductService _productService;
|
||||
private readonly IEmailService _emailService;
|
||||
private readonly ILogger<OrderService> _logger;
|
||||
public OrderService(
|
||||
IOrderRepository orderRepository,
|
||||
IProductService productService,
|
||||
IEmailService emailService,
|
||||
ILogger<OrderService> logger)
|
||||
{
|
||||
_orderRepository = orderRepository;
|
||||
_productService = productService;
|
||||
_emailService = emailService;
|
||||
_logger = logger;
|
||||
}
|
||||
// 核心业务方法
|
||||
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
|
||||
{
|
||||
// 方法体不超过30行
|
||||
}
|
||||
}
|
||||
// ❌ 如果类过大,考虑拆分
|
||||
// 例如:将订单创建、订单查询、订单取消拆分为不同的服务类
|
||||
public class OrderCreationService { }
|
||||
public class OrderQueryService { }
|
||||
public class OrderCancellationService
|
||||
```
|
||||
## 2. 封装原则
|
||||
### 2.1 字段私有化
|
||||
```csharp
|
||||
// ❌ 不建议:公开字段
|
||||
public class BankAccount
|
||||
{
|
||||
public decimal Balance; // 可被外部直接修改
|
||||
}
|
||||
// ✅ 建议:私有字段 + 公共属性
|
||||
public class BankAccount
|
||||
{
|
||||
private decimal _balance;
|
||||
public decimal Balance
|
||||
{
|
||||
get => _balance;
|
||||
private set => _balance = value; // 只能内部修改
|
||||
}
|
||||
// 通过方法控制状态变化
|
||||
public void Deposit(decimal amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
{
|
||||
throw new ArgumentException("存款金额必须大于0");
|
||||
}
|
||||
_balance += amount;
|
||||
}
|
||||
public void Withdraw(decimal amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
{
|
||||
throw new ArgumentException("取款金额必须大于0");
|
||||
}
|
||||
if (amount > _balance)
|
||||
{
|
||||
throw new InvalidOperationException("余额不足");
|
||||
}
|
||||
_balance -= amount;
|
||||
}
|
||||
}
|
||||
```
|
||||
### 2.2 属性设计
|
||||
```csharp
|
||||
public class Product
|
||||
{
|
||||
// ✅ 自动属性(简单场景)
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
// ✅ 只读属性(外部只能读取)
|
||||
public DateTime CreateTime { get; private set; }
|
||||
// ✅ 计算属性(不存储值)
|
||||
public decimal TotalPrice => Price * Quantity;
|
||||
// ✅ 带验证的属性
|
||||
private decimal _price;
|
||||
public decimal Price
|
||||
{
|
||||
get => _price;
|
||||
set
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
throw new ArgumentException("价格不能为负数");
|
||||
}
|
||||
_price = value;
|
||||
}
|
||||
}
|
||||
// ✅ 延迟初始化属性
|
||||
private List<Review> _reviews;
|
||||
public List<Review> Reviews => _reviews ??= new List<Review>();
|
||||
// ✅ init 访问器(只能在构造时设置)
|
||||
public string Code { get; init; }
|
||||
// ✅ required 修饰符(C# 11+)
|
||||
public required string Category { get; init; }
|
||||
}
|
||||
// 使用示例
|
||||
var product = new Product
|
||||
{
|
||||
Code = "P001",
|
||||
Category = "电子产品", // 必须设置
|
||||
Price = 999.99m
|
||||
};
|
||||
// product.Code = "P002"; // 编译错误,init 属性不可修改
|
||||
```
|
||||
### 2.3 对象不变性
|
||||
```csharp
|
||||
// ✅ 不可变对象(线程安全)
|
||||
public class Money
|
||||
{
|
||||
public decimal Amount { get; }
|
||||
public string Currency { get; }
|
||||
public Money(decimal amount, string currency)
|
||||
{
|
||||
if (amount < 0)
|
||||
{
|
||||
throw new ArgumentException("金额不能为负数");
|
||||
}
|
||||
Amount = amount;
|
||||
Currency = currency ?? throw new ArgumentNullException(nameof(currency));
|
||||
}
|
||||
// 操作返回新对象,而不修改当前对象
|
||||
public Money Add(Money other)
|
||||
{
|
||||
if (Currency != other.Currency)
|
||||
{
|
||||
throw new InvalidOperationException("货币类型不同");
|
||||
}
|
||||
return new Money(Amount + other.Amount, Currency);
|
||||
}
|
||||
public Money Multiply(decimal factor)
|
||||
{
|
||||
return new Money(Amount * factor, Currency);
|
||||
}
|
||||
}
|
||||
// ✅ 使用 record 创建不可变对象
|
||||
public record Address(string Street, string City, string ZipCode);
|
||||
// 使用
|
||||
var address = new Address("中山路100号", "上海", "200000");
|
||||
// address.Street = "新地址"; // 编译错误
|
||||
// 创建副本并修改
|
||||
var newAddress = address with { Street = "南京路200号" };
|
||||
```
|
||||
## 3. 继承使用原则
|
||||
### 3.1 何时使用继承
|
||||
```csharp
|
||||
// ✅ 正确使用继承
|
||||
public abstract class Animal
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public abstract void MakeSound();
|
||||
public virtual void Eat()
|
||||
{
|
||||
Console.WriteLine($"{Name} is eating");
|
||||
}
|
||||
}
|
||||
public class Dog : Animal
|
||||
{
|
||||
public override void MakeSound()
|
||||
{
|
||||
Console.WriteLine("Woof!");
|
||||
}
|
||||
}
|
||||
public class Cat : Animal
|
||||
{
|
||||
public override void MakeSound()
|
||||
{
|
||||
Console.WriteLine("Meow!");
|
||||
}
|
||||
public override void Eat()
|
||||
{
|
||||
base.Eat(); // 调用基类实现
|
||||
Console.WriteLine("Cat loves fish");
|
||||
}
|
||||
}
|
||||
// ❌ 错误使用继承:仅为复用代码
|
||||
public class Stack : List<int> // 不应该继承 List
|
||||
{
|
||||
// Stack 不是一个 List
|
||||
}
|
||||
// ✅ 正确:使用组合
|
||||
public class Stack
|
||||
{
|
||||
private readonly List<int> _items = new();
|
||||
public void Push(int item) => _items.Add(item);
|
||||
public int Pop()
|
||||
{
|
||||
var item = _items[^1];
|
||||
_items.RemoveAt(_items.Count - 1);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
```
|
||||
### 3.2 继承层次控制
|
||||
```csharp
|
||||
// ✅ 继承深度建议不超过3层
|
||||
// Level 1
|
||||
public abstract class Entity
|
||||
{
|
||||
public int Id { get; set; }
|
||||
}
|
||||
// Level 2
|
||||
public abstract class AuditableEntity : Entity
|
||||
{
|
||||
public DateTime CreateTime { get; set; }
|
||||
public DateTime? UpdateTime { get; set; }
|
||||
}
|
||||
// Level 3
|
||||
public class Product : AuditableEntity
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
}
|
||||
// ❌ 避免更深的继承层次
|
||||
// Level 4 - 过深
|
||||
public class SpecialProduct : Product { } // 考虑重新设计
|
||||
```
|
||||
### 3.3 虚方法使用规范
|
||||
```csharp
|
||||
public abstract class PaymentProcessor
|
||||
{
|
||||
// ✅ 模板方法模式
|
||||
public async Task<PaymentResult> ProcessPaymentAsync(PaymentRequest request)
|
||||
{
|
||||
// 1. 验证(子类可重写)
|
||||
ValidateRequest(request);
|
||||
// 2. 预处理(子类可重写)
|
||||
await PreProcessAsync(request);
|
||||
// 3. 核心处理(子类必须实现)
|
||||
var result = await ExecutePaymentAsync(request);
|
||||
// 4. 后处理(子类可重写)
|
||||
await PostProcessAsync(result);
|
||||
return result;
|
||||
}
|
||||
// 虚方法(可选重写)
|
||||
protected virtual void ValidateRequest(PaymentRequest request)
|
||||
{
|
||||
if (request.Amount <= 0)
|
||||
{
|
||||
throw new ArgumentException("支付金额必须大于0");
|
||||
}
|
||||
}
|
||||
protected virtual Task PreProcessAsync(PaymentRequest request)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
// 抽象方法(必须实现)
|
||||
protected abstract Task<PaymentResult> ExecutePaymentAsync(PaymentRequest request);
|
||||
protected virtual Task PostProcessAsync(PaymentResult result)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
// 具体实现
|
||||
public class AlipayProcessor : PaymentProcessor
|
||||
{
|
||||
protected override async Task<PaymentResult> ExecutePaymentAsync(PaymentRequest request)
|
||||
{
|
||||
// 支付宝支付逻辑
|
||||
return new PaymentResult { Success = true };
|
||||
}
|
||||
protected override async Task PostProcessAsync(PaymentResult result)
|
||||
{
|
||||
// 发送支付通知
|
||||
await base.PostProcessAsync(result);
|
||||
}
|
||||
}
|
||||
```
|
||||
## 4. 组合优于继承
|
||||
### 4.1 使用组合的场景
|
||||
```csharp
|
||||
// ❌ 错误:使用继承实现不同功能组合
|
||||
public class FlyingSwimmingAnimal : Animal { } // 会飞会游泳的动物?
|
||||
// ✅ 正确:使用组合和接口
|
||||
public interface IFlyable
|
||||
{
|
||||
void Fly();
|
||||
}
|
||||
public interface ISwimmable
|
||||
{
|
||||
void Swim();
|
||||
}
|
||||
public class Duck : Animal, IFlyable, ISwimmable
|
||||
{
|
||||
private readonly FlyingAbility _flyingAbility = new();
|
||||
private readonly SwimmingAbility _swimmingAbility = new();
|
||||
public void Fly() => _flyingAbility.Fly();
|
||||
public void Swim() => _swimmingAbility.Swim();
|
||||
public override void MakeSound()
|
||||
{
|
||||
Console.WriteLine("Quack!");
|
||||
}
|
||||
}
|
||||
public class FlyingAbility
|
||||
{
|
||||
public void Fly()
|
||||
{
|
||||
Console.WriteLine("Flying in the sky");
|
||||
}
|
||||
}
|
||||
public class SwimmingAbility
|
||||
{
|
||||
public void Swim()
|
||||
{
|
||||
Console.WriteLine("Swimming in the water");
|
||||
}
|
||||
}
|
||||
```
|
||||
### 4.2 策略模式代替继承
|
||||
```csharp
|
||||
// ✅ 使用策略模式提供不同行为
|
||||
public interface IShippingStrategy
|
||||
{
|
||||
decimal CalculateCost(decimal weight, decimal distance);
|
||||
}
|
||||
public class StandardShipping : IShippingStrategy
|
||||
{
|
||||
public decimal CalculateCost(decimal weight, decimal distance)
|
||||
{
|
||||
return weight * 0.5m + distance * 0.1m;
|
||||
}
|
||||
}
|
||||
public class ExpressShipping : IShippingStrategy
|
||||
{
|
||||
public decimal CalculateCost(decimal weight, decimal distance)
|
||||
{
|
||||
return weight * 1.0m + distance * 0.2m + 20m;
|
||||
}
|
||||
}
|
||||
public class Order
|
||||
{
|
||||
private IShippingStrategy _shippingStrategy;
|
||||
public void SetShippingStrategy(IShippingStrategy strategy)
|
||||
{
|
||||
_shippingStrategy = strategy ?? throw new ArgumentNullException(nameof(strategy));
|
||||
}
|
||||
public decimal CalculateShippingCost(decimal weight, decimal distance)
|
||||
{
|
||||
if (_shippingStrategy == null)
|
||||
{
|
||||
throw new InvalidOperationException("运输策略未设置");
|
||||
}
|
||||
return _shippingStrategy.CalculateCost(weight, distance);
|
||||
}
|
||||
}
|
||||
```
|
||||
## 5. 接口设计原则
|
||||
### 5.1 接口命名与定义
|
||||
```csharp
|
||||
// ✅ 接口命名以 I 开头
|
||||
public interface ICustomerRepository
|
||||
{
|
||||
Task<Customer> GetByIdAsync(int id);
|
||||
Task<IEnumerable<Customer>> GetAllAsync();
|
||||
Task<Customer> AddAsync(Customer customer);
|
||||
Task UpdateAsync(Customer customer);
|
||||
Task DeleteAsync(int id);
|
||||
}
|
||||
// ✅ 能力接口(形容词)
|
||||
public interface IDisposable
|
||||
{
|
||||
void Dispose();
|
||||
}
|
||||
public interface IComparable<T>
|
||||
{
|
||||
int CompareTo(T other);
|
||||
}
|
||||
// ✅ 服务接口(名词)
|
||||
public interface IEmailService
|
||||
{
|
||||
Task SendAsync(string to, string subject, string body);
|
||||
}
|
||||
public interface ILogger<T>
|
||||
{
|
||||
void LogInformation(string message);
|
||||
void LogError(Exception ex, string message);
|
||||
}
|
||||
```
|
||||
### 5.2 接口隔离
|
||||
```csharp
|
||||
// ❌ 不建议:臃肿的接口
|
||||
public interface IRepository
|
||||
{
|
||||
// CRUD 操作
|
||||
void Add();
|
||||
void Update();
|
||||
void Delete();
|
||||
void Get();
|
||||
// 批量操作
|
||||
void BulkAdd();
|
||||
void BulkUpdate();
|
||||
void BulkDelete();
|
||||
// 搜索功能
|
||||
void Search();
|
||||
void AdvancedSearch();
|
||||
// 导出功能
|
||||
void ExportToExcel();
|
||||
void ExportToPdf();
|
||||
}
|
||||
// ✅ 建议:接口隔离
|
||||
public interface IReadRepository<T>
|
||||
{
|
||||
Task<T> GetByIdAsync(int id);
|
||||
Task<IEnumerable<T>> GetAllAsync();
|
||||
}
|
||||
public interface IWriteRepository<T>
|
||||
{
|
||||
Task<T> AddAsync(T entity);
|
||||
Task UpdateAsync(T entity);
|
||||
Task DeleteAsync(int id);
|
||||
}
|
||||
public interface IBulkRepository<T>
|
||||
{
|
||||
Task BulkAddAsync(IEnumerable<T> entities);
|
||||
Task BulkUpdateAsync(IEnumerable<T> entities);
|
||||
}
|
||||
public interface ISearchableRepository<T>
|
||||
{
|
||||
Task<IEnumerable<T>> SearchAsync(SearchCriteria criteria);
|
||||
}
|
||||
// 组合使用
|
||||
public interface ICustomerRepository : IReadRepository<Customer>,
|
||||
IWriteRepository<Customer>,
|
||||
ISearchableRepository<Customer>
|
||||
{
|
||||
// 特定于 Customer 的方法
|
||||
Task<Customer> GetByEmailAsync(string email);
|
||||
}
|
||||
```
|
||||
### 5.3 显式接口实现
|
||||
```csharp
|
||||
public interface IAnimal
|
||||
{
|
||||
void Move();
|
||||
}
|
||||
public interface IRobot
|
||||
{
|
||||
void Move();
|
||||
}
|
||||
// ✅ 显式接口实现解决方法冲突
|
||||
public class RobotDog : IAnimal, IRobot
|
||||
{
|
||||
// 隐式实现(默认)
|
||||
public void Move()
|
||||
{
|
||||
Console.WriteLine("RobotDog moving");
|
||||
}
|
||||
// 显式实现 IAnimal
|
||||
void IAnimal.Move()
|
||||
{
|
||||
Console.WriteLine("Animal walking");
|
||||
}
|
||||
// 显式实现 IRobot
|
||||
void IRobot.Move()
|
||||
{
|
||||
Console.WriteLine("Robot moving mechanically");
|
||||
}
|
||||
}
|
||||
// 使用
|
||||
var robotDog = new RobotDog();
|
||||
robotDog.Move(); // "RobotDog moving"
|
||||
((IAnimal)robotDog).Move(); // "Animal walking"
|
||||
((IRobot)robotDog).Move(); // "Robot moving mechanically"
|
||||
```
|
||||
## 6. 抽象类使用场景
|
||||
### 6.1 抽象类 vs 接口
|
||||
```csharp
|
||||
// ✅ 使用抽象类:有共同实现逻辑
|
||||
public abstract class Document
|
||||
{
|
||||
// 公共属性
|
||||
public string Title { get; set; }
|
||||
public DateTime CreateTime { get; set; }
|
||||
// 公共方法实现
|
||||
public void Save()
|
||||
{
|
||||
ValidateBeforeSave();
|
||||
PerformSave();
|
||||
LogSave();
|
||||
}
|
||||
// 模板方法
|
||||
protected virtual void ValidateBeforeSave()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Title))
|
||||
{
|
||||
throw new ValidationException("标题不能为空");
|
||||
}
|
||||
}
|
||||
// 抽象方法(强制子类实现)
|
||||
protected abstract void PerformSave();
|
||||
// 虚方法(可选重写)
|
||||
protected virtual void LogSave()
|
||||
{
|
||||
Console.WriteLine($"Document '{Title}' saved at {DateTime.Now}");
|
||||
}
|
||||
}
|
||||
public class PdfDocument : Document
|
||||
{
|
||||
protected override void PerformSave()
|
||||
{
|
||||
// PDF 特定的保存逻辑
|
||||
Console.WriteLine("Saving as PDF");
|
||||
}
|
||||
}
|
||||
public class WordDocument : Document
|
||||
{
|
||||
protected override void PerformSave()
|
||||
{
|
||||
// Word 特定的保存逻辑
|
||||
Console.WriteLine("Saving as Word document");
|
||||
}
|
||||
protected override void ValidateBeforeSave()
|
||||
{
|
||||
base.ValidateBeforeSave();
|
||||
// 额外的验证逻辑
|
||||
}
|
||||
}
|
||||
// ✅ 使用接口:定义契约,无共同实现
|
||||
public interface INotificationSender
|
||||
{
|
||||
Task SendAsync(string recipient, string message);
|
||||
}
|
||||
public class EmailSender : INotificationSender
|
||||
{
|
||||
public async Task SendAsync(string recipient, string message)
|
||||
{
|
||||
// 邮件发送实现
|
||||
}
|
||||
}
|
||||
public class SmsSender : INotificationSender
|
||||
{
|
||||
public async Task SendAsync(string recipient, string message)
|
||||
{
|
||||
// 短信发送实现
|
||||
}
|
||||
}
|
||||
```
|
||||
### 6.2 抽象类设计要点
|
||||
```csharp
|
||||
public abstract class BaseEntity
|
||||
{
|
||||
// ✅ 抽象类可以有构造函数
|
||||
protected BaseEntity()
|
||||
{
|
||||
CreateTime = DateTime.UtcNow;
|
||||
Id = Guid.NewGuid();
|
||||
}
|
||||
// ✅ 可以有字段
|
||||
private readonly List<DomainEvent> _domainEvents = new();
|
||||
// 公共属性
|
||||
public Guid Id { get; private set; }
|
||||
public DateTime CreateTime { get; private set; }
|
||||
public DateTime? UpdateTime { get; protected set; }
|
||||
// ✅ 受保护的方法供子类使用
|
||||
protected void AddDomainEvent(DomainEvent domainEvent)
|
||||
{
|
||||
_domainEvents.Add(domainEvent);
|
||||
}
|
||||
protected void MarkAsUpdated()
|
||||
{
|
||||
UpdateTime = DateTime.UtcNow;
|
||||
}
|
||||
// 抽象方法
|
||||
public abstract void Validate();
|
||||
}
|
||||
public class Customer : BaseEntity
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
public override void Validate()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Name))
|
||||
{
|
||||
throw new ValidationException("客户名称不能为空");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Email))
|
||||
{
|
||||
throw new ValidationException("邮箱不能为空");
|
||||
}
|
||||
}
|
||||
public void UpdateEmail(string newEmail)
|
||||
{
|
||||
Email = newEmail;
|
||||
MarkAsUpdated(); // 使用基类方法
|
||||
AddDomainEvent(new CustomerEmailChangedEvent(Id, newEmail));
|
||||
}
|
||||
}
|
||||
```
|
||||
## 7. 密封类使用
|
||||
### 7.1 何时使用 `sealed`
|
||||
```csharp
|
||||
// ✅ 防止被继承(性能优化、设计意图)
|
||||
public sealed class ConfigurationManager
|
||||
{
|
||||
private readonly Dictionary<string, string> _settings = new();
|
||||
public string GetSetting(string key)
|
||||
{
|
||||
return _settings.TryGetValue(key, out var value) ? value : null;
|
||||
}
|
||||
}
|
||||
// ❌ 无法继承
|
||||
// public class CustomConfigManager : ConfigurationManager { } // 编译错误
|
||||
// ✅ 工具类密封
|
||||
public sealed class StringHelper
|
||||
{
|
||||
private StringHelper() { } // 私有构造防止实例化
|
||||
public static string Truncate(string value, int maxLength)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value) || value.Length <= maxLength)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
return value.Substring(0, maxLength) + "...";
|
||||
}
|
||||
}
|
||||
// ✅ 密封重写方法(防止进一步重写)
|
||||
public class BaseProcessor
|
||||
{
|
||||
public virtual void Process()
|
||||
{
|
||||
Console.WriteLine("Base processing");
|
||||
}
|
||||
}
|
||||
public class DerivedProcessor : BaseProcessor
|
||||
{
|
||||
// 密封此方法,子类不能再重写
|
||||
public sealed override void Process()
|
||||
{
|
||||
Console.WriteLine("Derived processing");
|
||||
base.Process();
|
||||
}
|
||||
}
|
||||
public class FurtherDerived : DerivedProcessor
|
||||
{
|
||||
// ❌ 编译错误:不能重写密封方法
|
||||
// public override void Process() { }
|
||||
}
|
||||
```
|
||||
## 8. 访问修饰符使用规范
|
||||
### 8.1 访问级别选择
|
||||
```csharp
|
||||
public class Product
|
||||
{
|
||||
// ✅ private - 只在类内部使用
|
||||
private decimal _costPrice;
|
||||
private List<PriceHistory> _priceHistory = new();
|
||||
// ✅ private protected - 仅当前类和派生类访问(同一程序集)
|
||||
private protected void RecordPriceChange(decimal oldPrice, decimal newPrice)
|
||||
{
|
||||
_priceHistory.Add(new PriceHistory(oldPrice, newPrice));
|
||||
}
|
||||
// ✅ protected - 当前类和派生类可访问
|
||||
protected decimal CostPrice => _costPrice;
|
||||
// ✅ internal - 同一程序集内可访问
|
||||
internal void SetCostPrice(decimal cost)
|
||||
{
|
||||
_costPrice = cost;
|
||||
}
|
||||
// ✅ protected internal - 同一程序集或派生类
|
||||
protected internal decimal CalculateMargin()
|
||||
{
|
||||
return Price - _costPrice;
|
||||
}
|
||||
// ✅ public - 所有地方可访问
|
||||
public decimal Price { get; set; }
|
||||
public string Name { get; set; }
|
||||
public decimal GetProfitMargin()
|
||||
{
|
||||
return (Price - _costPrice) / Price * 100;
|
||||
}
|
||||
}
|
||||
// 访问修饰符决策树:
|
||||
// 1. 只在当前类使用? -> private
|
||||
// 2. 需要子类访问? -> protected
|
||||
// 3. 同一程序集内使用? -> internal
|
||||
// 4. 需要外部调用? -> public
|
||||
// 5. 同程序集或子类? -> protected internal
|
||||
// 6. 仅同程序集的子类? -> private protected
|
||||
```
|
||||
### 8.2 最小权限原则
|
||||
```csharp
|
||||
// ✅ 建议:默认使用最小访问权限
|
||||
public class OrderProcessor
|
||||
{
|
||||
// 私有字段
|
||||
private readonly IOrderRepository _repository;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
// 公共构造函数
|
||||
public OrderProcessor(IOrderRepository repository, ILogger logger)
|
||||
{
|
||||
_repository = repository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
// 公共方法(对外API)
|
||||
public async Task<Order> ProcessOrderAsync(int orderId)
|
||||
{
|
||||
var order = await GetOrderAsync(orderId);
|
||||
ValidateOrder(order);
|
||||
await SaveOrderAsync(order);
|
||||
return order;
|
||||
}
|
||||
|
||||
// 私有方法(内部实现细节)
|
||||
private async Task<Order> GetOrderAsync(int orderId)
|
||||
{
|
||||
return await _repository.GetByIdAsync(orderId);
|
||||
}
|
||||
|
||||
private void ValidateOrder(Order order)
|
||||
{
|
||||
if (order == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(order));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveOrderAsync(Order order)
|
||||
{
|
||||
await _repository.UpdateAsync(order);
|
||||
_logger.LogInformation("Order {OrderId} saved", order.Id);
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ 不建议:不必要的公开
|
||||
public class BadOrderProcessor
|
||||
{
|
||||
// 不应该公开
|
||||
public IOrderRepository Repository { get; set; }
|
||||
|
||||
// 不应该公开内部实现
|
||||
public void ValidateOrder(Order order) { }
|
||||
public async Task SaveOrderAsync(Order order) { }
|
||||
}
|
||||
```
|
||||
45
docs/开发规范/2-异常日志/2.1-日志规约.md
Normal file
45
docs/开发规范/2-异常日志/2.1-日志规约.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# 2.1 日志规约
|
||||
|
||||
---
|
||||
|
||||
## 【强制】
|
||||
|
||||
1. **日志组件**:
|
||||
* **底层框架**:**Aegis.Core.Logs** 使用 **NLog** 作为日志记录提供程序。
|
||||
* **集中收集**:生产环境必须配置 **Seq**或**ELK** 作为日志的集中收集与分析端点。
|
||||
* **抽象使用**:代码中应依赖 `Microsoft.Extensions.Logging.ILogger<T>` 接口进行日志记录,避免直接依赖 NLog 的特定 API。
|
||||
|
||||
2. **API 请求日志**:
|
||||
* **自动化记录**:Web API 项目可以通过 **AOP 过滤器**(如 `[TypeFilter(typeof(LogAttribute))]` 或全局过滤器)自动记录 HTTP 请求的入参、出参、执行时间及客户端信息。
|
||||
* **避免冗余**:**禁止**在 Controller 方法内部手动记录 "进入方法"、"离开方法" 等流水账日志,除非有特殊的业务追踪需求。
|
||||
|
||||
3. **异常记录规范**:
|
||||
* **完整堆栈**:记录异常时,**必须**将 `Exception` 对象作为 `LogError` 方法的第一个参数传入。
|
||||
* *反例*:`_logger.LogError($"处理失败:{ex.Message}");` (丢失堆栈信息)
|
||||
* *正例*:`_logger.LogError(ex, "处理订单 {OrderId} 失败", orderId);`
|
||||
|
||||
4. **结构化日志**:
|
||||
* **使用模板**:必须使用**消息模板**(Message Templates)语法(使用 `{Property}` 占位符),**禁止**使用字符串拼接或 `string.Format`。这确保日志在 Seq 中可被索引和查询。
|
||||
* *正例*:`_logger.LogInformation("用户 {UserId} 修改了状态为 {Status}", userId, status);`
|
||||
|
||||
5. **默认上下文信息**:
|
||||
* **自动注入**:框架层(`Aegis.Core.Logs`)已统一配置日志 Layout,自动包含 **RequestId** (`${request-id}`) 和 **调用方法名** (`${callsite}`)。
|
||||
* **无需手动记录**:开发者在记录日志时,**无需**在消息体中重复记录当前方法名或 RequestId。
|
||||
* *说明*:默认 Layout 格式为 `${date}|${level:uppercase=true}|${request-id}|${callsite:includeNamespace=false}|${message}|${exception}`。
|
||||
|
||||
## 【推荐】
|
||||
|
||||
1. **日志级别定义**:
|
||||
* **Trace/Debug**:仅用于开发环境调试,生产环境默认关闭。
|
||||
* **Information**:记录系统关键流程节点(如:定时任务开始/结束、关键状态流转)。
|
||||
* **Warning**:记录非预期的但系统可自动恢复的业务异常(如:参数校验失败、第三方接口偶尔超时)。
|
||||
* **Error**:记录导致当前请求失败的系统异常(如:数据库连接失败、空指针异常)。
|
||||
* **Critical**:记录导致系统崩溃或无法提供服务的致命错误。
|
||||
|
||||
2. **日志保留策略**:
|
||||
* 本地文件日志建议按天滚动(`RollingFile`),保留 7-15 天。
|
||||
* Seq 服务器日志根据磁盘容量配置保留策略(通常 30 天)。
|
||||
|
||||
3. **敏感信息脱敏**:
|
||||
* 严禁在日志中明文记录密码、密钥、身份证号、银行卡号等敏感信息。
|
||||
|
||||
122
docs/开发规范/2-异常日志/2.2-异常处理.md
Normal file
122
docs/开发规范/2-异常日志/2.2-异常处理.md
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
# 2.2 异常处理
|
||||
|
||||
---
|
||||
|
||||
## 【强制】
|
||||
|
||||
1. **业务异常处理**:
|
||||
* **优先校验**:应优先使用条件判断(`if`)来检查业务规则是否满足,在发现问题时提前拦截。
|
||||
* **抛出异常**:当校验不通过需中断流程时,应抛出 `BusinessException` 或其子类,而不是 `System.Exception`。
|
||||
* **消息友好**:异常消息(Message)将直接作为错误提示返回给前端/客户端,必须确保消息内容用户友好且无敏感技术信息。
|
||||
* **记录异常**:在业务逻辑中抛出异常时,必须记录异常信息和堆栈到日志中,以便后续分析和排查问题。
|
||||
* *正例*:
|
||||
```csharp
|
||||
if (order.Status != OrderStatus.Created)
|
||||
{
|
||||
throw new BusinessException("当前订单状态不允许取消");
|
||||
}
|
||||
```
|
||||
|
||||
2. **全局异常处理**:
|
||||
* **中间件接管**:严禁在 Controller 层进行通用的 `try-catch` 捕获。Aegis 框架已内置全局异常处理中间件,会自动捕获未处理异常并转换为标准 JSON 响应。
|
||||
* **响应格式**:框架会将未处理异常统一转换为如下格式:
|
||||
```json
|
||||
{
|
||||
"code": 500,
|
||||
"message": "服务器内部错误 / 业务错误提示",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
3. **必须通过预检查规避可预见的异常**:对于 `NullReferenceException`, `ArgumentNullException`, `IndexOutOfRangeException` 等可以通过代码检查来避免的运行时异常,不应该使用 `try-catch` 来处理。
|
||||
|
||||
|
||||
*说明*:异常处理的性能开销远高于条件判断。编写健壮的代码,通过防御性编程(如卫语句)来处理可预见的问题,是程序员的基本素养。
|
||||
|
||||
*反例*:
|
||||
```csharp
|
||||
try
|
||||
{
|
||||
// obj 可能为 null
|
||||
obj.DoSomething();
|
||||
}
|
||||
catch (NullReferenceException ex)
|
||||
{
|
||||
_logger.LogError(ex, "obj was null");
|
||||
}
|
||||
```
|
||||
|
||||
*正例*:
|
||||
```csharp
|
||||
if (obj is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
obj.DoSomething();
|
||||
```
|
||||
|
||||
2. **禁止使用异常作为流程控制**:异常是为程序运行中的意外情况设计的,不应用于正常的业务逻辑判断和流程跳转。
|
||||
|
||||
3. **精确捕获异常类型**:`catch` 时应尽可能捕获具体的异常类型,而不是宽泛的 `System.Exception`。对不同的异常类型应有不同的处理逻辑。
|
||||
|
||||
*说明*:对大段代码进行笼统的 `try-catch`,会使程序无法对特定错误做出正确响应,不利于问题定位和系统稳定性。
|
||||
|
||||
*正例*:
|
||||
```csharp
|
||||
try
|
||||
{
|
||||
// ... 数据库操作
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
// 处理数据库相关的特定错误
|
||||
_logger.LogError(ex, "A database error occurred.");
|
||||
}
|
||||
catch (TimeoutException ex)
|
||||
{
|
||||
// 处理连接超时错误
|
||||
_logger.LogWarning(ex, "The database connection timed out.");
|
||||
}
|
||||
```
|
||||
|
||||
4. **捕获后必须处理或转抛**:捕获异常后,必须进行处理(如记录日志、事务回滚、向用户返回错误信息等)。如果当前层无法处理,应使用 `throw;` 语句将原始异常向上抛出。严禁“吞掉”异常(即空的 `catch` 块)。
|
||||
|
||||
5. **必须使用 `using` 语句管理资源**:对于实现了 `IDisposable` 接口的资源对象(如流、数据库连接、HttpClient 等),必须使用 `using` 语句来确保资源的正确释放。
|
||||
|
||||
*说明*:`using` 语句会自动生成 `try-finally` 块,并调用 `Dispose()` 方法,这是 .NET 中管理非托管资源的最安全、最简洁的方式。
|
||||
|
||||
*正例*:
|
||||
```csharp
|
||||
using (var stream = new FileStream("path/to/file", FileMode.Open))
|
||||
{
|
||||
// ... 对 stream 的操作
|
||||
}
|
||||
// 此处 stream 已被自动关闭和释放
|
||||
```
|
||||
|
||||
6. **禁止在 `finally` 块中使用 `return`**:`finally` 块中的 `return` 会覆盖 `try` 或 `catch` 块中的 `return` 或异常,导致非预期的行为。
|
||||
|
||||
## 【推荐】
|
||||
|
||||
1. **启用并遵循可空引用类型 (`Nullable Reference Types`)**:在项目(`.csproj`)中启用 `<Nullable>enable</Nullable>`,让编译器帮助你从静态层面发现潜在的 `NullReferenceException`。
|
||||
|
||||
2. **优雅地处理空引用**:
|
||||
- **级联调用**:使用空条件运算符 `?.` 和 `?[]` 来安全地访问可能为 null 的对象的成员或索引。
|
||||
*正例*: `string? postalCode = customer?.Address?.PostalCode;`
|
||||
- **返回值**:方法的返回值可以为 null,但必须在方法签名和 XML 注释中明确指出。调用方有责任处理 null 情况。
|
||||
|
||||
3. **使用自定义异常表达业务错误**:应定义有具体业务含义的自定义异常(继承自 `System.Exception`),而不是直接抛出泛泛的 `new Exception()`。
|
||||
|
||||
*正例*:
|
||||
```csharp
|
||||
public class OrderNotFoundException : Exception
|
||||
{
|
||||
public OrderNotFoundException(int orderId)
|
||||
: base($"Order with ID {orderId} was not found.") { }
|
||||
}
|
||||
```
|
||||
|
||||
## 【参考】
|
||||
|
||||
1. **API 的错误响应**:对于对外暴露的 API(如 ASP.NET Core Web API),应建立一个全局异常处理中间件。该中间件负责捕获所有未处理的异常,记录日志,并将其转换为统一、标准的 JSON 错误响应格式返回给客户端,而不是直接暴露异常堆栈信息。
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=127.0.0.1;uid=sa;pwd=1qaz!QAZ;Database=MiAssessment_Admin;MultipleActiveResultSets=true;pooling=true;min pool size=5;max pool size=32767;connect timeout=20;Encrypt=True;TrustServerCertificate=True;",
|
||||
"BusinessConnection": "Server=127.0.0.1;uid=sa;pwd=1qaz!QAZ;Database=MiAssessment_Business;MultipleActiveResultSets=true;pooling=true;min pool size=5;max pool size=32767;connect timeout=20;Encrypt=True;TrustServerCertificate=True;",
|
||||
"Redis": "127.0.0.1:6379,abortConnect=false,connectTimeout=5000"
|
||||
"DefaultConnection": "Server=192.168.195.15,1433;uid=sa;pwd=Dbt@com@123;Database=MiAssessment_Admin;MultipleActiveResultSets=true;pooling=true;min pool size=5;max pool size=32767;connect timeout=20;Encrypt=True;TrustServerCertificate=True;",
|
||||
"BusinessConnection": "Server=192.168.195.15,1433;uid=sa;pwd=Dbt@com@123;Database=MiAssessment_Business;MultipleActiveResultSets=true;pooling=true;min pool size=5;max pool size=32767;connect timeout=20;Encrypt=True;TrustServerCertificate=True;",
|
||||
"Redis": "192.168.195.15:6379,defaultDatabase=2,abortConnect=false,connectTimeout=5000"
|
||||
},
|
||||
"Jwt": {
|
||||
"Secret": "{{JWT_SECRET_AT_LEAST_32_CHARACTERS}}",
|
||||
"Issuer": "{{PROJECT_NAME}}.Admin",
|
||||
"Audience": "{{PROJECT_NAME}}.Admin.Client",
|
||||
"Secret": "MiAssessmentAdminSecretKey2024!@#$%^&*()_+",
|
||||
"Issuer": "MiAssessment.Admin",
|
||||
"Audience": "MiAssessment.Admin.Client",
|
||||
"ExpireMinutes": 1440
|
||||
},
|
||||
"Logging": {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=127.0.0.1;uid=sa;pwd=1qaz!QAZ;Database=MiAssessment_Business;MultipleActiveResultSets=true;pooling=true;min pool size=5;max pool size=32767;connect timeout=20;Encrypt=True;TrustServerCertificate=True;",
|
||||
"Redis": "127.0.0.1:6379,abortConnect=false,connectTimeout=5000"
|
||||
"DefaultConnection": "Server=192.168.195.15,1433;uid=sa;pwd=Dbt@com@123;Database=MiAssessment_Business;MultipleActiveResultSets=true;pooling=true;min pool size=5;max pool size=32767;connect timeout=20;Encrypt=True;TrustServerCertificate=True;",
|
||||
"Redis": "192.168.195.15:6379,defaultDatabase=2,abortConnect=false,connectTimeout=5000"
|
||||
},
|
||||
"AppSettings": {
|
||||
"IsTestEnvironment": true
|
||||
|
|
@ -9,26 +9,26 @@
|
|||
"WechatPaySettings": {
|
||||
"DefaultMerchant": {
|
||||
"Name": "默认商户",
|
||||
"MchId": "{{WECHAT_MCH_ID}}",
|
||||
"AppId": "{{WECHAT_APP_ID}}",
|
||||
"Key": "{{WECHAT_API_KEY}}",
|
||||
"MchId": "YOUR_WECHAT_MCH_ID",
|
||||
"AppId": "YOUR_WECHAT_APP_ID",
|
||||
"Key": "YOUR_WECHAT_API_KEY",
|
||||
"OrderPrefix": "ORD",
|
||||
"Weight": 1,
|
||||
"NotifyUrl": "{{WECHAT_NOTIFY_URL}}"
|
||||
"NotifyUrl": "https://your-domain.com/api/payment/notify"
|
||||
},
|
||||
"Merchants": [],
|
||||
"Miniprograms": [],
|
||||
"UnifiedOrderUrl": "https://api.mch.weixin.qq.com/pay/unifiedorder",
|
||||
"ShippingNotifyUrl": "https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info",
|
||||
"NotifyBaseUrl": "{{API_BASE_URL}}"
|
||||
"NotifyBaseUrl": "https://your-domain.com"
|
||||
},
|
||||
"AmapSettings": {
|
||||
"ApiKey": "{{AMAP_API_KEY}}"
|
||||
"ApiKey": "YOUR_AMAP_API_KEY"
|
||||
},
|
||||
"JwtSettings": {
|
||||
"Secret": "{{JWT_SECRET_AT_LEAST_32_CHARACTERS}}",
|
||||
"Issuer": "{{PROJECT_NAME}}",
|
||||
"Audience": "{{PROJECT_NAME}}Users",
|
||||
"Secret": "MiAssessmentApiSecretKey2024!@#$%^&*()_+",
|
||||
"Issuer": "MiAssessment",
|
||||
"Audience": "MiAssessmentUsers",
|
||||
"ExpirationMinutes": 1440,
|
||||
"RefreshTokenExpirationDays": 7
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user