软件工程第二次结对作业
| 这个作业属于哪个课程 | 福州大学 H202601 软件工程与软件工程实践 |
|---|---|
| 这个作业要求在哪里 | 软件工程第二次结对作业之程序实现 |
| 这个作业的目标 | 在第一次 Figma 原型的基础上实现可直接运行的校园失物招领 Web,完成发布、浏览与搜索、详情、联系方式、状态更新的业务闭环;通过 GitHub Fork/PR 进行结对协作,并使用白盒测试验证关键逻辑。 |
| 学号 | 102401328、102401318 |
| 结对成员 | 同学A :陈常群——102401318;同学B :马锦城——102401328 |
| 结对同学博客 | https://www.cnblogs.com/Jincheng888 |
| 本次作业博客地址 | https://www.cnblogs.com/Mortal810/p/23257466 |
| 第一次结对作业博客 | 第一次结对作业:需求分析与原型设计 |
| GitHub 项目仓库 | Mortal810/102401328-102401318 |
| 原型展示 | Figma:校园失物招领小程序原型 |
2026 秋软件工程第二次结对作业——「拾见」校园失物招领 Web 程序实现
一、项目概述与需求定位
1.1 选题背景
校园里丢失物品后,很多同学会在聊天群、朋友圈等渠道发布消息。但信息会被后续消息覆盖,拾到物品的同学也难以将线索与失主关联。第一次结对作业已经用 Figma 完成主要页面的需求分析和交互原型;第二次作业则是把原型中的页面和流程真正转换为能够操作的网页。
本项目定名为「拾见|福州大学校园失物招领」,选择 PC Chrome 可直接打开的 Web 形式。整个项目不需要启动服务器,也不依赖在线数据库或前端框架,助教下载源码后直接打开 index.html 即可。
1.2 核心功能与边界
| 模块 | 实际实现的功能 | 对应用户需求 |
|---|---|---|
| 首页 | 信息卡片、全部/寻物/招领切换、分类与进行中筛选 | 集中浏览信息 |
| 搜索及结果 | 按物品名称、类别、地点、描述、发布者关键词查找;无结果提示 | 快速定位线索 |
| 详情 | 类型、状态、地点、日期、说明、发布者和联系方式 | 核对物品特征 |
| 发布及成功 | 寻物/招领表单、校验、成功反馈、个人记录保存 | 发布信息 |
| 我的 | 当前浏览器的发布列表、统计、状态更新、个人资料 | 后续维护 |
| 社区交流 | 预置线索评论与本地新增评论 | 补充物品线索 |
为了控制复杂度,我们没有实现服务器账号、实名认证、实时私聊、地图定位和管理员后台。当前“发布者”识别基于同一浏览器的本地标识,不代表真正的跨设备账户认证。
二、结对分工与版本演进
2.1 两位同学的任务
| 成员 | 主要工作与可核查成果 |
|---|---|
| A(102401318) | 创建和维护主仓库,整理并提交初版源码,运行初版测试;查看并合并升级版 PR;针对时间校验错误创建 Issue、处理 validateDraft() 及测试补充,提交修复分支与 PR。 |
| B(102401328) | Fork 主仓库;将已有升级版源码集成至功能分支,检查界面、校园数据、评论与个人资料等模块,通过 PR 提交至 A 的主仓库;参与时间校验修复的代码审查。 |
| 双方配合 | 结合第一次 Figma 原型复核功能范围,按“源代码版本—分支—PR—Review—Merge”的方式集成变更,并基于测试输出和截图记录过程。 |
2.2 从初版到升级版
| 阶段 | 代码快照或记录 | 变化 |
|---|---|---|
| 初版 | 校园失物招领_WEB_完整项目.zip |
已具备发布、搜索、详情、复制联系方式、我的发布、状态更新等核心功能;20 项单元测试通过。 |
| 升级版 | 校园失物招领_WEB_升级版完整项目.zip |
优化蓝白色 UI,扩充至 26 条虚构校园示例数据,增加离线 SVG 插画、评论、个人资料;24 项单元测试通过。 |
| GitHub 集成 | PR #1 | B 的功能分支 feat/integrate-upgraded-web 合并至 A 的 main,保留实际合并记录。 |
| 缺陷修复 | PR #3 | A 修复非法时间被误判为日期错误的问题;补充 T25~T30;30 项单元测试通过,并有 Merged 截图。 |
图1-2:从初版到升级版的 Github 提交页面

