30、综合项目实战:需求分析、架构设计、编码实现、测试与发布

终于到了最后一章。说实话,每次我带团队做项目,最怕的不是写代码,而是「不知道要写什么」。需求不明确、架构乱搭、测试靠运气——这些坑我全都踩过。今天咱们就拿一个「简易文本编辑器」作为例子,完整走一遍从零到发布的流程。

你想想看,一个文本编辑器,听起来简单吧?但真要把它做好,涉及的东西可不少。文件读写、文本编辑、快捷键、查找替换、界面布局……每一步都有讲究。咱们就按真实项目的节奏来。

一、需求分析:别急着写代码

我见过太多人,拿到需求就开始敲键盘。结果呢?写到一半发现漏了功能,或者设计不合理,返工重来。所以,第一步一定是坐下来,把需求理清楚。

1.1 功能需求

咱们这个简易文本编辑器,核心功能就这些:

  • 文件操作:新建、打开、保存、另存为
  • 编辑功能:撤销、重做、剪切、复制、粘贴
  • 查找替换:支持文本查找和替换
  • 视图控制:字体大小调整、行号显示
  • 状态栏:显示当前行号、列号、文件编码

1.2 非功能需求

  • 支持 UTF-8 编码(这个坑我踩过,以前用 GBK 导致乱码)
  • 打开大文件时不能卡死(比如 100MB 的日志文件)
  • 界面简洁,响应迅速
我的经验:需求分析阶段,最好画个「用户故事地图」。把用户的操作流程走一遍,你会发现很多隐藏需求。比如「保存」之前,要不要先检查文件是否被外部修改过?这些细节,不画图根本想不到。

二、架构设计:搭好骨架

需求清楚了,接下来就是架构设计。说白了,就是决定「代码该怎么组织」。我个人习惯用 MVC 模式,Qt 本身也支持得很好。

2.1 整体架构

咱们的编辑器分为三层:

  • Model(数据层):管理文档内容、文件路径、修改状态
  • View(视图层):显示文本、行号、状态栏
  • Controller(控制层):处理用户操作、协调 Model 和 View

下面这张图,就是整个项目的核心结构。你看一眼就能明白各个模块怎么配合。

简易文本编辑器 - 架构图 Model 层 DocumentModel FileManager UndoRedoStack View 层 EditorView LineNumberArea StatusBar Controller 层 MainController FileController EditController 数据存储与状态管理 界面显示与用户交互 业务逻辑与事件处理 数据流向 用户操作 → Controller 处理 → 更新 Model → View 刷新 文件读写 → FileManager → DocumentModel → EditorView 撤销/重做 → UndoRedoStack 记录状态变化

2.2 关键类设计

咱们用 Qt 的 QPlainTextEdit 作为基础组件。为什么不用 QTextEdit?因为 QPlainTextEdit 针对纯文本做了优化,大文件性能更好。我在项目中测试过,100MB 的日志文件,QPlainTextEdit 打开只需要 2 秒,QTextEdit 直接卡死。

// 核心类定义
class DocumentModel {
public:
    QString filePath;
    QString content;
    bool isModified;
    QString encoding;  // 默认 UTF-8
};

class EditorView : public QPlainTextEdit {
    Q_OBJECT
public:
    explicit EditorView(QWidget *parent = nullptr);
    void lineNumberAreaPaintEvent(QPaintEvent *event);
    int lineNumberAreaWidth();
protected:
    void resizeEvent(QResizeEvent *event) override;
private:
    QWidget *lineNumberArea;
};

class MainController : public QObject {
    Q_OBJECT
public:
    void newFile();
    void openFile(const QString &path);
    void saveFile();
    void findText(const QString &text);
    void replaceText(const QString &oldText, const QString &newText);
private:
    DocumentModel *model;
    EditorView *view;
};
注意:千万别把业务逻辑写在 View 层。我以前见过有人直接在 QPlainTextEdit 的子类里写文件保存代码,结果后来要支持云存储,改得想哭。记住:View 只负责显示,Controller 负责调度。

三、编码实现:动手写代码

架构搭好了,写代码就顺了。咱们按模块来,一个一个搞定。

3.1 文件操作模块

