软件工程现场编程作业
| 作业所属课程 | 福州大学软件工程(202601) |
|---|---|
| 作业要求 | 个人记账本现场编程作业 |
| 作业的目标 | 在限定时间内,分工完成记账、查询、统计与本地存储模块,通过 GitHub 协作完成集成、测试和软件演示。 |
| 团队名称 | 鹦伟达 |
| 团队成员名字-学号 | 魏之皓—102401608;刘谦益—102401312;王智钦—102401609;庄凯堃—102401310;杜玉鹤—162404109;陈常群—102401318;李义东—102401215;张锦昊—042402115;何慧渊—102401135;蔡信坡—162404103;佘博海—102401137 |
一、项目概述
PocketLedger 是一款在本机运行的个人记账网页应用,支持收入与支出记录、流水查询、组合筛选、收支统计和数据导出。每笔账目包含日期、金额、类别和备注,数据以 JSON 文件保存在本地。
后端采用 Python 和 Flask,页面由 Jinja2 模板渲染,使用 CSS、原生 JavaScript 和 SVG 展示界面与图表。应用无需配置数据库,图表展示不依赖在线服务。
| 作业需求 | 实现方式 |
|---|---|
| 记录收入与支出 | 记账表单、字段校验、账目服务与本地 JSON 存储 |
| 查看最近记录 | 首页最近记录、流水排序和分页 |
| 收支统计 | 日期、类别、类型、金额范围和备注筛选,以及收入、支出、结余汇总 |
| 用户界面 | 统一导航、卡片布局、表单提示与流水表格 |
| 图表展示 | 分类支出占比和每日收支趋势 |
| 扩展功能 | CSV 导出、月度预算、类别管理、账目编辑及 JSON 备份恢复 |
二、团队分工与 GitHub 协作
下表依据团队方案列出主要职责,并关联代表性 PR 和提交;部分 PR 同时包含跨模块联调修改。
| 成员 | 负责模块与主要内容 | PR 记录 | 关键 commit |
|---|---|---|---|
| 魏之皓 102401608 |
应用入口、公共布局、首页、演示数据、导航与集成 | #10、#17、#24 | 283905b:应用入口 |
| 刘谦益 102401312 |
默认类别、类别规则与管理页面 | #11、#22 | eb52688:类别规则 |
| 王智钦 102401609 |
收支汇总、统计页、图表数据及 CSV 下载路由 | #7、#13、#26 | dc84117:统计模块 |
| 庄凯堃 102401310 |
分类支出图、每日趋势图及图表展示 | #4、#15、#23 | e844d5f:分类支出占比 |
| 杜玉鹤 162404109 |
查询参数解析、组合筛选、快捷日期与排序 | #1、#20 | 8d80663:组合筛选 |
| 陈常群 102401318 |
新增、编辑、删除相关路由,记账表单与类别联动 | #21 | 3eab836:记账界面 |
| 李义东 102401215 |
JSON 存储、原子写入、备份与恢复 | #14、#25 | 4759634:本地存储 |
| 张锦昊 042402115 |
最近记录、流水筛选列表、分页与流水页面布局 | #9、#27 | 80613f8:流水模块 |
| 何慧渊 102401135 |
日期、金额、类型、备注等字段校验与边界测试 | #16 | aec0b5a:校验模块 |
| 蔡信坡 162404103 |
CSV 文本生成、编码、转义、数据校验及文件名处理 | #3 | cbfd040:导出评审修正 |
| 佘博海 102401137 |
统一模型、账目增删改服务、月度总预算与分类预算 | #8 | c1d98d6:统一模型 |
模块之间通过统一的数据结构和接口连接:统计页调用筛选与 CSV 模块,预算页与记账服务复用存储模块。集成测试重点检查页面之间的数据一致性和操作流程。
仓库:LotteryCamp/LotteryTally;提交记录:Commits;验证代码版本:e8f6bee。
三、运行环境与启动方法
| 项目 | 配置 |
|---|---|
| 操作系统 | Windows 11 |
| Python | 3.12.14;项目要求 Python 3.11 及以上 |
| Flask | 3.1.3;项目依赖范围为 Flask 3.x |
| 页面与交互 | Jinja2、HTML、CSS、原生 JavaScript、SVG 图表 |
| 浏览器 | Google Chrome |
| 持久化 | 本地 JSON 文件 |
| 测试 | Python 标准库 unittest |
3.1 安装与启动
在仓库根目录打开 PowerShell,依次完成以下操作。
第一步:创建运行环境
python -m venv .venv
第二步:安装项目依赖
.\.venv\Scripts\python.exe -m pip install -e .
第三步:启动应用
.\.venv\Scripts\python.exe app.py
3.2 访问页面
3.3 独立演示账本(可选)
展开:使用演示数据启动
如需复现运行截图中的初始账本,先停止应用,再在同一 PowerShell 窗口执行:
.\.venv\Scripts\python.exe demo_data.py --output data/demo_ledger.json
$env:LEDGER_DATA_PATH = (Resolve-Path data/demo_ledger.json).Path
.\.venv\Scripts\python.exe app.py
已有演示文件时跳过第一行,生成脚本不会覆盖原文件。上面的环境变量用于在启动前统一账本路径。
该版本的 --data-file 参数在应用创建后生效,可能与存储模块的初始化路径不一致,因此演示账本采用上述环境变量方式配置。
四、软件运行截图与视频
以下运行截图使用 e8f6bee 版本的示例账本。初始账本包含工资收入 3,000 元、两笔饮食支出 300 元和 170 元,饮食预算设为 500 元。
4.1 首页与新增记账
首页显示收入 3,000 元、支出 470 元、结余 2,530 元和 3 笔记录。

