输入关键词开始搜索

软件设计全流程 — 从需求到部署

一、需求分析

核心产出

需求分析不是”列出功能”,而是厘清约束 + 量化指标

用户故事 ──→ 功能需求 ──→ 非功能需求(性能/安全/可用性)

                    └──→ 约束条件(平台/硬件/法规/工期)

文档模板

章节内容
业务背景谁用?解决什么问题?现有方案痛点?
功能需求用例图 + 优先级(MUST/SHOULD/COULD)
非功能需求性能指标(吞吐/延迟)、可靠性(MTBF)、安全等级
约束操作系统、硬件接口、网络环境、合规要求
验收标准每个需求的可验证条件

PulseQt 案例

要素内容
业务工业传感器数据采集,替代现有 LabVIEW 方案
功能TCP/串口双通道采集 → 实时曲线 → SQLite 存储 → 历史回放
非功能100Hz 数据流不丢帧、24h 连续运行、<5ms 绘制延迟
约束Windows 10+、Qt 6.5、RS232 串口、Modbus 协议

常见陷阱

  • 需求写的是方案而不是问题:"用 Redis 缓存" → 应写 "查询延迟 <50ms"
  • 非功能需求缺失:“能跑就行” → 上线后 1000 条/秒就崩
  • 验收标准模糊:“响应快” → 改为 "P99 延迟 <100ms"

二、系统架构 + 模块设计

需求分析和架构设计高度耦合,合并为系统设计阶段。

架构模式选型

模式适用场景不足
分层架构企业应用、上位机层间耦合可能导致级联修改
管道-过滤器数据流处理、编译器不适合交互式 UI
微内核IDE、插件系统性能开销
微服务多团队、独立部署网络延迟、运维复杂度
CQRS/事件溯源审计系统、高读写比实现复杂度高

PulseQt 选型:分层架构

┌──────────────────────────────────┐
│            表示层 (UI)            │
│  MainWindow / RealTimeChart /    │
│  QTableView / SettingsDialog     │
├──────────────────────────────────┤
│           业务逻辑层              │
│  ProtocolDecoder / DataBuffer /  │
│  DatabaseManager                 │
├──────────────────────────────────┤
│           通信适配层              │
│  TcpWorker / SerialWorker /      │
│  ChannelManager                  │
├──────────────────────────────────┤
│           硬件抽象层              │
│  QTcpSocket / QSerialPort /      │
│  QSqlDatabase                    │
└──────────────────────────────────┘

层间通信:信号槽 (QueuedConnection)
依赖方向:上层依赖下层,下层不知上层

选型考量点

为什么选分层而不是管道-过滤器?

考量分层管道-过滤器
有 UI 交互(缩放、拖拽)❌ 数据单向流动
需要历史数据回放✅ 任意层可查询❌ 需要重新流过管道
多通道(TCP/串口)切换✅ 适配层抽象❌ 需要不同的管道链
调试友好✅ 层间信号可拦截❌ 中间过滤节点难定位

为什么不是微服务? 单机桌面应用,不需要网络分布,微服务的服务发现/负载均衡/熔断全是过度设计。

模块划分原则

高内聚 + 低耦合 + 单一职责

✅ 好的拆分:
  ProtocolDecoder  — 只管帧解析,不管数据存哪、UI 怎么画
  DatabaseManager  — 只管读写 DB,不管数据从哪来

❌ 坏的拆分:
  DataProcessor    — 既解析协议、又写数据库、又更新 UI

三、通信协议 + 接口设计

协议和接口本质相同——都是组件间的契约,合并设计。

协议设计三要素

要素说明PulseQt 实现
帧格式如何定界、校验A5 5A 帧头 + 长度 + CRC16
交互模式请求-响应/发布-订阅/双向设备主动上报 + 上位机心跳请求
异常处理超时、CRC 失败、断线重连30s 心跳超时 → 自动重连

帧格式设计对比

方案定界方式优点缺点
固定帧头+长度Header(2B) + Len(2B)高效、易实现帧头可能出现在数据中
转义字符0x7E 帧界 + 0x7D 转义数据透明带宽膨胀、实现复杂
Modbus3.5 字符间隔行业标准仅 ASCII/RTU 模式
JSON/文本行\n 分隔人类可读、易调试二进制数据需 Base64

PulseQt 选固定帧头+长度:工业场景二进制数据为主,效率优先。加 CRC16 校验,帧头冲突通过 A5 5A 双字节降低概率。

API 接口设计

上位机内部同样需要接口契约:

// 通信层对外接口(信号)
signals:
    void rawDataReceived(const QByteArray &data);  // 原始字节
    void connectionStateChanged(bool connected);

// 数据层对外接口
class DataBuffer {
public:
    void push(const DataPoint &dp);              // 线程安全写入
    QVector<DataPoint> snapshot() const;         // 线程安全快照
signals:
    void bufferUpdated(int count);
};

// 存储层对外接口
class DatabaseManager {
public:
    void insert(const DataPoint &dp);            // 缓冲写入
    void flush();                                // 强制落盘
    QVector<DataPoint> queryRange(uint64_t from, uint64_t to);
};

接口设计原则

1. 契约优先:先定义接口签名,再实现
2. 最小接口:只暴露需要的,不暴露实现细节
3. 线程安全明确化:哪个线程调用?谁负责锁?
4. 错误返回明确:异常/optional/error_code,不用 -1 魔法值
5. 向后兼容:新增字段用默认值,不删旧字段

四、数据库设计

建模流程

实体识别 ──→ 属性定义 ──→ 关系建模 ──→ 索引优化 ──→ 读写分离策略

PulseQt 数据库设计

