软件工程现场编程

2026秋软件工程实践 蛋仔派队现场编程总结

项目 内容
作业所属课程 H202601 软件工程与软件工程实践
作业要求 2026秋软工实践团队作业-现场编程
作业的目标 通过 12 人团队协作,在规定时间内完成一个能够实际运行的个人记账 Web 应用。要求团队合理分工、每人独立编写各自负责的功能模块,利用 GitHub 进行版本管理与代码审查,完成软件测试、现场演示及正式版本发布,并记录开发过程中的问题与收获。
团队名称 蛋仔派队
团队成员名字-学号 张诗培-102402145、张家祥-102402144、郑仕廷-102402146、郑宇嘉-102402147、叶腾骏-102402142、周凯-102402148、周山翔-102402149、周子钦-102402150、朱铭浩-102402151、韩尔洋-102402154、崔佳静-102402155、王昊-102402156

一、团队分工

1.1 项目介绍

本次现场编程,我们团队选择开发一款轻量级的个人记账应用——蛋仔记账本(Danzai Ledger)。

项目面向个人日常收支管理场景,用户可以记录每一笔收入与支出,按日期、类型、类别查询账单,并通过统计数据和类别图表了解自己的消费结构。

项目采用 Python + FastAPI + HTML/CSS/JavaScript 的技术方案,数据保存在本地 SQLite 文件(backend/data/account_book.db)中,首次启动自动建库建表,不需要单独安装数据库服务,也不依赖任何 npm 打包流程,把开发重点放在业务功能、团队协作与前后端集成上。

最终完成的主要功能包括:

  1. 新增记账记录:选择收入或支出,填写日期、金额、类别和备注,金额自动由「元」换算为整数「分」存储。
  2. 记录管理:后端实现记录的查询、新增、删除接口。
  3. 条件筛选:支持按日期范围、收支类型、类别组合筛选,多个条件之间为「与」关系。
  4. 收支统计:自动计算收入总额、支出总额、结余,空数据时返回 0 而不是 null。
  5. 分类统计与图表:按类别汇总金额并降序排行,前端用纯 CSS 条形图展示各类别占比。
  6. 数据持久化:记录写入本地 SQLite 文件,刷新页面或重启服务后仍可读取。
  7. Excel 导出:把当前筛选结果导出为 xlsx,含表头样式、冻结首行与金额两位小数格式。
  8. 异常输入处理:对金额为零、删除不存在的记录、日期区间倒置等情况分别返回 422 / 404 / 400,并给出可读的错误文案。

1.2 团队成员职责分工

为使 12 名成员能够并行开发,我们将项目划分为数据层、业务层、统计层、Web 接口层、前端层五个部分,进一步拆分为 12 个主要模块。其中组长张诗培除负责数据层外,还承担任务划分、接口协调、Pull Request 审查、代码合并与最终集成验收工作。

分工原则:

  • 每名成员负责一个主要模块,明确职责边界与所改文件范围。
  • 各模块依据统一接口契约开发:字段名、金额单位、错误码在动第一行代码之前先定完。
  • 使用个人 GitHub 账号提交本人实际编写的代码,通过 Pull Request 提交功能,由组长检查后合并。
  • 集成过程中出现跨模块问题,由相关成员协同排查,不跨人代改代码。
# 成员(姓名-学号) 承担模块 主要交付文件
1 张诗培-102402145 项目集成、仓库管理、数据层 仓库与分支规范、PR #3/#8/#11/#12 的审查与合并、backend/database/init_db.py、backend/database/connection.py、前后端接口对齐
2 张家祥-102402144 项目骨架、前端页面结构、项目文档 docs/PRD.md、docs/DEVELOPMENT_GUIDE.md、docs/TEAM_TASK_ASSIGNMENT.md、全部目录骨架与函数桩、backend/main.py、frontend/index.html 初版布局
3 周山翔-102402149 账单查询与条件筛选 backend/repositories/query_repository.py、backend/services/query_service.py、backend/tests/test_query_module.py
4 韩尔洋-102402154 收支记录新增 / 删除 backend/services/record_service.py、backend/repositories/record_repository.py、backend/api/records.py
5 周子钦-102402150 收支统计 backend/services/statistics_service.py、backend/api/statistics.py、docs/MEMBER_E_STATISTICS.md
6 朱铭浩-102402151 前端界面与交互主力(列表 / 分页 / 删除 / 导出 / 一键启动) frontend/css/style.css、frontend/css/app.css、frontend/js/records.js、frontend/js/main.js、start.bat、backend/services/export_service.py
7 周凯-102402148 仪表盘与统计可视化 frontend/js/statistics.js(概览卡片、类别汇总、CSS 条形图、数据来源标识)
8 郑宇嘉-102402147 新增记账表单 frontend/js/records.js 中的表单交互与前端校验部分
9 郑仕廷-102402146 历史账单列表 账单列表的展示 / 筛选 / 删除原型实现
10 王昊-102402156 后端接口测试 接口用例清单与缺陷记录
11 崔佳静-102402155 前后端联调 验收清单、问题复测记录
12 叶腾骏-102402142 使用说明与测试 readme撰写和测试