新增一笔 2026-11-07 的饮食支出,金额 45 元,备注为“晚餐(联动演示)”。

4.2 流水列表
新增后流水共 4 笔。筛选条件采用网格布局,表格统一列间距,并以颜色区分收入和支出。

4.3 预算联动
饮食支出从 470 元增加到 515 元,超过 500 元预算 15 元。页面按账目日期归属月份计算,收入不计入支出预算。

4.4 筛选、统计、图表与 CSV
筛选“本月、饮食、支出”,按金额降序排列,得到 3 笔支出,合计 515 元。图表和 CSV 使用相同的筛选结果;导出文件包含这 3 笔记录,且仅有一个 UTF-8 BOM。

导出样本:filtered_expenses.csv。金额从高到低为 300.00、170.00、45.00 元。
4.5 类别、备份与恢复
类别页面提供创建、改名、停用和重新启用的入口。备份页面可下载包含账目、类别和预算的 JSON 文件,并上传备份进行恢复。


下载含 4 笔账目的备份后,通过编辑路由将新增账目的金额从 45 元改为 20 元。饮食支出随之变为 490 元,预算剩余 10 元。

上传备份并恢复后,账目金额回到 45 元,首页总支出为 515 元,结余为 2,485 元。

4.6 演示视频
视频演示了新增记账、流水查询、收支统计、图表展示和 CSV 导出,并展示预算、类别管理及备份页面。
视频由何慧渊录制,时长约 1 分 28 秒。视频与上述截图使用不同账本,金额以各自画面为准。
五、软件实现的关键代码
以下代码节选省略了部分上下文、类型注解和文档字符串,并补充说明性注释;核心逻辑与所列仓库版本一致。
5.1 本地 JSON 写入:先写临时文件,再替换正式文件
lotterytally/storage.py 中的 _atomic_write 负责更新账本文件:
def _atomic_write(path, data):
parent = os.path.dirname(path) or "."
# 临时文件放在账本同一目录,保证替换发生在同一文件系统。
fd, tmp_path = tempfile.mkstemp(
prefix=".ledger-", suffix=".tmp", dir=parent
)
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
f.flush()
os.fsync(f.fileno())
# 临时文件写入完成后,才替换正式账本。
os.replace(tmp_path, path)
except BaseException:
try:
os.remove(tmp_path)
except OSError:
pass
raise
update_ledger 在进程内锁保护下完成读取、修改、校验和写入,减少并发覆盖与文件写入中断的风险。该机制适用于当前的单进程运行方式。
5.2 组合筛选:同一套条件供流水、统计与导出使用
lotterytally/filters.py 中的 filter_entries 根据多个条件筛选账目,核心实现如下:
def filter_entries(entries, criteria):
start, end = criteria["start_date"], criteria["end_date"]
category, entry_type = criteria["category_id"], criteria["type"]
minimum = criteria["min_amount_cents"]
maximum = criteria["max_amount_cents"]
keyword = criteria["q"]
# 各条件按 AND 组合;日期、金额上下界均包含边界值。
return [entry for entry in entries if (
(start is None or entry["date"] >= start)
and (end is None or entry["date"] <= end)
and (category is None or entry["category_id"] == category)
and (entry_type is None or entry["type"] == entry_type)
and (minimum is None or entry["amount_cents"] >= minimum)
and (maximum is None or entry["amount_cents"] <= maximum)
and (not keyword or keyword in entry.get("note", ""))
)]
金额先转换为整数分,比较与汇总都使用整数。流水分页仅截取显示记录,统计与 CSV 接收完整匹配集合,避免导出时漏掉其他页的账目。导出模块默认保留调用方传入的顺序。
5.3 CSV 输出:保留原文、处理公式型前缀
lotterytally/csv_export.py 中的 _escape_cell 保留字段原文,并处理公式前缀:
def _escape_cell(value):
text = "" if value is None else str(value)
# 只对用于判断的副本去掉前导普通空格,保留原文。
probe = text.lstrip(" ")
if probe and probe[0] in FORMULA_PREFIX:
return "'" + text
return text
FORMULA_PREFIX 包含 =、+、-、@、制表符和回车。匹配时在字段前加单引号,降低表格软件将文本解析为公式的风险;逗号、双引号和换行由 csv.writer 转义。测试覆盖上述前缀,其他软件或字符组合需单独验证。
金额按整数分转换,输出时固定两位小数;最终 CSV 文本只在开头加一次 BOM:
# export_csv 中的语句及金额表达式,省略循环上下文
writer = csv.writer(buf, lineterminator="\r\n")
writer.writerow(HEADER)
# 每条记录的金额列使用:
amount_text = "{}.{:02d}".format(
row["amount_cents"] // 100, row["amount_cents"] % 100
)
# 各行写入完成后,返回带 BOM 的文本:
return "\ufeff" + buf.getvalue()
下载路由将文本编码为 UTF-8。模块同时校验日期、收支类型和金额;非法记录会触发包含记录序号的错误,不会被静默转换为零。
六、功能与实现亮点
6.1 筛选、统计与导出采用相同口径
流水、统计和 CSV 复用筛选规则,金额统一以整数分计算。CSV 导出保留筛选条件和排序,并覆盖全部匹配记录,不受流水分页限制。截图对应的联调验证中,统计页与导出文件均包含 3 笔饮食支出,合计 515 元。
6.2 预算根据账目动态计算
账本保存预算上限,已用金额由当月支出汇总得到。新增和修改账目后,预算结果同步更新;月份按账目日期确定,补录其他月份的支出不会计入本月预算。
6.3 本地保存与可恢复的备份
账目、类别和预算保存在同一份 JSON 文件中。写入前进行校验,写入时使用临时文件替换;恢复备份时校验上传内容,并保留恢复前的账本。下载、修改和恢复的验证结果表明,恢复后的账目与汇总金额均与备份一致。
七、测试与功能验证
2026-11-07 17:20,在提交 e8f6bee 上运行:
python -m unittest discover -s test -v
Ran 393 tests in 2.545s
OK
其中 CSV 模块 35 项测试通过,覆盖金额转换、导出顺序、空数据、特殊字符、输入校验、原文保留和文件名处理。
以下为截图对应的本机自动化联调结果,与第四节视频使用的账本不同。
| 验证操作 | 结果 |
|---|---|
| 新增饮食支出 45 元 | 保存成功,流水从 3 笔增为 4 笔 |
| 查看预算 | 饮食已用 515 元,预算 500 元,超支 15 元 |
| 按本月饮食支出筛选并下载 CSV | 3 笔,合计 515 元,单个 UTF-8 BOM |
| 修改该账目为 20 元 | 饮食已用 490 元,剩余 10 元 |
| 下载并恢复 JSON 备份 | 该账目恢复为 45 元,账本恢复为备份状态 |
| 浏览器脚本错误 | 上述自动化联调期间未记录到 JavaScript 错误 |
测试记录:自动测试输出、测试版本与环境、浏览器操作记录。流水页操作按钮和命令行账本路径问题列入后续改进。
八、GitHub 协作中的三个问题
结合成员的协作反馈和仓库记录,以下复盘公共文件修改、分支合并和最终版本确认中的协作问题。
8.1 多人修改公共文件,容易影响其他模块
现场开发中,多位成员的任务都涉及前端页面和公共文件,职责存在交叉。成员反馈,提交到 GitHub 的代码曾出现对接不上或冲突的情况。如果修改公共模板、应用入口或接口时没有先了解队友的改动,就可能覆盖已有逻辑,或使依赖该文件的其他模块无法正常运行。
这次协作让我们认识到,文件边界需要在开工时明确。后续应为公共文件确定主要维护人,修改前同步影响范围;每次提交尽量围绕一个问题,提交前查看改动差异,避免把无关文件或旧版本一并带入仓库。
依据:魏之皓关于前端分工交叉、文件对接和冲突的成员反馈,以及李义东关于规范提交和仓库管理的建议。
8.2 分支合并后,接口与依赖仍需联调
各成员在不同分支开发,单个模块完成后,还需要接入共同的应用入口、存储接口和页面导航。仓库中的联调修改包括连接记账与流水路由、接入共享筛选规则,以及将各页面的导航列表集中到 navigation.py。这些改动说明,模块分别提交后,仍有跨模块的连接工作需要完成。
合并检查既要处理代码冲突,也要核对接口和数据是否一致。后续应在合并前同步主分支,检查涉及的公共文件;合并后再验证“新增记账—流水查询—统计—导出”的完整流程,确认各模块能够共同运行。
记录:PR #20、PR #24、统一导航提交 b1c14db。
8.3 合并与撤销操作后,需要核对最终版本
流水页样式调整通过 PR #27 合并后,经 PR #28 撤销,随后又由 PR #29 恢复。一次已合并的修改仍可能被后续提交撤销,最终交付的内容需要以主分支实际保留的代码为准。
这一过程提醒我们,提出 PR、完成合并和最终版本保留修改是不同的状态。合并或撤销前,应确认操作对象和影响范围;操作完成后,再核对主分支差异与页面效果。发布时应确认版本包含哪些提交,避免使用仍停留在旧分支或缺少已完成修改的代码。
九、团队成员的编程体验
魏之皓(102401608)
借助 Agent 虽然显著提升效率,但也藏着很多问题。比如,Agent 规划的团队分工并不是很合理,好几个人一起做前端,大家提交到 GitHub 上的文件经常对接不上或冲突,反而影响进度。
刘谦益(102401312)
本次团队编程中,我负责类别模块,实现了默认类别、分类查询及类别的创建、改名、停用和恢复。我先提交独立业务逻辑,再接入公共页面与存储接口,分阶段完成开发。过程中,我认识到类别改名必须保留 ID,停用也不能删除历史引用,这些细节关系到其他模块能否正常运行。我借助 AI 辅助开发和排错,通过 Git 分支和 PR 提交代码,类别相关的 30 项测试全部通过。这次实践让我熟悉了团队协作流程,也体会到接口统一和测试验证的重要性。
王智钦(102401609)
本次现场编程我负责收支汇总、统计看板与 CSV 导出路由模块(summary.py、stats.py、stats.html)。最大的体会是“接口先行”对 11 人并行开发至关重要:提前统一整数分计价和筛选、图表、导出的字典契约后,我能先用内存桩独立跑通 19 项单测,再与队友模块对接。联调中借助 Copilot 审查与全量单测快速定位了配置路径和默认类别 ID 差异,也协助补充了记账金额上限校验,深刻体会到规范 PR 流程与自动化测试在团队高效协作中的价值。
庄凯堃(102401310)
这次课上现场编程,我们团队成员完成记账本这个项目。我们先分头完成记账、存储、筛选、统计和图表等模块,再集中联调。我负责图表,平时主要和负责统计页的同学对接。到了联调时,我才更直观地感受到,一次筛选能不能正确更新图表,取决于前面的账目读取、筛选和汇总能不能顺利衔接。
就是每个人写好自己那一块之后,还得让队友知道怎么接、目前测到了哪一步。这次经历让我更习惯主动同步进度,说清楚自己改了什么、验证了什么,还有哪些地方需要一起确认。下次再做多人现场编程,我会在开工前和相关同学约定好接口和验收方式,给全组多留一点联调和演示的时间。
杜玉鹤(162404109)
这一次我负责记账本的筛选模块,实现日期、类别和金额等组合查询。联调过程中,我体会到统一接口的重要性,也认识到分页、统计和导出需保持数据一致。通过边界测试和问题修复,我加深了对模块协作与错误处理的理解。
陈常群(102401318)
这次是我第一次从头到尾完整负责一个 Web 应用的前端部分。我主要负责记账表单、记录列表和统计页的界面与交互:先把页面的结构搭好,让表单输入能及时校验,日期、金额和类别也不会被随意填进去;再和负责数据模块的同学约定 JSON 的字段格式,联调时把界面真正接上读写逻辑。中途遇到最多的问题是把统计结果渲染出来时前后端字段不一致,最后一起对照接口文档把数据格式统一了。
过程中我体会到,前端不只是把界面画得好看,更要主动理解数据是怎么流转的,前后端接口的约定越早定好,后面返工就越少。用 Git 分支协作、提交前互相看一遍代码,也让我第一次真切地感受到团队开发的节奏和责任分工。
李义东(102401215)
我们需要熟悉语言生态内常见的开发工具、模式和基于 Git 与 GitHub 的协作模式;做好仓库的权限管理,通过文档和 GitHub 的管理功能规范提交行为。不能把重担集中到一两个人身上,真的要爆了 QwQ。
张锦昊(042402115)
这次团队编程中,我负责账目流水页,实现了筛选、排序和分页。联调时,流水页和应用入口的接口没对上。接通后,我们用演示账本验证了新增一笔账后能回到流水页查到记录。以后做小组项目,我会先和队友约定好接口,再一起跑一遍完整流程。
何慧渊(102401135)
我负责校验模块,最大的收获是读队友的代码。为了确认我抛的异常能被筛选模块接住,我把他的分支拉下来,装上自己的实现跑完整回归。也是在那时发现,金额区间的解析后来直接换成了我的函数,接口没白定。
蔡信坡(162404103)
这次我负责 CSV 导出模块。使用 Agent 能提高开发效率,但生成的代码也可能出错,需要反复检查和验证。这让我认识到,工具生成的内容不能直接照搬,还要结合实际功能进行测试。
佘博海(102401137)
我负责模型、账目服务与预算:统一账本结构与校验,实现账目新增、编辑、删除,让预算按账目日期实时汇总——改一笔账,预算立刻跟着变。联调时最费劲的是把模板、蓝图和应用入口对齐,最后补了 76 个用例。
十、SP/PSP 时间记录
下午集中开发与联调约 3—4 小时。下表参照 PSP 2.1 活动分类;由于未逐项计时,两列分钟数均为事后估算,不是现场计时记录,也不能从提交时间直接推算。
时间按整场经过时间统计,单位为分钟,并行活动按主要任务计一次。数据反映团队阶段用时,不代表累计人时或个人 PSP 记录,后续博客撰写与视频补录不计入。
10.1 仓库时间线与估算依据
以下时间均为 2026-11-07 北京时间,来自 Git 提交和 GitHub PR 记录,表示任务推进节点。
| 时间 | 可核对的记录 | 对应活动 |
|---|---|---|
| 14:12:06 | 仓库首次提交 b6c3996 | 仓库初始化 |
| 14:26:22—15:34:47 | 筛选、CSV、图表、统计、流水、类别的首批 PR 陆续创建和合并,见 #1、#3、#4、#7、#9、#11 | 各模块并行设计、编码、自测和复审 |
| 15:21:13—16:16:06 | 模型与预算、应用入口、存储备份及字段校验的 PR 陆续创建和合并,见 #8、#10、#14、#16 | 基础服务、页面入口和数据接口接入,与前一阶段有重叠 |
| 16:28:53—16:38:35 | #20、#21、#22、#23、#25 陆续创建并合并 | 筛选、记账、类别、图表与备份的集中联调修正 |
| 16:58:48—17:07:38 | 金额上限修正 #26、页面路由接入 #24 以及流水样式与撤销恢复 #27—#29 陆续合并 | 收尾修正与集成,最后一次合并晚于 17:00 |
| 17:20 | 本文第七节的 393 项测试结果 | 主分支测试与结果整理 |
CSV 模块的 PR #3 于 14:50:52 创建、15:05:50 合并。该跨度包含评审和修正,不等于个人编码用时。
10.2 团队阶段用时表(参考 PSP 2.1)
| PSP 2.1 | 活动 | 计划参考(分钟,事后估算) | 回顾参考(分钟,事后估算) |
|---|---|---|---|
| Planning | 计划小计 | 10 | 10 |
| Estimate | 明确范围、分工与任务量估计 | 10 | 10 |
| Development | 开发小计 | 145 | 165 |
| Analysis | 需求分析、数据与接口约定 | 10 | 10 |
| Design Spec | 编写模块设计与接口说明 | 10 | 10 |
| Design Review | 设计复审、依赖核对 | 5 | 5 |
| Coding Standard | 统一目录、命名和代码规范 | 5 | 5 |
| Design | 具体功能、页面和数据结构设计 | 10 | 10 |
| Coding | 各模块并行编码与页面实现 | 70 | 75 |
| Code Review | PR 复审、阅读代码和按意见修改 | 10 | 15 |
| Test | 自测、集成测试、问题定位与联调修改 | 25 | 35 |
| Reporting | 报告小计 | 25 | 35 |
| Test Report | 整理测试结果与验收记录 | 5 | 10 |
| Size Measurement | 整理模块、提交和测试规模信息 | 5 | 5 |
| Postmortem & Improvement | 简短复盘、整理问题与改进建议 | 15 | 20 |
| Total | 总计(仅将三个阶段小计相加) | 180 | 210 |
计划参考值合计 3 小时,回顾估算合计 3.5 小时。总计由三个阶段小计相加,明细不重复计入。
各活动的分钟数按整场总时长粗略分配。仓库记录用于核对任务节点,不能证明这些活动各自实际花费的时间。
十一、交付链接
| 交付项 | 链接或状态 |
|---|---|
| GitHub 仓库 | https://github.com/LotteryCamp/LotteryTally |
| 代码提交记录 | https://github.com/LotteryCamp/LotteryTally/commits/main/ |
| 验证代码版本 | e8f6bee3adb440cbd1ada48b0733abb1acc19ef0 |
| 演示视频 | 本地视频见第四节;【 https://www.bilibili.com/video/BV1oX4J6QEzd/?spm_id_from=333.1387.list.card_archive.click&vd_source=019cf11f6069a9f2c67cf9836c09b1f7 】 |

浙公网安备 33010602011771号