三、PSP 个人软件过程记录
3.1 A(102401318)PSP
| PSP2.1 阶段 | 内容 | 预估 / min | 回顾估算 / min* |
|---|---|---|---|
| Planning / Estimate | 任务规划与时间估计 | 30 | 35 |
| Development | 开发阶段小计 | 835 | 960 |
| ├ Analysis | 需求与技术学习 | 70 | 85 |
| ├ Design Spec | 设计说明 | 45 | 50 |
| ├ Design Review | 设计复审 | 30 | 35 |
| ├ Coding Standard | 代码规范 | 20 | 25 |
| ├ Design | 具体设计 | 90 | 95 |
| ├ Coding | 初版整合、校验修复等 | 360 | 390 |
| ├ Code Review | 代码差异检查 | 70 | 85 |
| └ Test | 单元测试、问题排查和修复 | 150 | 195 |
| Reporting | 报告阶段小计 | 125 | 150 |
| ├ Test Report | 测试说明 | 60 | 70 |
| ├ Size Measurement | 工作量统计 | 20 | 25 |
| └ Postmortem | 复盘与改进 | 45 | 55 |
| 总计 | Planning + Development + Reporting | 990 | 1145* |
3.2 B(102401328)PSP
| PSP2.1 阶段 | 内容 | 预估 / min | 回顾估算 / min* |
|---|---|---|---|
| Planning / Estimate | 任务规划与时间估计 | 30 | 35 |
| Development | 开发阶段小计 | 855 | 1005 |
| ├ Analysis | 原型理解与技术学习 | 60 | 75 |
| ├ Design Spec | 设计说明 | 45 | 55 |
| ├ Design Review | 原型与界面复审 | 30 | 40 |
| ├ Coding Standard | 代码规范 | 20 | 20 |
| ├ Design | 页面与交互方案 | 90 | 110 |
| ├ Coding | 升级版模块整合与优化 | 380 | 420 |
| ├ Code Review | PR 差异审查 | 70 | 95 |
| └ Test | Chrome 走查与测试 | 160 | 190 |
| Reporting | 报告阶段小计 | 125 | 140 |
| ├ Test Report | 测试说明 | 60 | 65 |
| ├ Size Measurement | 工作量统计 | 20 | 25 |
| └ Postmortem | 复盘与改进 | 45 | 50 |
| 总计 | Planning + Development + Reporting | 1010 | 1180* |
从任务结构看,版本整合、Chrome 走查和 GitHub 网络问题处理更容易导致时间超出最初预期。后续应按任务启动与结束时间实时记录,而不是仅凭项目结果反推。
四、解题思路、架构设计与关键流程
4.1 技术方案
项目采用 HTML + CSS + 原生 JavaScript。index.html 包含七个页面视图,js/app.js 负责页面切换、事件和 DOM 渲染;js/core.js 负责不依赖浏览器环境的业务规则;js/storage.js 统一封装本地发布数据读写;个人资料、评论由 js/community.js 维护。这样的拆分使核心规则能够直接在 Node.js 中进行测试,而用户无需安装依赖就能在 Chrome 中运行网页。
图3:Web 模块与数据流

4.2 业务闭环
图4:发布到状态维护的核心流程