二、程序运行环境

2.1 开发环境

项目 具体环境
操作系统 Windows 11(开发与现场演示环境),后端代码本身跨平台
编程语言 Python 3.13(实测 3.13.14 / 3.13.12 均可运行)
后端框架 FastAPI、uvicorn、Pydantic v2
前端技术 HTML5、CSS3、原生 JavaScript(ES2017),无 npm、无打包
数据存储 SQLite(Python 标准库 sqlite3,数据文件 backend/data/account_book.db)
导出依赖 openpyxl(生成 xlsx)
测试方式 仓库内自带 backend/tests/test_query_module.py,可用标准库 unittest 风格直接运行
版本控制 Git、GitHub(分支 + Pull Request 协作)
浏览器 Edge / Chrome 最新版(演示使用 Edge)
端口约定 后端 127.0.0.1:8000,前端静态服务 5500(CORS 白名单只放行这两个源)

项目使用 backend/requirements.txt 管理 Python 依赖,仅包含 fastapi、uvicorn、openpyxl 三项。

2.2 项目运行步骤

第一步:克隆 GitHub 仓库。

git clone https://github.com/zsp-c/personal_expense_tracker.git
cd personal_expense_tracker

第二步:创建 Python 虚拟环境。

py -3 -m venv .venv

第三步:安装项目依赖。

.\.venv\Scripts\python.exe -m pip install -r backend\requirements.txt

第四步:启动后端服务。

cd backend
..\.venv\Scripts\python.exe -m uvicorn main:app --host 127.0.0.1 --port 8000

后端启动后会自动建库建表,接口文档地址:

http://127.0.0.1:8000/docs

第五步:另开一个终端,在仓库根目录启动前端静态服务。

python -m http.server 5500

注意:静态服务必须起在仓库根目录而不是 frontend/,否则页面里用 ../img/ 引用的校徽、背景图会全部 404。

第六步:浏览器访问。

http://localhost:5500/frontend/index.html

即可进入个人记账本界面。

也可以直接双击 start.bat 一键启动。 脚本会依次检查虚拟环境是否存在、fastapi/uvicorn/openpyxl 是否齐备、8000 与 5500 端口是否已被占用(占用则复用,不重复拉进程),然后轮询等待后端真正就绪再自动打开浏览器。

2.3 自动化测试环境

在 backend 目录下执行:

python tests/test_query_module.py

现场集成验收时,自动化测试结果为:

12 passed

测试覆盖查询与筛选模块的正常路径与异常输入,包括:日期范围筛选、类型筛选、类别筛选、组合条件、limit 截断、非法日期格式、日期区间倒置、非法类型、空类别、SQL 注入字符串等场景。

三、软件运行截图和视频

3.1 软件首页

首页集中展示三块内容:收支概览(总收入 / 总支出 / 结余)、新增记账表单、历史账单列表。顶部导航可切换到统计页。

概览卡片上的数字来自 GET /api/stats/summary,新增或删除记录后无需刷新页面,卡片会通过自定义事件自动重算。

image

3.2 新增收支记录

用户可选择收入或支出类型,并填写日期、金额、类别和备注。类型切换时下方的类别选项随之变化(收入侧为工资、奖金等,支出侧为饮食、交通、购物等)。

提交前前端先做一次校验:金额必须为正数且最多两位小数,日期默认今天且不允许晚于今天,类别必填。校验不通过时按字段标红对应输入框,不必等一次网络往返。

