软件工程现场编程作业

作业所属课程 福州大学软件工程(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 访问页面

浏览器访问:http://127.0.0.1:5000

账本保存位置:data/ledger.json

停止应用:回到启动窗口,按 Ctrl+C。

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 笔记录。

01_home

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

02_entry_form

4.2 流水列表

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

03_history

4.3 预算联动

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

04_budget_over

4.4 筛选、统计、图表与 CSV

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

05_stats_filtered

导出样本:filtered_expenses.csv。金额从高到低为 300.00、170.00、45.00 元。

4.5 类别、备份与恢复

类别页面提供创建、改名、停用和重新启用的入口。备份页面可下载包含账目、类别和预算的 JSON 文件,并上传备份进行恢复。

06_categories

07_backup

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

08_budget_after_edit

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

09_home_after_restore

4.6 演示视频

功能演示视频,约 1 分 28 秒

视频演示了新增记账、流水查询、收支统计、图表展示和 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、完成合并和最终版本保留修改是不同的状态。合并或撤销前,应确认操作对象和影响范围;操作完成后,再核对主分支差异与页面效果。发布时应确认版本包含哪些提交,避免使用仍停留在旧分支或缺少已完成修改的代码。

记录:PR #27、PR #28、PR #29。

九、团队成员的编程体验

魏之皓(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 小时。总计由三个阶段小计相加,明细不重复计入。

各活动的分钟数按整场总时长粗略分配。仓库记录用于核对任务节点,不能证明这些活动各自实际花费的时间。

参考:课程 SP 资料;仓库提交与 PR 时间记录。

十一、交付链接

交付项 链接或状态
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 】
posted @ 2026-11-07 22:56  caipo  阅读(0)  评论(0)    收藏  举报