摘要:在经历了Web Forms、MVC、.NET Core到如今.NET 8的十余年开发历程后,我亲眼目睹项目如何从整洁走向混乱——并非因为开发者不够努力,而是因为忽视了最佳实践。
在经历了Web Forms、MVC、.NET Core到如今.NET 8的十余年开发历程后,我亲眼目睹项目如何从整洁走向混乱——并非因为开发者不够努力,而是因为忽视了最佳实践。
以下是我通过艰难教训或从优秀团队中学到的30多个最佳实践。我将以实用方式分享,只讲真正有帮助的内容。
原因:保持代码低耦合和可测试性。没有DI,逻辑会紧耦合,导致代码僵化且难以测试。
错误示例:
正确示例:
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;原因:静默捕获异常会隐藏错误。
错误示例:
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{public OrderValidator{RuleFor(x => x.Amount).GreaterThan(0);}}原因:防止注入攻击和系统崩溃。
使用[ApiController]和模型验证。
原因:代码应自解释。
避免DoTask,推荐GenerateReportForUser。
原因:防止未授权访问。
原因:更易导航和扩展。
分层:
• 控制器• 服务层• 仓储层• 模型/DTOs原因:日志帮助调试和监控应用。
_logger.LogInformation("订单已处理: {@order}", order);原因:避免魔法字符串。
services.Configure(config.GetSection("MySettings"));原因:保持控制器代码整洁。例如:日志、CORS、认证、异常处理。
原因:提高频繁请求的性能。
IMemoryCache.TryGetValue(key, out result);原因:防止内存溢出并提升性能。
.Skip(page * pageSize).Take(pageSize);23. 使用MiniProfiler或BenchmarkDotNet进行分析原因:发现慢查询/方法。
原因:防止NullReferenceException。
var name = user?.Profile?.Name;原因:非线程安全。破坏可测试性。
使用DI配合Singleton或Scoped服务。
原因:避免破坏现有客户端。
原因:减少样板映射代码。
var userDto = userMapper.ToUserDto(user);原因:隐藏真实问题。只包装高风险调用。
原因:端到端安全网。
var client = factory.CreateClient;原因:安全风险。
使用掩码:
_logger.Log("Token: ****");原因:帮助开发者测试和理解API。
builder.Services.AddSwaggerGen;原因:允许优雅关闭和响应式API。
public async Task GetOrders(CancellationToken cancellationToken)编写整洁、可维护的代码是一种习惯。最佳实践就是您的地图——忽略它们,您将陷入技术债的丛林。
来源:架构师老卢