image

3.3 账单筛选与删除

系统支持以下筛选条件:

  • 日期范围(开始日期、结束日期);
  • 收入或支出类型;
  • 收支类别。

多个条件之间采用「与」关系,只有全部满足的记录才会出现在列表中。列表支持分页加载,每条记录右侧提供删除按钮,删除前有二次确认,删除成功后概览与统计同步刷新。

image

3.4 收支统计与分类图表

统计页展示当前筛选范围内的收入、支出与结余,并按类别汇总金额、以降序排列,右侧用纯 CSS 条形图展示各类别占比,条形长度按该类别占最大类别的比例计算,不依赖任何外部图表库。

页面左上角有数据来源徽标:绿色「实时数据」表示来自后端接口,橙色「演示数据 · 已自动降级」表示接口不可用时改用本地流水重算。

image

3.5 异常输入处理

在集成验收时,我们逐条验证了异常输入的实际返回,结果如下:

场景 请求 实际返回
新增收入 5000 元 POST /api/records/ {"id":1,"message":"添加成功"}
新增支出 30 元 / 20 元 POST /api/records/ {"id":2,...}、{"id":3,...}
收支汇总 GET /api/stats/summary {"income":500000,"expense":5000,"balance":495000}
分类统计(支出) GET /api/stats/category?type=expense [{"category":"饮食","total":3000},{"category":"交通","total":2000}]
金额填 0 POST /api/records/ 422(Pydantic 在入口拦截)
删除不存在的记录 DELETE /api/records/999 404 {"detail":"记录 id=999 不存在"}
开始日期晚于结束日期 GET /api/records/?start_date=...&end_date=... 400 {"detail":"start_date 不能晚于 end_date"}
导出 Excel GET /api/records/export 5307 字节 xlsx,响应头 filename*=UTF-8''账单_20261107.xlsx

3.6 接口文档(Swagger)

后端所有接口通过 FastAPI 自动生成文档,现场演示时可直接在 /docs 页面上发请求,便于助教核对每个接口的实际行为。

image

3.7 软件演示视频

视频地址:通过网盘分享的文件:20261107_个人记账.mp4

链接: https://pan.baidu.com/s/1Wn1mkg4tdmOiVM3GDkI96Q?pwd=dmck 提取码: dmck

四、软件实现的关键代码

本项目采用分层、模块化的设计方式:数据层负责建表与连接,模型层负责请求体校验,业务层(记录 / 查询 / 统计 / 导出)承载业务规则,路由层把功能暴露为 HTTP 接口,前端按职责拆成 api.js、records.js、statistics.js、main.js 四个文件。

以下截取四处最能体现设计取舍的代码,均摘自项目正式版本。

4.1 建表:约束写在表结构里(数据库层)

文件:backend/database/init_db.py

金额必须大于 0、类型只能是收入或支出——这类规则如果只在 Python 里判断,换个入口(导出、脚本、直接连库)就绕过去了。因此约束同时落在建表语句里,并用 IF NOT EXISTS 保证服务反复重启只建一次表。

def init_database() -> None:
    """创建数据表(幂等,可重复调用)。"""
    conn = get_connection()
    try:
        conn.execute(
            """
            CREATE TABLE IF NOT EXISTS records (      -- 幂等:重复启动不会重复建表或报错
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                date TEXT NOT NULL,                    -- YYYY-MM-DD,日期字符串可直接比较大小
                type TEXT NOT NULL
                    CHECK(type IN ('income', 'expense')),   -- 数据库层兜底,防止写入脏类型
                amount INTEGER NOT NULL
                    CHECK(amount > 0),                 -- 金额是整数「分」,0 与负数写不进去
                category TEXT NOT NULL,
                remark TEXT DEFAULT '',                -- 备注允许为空字符串,前端显示为「—」
                created_at TEXT DEFAULT CURRENT_TIMESTAMP
            )
            """
        )
        conn.commit()
    finally:
        conn.close()                                   # 连接在本函数内关闭,不依赖连接池

实现说明:

建表语句本身承担了一部分数据校验职责。前端校验可能被绕过、脚本可能直接写库,但 CHECK 约束不会。

