在经历了Web Forms、MVC、.NET Core到如今.NET 8的十余年开发历程后,我亲眼目睹项目如何从整洁走向混乱——并非因为开发者不够努力,而是因为忽视了最佳实践。
以下是我通过艰难教训或从优秀团队中学到的30多个最佳实践。我将以实用方式分享,只讲真正有帮助的内容。
原因:保持代码低耦合和可测试性。没有DI,逻辑会紧耦合,导致代码僵化且难以测试。
错误示例:
var service = new MyService();
正确示例:
public class MyController
{
private readonly IMyService _service;
public MyController(IMyService service)
{
_service = service;
}
}
🔍 我的经验:曾参与一个没有DI的项目——所有服务都用new创建。我们无法为测试模拟任何东西。改用DI不仅使代码可测试,还更好地分离了关注点。
原因:早期发现错误并防止回归。特别关注核心业务规则。
使用工具:xUnit、Moq或NSubstitute。
[Fact]
public void Should_Return_True_When_Valid()
{
var validator = new OrderValidator();
var result = validator.IsValid("ORD123");
Assert.True(result);
}
原因:控制器应只负责协调。业务逻辑会导致控制器臃肿,且逻辑不可测试、重复。
错误示例:
public IActionResult Save(Order order)
{
if(order.Amount > 1000)
order.IsDiscounted = true;
// 保存到数据库
}
更好做法:
public IActionResult Save(Order order)
{
_orderService.ApplyDiscount(order);
_orderService.Save(order);
}
原因:代码中的密钥是重大安全风险,也使环境管理困难。
使用:IConfiguration、appsettings.json或Azure Key Vault。
错误示例:
var apiKey = "SECRET123";
正确示例:
// appsettings.json
"MyService": {
"ApiKey": "xyz"
}
var key = _config["MyService:ApiKey"];
原因:集中式错误处理避免重复try-catch,允许记录、跟踪和返回适当消息。
app.UseExceptionHandler("/error");
或编写中间件:
public class ErrorHandlingMiddleware
{
public async Task Invoke(HttpContext context)
{
try { await _next(context); }
catch(Exception ex)
{
_logger.LogError(ex, "未处理异常");
context.Response.StatusCode = 500;
}
}
}
原因:这些原则创建可维护、可扩展和可测试的代码。
原因:防止线程阻塞。避免.Result或.Wait()。
错误示例:
var data = service.GetData().Result;
正确示例:
var data = await service.GetDataAsync();
💡 真实案例:曾因.Result导致生产环境死锁,花了2小时才定位。
原因:资源泄漏导致内存和性能问题。
使用using语句:
using (var client = new HttpClient()) { ... }
使用IHttpClientFactory避免频繁实例化。
原因:避免套接字耗尽并提高可测试性。
services.AddHttpClient<IMyService, MyService>();
原因:静默捕获异常会隐藏错误。
错误示例:
try { ... } catch { }
正确示例:
catch(Exception ex)
{
_logger.LogError(ex, "操作失败");
}
原因:防止过度提交并减少与数据库的紧耦合。
public class OrderDto
{
public int Id { get; set; }
public decimal Amount { get; set; }
}
原因:更易理解、测试和维护。
将大方法拆分为小方法。
原因:将验证逻辑移出模型和控制器。
public class OrderValidator : AbstractValidator<OrderDto>
{
public OrderValidator()
{
RuleFor(x => x.Amount).GreaterThan(0);
}
}
原因:防止注入攻击和系统崩溃。
使用[ApiController]和模型验证。
原因:代码应自解释。
避免DoTask(),推荐GenerateReportForUser()。
原因:防止未授权访问。
[Authorize(Roles = "Admin")]
原因:更易导航和扩展。
分层:
原因:日志帮助调试和监控应用。
_logger.LogInformation("订单已处理: {@order}", order);
原因:避免魔法字符串。
services.Configure<MySettings>(config.GetSection("MySettings"));
原因:保持控制器代码整洁。例如:日志、CORS、认证、异常处理。
原因:提高频繁请求的性能。
IMemoryCache.TryGetValue(key, out result);
原因:防止内存溢出并提升性能。
.Skip(page * pageSize).Take(pageSize);
原因:发现慢查询/方法。
原因:防止NullReferenceException。
var name = user?.Profile?.Name;
原因:非线程安全。破坏可测试性。
使用DI配合Singleton或Scoped服务。
原因:避免破坏现有客户端。
[Route("api/v1/[controller]")]
原因:减少样板映射代码。
var userDto = userMapper.ToUserDto(user);
原因:隐藏真实问题。只包装高风险调用。
原因:端到端安全网。
var client = factory.CreateClient();
原因:安全风险。
使用掩码:
_logger.Log("Token: ****");
原因:帮助开发者测试和理解API。
builder.Services.AddSwaggerGen();
原因:允许优雅关闭和响应式API。
public async Task GetOrders(CancellationToken cancellationToken)
编写整洁、可维护的代码是一种习惯。最佳实践就是您的地图——忽略它们,您将陷入技术债的丛林。