这个流程着重解决第一次原型中容易被忽略的“信息结束后怎么办”:只有当前浏览器身份匹配的自发布信息允许变为 finished,寻物类型显示“已找到”,招领类型显示“已归还”;修改之后,其他视图重新渲染相同状态。
4.3 核心数据结构
每一条自行发布的物品记录由 createItem() 创建,关键字段为:
{
id: 'item-...',
ownerId: 'client-...', // 当前浏览器标识
type: 'lost', // lost / found
name: '黑色折叠伞',
category: '雨伞',
location: '西三教学楼',
date: '2026-10-08',
time: '16:30',
description: '伞柄有白色挂绳',
contact: 'QQ:12345678',
publisher: '张同学',
status: 'active', // active / finished
createdAt: 0,
isDemo: false
}
这里展示的是数据结构示意,并非某次真实失物发布的数据库记录。预置数据由 sample-data.js 提供,用户自己发布的数据由 storage.js 存入 Chrome 的 localStorage。
4.4 关键代码一:多字段关键词搜索
js/core.js:
function searchItems(items, keyword) {
const q = clean(keyword).toLocaleLowerCase();
if (!q) return [...items];
return items.filter(item =>
[item.name, item.category, item.location,
item.description, item.publisher]
.some(value => clean(value).toLocaleLowerCase().includes(q))
);
}
将名称、类别、地点、描述和发布者作为匹配范围,能够兼容“耳机”“西三”“晋江楼学习中心”等查询。空关键词时返回所有记录,由后续筛选模块决定最终展示结果。搜索采用简单包含匹配,未引入复杂语义检索。
4.5 关键代码二:发布者权限和状态流转
js/core.js 中的核心函数:
function finishItem(items, itemId, ownerId) {
const idx = items.findIndex(item => item.id === itemId);
if (idx < 0) return { ok: false, reason: 'not_found', message: '该信息不存在' };
const item = items[idx];
if (item.isDemo || item.ownerId !== ownerId)
return { ok: false, reason: 'forbidden', message: '只能修改自己发布的信息' };
if (item.status === 'finished')
return { ok: false, reason: 'already_finished', message: '这条信息已经完成' };
if (!Object.hasOwn(TYPE_LABEL, item.type))
return { ok: false, reason: 'invalid_type', message: '信息类型无效' };
return {
ok: true,
items: items.map((entry, i) =>
i === idx ? { ...entry, status: 'finished' } : entry)
};
}
先判断记录是否存在、是否属于发布者、是否已结束,再返回新的物品数组。这些独立分支也为白盒测试提供了直接入口。该权限属于本地浏览器层面的交互限制,无法替代服务器认证与访问控制。
4.6 关键代码三:日期和时间的独立验证
一次可复现的问题是输入 25:61 时,旧逻辑可能错误提示“日期无效”。修复后,validateDraft() 先检查格式,再单独检查小时和分钟,并保持未来时间判断:
if (!/^\d{2}:\d{2}$/.test(timeText)) {
errors.time = '请选择有效时间';
} else {
const [hour, minute] = timeText.split(':').map(Number);
if (hour > 23 || minute > 59)
errors.time = '时间无效';
}
if (!errors.date && !errors.time) {
const parsed = new Date(`${dateText}T${timeText}:00`);
if (Number.isNaN(parsed.getTime())) {
errors.time = '时间无效';
} else if (parsed.getTime() > now.getTime()) {
errors.date = '发生时间不能晚于当前时间';
}
}
上段为修复版 js/core.js 中与时间相关的片段;真实日历日期判断也保留在该函数中。修复目的不是改变输入格式,而是让报错字段与错误原因对应,并使边界输入能够单独测试。
五、附加特点:设计意义、实现方式与展示
5.1 校园场景丰富与离线 SVG 插画
升级版预置 26 条虚构记录,覆盖校园卡、钥匙、电子产品、雨伞、书籍、水杯、证件、运动用品等类别,地点包含青春广场、晋江楼学习中心、西三教学楼、福友阁、丁香园食堂、第一田径场、宏晖馆等校园场景。插画由 js/illustrations.js 根据物品类别生成 SVG,不依赖网络图片链接,离线打开也能显示。
与通用占位图相比,分类插画更容易帮助用户从卡片中快速识别物品类别。但这些图是示意画,不能作为真实物品外观或划痕的证据。
图5:首页实际升级版页面预览