日期用 TEXT 存 YYYY-MM-DD,是因为该格式的字典序与时间序一致,WHERE date >= ? 直接用字符串比较即可,不需要引入日期类型转换。

4.2 收支汇总统计(统计层)

文件:backend/services/statistics_service.py

如果按「查两次、在 Python 里相减」的写法,空表时 SUM() 返回的是 NULL,前端拿到就是 NaN。我们用 CASE WHEN 把两类金额一次算出来,再用 COALESCE 把空结果压成 0。

def get_summary(start_date=None, end_date=None) -> dict:
    """返回 {"income": int, "expense": int, "balance": int}(单位:分)。"""
    where, params = _build_where(start_date, end_date)   # 只拼 " WHERE date >= ? AND date <= ?",不拼用户输入
    sql = (
        "SELECT "
        # 一条 SQL 里按 type 分流求和,避免两次查询之间数据被改动导致收支对不上
        "COALESCE(SUM(CASE WHEN type = 'income' THEN amount ELSE 0 END), 0) AS income, "
        "COALESCE(SUM(CASE WHEN type = 'expense' THEN amount ELSE 0 END), 0) AS expense "
        f"FROM records{where}"
    )
    conn = get_connection()
    try:
        cur = conn.cursor()
        cur.execute(sql, params)      # 参数化查询:日期范围走 ? 占位符
        row = cur.fetchone()
        income, expense = int(row["income"]), int(row["expense"])
    finally:
        conn.close()
    return {"income": income, "expense": expense, "balance": income - expense}

实现说明:

统计逻辑独立于路由,输入只有筛选条件,输出是纯字典,可以脱离 Web 层直接测试。分类统计复用同一个 _build_where(),只是把 GROUP BY category 换成按类别聚合,两个统计口径因此不会出现偏差。

4.3 查询与参数校验(查询层)

文件:backend/services/query_service.py;文末的全局异常处理器在 backend/main.py

查询接口是前端最容易传错参数的地方。我们的原则是:用户能看懂的输入错误 → 400 并说人话;机器传的数量越界 → 静默截断,不打断整页。

def list_records(start_date=None, end_date=None, type=None,
                 category=None, limit=None, offset=0) -> list[dict]:
    if start_date: _validate_date(start_date, "start_date")      # 格式不对 -> "应为 YYYY-MM-DD,当前值:xxx"
    if end_date:   _validate_date(end_date, "end_date")
    if start_date and end_date and start_date > end_date:
        raise ValueError("start_date 不能晚于 end_date")          # 区间颠倒必须报错,否则用户以为「没数据」
    if type and type not in VALID_TYPES:
        raise ValueError("type 只能是 income 或 expense")
    if category is not None and not category.strip():
        raise ValueError("category 不能为空字符串")

    # limit 越界不报错:前端误传 99999 时截到上限,页面仍能正常渲染
    effective_limit = (query_repository.DEFAULT_LIMIT if limit is None
                       else max(1, min(int(limit), query_repository.MAX_LIMIT)))
    return query_repository.query_records(start_date=start_date, end_date=end_date,
                                          type=type, category=category,
                                          limit=effective_limit, offset=offset)

Service 层抛出的 ValueError,由一个全局异常处理器统一翻译成 400,不用在 8 个接口里各写一遍 try/except:

@app.exception_handler(ValueError)
async def value_error_handler(request: Request, exc: ValueError):
    """将 Service 层的 ValueError 统一转换为 400,错误文案直接透给前端。"""
    return JSONResponse(status_code=400, content={"detail": str(exc)})

实现说明:

动态 WHERE 的拼装只拼列名和 ? 占位符,条件值一律通过参数传入,因此类别里写 '; DROP TABLE records; -- 也只会被当作普通字符串匹配,这一点在单元测试中有专门用例。

「什么该报错、什么该兜底」需要提前约定:日期颠倒、类型拼错是调用方的问题,必须明确告知;而 limit 只是数量参数,截断比报错对用户更友好。

4.4 金额换算:元与分只在这两个函数里互转(前端 API 层)

文件:frontend/js/api.js

浮点数做钱一定会出事,所以团队约定的第一条就是:数据库、接口、内部运算全部是整数「分」,只有「用户输入」和「屏幕显示」两个瞬间才碰「元」,而这两个瞬间收敛到 yuanToFen / fenToYuan 两个函数。

