在多年的开发过程中,我经常遇到一些反复出现的问题。这些问题不仅影响代码质量,还会增加开发成本,浪费大量时间。本文将从实战经验出发,分析这些常见反模式及其解决方案。

一、职责边界模糊 Link to heading

这是我在反复遇到的一种问题类型。很多人为了快速实现功能,把业务逻辑、UI交互、数据存储等各种职责混杂在一起。举个例子:

// 问题代码:职责混乱的通讯类
public class SerialCommunication
{
    private SerialPort _serial;
    private List<ProductionData> _businessData;      // 错误:业务数据
    private Dictionary<string, object> _deviceState;  // 错误:状态管理
    private bool _isCalibrating;                     // 错误:业务状态
    
    public void ProcessProductionData()
    {
        var data = ReadData();
        // 直接在这里处理业务逻辑
        if (data.Contains("ERROR"))
        {
            UpdateUIStatus("设备错误");              // 错误:UI操作
            SaveErrorLog(data);                      // 错误:日志操作
        }
        
        _businessData.Add(new ProductionData(data)); // 错误:业务数据处理
        
        if (NeedsCalibration())                     // 错误:校准逻辑
        {
            StartCalibration();
        }
    }
}

这种设计的问题: Link to heading

  1. 耦合度过高

    • 通讯逻辑和业务处理耦合在一起
    • 修改业务流程不得不动通讯代码
    • 测试单一功能几乎不可能
    • 维护时牵一发而动全身
  2. 职责不清

    • 单个类承担了通讯、业务、UI和日志等多重职责
    • Bug定位困难,改一处可能影响多个功能
    • 代码理解成本高,新人难以接手
    • 类越来越臃肿,最终无法维护
  3. 扩展困难

    • 增加新功能必须修改核心代码
    • 替换底层实现(如网络通讯替代串口)困难
    • 无法进行有效的单元测试
    • 依赖注入几乎不可能实现

改进后的分层设计: Link to heading

// 1. 通讯接口定义
public interface ISerialDevice : IDisposable
{
    bool IsConnected { get; }
    SerialSettings Settings { get; }
    Task<byte[]> ReadDataAsync(CancellationToken cancellationToken = default);
    Task<bool> SendDataAsync(byte[] data, CancellationToken cancellationToken = default);
    Task<bool> ConnectAsync(CancellationToken cancellationToken = default);
    Task DisconnectAsync();
}
// 2. 基础设施层:串口通讯实现
public class SerialDevice : ISerialDevice
{
    private readonly SerialPort _serial;
    private readonly ILogger<SerialDevice> _logger;
    
    public SerialDevice(SerialSettings settings, ILogger<SerialDevice> logger)
    {
        _serial = new SerialPort(settings.PortName, settings.BaudRate);
        _logger = logger;
    }
    
    public async Task<byte[]> ReadDataAsync(CancellationToken cancellationToken = default)
    {
        if (!IsConnected)
        {
            throw new DeviceCommunicationException("设备未连接");
        }

        try
        {
            return await Task.Run(() => 
            {
                if (_serial.BytesToRead == 0)
                {
                    return Array.Empty<byte>();
                }
                
                var buffer = new byte[_serial.BytesToRead];
                _serial.Read(buffer, 0, buffer.Length);
                return buffer;
            }, cancellationToken);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Serial read error");
            throw new DeviceCommunicationException("读取数据失败", ex);
        }
    }
}
// 3. 业务层:设备控制
public class DeviceController
{
    private readonly ISerialDevice _device;
    private readonly IDeviceStateManager _stateManager;
    private readonly ICalibrationService _calibrationService;
    private readonly ILogger<DeviceController> _logger;
    
    public async Task ProcessDataAsync(CancellationToken cancellationToken = default)
    {
        try
        {
            var data = await _device.ReadDataAsync(cancellationToken);
            if (data.Length == 0)
            {
                _logger.LogDebug("No data received");
                return;
            }

            var productionData = await ParseDataAsync(data);
            await _stateManager.UpdateStateAsync(productionData, cancellationToken);
            
            if (await _calibrationService.NeedsCalibrationAsync(cancellationToken))
            {
                await _calibrationService.RequestCalibrationAsync(cancellationToken);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing data");
            throw new DeviceOperationException("数据处理错误", ex);
        }
    }
}

为什么这样设计更好: Link to heading

  1. 职责分离

    • 通讯层只负责数据传输,不涉及业务
    • 业务层专注于业务逻辑实现
    • 每个组件都有明确的边界,职责清晰
  2. 依赖倒置

    • 高层模块依赖抽象接口
    • 实现细节被隐藏在接口背后
    • 可以轻松替换实现(如用于测试的模拟设备)
  3. 可测试性提高

    • 业务逻辑可以独立测试
    • 通讯层可以单独验证
    • 可以使用模拟对象替代真实依赖
  4. 异常处理更清晰

    • 每一层都有针对性的异常处理
    • 错误信息更加明确和有针对性
    • 日志记录更加结构化

二、过度设计与简单问题复杂化 Link to heading

我遇到一个典型的案例:客户提出了一个简单的需求 —— 设备每天需要进行两次校准。然而,外包团队在分析这个需求时,把它想得无比复杂,甚至已经复杂到他们认为无法实现了。

外包团队的思考方向: Link to heading

  • 如果用户关闭了软件怎么办?
  • 如何精确计算是否到了校准时间?
  • 软件关闭期间如何继续计时?
  • 如果系统时间被修改了怎么处理?

这种过度思考不仅没有解决问题,反而把简单的需求复杂化了。

实际上,这个需求非常简单: Link to heading