5.2 一键复制联系方式与多维筛选
详情页保留联系方式展示和复制入口,减少反复手动选中文字的操作。首页和搜索结果支持类型、类别、进行中状态等筛选条件,可减少无关记录。
function filterItems(items, { type = 'all', category = 'all', activeOnly = false } = {}) {
return items.filter(item =>
(type === 'all' || item.type === type) &&
(category === 'all' || item.category === category) &&
(!activeOnly || item.status === 'active')
);
}
5.3 评论线索与个人资料
js/community.js 在同一浏览器中保存评论和个人资料。评论输入会先过滤空白,并限制长度,避免空内容和过长留言直接进入界面。
function validateComment(input) {
const content = String(input || '').trim();
if (!content) return { ok: false, message: '请先输入留言内容' };
if (content.length > 220)
return { ok: false, message: '留言最多220字' };
return { ok: true, content };
}
保存评论时,模块会把本地评论与示例评论合并展示,同时对单条信息在当前浏览器的新增评论数量设置上限。个人资料支持昵称、学院和签名。该功能帮助补充物品线索,但不是跨用户实时讨论系统。
图6:物品详情实际升级版页面预览

六、项目目录结构与运行说明
6.1 最终升级版目录
102401328-102401318/
├── index.html # 七个页面的入口
├── README.md # 使用与项目介绍
├── package.json # npm test 命令
├── .gitignore
├── css/
│ ├── base.css # 通用样式与响应式基础
│ ├── layout.css # 首页、卡片、导航等布局
│ ├── pages.css # 各业务页面样式
│ └── visual-upgrade.css # 升级后的视觉细节
├── js/
│ ├── core.js # 业务纯函数、校验、状态流转
│ ├── app.js # 页面控制与 DOM 交互
│ ├── storage.js # 浏览器本地数据存储
│ ├── sample-data.js # 26 条虚构初始数据
│ ├── community.js # 评论和个人资料
│ ├── illustrations.js # 离线 SVG 插画
│ ├── search.js # 搜索组合与排序封装
│ ├── detail.js # 详情查询与权限判断辅助
│ └── my-posts.js # 我的发布与统计辅助
├── tests/
│ └── core.test.cjs # Node.js 内置单元测试
└── docs/
├── 功能与测试清单.md
└── PSP_两人预估模板.md
升级版没有必须单独下载的在线图片素材文件夹或前端第三方框架目录:图标、插画以 JavaScript/SVG 和 CSS 的方式提供。完整运行依赖只有仓库中 index.html 所引用的本地 CSS/JS 文件。
6.2 如何运行
-
打开 GitHub 主仓库,选择 Code → Download ZIP,或执行:
git clone https://github.com/Mortal810/102401328-102401318.git -
解压或进入项目根目录,保持
css/、js/与index.html的相对目录不变。 -
使用 Google Chrome 双击
index.html,不需要 Node.js、npm install或服务器。 -
在首页搜索“耳机”或“青春广场”查看结果;打开详情查看联系方式;点击“发布”新增一条记录,再从“我的发布”修改状态。
-
如需运行单元测试,安装 Node.js 18 或以上版本,在项目根目录输入:
npm.cmd test在不限制 PowerShell 脚本的终端也可以使用
npm test。本项目调用 Node.js 内置测试运行器,不必安装 Mocha 等依赖。
数据持久化采用 Chrome 的 localStorage:一般情况下同一浏览器中刷新后仍可读取,但不同设备不会同步;浏览器策略或清空数据可能导致数据丢失。预置记录及联系方式均是虚构测试素材。
七、单元测试设计、教程与真实结果
7.1 为什么选择 node:test
为了满足助教克隆后直接运行的要求,项目未引入第三方测试框架,使用 Node.js 内置 node:test 与 node:assert/strict。业务核心放在可被 require('../js/core.js') 加载的模块中,使单元测试不需要 Chrome DOM,也不需要启动服务。
最小使用方法如下:
const test = require('node:test');
const assert = require('node:assert/strict');
const core = require('../js/core.js');
test('非法时间应归入 time 错误', () => {
// 准备合法的其他字段,仅替换 time,定位目标分支
const result = core.validateDraft(make({ time: '25:61' }), now);
assert.equal(result.valid, false);
assert.equal(result.errors.time, '时间无效');
assert.equal(result.errors.date, undefined);
});
上面引用了项目测试文件已有的 make() 和 now 公共测试辅助变量,并非可以独立执行的完整文件。执行 npm.cmd test 会运行 tests/core.test.cjs 中的全部测试。
7.2 白盒测试思路与用例
测试覆盖必填字段和长度限制、非法日期、搜索匹配与空结果、分类组合、发布者校验、已完成状态的重复操作、HTML 转义、评论与个人资料校验,并补充了时分边界的分支测试。
| 分组 | 用例编号 | 验证点 |
|---|---|---|
| 发布与输入 | T01~T09 | 寻物/招领创建、空名称/联系方式、未知类别、未来日期、非法日历日期、过长描述、发布者数据 |
| 搜索与筛选 | T10~T14 | 名称、地点、无结果、昵称、类别和状态组合 |
| 状态与权限 | T15~T18 | 已找到、已归还、非发布者拒绝、已完成重复操作拒绝 |
| 安全与交互 | T19~T24 | HTML 转义、个人资料、空评论、评论合并、26 条示例数据检查 |
| 时间缺陷回归 | T25~T30 | 25:61、24:00、12:60、00:00、23:59、日期与时间同时非法 |
典型的 T25 代码:
test('T25 校验:非法时分 25:61 归类到 time 而非 date', () => {
const result = core.validateDraft(make({ time: '25:61' }), now);
assert.equal(result.valid, false);
assert.equal(result.errors.time, '时间无效');
assert.equal(result.errors.date, undefined);
});
测试采用固定 now 时间基准,隔离待测条件,既覆盖正常输入,也覆盖空值、非法值、边界值、越权与重复操作。对于可能故意输入 25:61、2026-02-30、空白评论或未知类别的测试人员,程序应给出正确的错误结果,而不是出现未处理异常。
7.3 三次版本测试结果
| 版本 | 执行方式 | 总数 | 通过 | 失败 | 证据 |
|---|---|---|---|---|---|
| 初版 | npm.cmd test |
20 | 20 | 0 | 初版测试截图 |
| 升级版 | npm.cmd test |
24 | 24 | 0 | 升级版测试截图 |
| 时间校验修复版 | npm.cmd test |
30 | 30 | 0 | A 的本地复测截图 |
图7:初版20项测试通过