/** 元 -> 分:提交前调用。Math.round 兜住 19.99 * 100 = 1998.9999 这类二进制误差 */
function yuanToFen(value) {
    return Math.round(Number(value) * 100);
}

/** 分 -> 元:展示前调用,固定两位小数 */
function fenToYuan(value) {
    return (Number(value) / 100).toFixed(2);
}

实现说明:

在整个项目里搜 * 100 和 / 100,只能在这两个函数里搜到,其余业务代码一律用分运算。这条约定在写第一行代码之前就定下了,因此后端统计、前端展示、Excel 导出三处的金额口径天然一致,没有出现过「页面 4950、导出 49.5」这类问题。

五、软件的亮点

5.1 亮点一:把金额单位变成一条不可绕过的约定

「元 / 分」不一致是记账类程序最经典的 bug 来源:接口 A 收元、接口 B 收分,最后统计永远对不上。我们的做法是把它变成一条不可绕过的约定:数据库存的、接口传的、Python 算的,全是整数分;只有 yuanToFen() / fenToYuan() 两个函数允许出现「元」。

因此整个仓库里搜 * 100 和 / 100 只能搜到这两处,新人改代码时几乎没有犯错空间。实测新增 5000 元收入与 30 元、20 元支出后,summary 返回 {"income":500000,"expense":5000,"balance":495000},导出 Excel 中显示为 5000.00 / 30.00 / 20.00,三处口径完全一致。

5.2 亮点二:后端不可用时优雅降级,并诚实标注数据来源

现场演示最怕的是「后端没启动 / 端口被占 / 忘开服务」,页面一片空白。统计模块做了三级降级:后端可用 → 走 /api/stats;后端报错或 api.js 未加载 → 用内置的原始流水在本地重算(不是写死汇总数字,所以逻辑与真实路径完全一致)。

同时卡片左上角显示绿色「实时数据」或橙色「演示数据 · 已自动降级」,鼠标悬停可看到降级原因。接口一恢复,无需改代码就自动切回真实数据。 这条设计让我们在演示时敢于直接关掉后端,说明「这不是假的」。

// frontend/js/statistics.js
var message = isDemo
    ? "演示数据 · 后端统计接口未连接,已自动降级"
    : "实时数据 · 来自后端 /api/stats";

5.3 亮点三:一键启动脚本把「环境问题」挡在演示之前

每人本地环境都不一样,最容易浪费时间的不是写代码而是「你那边能跑我这边跑不起来」。start.bat 把四件事做成了一条路径:检查 .venv 是否存在 → 检查 fastapi/uvicorn/openpyxl 是否齐备 → 检测 8000/5500 端口是否已被占用(占用就复用,不重复拉进程)→ 轮询等后端真正就绪再打开浏览器,而不是 timeout 3 赌它启得快。

还有一个反直觉的细节:静态服务必须起在仓库根目录而不是 frontend/,否则页面里的 ../img/校徽.png 全部 404——这个坑也写进了脚本注释里。

六、开发过程中收获较大的事件

6.1 事件一:三个人同时改 records.js,一次整份覆盖带走了别人的逻辑

问题背景:

records.js 是前端改动最频繁的文件,表单、列表、分页、删除都在里面。功能分支合并后,账单列表能显示,但「加载更多」失效了,而该分支自己单独测试是好的。

解决过程:

排查发现 records.js 被两个人各自整份重写后合并,后合并的一方以「我的版本更完整」为由整文件覆盖,把另一方新增的分页状态变量覆盖掉了。团队当场定下文件所有权规则(写在 docs/TEAM_TASK_ASSIGNMENT.md 第四节):一个文件只有一个主负责人,其他人要改必须走小 PR 且只改自己那段。records.js 以功能更完整的一版为基线,另一方保留的分页思路以 patch 形式补回。

收获:

「谁写的文件谁负责」不是官僚,是并行开发的必需品。冲突本身不可怕,可怕的是覆盖式合并——它不产生冲突标记,所以没人会去看 diff。此后我们的约定是:动公共文件之前先在群里说一句,合并前先看对方的 diff 有没有被自己抹掉。

6.2 事件二:start.bat 里的中文全是乱码,以及那个必须放对位置的静态服务

问题背景:

一键启动脚本在作者电脑上正常,同学拉下来双击后满屏 锟斤拷;另外页面打开后所有校徽、背景图 404。

解决过程:

.bat 默认按 GBK 解析,文件被存成 UTF-8 就必然乱码;而把静态服务起在 frontend/ 目录,会让页面里用 ../img/ 引的资源全部越界。解决方式是 .bat 统一用 GBK + CRLF 保存,并加 .gitattributes 强制该文件以 CRLF 检出(否则 Linux 同学一提交就变成 LF);静态服务改为在仓库根目录启动,并把这个前提写进启动说明。

收获:

跨平台的坑往往不在代码里,在文件编码和换行符里。.gitattributes 这种「一次配置、永久生效」的东西,越早知道越好。

七、每个组员的团队编程体验

七、每个组员的团队编程体验

本节由 12 名团队成员结合本人实际开发经历填写,重点说明所承担的任务、遇到的困难、解决方式以及本次团队编程的收获。以下按分工表顺序排列,按各人实际承担模块填写。

7.1 张诗培(组长:项目集成与数据层)

本次团队编程中,我的主要工作其实不是写功能,而是「让别人写的东西能装到一起」:定目录、定接口、定金额单位,负责 backend/database/ 下的建表与连接管理,然后不停地看 PR 的 diff,判断这次合并会不会踩到别人的文件。