-- 采集数据表(写入密集型)
CREATE TABLE data_points (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    timestamp  INTEGER NOT NULL,          -- 毫秒时间戳
    channel    INTEGER NOT NULL DEFAULT 0,
    value      REAL    NOT NULL,
    unit       TEXT,
    alarm      INTEGER NOT NULL DEFAULT 0 -- 0=正常 1=警告 2=异常
);

-- 写入优化索引
CREATE INDEX idx_timestamp ON data_points(timestamp);
CREATE INDEX idx_channel_ts ON data_points(channel, timestamp);

-- 配置表(读多写少,键值对模式)
CREATE TABLE config (
    key   TEXT PRIMARY KEY,
    value TEXT NOT NULL
);

-- 会话表(设备连接记录)
CREATE TABLE sessions (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    start_time INTEGER NOT NULL,
    end_time   INTEGER,
    device_id  TEXT
);

存储引擎选型

考量SQLite (PulseQt 选择)PostgreSQL
部署复杂度零 — 单文件需要 service
并发写入单写者(WAL 缓解)多写者
数据量<1TB 胜任无限
数据类型弱类型强类型 + JSON/数组
适用场景嵌入式、桌面单机服务端、多用户

写入优化策略

采集场景:100Hz × 3 通道 = 每秒 300 条 INSERT

❌ 每条 INSERT 单独提交 → 300 次 fsync/s → 极慢
✅ 批量提交:缓冲区满 100 条 → 事务提交 → 3 次 fsync/s

❌ 默认 journal 模式 → 读写互斥
✅ PRAGMA journal_mode=WAL → 读写并发

❌ 永远不清理 → DB 文件无限增长
✅ 定时 DELETE + VACUUM(或按时间分区)

五、测试设计

测试金字塔

          ┌──────┐
          │ E2E  │  少:完整流程(串口→UI 显示)
         ┌┴──────┴┐
         │  集成   │  中:模块间信号槽通路
        ┌┴────────┴┐
        │   单元    │  多:ProtocolDecoder 状态机
       └───────────┘

各层测试策略

层级测什么工具PulseQt 示例
单元协议解码、CRC 计算、坐标变换GTest/QTesttimeToPixelX(ts) 边界值
集成信号槽链路、DB 读写线程安全QTestWorker→Parser→Buffer 链路
E2E模拟器发帧 → 曲线显示自研 mockPython 模拟器 100Hz 24h 挂机
性能100Hz 丢帧率、绘制帧率自研1h 连续采集帧计数 vs 期望

测试用例模板

用例 ID: TC-PROTO-001
标题: CRC 校验错误丢弃帧
前置: ProtocolDecoder 就绪
步骤:
  1. 构造合法帧 → feed(validFrame)
  2. 构造 CRC 错误帧 → feed(corruptFrame)
  3. 构造合法帧 → feed(validFrame2)
预期:
  frameDecoded 信号发射 2 次(错误帧被丢弃)
  第 1 次数据 == validFrame 载荷
  第 2 次数据 == validFrame2 载荷

测试数据管理

// 协议测试数据:用十六进制字面量定义
const QByteArray VALID_FRAME = QByteArray::fromHex(
    "A55A000C01000203E8D4C2"  // 帧头+长度+类型+4字节整数+CRC
);

// 大量数据点:用循环生成
auto testData = generateDataPoints(10000,    // 数量
    DataPoint{0, 0.0},                       // 起始
    DataPoint{10000, 100.0});                 // 终止(线性插值)

六、部署与运维设计

部署不是最后才想的——在设计阶段就要决策

决策清单

决策选项PulseQt
发行方式MSI/Portable/AppImageInno Setup
自动更新Sparkle/自研暂不需要
配置管理注册表/ini/环境变量SQLite config 表
日志文件/syslog/DB文件 + 分级
监控健康检查端点/看门狗心跳自检
备份全量/增量/实时定时备份 .db

版本策略

v1.0.0
│  ├─ 主版本:不兼容的 API 变更
│  ├─ 次版本:向后兼容的新功能
│  └─ 修订版:向后兼容的 Bug 修复

Git 分支策略(单人项目):
  master   — 稳定版本,可随时发布
  develop  — 开发分支,功能完成后合入 master
  feature/*— 大功能分支

日志设计

// 分级日志
enum LogLevel { DEBUG, INFO, WARN, ERROR, FATAL };

// 格式:时间 [级别] 模块: 消息
// 2025-01-25 14:30:00 [INFO] TcpWorker: Connected to 192.168.1.100:502
// 2025-01-25 14:30:05 [WARN] Heartbeat: Missed 2, retrying...

// 文件策略:按日期滚动,保留 30 天
log_2025-01-25.log
log_2025-01-24.log
...

部署检查清单

☐ Qt 运行时 DLL 已打包(windeployqt)
☐ SQLite/OpenSSL 等第三方库已包含
☐ Visual C++ 运行时已附带(或静态链接 /MT)
☐ 配置文件有默认值,首次运行自动生成
☐ 安装包有管理员权限声明(如需要)
☐ 安装路径无中文/空格(防止工具链问题)
☐ 崩溃时生成 dump 文件(Win: SetUnhandledExceptionFilter)

设计阶段总览

需求分析 ──→ 架构选型 ──→ 协议/接口定义 ──→ 数据库建模
    │                                    │
    │              ┌─────────────────────┘
    │              ▼
    │         模块详细设计
    │              │
    │    ┌─────────┴─────────┐
    │    ▼                   ▼
    │  测试设计            部署设计
    │    │                   │
    └────┴───────────────────┴──→ 验收交付

每个阶段的选择都在约束条件下做权衡:性能 vs 可维护性、简单 vs 灵活、开发速度 vs 代码质量。没有标准答案,只有适合当前项目的最优解。