图8:升级版24项测试通过

图9:时间校验修复后30项测试通过

这些证据表明已执行的单元测试用例全部通过,不代表程序不存在任何缺陷;例如跨设备同步和真实账户权限不在该测试范围内。
八、GitHub 签入、Fork、PR 与双人协作证据
8.1 A:建立仓库,导入初版
A 在 GitHub 建立 Mortal810/102401328-102401318,在本地初始化 main,将初版源码导入,并在 20 项测试通过后形成 chore: import verified initial web snapshot 类型的初始提交。
图10:A创建GitHub主仓库

图11:A本地初版Commit

图12:GitHub仓库文件

8.2 B:升级版集成 PR #1
B 使用自己的 Fork,在功能分支 feat/integrate-upgraded-web 中整合升级版源码,再向 A 主仓库发起 PR。该 PR 的真实记录显示作者分支来自 B 的 Fork,目标是 Mortal810:main。
图13:B发起升级版Pull Request

A 查看 Files changed 中的主要源码差异,并留下审核评论。
图14:升级版审查评论

升级版 PR #1 的截图明确显示 Merged,其合并对象为主仓库 main,分支来自 B 的 Fork。这一部分具有可核查的实际合并证据。
图15:升级版PR合并成功

8.3 A:时间校验缺陷修复 PR #3
随后检查 validateDraft() 时定位到一个边界问题:非法时间 25:61 可能被归到日期错误。A 在 GitHub 创建相应 Issue,修改 js/core.js 并增加 T25~T30 六个测试;本地测试输出显示 30/30 通过。修复经独立分支 fix/invalid-time-validation 提交。
图16:创建时间校验Issue的编辑过程

图16 是 Issue 编辑表单截图;修复 PR 页面关联该 Issue,可以相互核对。
图17:时间校验修复Commit

图18:修复分支已推送到GitHub

图19:A发起时间校验修复PR

图20:B审查时间校验修复

修复 PR #3 的截图显示 Merged,表明修复代码已经通过 GitHub PR 合并进入主仓库。
图21:修复PR合并成功