最大的体会是约定要在写第一行代码之前定完——我们前期花时间讨论数据库字段和金额单位,后面几个小时几乎没有因为「字段对不上」返工。另外作为组长,最难的不是技术判断而是取舍:有人做了但没合并(PR #4),要在不打击积极性的前提下说清为什么不用,最后我们把分支和 PR 都保留下来,让过程可查。

7.2 张家祥(项目骨架与前端页面结构)

我搭的是空架子:目录、分层、每个模块留 TODO(成员 X) 的桩函数,三份文档(PRD、开发规范、任务分工),以及 frontend/index.html 的初版布局。当时担心「写文档浪费时间」,结果恰恰是这份分工文档在后期几个人抢同一个文件时救了场。

体会:骨架的价值不是代码,是让 12 个人对「东西该长在哪」有同一个答案。

7.3 周山翔(账单查询与条件筛选)

我负责查询模块,最花时间的不是写 SQL,而是想清楚「哪些参数该报错、哪些该兜底」,以及把动态 WHERE 拼装做成全程 ? 参数绑定。

另外补了 12 个单元测试,其中 SQL 注入那条最有用——它证明了即使 category 传 '; DROP TABLE records; --,也只会被当成普通字符串匹配。

7.4 韩尔洋(收支记录新增 / 删除)

我写增删改的 Repository 和 Service。踩得最实的坑是「删除不存在的记录」该返回什么:一开始 rowcount 没判断,接口永远返回成功,前端以为删掉了。

改成用 cursor.rowcount > 0 判断、Service 抛 ValueError、API 层转 404 之后才符合直觉。这件事让我意识到返回值的语义也是接口的一部分。

7.5 周子钦(收支统计)

统计模块看着最简单,其实边界最多:空表、只有收入没有支出、按日期筛选后无数据。SUM() 在没有记录时返回 NULL 而不是 0,前端拿到就是 NaN,所以全部用 COALESCE(...,0) 兜住。

体会:「没有数据」也是一种必须设计的状态,不能等它在前端渲染成 NaN 才想起来。

7.6 朱铭浩(前端界面与交互主力)

我改的文件最多,也最容易被覆盖,所以对「文件所有权」感触最深。

前端这边我做得最用力的是错误提示:金额格式、日期不能选未来、接口失败、删除二次确认,每一种都有对应文案,绝不静默失败。另外 records:changed 这个自定义事件是我比较满意的一个决定,它让列表和统计彻底解耦,统计页可以单独打开也不报错。

7.7 周凯(仪表盘与统计可视化)

我负责概览卡片、类别占比和收支对比条形图。为了让「后端没启动也能演示」,我做了本地降级加数据来源徽标,原则是可以降级,但不能骗人:降级时必须明确告诉用户这是演示数据。

图全部用 CSS 画,没有引外部库,断网也能跑。

7.8 郑宇嘉(新增记账表单)

我负责表单交互:类型切换、金额校验(正则限制两位小数)、日期默认今天且不能选未来、提交时把元转成分。

最纠结的是「保存成功后要不要清空表单」,最后决定只清空金额和备注、保留日期和类别,方便连续记账。这种小决定没人会写进需求文档,但直接影响用起来顺不顺手。

7.9 郑仕廷(历史账单列表原型)

我实现了一版账单列表(展示 / 筛选 / 删除),评审时发现与主实现的思路重叠。虽然 PR 最终关闭没有合并,但我的筛选控件与删除交互被主实现吸收了一部分。

体会:并行开发里「做了但没合并」也是有效工作,前提是把 PR 留下来,让过程可查。

7.10 王昊(后端接口测试)

我主要负责后端接口的测试与缺陷记录。在 /docs 页面上逐条核对了所有接口的正常与异常返回,把「金额为 0 返回 422」「删除不存在返回 404」「日期区间倒置返回 400」这几条固化成接口用例清单。在这个过程中,我深刻体会到了清晰的错误码和错误文案对前端联调有多重要,一个模糊的 500 错误能让前端排查半天。

7.11 崔佳静(前后端联调)

我负责前后端的联调与质量保障。工作重点在于确认端口与 CORS 白名单,并顺着「新增 → 列表刷新 → 概览数字变化 → 删除 → 统计同步」走完整业务链路,记录每一步的实际响应。

服务重启后重新打开页面确认数据还在,这一步让我第一次真切理解「持久化」的意义。联调不是简单的把前端地址指向后端,而是要确保两端对数据的语义认知是一致的。

7.12 叶腾骏(使用说明与测试)

我负责 README 的撰写以及配合进行测试工作。最大的收获是发现「自己看得懂」和「别人照着能跑起来」完全是两件事——把启动步骤写成命令后,我按新同学视角在干净目录重跑了一遍,才补上「静态服务必须起在仓库根目录」这个坑。同时,在整理测试用例时,也帮助我理清了项目的核心业务流程和边界情况。

八、GitHub仓库地址和Commit记录

8.1 GitHub仓库地址

项目仓库:

https://github.com/zsp-c/personal_expense_tracker

本次项目采用 GitHub 进行代码托管、功能分支管理、Pull Request 代码审查以及最终版本发布。

默认分支是 dev,它也是本次开发的主线:40 次提交(20 次功能提交、20 次合并提交,时间跨度 14:26–17:25),所有模块的开发与集成都发生在这条分支上。main 分支在 dev 基础上补充了 Excel 导出接口、一键启动脚本与页面排版修复,共 44 次提交(最后更新 19:06),作为最终交付版本。另有 9 个 feature/* 分支与 12 个 Pull Request(其中 11 个已合并)。

8.2 Commit记录

image
image
image
image

九、总结

通过本次现场编程,我们完成了从任务划分、独立开发、GitHub 协作,到模块整合、测试和版本发布的完整流程。

技术上,最大的收获是把「约定」当成了工程问题来对待:金额单位、日期格式、错误码、文件所有权,这些看起来是文档里的几句话,实际上决定了十几个人并行开发时会不会互相踩踏。

协作上,我们体会到完成自己的模块只是第一步。只有当各模块成功合并、通过整体测试,并且能在浏览器里走完一条完整的业务链路(记一笔 → 看到列表 → 概览变化 → 导出 Excel),才说明软件真正达到了可交付状态。


附录 A:贡献度评分

# 成员 建议分值 依据
1 张诗培(102402145) 1.05 项目集成、仓库管理、数据层
2 朱铭浩(102402151) 1.1 前端界面与交互主力
3 张家祥(102402144) 1.1 项目骨架、前端页面结构、项目文档
4 周山翔(102402149) 1 账单查询与条件筛选
5 周子钦(102402150) 1 收支统计
6 韩尔洋(102402154) 1 收支记录新增 / 删除
7 周凯(102402148) 1 仪表盘与统计可视化
8 郑宇嘉(102402147) 0.95 新增记账表单
9 郑仕廷(102402146) 1 历史账单列表
10 王昊(102402156) 0.95 后端接口测试
11 崔佳静(102402155) 0.95 前后端联调
12 叶腾骏(102402142) 0.9 使用说明与测试
posted @ 2026-11-07 21:31  zsp-  阅读(16)  评论(0)    收藏  举报