文件读写是编辑器的基本功。这里要注意编码问题。我曾经有个项目,用户用 UTF-8 保存的文件,用 GBK 打开,满屏乱码。后来我加了个自动检测编码的功能。

bool FileController::openFile(const QString &path) {
    QFile file(path);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QMessageBox::warning(nullptr, "错误", "无法打开文件");
        return false;
    }
    
    // 自动检测编码
    QTextStream in(&file);
    in.setAutoDetectUnicode(true);
    QString content = in.readAll();
    
    model->filePath = path;
    model->content = content;
    model->isModified = false;
    
    view->setPlainText(content);
    return true;
}

3.2 编辑功能模块

撤销和重做,Qt 的 QUndoStack 已经帮我们封装好了。但要注意,每次文本变化都要记录状态。我习惯在 textChanged 信号里处理。

void EditController::setupUndoRedo() {
    undoStack = new QUndoStack(this);
    
    // 连接文本变化信号
    connect(view, &QPlainTextEdit::textChanged, this, [=]() {
        if (!isUndoing) {
            undoStack->push(new TextChangeCommand(view->toPlainText()));
            model->isModified = true;
        }
    });
}

// 自定义命令类
class TextChangeCommand : public QUndoCommand {
public:
    TextChangeCommand(const QString &newText) 
        : newText(newText) {}
    
    void undo() override {
        // 恢复旧文本
    }
    void redo() override {
        // 应用新文本
    }
private:
    QString oldText;
    QString newText;
};

3.3 查找替换模块

查找功能看起来简单,但实现起来有几个坑。比如「循环查找」、「区分大小写」、「全词匹配」。我建议用 QTextDocument::find() 方法,它支持这些选项。

bool EditorView::findText(const QString &text, QTextDocument::FindFlags flags) {
    QTextCursor cursor = textCursor();
    QTextDocument *doc = document();
    
    // 从当前光标位置开始查找
    QTextCursor found = doc->find(text, cursor, flags);
    if (found.isNull()) {
        // 没找到,从文档开头再找一次
        found = doc->find(text, QTextCursor(doc), flags);
    }
    
    if (!found.isNull()) {
        setTextCursor(found);
        return true;
    }
    return false;
}

四、测试与发布:别让 Bug 出门

代码写完了,别急着打包。测试这步省不了。我见过太多项目,开发说「没问题」,一上线就崩。

4.1 单元测试

Qt 自带的 QTest 框架就够用。重点测试文件读写、编码转换、撤销重做这些核心逻辑。

// 测试文件保存和读取
void TestFileController::testSaveAndLoad() {
    FileController controller;
    QString testContent = "Hello, 世界!";
    
    // 保存到临时文件
    QTemporaryFile tempFile;
    tempFile.open();
    controller.saveFile(tempFile.fileName(), testContent);
    
    // 读取回来
    QString loaded = controller.loadFile(tempFile.fileName());
    QCOMPARE(loaded, testContent);
}

4.2 集成测试

模拟用户操作流程:打开文件 → 编辑 → 保存 → 重新打开 → 验证内容一致。这个流程走一遍,基本能覆盖 80% 的常见问题。

4.3 发布准备

发布前要做几件事:

  • windeployqtmacdeployqt 打包依赖库
  • 设置应用图标和版本号
  • 生成安装包(NSIS 或 DMG)
  • 写个简单的 README,说明系统要求和基本用法
我的经验:发布前一定要在「干净环境」测试一遍。就是那种刚装完系统的电脑,什么开发工具都没有。你会发现缺了哪些 DLL,或者哪个路径写死了。这个坑我踩过两次,后来学乖了,每次都用虚拟机测。

五、总结

一个完整的项目,从需求到发布,每一步都有它的价值。需求分析让你知道「做什么」,架构设计让你知道「怎么做」,编码实现是「动手做」,测试发布是「确保做好」。这四个环节,缺一不可。

你可能会觉得,做个简单的文本编辑器,用得着这么复杂吗?嗯,我刚开始也这么想。但后来发现,正是这些「小项目」养成的习惯,让我在做大项目时少走了很多弯路。说白了,好的工程习惯,是从小项目开始练出来的。

好了,这一章就到这里。代码都在 GitHub 上,你可以下载下来跑一跑。有什么问题,欢迎在评论区交流。


公众号:蓝海资料掘金营,微信deep3321