8.4 Commit 规范与过程体会
本次版本历史中出现的代表性提交信息包括:
chore: import verified initial web snapshot
feat(upgrade): integrate richer campus UI and community features
fix(validation): report invalid time separately
其中 chore 表示项目初始化或整理,feat 表示功能整合,fix 表示缺陷修复。这样的文字比 update 或 final 更容易对应到变更目的。

九、实际遇到的问题、解决方法与收获
9.1 PowerShell 禁止执行 npm.ps1
初次运行 npm test 时,PowerShell 因执行策略拒绝加载 npm.ps1,测试并未开始。根据终端报错定位到 PowerShell 脚本执行策略后,改用:
npm.cmd test
不修改系统级安全策略即可运行 Node.js 测试。之后初版和升级版均通过了相应单元测试。这使我们认识到:终端命令失败不一定代表程序本身失败,应先区分环境错误与业务逻辑错误。
9.2 GitHub HTTPS 连接超时
本地修复分支 Push 阶段多次出现 Failed to connect to github.com:443。我们分别尝试 git ls-remote、HTTP/1.1、Test-NetConnection、curl -I,并检查 Git、环境变量与 WinHTTP 代理。部分检查显示 TCP 443 可达,而 curl HTTPS 请求仍超时;临时假设的本地 127.0.0.1:7890 代理端口也不可用。
后来提供的 A-04-修复分支Push成功.png 及 PR #3 Merged 页面证明,修复分支最终已到达 GitHub 并被合并;但现有记录不足以确定究竟通过哪一项网络变更恢复了连接,所以不把某种代理配置或网络切换写成已经证实的最终解决办法。该经历提醒我们,源代码提交可以先在本地完成,网络恢复后再推送,而不需要反复初始化仓库。
9.3 分支重复创建
运行 git switch -c fix/invalid-time-validation 后再次执行同一命令,出现 a branch named ... already exists。用 git branch -vv 查看分支,发现修复分支已经创建,改用 git switch fix/invalid-time-validation 直接切换即可。这里的关键是区分“创建分支”和“切换到已有分支”。
9.4 非法时间错误归类
初始时间验证仅使用 HH:mm 形式的正则表达式,没有独立校验小时和分钟,导致 25:61 一类输入无法得到准确的 time 字段错误。A 将日期与时间分别校验,限制小时、分钟范围,保留真实日历日期与未来时间判断;新增六个回归和边界测试。最终 30 项测试全部通过。这比仅在界面中显示“请输入正确时间”更有意义,因为明确修复了核心业务逻辑中的错误分支。
十、项目效果与尚存局限
最终源码能通过 Chrome 直接打开,预置校园物品、搜索、发布、详情、联系入口、个人信息和评论功能均具备对应实现。单元测试已经核验重要业务函数,其中测试记录显示了从 20 项扩展到 24 项、再到 30 项的过程。
同时,需要说明目前仍属于本地单机 Web 版本:跨设备不共享记录,不具备服务器登录认证,不提供实时聊天室,且示例数据与照片插画均不代表真实物品。
十一、结对伙伴评价
值得学习的是,B 在沿用第一次原型的同时关注视觉统一和交互完整性,能够围绕校园场景、分类信息、评论与个人资料等内容整理升级版,并通过 Fork 和 PR 提交可审查的变更。代码层面不仅要让界面好看,也需要考虑如何让不同模块衔接,这是本次配合中的重要收获。可以改进的地方是:大型升级版一次整合的差异较多,以后宜将界面、数据、评论等模块拆成更小的 PR,便于审查、回退与定位问题。
十二、个人总结
第一次结对作业更注重界面内容和操作路径,第二次则让我认识到原型中的每个按钮最终都要对应真实逻辑。升级版整合时,页面不仅需要延续蓝白色和七页结构,还要有可以搜索的校园数据、符合物品类别的插画、可查看和保存的评论以及“我的”页面信息。通过 Fork、功能分支和 PR 提交变更,我开始理解为什么同伴需要逐文件阅读差异,而不能只看网页截图。在配合 A 的修复审查时,也更加注意输入边界、测试用例和回归风险。这次实践的收获不是堆叠更多页面,而是懂得在功能范围、用户体验和可维护性之间做取舍。今后希望把升级工作拆成多个较小的 PR,并加强 Chrome 中的逐流程记录。
浙公网安备 33010602011771号