  1. 核心需求

    • 每天校准两次
    • 两次间隔X小时
  2. 我是如何分析和确认需求的

    • 了解这个需求的目的
    • 确认了操作员工作的时间
    • 了解客户想要的是什么效果
    • 每次操作之前都校准,太浪费时间,所以他们建议每天两次
    • 客户实际上根本不关心几点校准,只是必须X小时校准一次
  3. 我给出的方案

    • 如果是早上操作员上班的时间,要求必须校准一次
    • 保存一个文件来记录上一次校准的时间
    • 启动软件读取这个时间来判断是否需要校准
    • 要求客户不要乱改系统时间

就这么简单,需求就实现了。

这个案例的启示: Link to heading

  1. 从业务角度思考

    • 理解用户真正的需求是什么
    • 关注实际的业务场景
    • 不要被技术细节带偏
  2. 避免过度思考

    • 系统时间被修改是用户的责任
    • 不是所有边界情况都需要处理
    • 有些问题根本不需要技术解决
  3. 保持方案简单

    • 简单的问题用简单的方法解决
    • 不要为了技术而技术
    • 可维护性比技术先进性更重要

这个例子很好地说明了为什么我们需要"跳出技术思维",从业务角度思考问题。有时候,最好的解决方案就是最简单的方案。过度设计不仅会浪费开发时间,还会增加维护成本,最终可能导致项目失败。

三、不当的抽象层次 Link to heading

在一个制造执行系统中,我曾见过这样的代码:

// 错误的抽象方式
public class ProductionLineController
{
    private readonly SqlConnection _connection;
    private readonly SerialPort _serialPort;
    private readonly Form _mainForm;
    
    public void ProcessProduct()
    {
        // 直接操作底层细节
        _serialPort.Write("GET_DATA");
        var data = _serialPort.ReadLine();
        
        // 混合了UI操作
        _mainForm.BeginInvoke(() => 
            _mainForm.StatusLabel.Text = "Processing...");
        
        // 直接的数据库操作
        using var command = _connection.CreateCommand();
        command.CommandText = "INSERT INTO Products ...";
        command.ExecuteNonQuery();
    }
}

这种设计的问题: Link to heading

  1. 关注点混乱

    • 同时处理UI、数据库和设备通讯
    • 违反单一职责原则
    • 测试几乎不可能
  2. 难以维护

    • 修改一处功能需要理解整体逻辑
    • 容易引入副作用
    • 很难重用代码片段

正确的分层抽象: Link to heading

// 1. 领域模型
public record ProductionData
{
    public string ProductId { get; init; }
    public DateTime Timestamp { get; init; }
    public ProductionStatus Status { get; init; }
}
// 2. 设备通讯抽象
public interface IProductionDevice
{
    Task<ProductionData> GetProductionDataAsync();
}
// 3. 数据访问抽象
public interface IProductionRepository
{
    Task SaveProductionDataAsync(ProductionData data);
}
// 4. 业务服务
public class ProductionService
{
    private readonly IProductionDevice _device;
    private readonly IProductionRepository _repository;
    private readonly ILogger<ProductionService> _logger;

    public ProductionService(
        IProductionDevice device,
        IProductionRepository repository,
        ILogger<ProductionService> logger)
    {
        _device = device;
        _repository = repository;
        _logger = logger;
    }

    public async Task ProcessProductAsync()
    {
        try
        {
            var data = await _device.GetProductionDataAsync();
            await _repository.SaveProductionDataAsync(data);
            
            // 发布领域事件
            await _eventPublisher.PublishAsync(
                new ProductionCompletedEvent(data));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Production process failed");
            throw;
        }
    }
}
// 5. UI层
public class ProductionViewModel : INotifyPropertyChanged
{
    private readonly IProductionService _productionService;
    private string _status;

    public string Status
    {
        get => _status;
        private set
        {
            _status = value;
            OnPropertyChanged();
        }
    }

    public async Task StartProductionAsync()
    {
        Status = "Processing...";
        try
        {
            await _productionService.ProcessProductAsync();
            Status = "Completed";
        }
        catch
        {
            Status = "Error";
            throw;
        }
    }
}

为什么这样的抽象更好: Link to heading

  1. 层次清晰

    • 领域模型承载业务概念
    • 各层职责明确,边界分明
    • 关注点分离,便于独立开发
  2. 领域驱动设计思想

    • 通过领域模型表达核心业务
    • 领域事件处理副作用和集成
    • 业务规则集中在领域层
  3. 测试友好

    • 每一层可以独立测试
    • 可以轻松模拟依赖组件
    • 便于进行集成测试

四、代码重复与堆砌 Link to heading

在一个生产线管理系统中,代码重复是最常见的问题:

// 问题代码:重复的业务逻辑
public class ProductionLine1Controller
{
    public void ProcessData()
    {
        // 复制粘贴的代码
        var data = ReadSerialData();
        if (data.Contains("ERROR"))
        {
            LogError();
            return;
        }
        SaveToDatabase(data);
    }
}

public class ProductionLine2Controller
{
    public void ProcessData()
    {
        // 完全相同的逻辑
        var data = ReadSerialData();
        if (data.Contains("ERROR"))
        {
            LogError();
            return;
        }
        SaveToDatabase(data);
    }
}

优化方案: Link to heading

// 1. 抽象共同的接口
public interface IProductionLine
{
    Task<ProcessResult> ProcessDataAsync();
    string LineId { get; }
}